summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2022-04-20 12:39:36 -0500
committerMatt A. Tobin <email@mattatobin.com>2022-04-20 12:39:36 -0500
commit7d68843eb91243b552ab0202e784ce307eda7dc1 (patch)
treed6352487c1d32b06928f43344d41ba1ca1b0fac7
parent960b7eb6fea2a84a2c3f4a38c5b2c152002f2fb3 (diff)
parent7c598437c237a3a6da4f74f611bc2f043c32abc7 (diff)
downloadaura-central-7d68843eb91243b552ab0202e784ce307eda7dc1.tar.gz
Merge branch 'backout-emegmp-removal' into TRUNK
-rw-r--r--components/addons/content/OpenH264-license.txt59
-rw-r--r--components/addons/content/extensions.js19
-rw-r--r--components/addons/content/extensions.xml6
-rw-r--r--components/addons/content/gmpPrefs.xul8
-rw-r--r--components/addons/extensions.manifest1
-rw-r--r--components/addons/jar.mn2
-rw-r--r--components/addons/locale/extensions.properties4
-rw-r--r--components/addons/moz.build4
-rw-r--r--components/addons/src/GMPInstallManager.jsm917
-rw-r--r--components/addons/src/GMPProvider.jsm605
-rw-r--r--components/addons/src/GMPUtils.jsm187
-rw-r--r--components/addons/src/ProductAddonChecker.jsm464
-rw-r--r--components/crashes/CrashManager.jsm3
-rw-r--r--components/crashes/CrashService.js3
-rw-r--r--components/crashes/nsICrashService.idl1
-rw-r--r--components/global/content/gmp-sources/openh264.json57
-rw-r--r--components/global/content/gmp-sources/widevinecdm.json49
-rw-r--r--components/global/content/process-content.js7
-rw-r--r--components/global/jar.mn2
-rw-r--r--dom/base/Navigator.cpp141
-rw-r--r--dom/base/Navigator.h12
-rw-r--r--dom/base/nsDocument.cpp34
-rw-r--r--dom/base/nsDocument.h4
-rw-r--r--dom/canvas/CanvasRenderingContext2D.cpp7
-rw-r--r--dom/html/HTMLMediaElement.cpp322
-rw-r--r--dom/html/HTMLMediaElement.h37
-rw-r--r--dom/ipc/ContentChild.cpp17
-rw-r--r--dom/ipc/ContentChild.h7
-rw-r--r--dom/ipc/ContentParent.cpp18
-rw-r--r--dom/ipc/ContentParent.h6
-rw-r--r--dom/ipc/PContent.ipdl27
-rw-r--r--dom/locales/en-US/chrome/plugins.properties10
-rw-r--r--dom/media/AbstractMediaDecoder.h8
-rw-r--r--dom/media/DecoderDoctorDiagnostics.cpp7
-rw-r--r--dom/media/DecoderDoctorDiagnostics.h9
-rw-r--r--dom/media/MediaDecoder.cpp53
-rw-r--r--dom/media/MediaDecoder.h22
-rw-r--r--dom/media/MediaDecoderOwner.h8
-rw-r--r--dom/media/MediaDecoderReader.h7
-rw-r--r--dom/media/MediaDecoderReaderWrapper.h4
-rw-r--r--dom/media/MediaDecoderStateMachine.cpp62
-rw-r--r--dom/media/MediaDecoderStateMachine.h8
-rw-r--r--dom/media/MediaFormatReader.cpp68
-rw-r--r--dom/media/MediaFormatReader.h15
-rw-r--r--dom/media/MediaPrefs.h7
-rw-r--r--dom/media/eme/CDMCaps.cpp169
-rw-r--r--dom/media/eme/CDMCaps.h112
-rw-r--r--dom/media/eme/CDMProxy.h278
-rw-r--r--dom/media/eme/DecryptorProxyCallback.h54
-rw-r--r--dom/media/eme/DetailedPromise.cpp57
-rw-r--r--dom/media/eme/DetailedPromise.h57
-rw-r--r--dom/media/eme/EMEUtils.cpp86
-rw-r--r--dom/media/eme/EMEUtils.h104
-rw-r--r--dom/media/eme/MediaEncryptedEvent.cpp128
-rw-r--r--dom/media/eme/MediaEncryptedEvent.h66
-rw-r--r--dom/media/eme/MediaKeyError.cpp39
-rw-r--r--dom/media/eme/MediaKeyError.h38
-rw-r--r--dom/media/eme/MediaKeyMessageEvent.cpp122
-rw-r--r--dom/media/eme/MediaKeyMessageEvent.h66
-rw-r--r--dom/media/eme/MediaKeySession.cpp670
-rw-r--r--dom/media/eme/MediaKeySession.h136
-rw-r--r--dom/media/eme/MediaKeyStatusMap.cpp120
-rw-r--r--dom/media/eme/MediaKeyStatusMap.h96
-rw-r--r--dom/media/eme/MediaKeySystemAccess.cpp1042
-rw-r--r--dom/media/eme/MediaKeySystemAccess.h81
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.cpp336
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.h83
-rw-r--r--dom/media/eme/MediaKeys.cpp579
-rw-r--r--dom/media/eme/MediaKeys.h167
-rw-r--r--dom/media/eme/moz.build41
-rw-r--r--dom/media/fmp4/MP4Decoder.cpp4
-rw-r--r--dom/media/gmp-plugin-openh264/fakeopenh264.info4
-rw-r--r--dom/media/gmp-plugin-openh264/fakeopenh264.voucher1
-rw-r--r--dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp429
-rw-r--r--dom/media/gmp-plugin-openh264/moz.build29
-rw-r--r--dom/media/gmp-plugin/fake.info5
-rw-r--r--dom/media/gmp-plugin/fake.voucher1
-rw-r--r--dom/media/gmp-plugin/gmp-fake.cpp98
-rw-r--r--dom/media/gmp-plugin/gmp-test-decryptor.cpp608
-rw-r--r--dom/media/gmp-plugin/gmp-test-decryptor.h102
-rw-r--r--dom/media/gmp-plugin/gmp-test-output-protection.h129
-rw-r--r--dom/media/gmp-plugin/gmp-test-storage.cpp233
-rw-r--r--dom/media/gmp-plugin/gmp-test-storage.h63
-rw-r--r--dom/media/gmp-plugin/moz.build31
-rw-r--r--dom/media/gmp/GMPAudioDecoderChild.cpp172
-rw-r--r--dom/media/gmp/GMPAudioDecoderChild.h51
-rw-r--r--dom/media/gmp/GMPAudioDecoderParent.cpp369
-rw-r--r--dom/media/gmp/GMPAudioDecoderParent.h72
-rw-r--r--dom/media/gmp/GMPAudioDecoderProxy.h48
-rw-r--r--dom/media/gmp/GMPAudioHost.cpp162
-rw-r--r--dom/media/gmp/GMPAudioHost.h71
-rw-r--r--dom/media/gmp/GMPCDMCallbackProxy.cpp250
-rw-r--r--dom/media/gmp/GMPCDMCallbackProxy.h71
-rw-r--r--dom/media/gmp/GMPCDMProxy.cpp798
-rw-r--r--dom/media/gmp/GMPCDMProxy.h265
-rw-r--r--dom/media/gmp/GMPCallbackBase.h21
-rw-r--r--dom/media/gmp/GMPChild.cpp500
-rw-r--r--dom/media/gmp/GMPChild.h92
-rw-r--r--dom/media/gmp/GMPContentChild.cpp320
-rw-r--r--dom/media/gmp/GMPContentChild.h58
-rw-r--r--dom/media/gmp/GMPContentParent.cpp298
-rw-r--r--dom/media/gmp/GMPContentParent.h103
-rw-r--r--dom/media/gmp/GMPCrashHelperHolder.h81
-rw-r--r--dom/media/gmp/GMPDecryptorChild.cpp403
-rw-r--r--dom/media/gmp/GMPDecryptorChild.h142
-rw-r--r--dom/media/gmp/GMPDecryptorParent.cpp537
-rw-r--r--dom/media/gmp/GMPDecryptorParent.h129
-rw-r--r--dom/media/gmp/GMPDecryptorProxy.h70
-rw-r--r--dom/media/gmp/GMPDiskStorage.cpp499
-rw-r--r--dom/media/gmp/GMPEncryptedBufferDataImpl.cpp132
-rw-r--r--dom/media/gmp/GMPEncryptedBufferDataImpl.h92
-rw-r--r--dom/media/gmp/GMPLoader.cpp209
-rw-r--r--dom/media/gmp/GMPLoader.h96
-rw-r--r--dom/media/gmp/GMPMemoryStorage.cpp95
-rw-r--r--dom/media/gmp/GMPMessageUtils.h253
-rw-r--r--dom/media/gmp/GMPParent.cpp982
-rw-r--r--dom/media/gmp/GMPParent.h242
-rw-r--r--dom/media/gmp/GMPPlatform.cpp300
-rw-r--r--dom/media/gmp/GMPPlatform.h56
-rw-r--r--dom/media/gmp/GMPProcessChild.cpp82
-rw-r--r--dom/media/gmp/GMPProcessChild.h43
-rw-r--r--dom/media/gmp/GMPProcessParent.cpp84
-rw-r--r--dom/media/gmp/GMPProcessParent.h52
-rw-r--r--dom/media/gmp/GMPService.cpp557
-rw-r--r--dom/media/gmp/GMPService.h142
-rw-r--r--dom/media/gmp/GMPServiceChild.cpp478
-rw-r--r--dom/media/gmp/GMPServiceChild.h105
-rw-r--r--dom/media/gmp/GMPServiceParent.cpp1933
-rw-r--r--dom/media/gmp/GMPServiceParent.h261
-rw-r--r--dom/media/gmp/GMPSharedMemManager.cpp98
-rw-r--r--dom/media/gmp/GMPSharedMemManager.h82
-rw-r--r--dom/media/gmp/GMPStorage.h41
-rw-r--r--dom/media/gmp/GMPStorageChild.cpp380
-rw-r--r--dom/media/gmp/GMPStorageChild.h118
-rw-r--r--dom/media/gmp/GMPStorageParent.cpp220
-rw-r--r--dom/media/gmp/GMPStorageParent.h49
-rw-r--r--dom/media/gmp/GMPTimerChild.cpp67
-rw-r--r--dom/media/gmp/GMPTimerChild.h46
-rw-r--r--dom/media/gmp/GMPTimerParent.cpp123
-rw-r--r--dom/media/gmp/GMPTimerParent.h61
-rw-r--r--dom/media/gmp/GMPTypes.ipdlh86
-rw-r--r--dom/media/gmp/GMPUtils.cpp230
-rw-r--r--dom/media/gmp/GMPUtils.h89
-rw-r--r--dom/media/gmp/GMPVideoDecoderChild.cpp254
-rw-r--r--dom/media/gmp/GMPVideoDecoderChild.h75
-rw-r--r--dom/media/gmp/GMPVideoDecoderParent.cpp513
-rw-r--r--dom/media/gmp/GMPVideoDecoderParent.h104
-rw-r--r--dom/media/gmp/GMPVideoDecoderProxy.h56
-rw-r--r--dom/media/gmp/GMPVideoEncodedFrameImpl.cpp324
-rw-r--r--dom/media/gmp/GMPVideoEncodedFrameImpl.h122
-rw-r--r--dom/media/gmp/GMPVideoEncoderChild.cpp235
-rw-r--r--dom/media/gmp/GMPVideoEncoderChild.h75
-rw-r--r--dom/media/gmp/GMPVideoEncoderParent.cpp382
-rw-r--r--dom/media/gmp/GMPVideoEncoderParent.h93
-rw-r--r--dom/media/gmp/GMPVideoEncoderProxy.h56
-rw-r--r--dom/media/gmp/GMPVideoHost.cpp120
-rw-r--r--dom/media/gmp/GMPVideoHost.h57
-rw-r--r--dom/media/gmp/GMPVideoPlaneImpl.cpp225
-rw-r--r--dom/media/gmp/GMPVideoPlaneImpl.h66
-rw-r--r--dom/media/gmp/GMPVideoi420FrameImpl.cpp365
-rw-r--r--dom/media/gmp/GMPVideoi420FrameImpl.h84
-rw-r--r--dom/media/gmp/PGMP.ipdl41
-rw-r--r--dom/media/gmp/PGMPAudioDecoder.ipdl37
-rw-r--r--dom/media/gmp/PGMPContent.ipdl33
-rw-r--r--dom/media/gmp/PGMPDecryptor.ipdl92
-rw-r--r--dom/media/gmp/PGMPService.ipdl32
-rw-r--r--dom/media/gmp/PGMPStorage.ipdl36
-rw-r--r--dom/media/gmp/PGMPTimer.ipdl22
-rw-r--r--dom/media/gmp/PGMPVideoDecoder.ipdl50
-rw-r--r--dom/media/gmp/PGMPVideoEncoder.ipdl48
-rw-r--r--dom/media/gmp/README.txt1
-rw-r--r--dom/media/gmp/gmp-api/gmp-async-shutdown.h54
-rw-r--r--dom/media/gmp/gmp-api/gmp-audio-codec.h43
-rw-r--r--dom/media/gmp/gmp-api/gmp-audio-decode.h84
-rw-r--r--dom/media/gmp/gmp-api/gmp-audio-host.h32
-rw-r--r--dom/media/gmp/gmp-api/gmp-audio-samples.h74
-rw-r--r--dom/media/gmp/gmp-api/gmp-decryption.h459
-rw-r--r--dom/media/gmp/gmp-api/gmp-entrypoints.h75
-rw-r--r--dom/media/gmp/gmp-api/gmp-errors.h58
-rw-r--r--dom/media/gmp/gmp-api/gmp-platform.h118
-rw-r--r--dom/media/gmp/gmp-api/gmp-storage.h141
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-codec.h226
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-decode.h127
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-encode.h135
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame-encoded.h99
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame-i420.h132
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame.h51
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-host.h53
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-plane.h94
-rw-r--r--dom/media/gmp/moz.build155
-rw-r--r--dom/media/gmp/mozIGeckoMediaPluginChromeService.idl51
-rw-r--r--dom/media/gmp/mozIGeckoMediaPluginService.idl169
-rw-r--r--dom/media/gmp/rlz/COPYING14
-rw-r--r--dom/media/gmp/rlz/GMPDeviceBinding.cpp152
-rw-r--r--dom/media/gmp/rlz/GMPDeviceBinding.h21
-rw-r--r--dom/media/gmp/rlz/README.mozilla6
-rw-r--r--dom/media/gmp/rlz/lib/assert.h14
-rw-r--r--dom/media/gmp/rlz/lib/machine_id.h19
-rw-r--r--dom/media/gmp/rlz/lib/string_utils.cc34
-rw-r--r--dom/media/gmp/rlz/lib/string_utils.h20
-rw-r--r--dom/media/gmp/rlz/mac/lib/machine_id_mac.cc320
-rw-r--r--dom/media/gmp/rlz/moz.build34
-rw-r--r--dom/media/gmp/rlz/sha256.c469
-rw-r--r--dom/media/gmp/rlz/sha256.h46
-rw-r--r--dom/media/gmp/rlz/win/lib/machine_id_win.cc134
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineAdapter.cpp168
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineAdapter.h59
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp554
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineDecryptor.h132
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineFileIO.cpp97
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineFileIO.h46
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineUtils.cpp95
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineUtils.h73
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp400
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h80
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp126
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoFrame.h50
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module.h1278
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module_export.h22
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module_ext.h64
-rw-r--r--dom/media/gmp/widevine-adapter/moz.build24
-rw-r--r--dom/media/gtest/GMPTestMonitor.h46
-rw-r--r--dom/media/gtest/TestGMPCrossOrigin.cpp1546
-rw-r--r--dom/media/gtest/TestGMPRemoveAndDelete.cpp490
-rw-r--r--dom/media/gtest/TestGMPUtils.cpp60
-rw-r--r--dom/media/gtest/moz.build4
-rw-r--r--dom/media/mediasource/TrackBuffersManager.cpp36
-rw-r--r--dom/media/moz.build9
-rw-r--r--dom/media/platforms/PDMFactory.cpp29
-rw-r--r--dom/media/platforms/PDMFactory.h15
-rw-r--r--dom/media/platforms/PlatformDecoderModule.h4
-rw-r--r--dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp44
-rw-r--r--dom/media/platforms/agnostic/eme/EMEAudioDecoder.h37
-rw-r--r--dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp307
-rw-r--r--dom/media/platforms/agnostic/eme/EMEDecoderModule.h52
-rw-r--r--dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp68
-rw-r--r--dom/media/platforms/agnostic/eme/EMEVideoDecoder.h45
-rw-r--r--dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp85
-rw-r--r--dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h58
-rw-r--r--dom/media/platforms/agnostic/eme/moz.build22
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp306
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h112
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp170
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPDecoderModule.h57
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp388
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h122
-rw-r--r--dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp90
-rw-r--r--dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h181
-rw-r--r--dom/media/platforms/agnostic/gmp/moz.build23
-rw-r--r--dom/media/platforms/moz.build4
-rw-r--r--dom/media/platforms/wmf/WMFVideoMFTManager.cpp16
-rw-r--r--dom/media/platforms/wrappers/H264Converter.cpp4
-rw-r--r--dom/media/platforms/wrappers/H264Converter.h1
-rw-r--r--dom/media/test/mochitest.ini33
-rw-r--r--dom/media/test/test_eme_request_notifications.html88
-rw-r--r--dom/media/test/test_gmp_playback.html40
-rwxr-xr-xdom/media/webaudio/AudioContext.cpp8
-rw-r--r--dom/media/webaudio/BufferDecoder.cpp10
-rw-r--r--dom/media/webaudio/BufferDecoder.h5
-rw-r--r--dom/media/webaudio/MediaBufferDecoder.cpp22
-rw-r--r--dom/media/webaudio/moz.build3
-rw-r--r--dom/webidl/HTMLMediaElement.webidl18
-rw-r--r--dom/webidl/MediaEncryptedEvent.webidl23
-rw-r--r--dom/webidl/MediaKeyError.webidl19
-rw-r--r--dom/webidl/MediaKeyMessageEvent.webidl30
-rw-r--r--dom/webidl/MediaKeySession.webidl47
-rw-r--r--dom/webidl/MediaKeyStatusMap.webidl30
-rw-r--r--dom/webidl/MediaKeySystemAccess.webidl41
-rw-r--r--dom/webidl/MediaKeys.webidl30
-rw-r--r--dom/webidl/MediaKeysRequestStatus.webidl23
-rw-r--r--dom/webidl/PluginCrashedEvent.webidl2
-rw-r--r--dom/webidl/WidevineCDMManifest.webidl15
-rw-r--r--dom/webidl/moz.build13
-rw-r--r--ipc/app/moz.build9
-rw-r--r--ipc/contentproc/plugin-container.cpp20
-rw-r--r--layout/base/nsLayoutUtils.cpp6
-rw-r--r--layout/build/nsLayoutModule.cpp7
-rw-r--r--libs/gmp-clearkey/0.1/AnnexB.cpp79
-rw-r--r--libs/gmp-clearkey/0.1/AnnexB.h32
-rw-r--r--libs/gmp-clearkey/0.1/ArrayUtils.h23
-rw-r--r--libs/gmp-clearkey/0.1/AudioDecoder.cpp312
-rw-r--r--libs/gmp-clearkey/0.1/AudioDecoder.h73
-rw-r--r--libs/gmp-clearkey/0.1/BigEndian.h68
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyAsyncShutdown.cpp45
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyAsyncShutdown.h37
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyBase64.cpp99
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyBase64.h28
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp241
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyDecryptionManager.h101
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyPersistence.cpp260
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyPersistence.h53
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeySession.cpp94
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeySession.h58
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeySessionManager.cpp418
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeySessionManager.h100
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyStorage.cpp194
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyStorage.h48
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyUtils.cpp541
-rw-r--r--libs/gmp-clearkey/0.1/ClearKeyUtils.h114
-rw-r--r--libs/gmp-clearkey/0.1/RefCounted.h116
-rw-r--r--libs/gmp-clearkey/0.1/VideoDecoder.cpp455
-rw-r--r--libs/gmp-clearkey/0.1/VideoDecoder.h110
-rw-r--r--libs/gmp-clearkey/0.1/WMFAACDecoder.cpp366
-rw-r--r--libs/gmp-clearkey/0.1/WMFAACDecoder.h74
-rw-r--r--libs/gmp-clearkey/0.1/WMFH264Decoder.cpp365
-rw-r--r--libs/gmp-clearkey/0.1/WMFH264Decoder.h78
-rw-r--r--libs/gmp-clearkey/0.1/WMFSymbols.h20
-rw-r--r--libs/gmp-clearkey/0.1/WMFUtils.cpp276
-rw-r--r--libs/gmp-clearkey/0.1/WMFUtils.h269
-rw-r--r--libs/gmp-clearkey/0.1/clearkey.info.in10
-rw-r--r--libs/gmp-clearkey/0.1/gmp-clearkey.cpp89
-rw-r--r--libs/gmp-clearkey/0.1/gmp-task-utils-generated.h1938
-rw-r--r--libs/gmp-clearkey/0.1/gmp-task-utils.h47
-rw-r--r--libs/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp92
-rw-r--r--libs/gmp-clearkey/0.1/gtest/moz.build14
-rw-r--r--libs/gmp-clearkey/0.1/moz.build74
-rw-r--r--libs/gmp-clearkey/0.1/openaes/LICENSE27
-rw-r--r--libs/gmp-clearkey/0.1/openaes/oaes_common.h77
-rw-r--r--libs/gmp-clearkey/0.1/openaes/oaes_config.h46
-rw-r--r--libs/gmp-clearkey/0.1/openaes/oaes_lib.c1393
-rw-r--r--libs/gmp-clearkey/0.1/openaes/oaes_lib.h168
-rw-r--r--libs/gmp-clearkey/0.1/openaes/standard.h57
-rw-r--r--libs/moz.build3
-rw-r--r--modules/ForgetAboutSite.jsm9
-rw-r--r--python/mozbuild/mozbuild/mach_commands.py10
-rw-r--r--system/graphics/layers/ipc/PAPZCTreeManager.ipdl4
-rw-r--r--system/graphics/thebes/gfxPrefs.cpp2
-rw-r--r--system/platform-prefs.js46
-rw-r--r--system/runtime/nsAppRunner.cpp1
-rw-r--r--system/runtime/nsEmbedFunctions.cpp21
-rw-r--r--testing/mochitest/runtests.py8
-rw-r--r--testing/mochitest/runtestsremote.py4
-rw-r--r--xpcom/build/XREChildData.h28
-rw-r--r--xpcom/build/moz.build1
-rw-r--r--xpcom/build/nsXULAppAPI.h13
-rw-r--r--xpcom/system/nsIXULRuntime.idl3
336 files changed, 48099 insertions, 33 deletions
diff --git a/components/addons/content/OpenH264-license.txt b/components/addons/content/OpenH264-license.txt
new file mode 100644
index 000000000..ad37989b8
--- /dev/null
+++ b/components/addons/content/OpenH264-license.txt
@@ -0,0 +1,59 @@
+-------------------------------------------------------
+About The Cisco-Provided Binary of OpenH264 Video Codec
+-------------------------------------------------------
+
+Cisco provides this program under the terms of the BSD license.
+
+Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met.
+
+As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk. Your rights from Cisco under the BSD license are not affected by this choice.
+
+For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary
+
+A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org
+
+-----------
+BSD License
+-----------
+
+Copyright © 2014 Cisco Systems, Inc.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-----------------------------------------
+AVC/H.264 Patent Portfolio License Notice
+-----------------------------------------
+
+The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software:
+
+THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEO”) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO. NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM
+
+Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla
+
+---------------------------------------------
+AVC/H.264 Patent Portfolio License Conditions
+---------------------------------------------
+
+In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met:
+
+1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device;
+
+2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary;
+
+3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text:
+
+ "OpenH264 Video Codec provided by Cisco Systems, Inc."
+
+4. Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user.
+
+
+
+ v1.0
diff --git a/components/addons/content/extensions.js b/components/addons/content/extensions.js
index 11c853524..fe84bc460 100644
--- a/components/addons/content/extensions.js
+++ b/components/addons/content/extensions.js
@@ -1014,7 +1014,7 @@ var gViewController = {
cmd_showItemPreferences: {
isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) {
if (!aAddon ||
- !aAddon.isActive ||
+ (!aAddon.isActive && !aAddon.isGMPlugin) ||
!aAddon.optionsURL) {
return false;
}
@@ -2753,7 +2753,15 @@ var gDetailView = {
var fullDesc = document.getElementById("detail-fulldesc");
if (aAddon.fullDescription) {
- fullDesc.textContent = aAddon.fullDescription;
+ // The following is part of an awful hack to include the licenses for GMP
+ // plugins without having bug 624602 fixed yet, and intentionally ignores
+ // localisation.
+ if (aAddon.isGMPlugin) {
+ fullDesc.innerHTML = aAddon.fullDescription;
+ } else {
+ fullDesc.textContent = aAddon.fullDescription;
+ }
+
fullDesc.hidden = false;
} else {
fullDesc.hidden = true;
@@ -3028,6 +3036,13 @@ var gDetailView = {
errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
errorLink.href = this._addon.blocklistURL;
errorLink.hidden = false;
+ } else if (this._addon.isGMPlugin && !this._addon.isInstalled &&
+ this._addon.isActive) {
+ this.node.setAttribute("notification", "warning");
+ let warning = document.getElementById("detail-warning");
+ warning.textContent =
+ gStrings.ext.formatStringFromName("details.notification.gmpPending",
+ [this._addon.name], 1);
} else {
this.node.removeAttribute("notification");
}
diff --git a/components/addons/content/extensions.xml b/components/addons/content/extensions.xml
index 9ea6a50df..f862b0f7e 100644
--- a/components/addons/content/extensions.xml
+++ b/components/addons/content/extensions.xml
@@ -1328,6 +1328,12 @@
this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableNoUpdate.link");
this._errorLink.href = this.mAddon.blocklistURL;
this._errorLink.hidden = false;
+ } else if (this.mAddon.isGMPlugin && !this.mAddon.isInstalled &&
+ this.mAddon.isActive) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent =
+ gStrings.ext.formatStringFromName("notification.gmpPending",
+ [this.mAddon.name], 1);
} else {
this.removeAttribute("notification");
}
diff --git a/components/addons/content/gmpPrefs.xul b/components/addons/content/gmpPrefs.xul
new file mode 100644
index 000000000..ea7ee92fa
--- /dev/null
+++ b/components/addons/content/gmpPrefs.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+- License, v. 2.0. If a copy of the MPL was not distributed with this file,
+- You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- This is intentionally empty and a dummy to let the GMPProvider
+ have a preferences button in the list view. -->
diff --git a/components/addons/extensions.manifest b/components/addons/extensions.manifest
index 35d605575..b56152e10 100644
--- a/components/addons/extensions.manifest
+++ b/components/addons/extensions.manifest
@@ -13,3 +13,4 @@ contract @mozilla.org/addons/installtrigger;1 {9df8ef2b-94da-45c9-ab9f-132eb55fd
category JavaScript-global-property InstallTrigger @mozilla.org/addons/installtrigger;1
category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm
+category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.jsm
diff --git a/components/addons/jar.mn b/components/addons/jar.mn
index 0f4bfce26..878be4df1 100644
--- a/components/addons/jar.mn
+++ b/components/addons/jar.mn
@@ -29,6 +29,8 @@ toolkit.jar:
content/mozapps/extensions/newaddon.js (content/newaddon.js)
content/mozapps/extensions/setting.xml (content/setting.xml)
content/mozapps/extensions/pluginPrefs.xul (content/pluginPrefs.xul)
+ content/mozapps/extensions/gmpPrefs.xul (content/gmpPrefs.xul)
+ content/mozapps/extensions/OpenH264-license.txt (content/OpenH264-license.txt)
content/mozapps/xpinstall/xpinstallConfirm.xul (content/xpinstallConfirm.xul)
content/mozapps/xpinstall/xpinstallConfirm.js (content/xpinstallConfirm.js)
content/mozapps/xpinstall/xpinstallConfirm.css (content/xpinstallConfirm.css)
diff --git a/components/addons/locale/extensions.properties b/components/addons/locale/extensions.properties
index 43ab02a47..c5de4f0c0 100644
--- a/components/addons/locale/extensions.properties
+++ b/components/addons/locale/extensions.properties
@@ -57,6 +57,8 @@ notification.downloadError.retry.tooltip=Try downloading this add-on again
notification.installError=There was an error installing %1$S.
notification.installError.retry=Try again
notification.installError.retry.tooltip=Try downloading and installing this add-on again
+#LOCALIZATION NOTE (notification.gmpPending) %1$S is the add-on name.
+notification.gmpPending=%1$S will be installed shortly.
#LOCALIZATION NOTE (contributionAmount2) %S is the currency amount recommended for contributions
contributionAmount2=Suggested Contribution: %S
@@ -98,6 +100,8 @@ details.notification.install=%1$S will be installed after you restart %2$S.
details.notification.uninstall=%1$S will be uninstalled after you restart %2$S.
#LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name
details.notification.upgrade=%1$S will be updated after you restart %2$S.
+#LOCALIZATION NOTE (details.notification.gmpPending) %1$S is the add-on name
+details.notification.gmpPending=%1$S will be installed shortly.
installFromFile.dialogTitle=Select add-on to install
installFromFile.filterName=Add-ons
diff --git a/components/addons/moz.build b/components/addons/moz.build
index 22193c1e7..6b1e2fe93 100644
--- a/components/addons/moz.build
+++ b/components/addons/moz.build
@@ -39,6 +39,8 @@ EXTRA_JS_MODULES += [
EXTRA_PP_JS_MODULES += [
'src/AddonManager.jsm',
+ 'src/GMPInstallManager.jsm',
+ 'src/GMPUtils.jsm',
]
EXTRA_JS_MODULES.addons += [
@@ -46,8 +48,10 @@ EXTRA_JS_MODULES.addons += [
'src/AddonRepository.jsm',
'src/AddonRepository_SQLiteMigrator.jsm',
'src/Content.js',
+ 'src/GMPProvider.jsm',
'src/LightweightThemeImageOptimizer.jsm',
'src/PluginProvider.jsm',
+ 'src/ProductAddonChecker.jsm',
'src/SpellCheckDictionaryBootstrap.js',
]
diff --git a/components/addons/src/GMPInstallManager.jsm b/components/addons/src/GMPInstallManager.jsm
new file mode 100644
index 000000000..fe4e2de10
--- /dev/null
+++ b/components/addons/src/GMPInstallManager.jsm
@@ -0,0 +1,917 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} =
+ Components;
+// Chunk size for the incremental downloader
+const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
+// Incremental downloader interval
+const DOWNLOAD_INTERVAL = 0;
+// 1 day default
+const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/GMPUtils.jsm");
+
+this.EXPORTED_SYMBOLS = ["GMPInstallManager", "GMPExtractor", "GMPDownloader",
+ "GMPAddon"];
+
+var gLocale = null;
+
+// Shared code for suppressing bad cert dialogs
+XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
+ let temp = { };
+ Cu.import("resource://gre/modules/CertUtils.jsm", temp);
+ return temp;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+
+/**
+ * Number of milliseconds after which we need to cancel `checkForAddons`.
+ *
+ * Bug 1087674 suggests that the XHR we use in `checkForAddons` may
+ * never terminate in presence of network nuisances (e.g. strange
+ * antivirus behavior). This timeout is a defensive measure to ensure
+ * that we fail cleanly in such case.
+ */
+const CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS = 20000;
+
+function getScopedLogger(prefix) {
+ // `PARENT_LOGGER_ID.` being passed here effectively links this logger
+ // to the parentLogger.
+ return Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP", prefix + " ");
+}
+
+// This is copied directly from nsUpdateService.js
+// It is used for calculating the URL string w/ var replacement.
+// TODO: refactor this out somewhere else
+XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
+ let osVersion;
+ let sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ try {
+ osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
+ }
+ catch (e) {
+ LOG("gOSVersion - OS Version unknown: updates are not possible.");
+ }
+
+ if (osVersion) {
+#ifdef XP_WIN
+ const BYTE = ctypes.uint8_t;
+ const WORD = ctypes.uint16_t;
+ const DWORD = ctypes.uint32_t;
+ const WCHAR = ctypes.char16_t;
+ const BOOL = ctypes.int;
+
+ // This structure is described at:
+ // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
+ const SZCSDVERSIONLENGTH = 128;
+ const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
+ [
+ {dwOSVersionInfoSize: DWORD},
+ {dwMajorVersion: DWORD},
+ {dwMinorVersion: DWORD},
+ {dwBuildNumber: DWORD},
+ {dwPlatformId: DWORD},
+ {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
+ {wServicePackMajor: WORD},
+ {wServicePackMinor: WORD},
+ {wSuiteMask: WORD},
+ {wProductType: BYTE},
+ {wReserved: BYTE}
+ ]);
+
+ // This structure is described at:
+ // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
+ const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
+ [
+ {wProcessorArchitecture: WORD},
+ {wReserved: WORD},
+ {dwPageSize: DWORD},
+ {lpMinimumApplicationAddress: ctypes.voidptr_t},
+ {lpMaximumApplicationAddress: ctypes.voidptr_t},
+ {dwActiveProcessorMask: DWORD.ptr},
+ {dwNumberOfProcessors: DWORD},
+ {dwProcessorType: DWORD},
+ {dwAllocationGranularity: DWORD},
+ {wProcessorLevel: WORD},
+ {wProcessorRevision: WORD}
+ ]);
+
+ let kernel32 = false;
+ try {
+ kernel32 = ctypes.open("Kernel32");
+ } catch (e) {
+ LOG("gOSVersion - Unable to open kernel32! " + e);
+ osVersion += ".unknown (unknown)";
+ }
+
+ if(kernel32) {
+ try {
+ // Get Service pack info
+ try {
+ let GetVersionEx = kernel32.declare("GetVersionExW",
+ ctypes.default_abi,
+ BOOL,
+ OSVERSIONINFOEXW.ptr);
+ let winVer = OSVERSIONINFOEXW();
+ winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
+
+ if(0 !== GetVersionEx(winVer.address())) {
+ osVersion += "." + winVer.wServicePackMajor
+ + "." + winVer.wServicePackMinor;
+ } else {
+ LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
+ osVersion += ".unknown";
+ }
+ } catch (e) {
+ LOG("gOSVersion - error getting service pack information. Exception: " + e);
+ osVersion += ".unknown";
+ }
+
+ // Get processor architecture
+ let arch = "unknown";
+ try {
+ let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
+ ctypes.default_abi,
+ ctypes.void_t,
+ SYSTEM_INFO.ptr);
+ let sysInfo = SYSTEM_INFO();
+ // Default to unknown
+ sysInfo.wProcessorArchitecture = 0xffff;
+
+ GetNativeSystemInfo(sysInfo.address());
+ switch(sysInfo.wProcessorArchitecture) {
+ case 9:
+ arch = "x64";
+ break;
+ case 6:
+ arch = "IA64";
+ break;
+ case 0:
+ arch = "x86";
+ break;
+ }
+ } catch (e) {
+ LOG("gOSVersion - error getting processor architecture. Exception: " + e);
+ } finally {
+ osVersion += " (" + arch + ")";
+ }
+ } finally {
+ kernel32.close();
+ }
+ }
+#endif
+
+ try {
+ osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
+ }
+ catch (e) {
+ // Not all platforms have a secondary widget library, so an error is nothing to worry about.
+ }
+ osVersion = encodeURIComponent(osVersion);
+ }
+ return osVersion;
+});
+
+/**
+ * Provides an easy API for downloading and installing GMP Addons
+ */
+function GMPInstallManager() {
+}
+/**
+ * Temp file name used for downloading
+ */
+GMPInstallManager.prototype = {
+ /**
+ * Obtains a URL with replacement of vars
+ */
+ _getURL: function() {
+ let log = getScopedLogger("GMPInstallManager._getURL");
+ // Use the override URL if it is specified. The override URL is just like
+ // the normal URL but it does not check the cert.
+ let url = GMPPrefs.get(GMPPrefs.KEY_URL_OVERRIDE);
+ if (url) {
+ log.info("Using override url: " + url);
+ } else {
+ url = GMPPrefs.get(GMPPrefs.KEY_URL);
+ log.info("Using url: " + url);
+ }
+
+ url = UpdateUtils.formatUpdateURL(url);
+ log.info("Using url (with replacement): " + url);
+ return url;
+ },
+ /**
+ * Performs an addon check.
+ * @return a promise which will be resolved or rejected.
+ * The promise is resolved with an array of GMPAddons
+ * The promise is rejected with an object with properties:
+ * target: The XHR request object
+ * status: The HTTP status code
+ * type: Sometimes specifies type of rejection
+ */
+ checkForAddons: function() {
+ let log = getScopedLogger("GMPInstallManager.checkForAddons");
+ if (this._deferred) {
+ log.error("checkForAddons already called");
+ return Promise.reject({type: "alreadycalled"});
+ }
+ this._deferred = Promise.defer();
+ let url = this._getURL();
+
+ this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsISupports);
+ // This is here to let unit test code override XHR
+ if (this._request.wrappedJSObject) {
+ this._request = this._request.wrappedJSObject;
+ }
+ this._request.open("GET", url, true);
+ let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true);
+ this._request.channel.notificationCallbacks =
+ new gCertUtils.BadCertHandler(allowNonBuiltIn);
+ // Prevent the request from reading from the cache.
+ this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ // Prevent the request from writing to the cache.
+ this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+
+ this._request.overrideMimeType("text/xml");
+ // The Cache-Control header is only interpreted by proxies and the
+ // final destination. It does not help if a resource is already
+ // cached locally.
+ this._request.setRequestHeader("Cache-Control", "no-cache");
+ // HTTP/1.0 servers might not implement Cache-Control and
+ // might only implement Pragma: no-cache
+ this._request.setRequestHeader("Pragma", "no-cache");
+
+ this._request.timeout = CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS;
+ this._request.addEventListener("error", event => this.onFailXML("onErrorXML", event), false);
+ this._request.addEventListener("abort", event => this.onFailXML("onAbortXML", event), false);
+ this._request.addEventListener("timeout", event => this.onFailXML("onTimeoutXML", event), false);
+ this._request.addEventListener("load", event => this.onLoadXML(event), false);
+
+ log.info("sending request to: " + url);
+ this._request.send(null);
+
+ return this._deferred.promise;
+ },
+ /**
+ * Installs the specified addon and calls a callback when done.
+ * @param gmpAddon The GMPAddon object to install
+ * @return a promise which will be resolved or rejected
+ * The promise will resolve with an array of paths that were extracted
+ * The promise will reject with an error object:
+ * target: The XHR request object
+ * status: The HTTP status code
+ * type: A string to represent the type of error
+ * downloaderr, verifyerr or previouserrorencountered
+ */
+ installAddon: function(gmpAddon) {
+ if (this._deferred) {
+ log.error("previous error encountered");
+ return Promise.reject({type: "previouserrorencountered"});
+ }
+ this.gmpDownloader = new GMPDownloader(gmpAddon);
+ return this.gmpDownloader.start();
+ },
+ _getTimeSinceLastCheck: function() {
+ let now = Math.round(Date.now() / 1000);
+ // Default to 0 here because `now - 0` will be returned later if that case
+ // is hit. We want a large value so a check will occur.
+ let lastCheck = GMPPrefs.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
+ // Handle clock jumps, return now since we want it to represent
+ // a lot of time has passed since the last check.
+ if (now < lastCheck) {
+ return now;
+ }
+ return now - lastCheck;
+ },
+ get _isEMEEnabled() {
+ return GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true);
+ },
+ _isAddonUpdateEnabled: function(aAddon) {
+ return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ENABLED, true, aAddon) &&
+ GMPPrefs.get(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, aAddon);
+ },
+ _updateLastCheck: function() {
+ let now = Math.round(Date.now() / 1000);
+ GMPPrefs.set(GMPPrefs.KEY_UPDATE_LAST_CHECK, now);
+ },
+ _versionchangeOccurred: function() {
+ let savedBuildID = GMPPrefs.get(GMPPrefs.KEY_BUILDID, null);
+ let buildID = Services.appinfo.platformBuildID;
+ if (savedBuildID == buildID) {
+ return false;
+ }
+ GMPPrefs.set(GMPPrefs.KEY_BUILDID, buildID);
+ return true;
+ },
+ /**
+ * Wrapper for checkForAddons and installAddon.
+ * Will only install if not already installed and will log the results.
+ * This will only install/update the OpenH264 and EME plugins
+ * @return a promise which will be resolved if all addons could be installed
+ * successfully, rejected otherwise.
+ */
+ simpleCheckAndInstall: Task.async(function*() {
+ let log = getScopedLogger("GMPInstallManager.simpleCheckAndInstall");
+
+ if (this._versionchangeOccurred()) {
+ log.info("A version change occurred. Ignoring " +
+ "media.gmp-manager.lastCheck to check immediately for " +
+ "new or updated GMPs.");
+ } else {
+ let secondsBetweenChecks =
+ GMPPrefs.get(GMPPrefs.KEY_SECONDS_BETWEEN_CHECKS,
+ DEFAULT_SECONDS_BETWEEN_CHECKS)
+ let secondsSinceLast = this._getTimeSinceLastCheck();
+ log.info("Last check was: " + secondsSinceLast +
+ " seconds ago, minimum seconds: " + secondsBetweenChecks);
+ if (secondsBetweenChecks > secondsSinceLast) {
+ log.info("Will not check for updates.");
+ return {status: "too-frequent-no-check"};
+ }
+ }
+
+ try {
+ let gmpAddons = yield this.checkForAddons();
+ this._updateLastCheck();
+ log.info("Found " + gmpAddons.length + " addons advertised.");
+ let addonsToInstall = gmpAddons.filter(function(gmpAddon) {
+ log.info("Found addon: " + gmpAddon.toString());
+
+ if (!gmpAddon.isValid || GMPUtils.isPluginHidden(gmpAddon) ||
+ gmpAddon.isInstalled) {
+ log.info("Addon invalid, hidden or already installed.");
+ return false;
+ }
+
+ let addonUpdateEnabled = false;
+ if (GMP_PLUGIN_IDS.indexOf(gmpAddon.id) >= 0) {
+ addonUpdateEnabled = this._isAddonUpdateEnabled(gmpAddon.id);
+ if (!addonUpdateEnabled) {
+ log.info("Auto-update is off for " + gmpAddon.id +
+ ", skipping check.");
+ }
+ } else {
+ // Currently, we only support installs of OpenH264 and EME plugins.
+ log.info("Auto-update is off for unknown plugin '" + gmpAddon.id +
+ "', skipping check.");
+ }
+
+ return addonUpdateEnabled;
+ }, this);
+
+ if (!addonsToInstall.length) {
+ log.info("No new addons to install, returning");
+ return {status: "nothing-new-to-install"};
+ }
+
+ let installResults = [];
+ let failureEncountered = false;
+ for (let addon of addonsToInstall) {
+ try {
+ yield this.installAddon(addon);
+ installResults.push({
+ id: addon.id,
+ result: "succeeded",
+ });
+ } catch (e) {
+ failureEncountered = true;
+ installResults.push({
+ id: addon.id,
+ result: "failed",
+ });
+ }
+ }
+ if (failureEncountered) {
+ throw {status: "failed",
+ results: installResults};
+ }
+ return {status: "succeeded",
+ results: installResults};
+ } catch(e) {
+ log.error("Could not check for addons", e);
+ throw e;
+ }
+ }),
+
+ /**
+ * Makes sure everything is cleaned up
+ */
+ uninit: function() {
+ let log = getScopedLogger("GMPInstallManager.uninit");
+ if (this._request) {
+ log.info("Aborting request");
+ this._request.abort();
+ }
+ if (this._deferred) {
+ log.info("Rejecting deferred");
+ this._deferred.reject({type: "uninitialized"});
+ }
+ log.info("Done cleanup");
+ },
+
+ /**
+ * If set to true, specifies to leave the temporary downloaded zip file.
+ * This is useful for tests.
+ */
+ overrideLeaveDownloadedZip: false,
+
+ /**
+ * The XMLHttpRequest succeeded and the document was loaded.
+ * @param event The nsIDOMEvent for the load
+ */
+ onLoadXML: function(event) {
+ let log = getScopedLogger("GMPInstallManager.onLoadXML");
+ try {
+ log.info("request completed downloading document");
+ let certs = null;
+ if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) &&
+ GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
+ certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
+ }
+
+ let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN,
+ true);
+ log.info("allowNonBuiltIn: " + allowNonBuiltIn);
+
+ gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
+
+ this.parseResponseXML();
+ } catch (ex) {
+ log.error("could not load xml: " + ex);
+ this._deferred.reject({
+ target: event.target,
+ status: this._getChannelStatus(event.target),
+ message: "" + ex,
+ });
+ delete this._deferred;
+ }
+ },
+
+ /**
+ * Returns the status code for the XMLHttpRequest
+ */
+ _getChannelStatus: function(request) {
+ let log = getScopedLogger("GMPInstallManager._getChannelStatus");
+ let status = null;
+ try {
+ status = request.status;
+ log.info("request.status is: " + request.status);
+ }
+ catch (e) {
+ }
+
+ if (status == null) {
+ status = request.channel.QueryInterface(Ci.nsIRequest).status;
+ }
+ return status;
+ },
+
+ /**
+ * There was an error of some kind during the XMLHttpRequest. This
+ * error may have been caused by external factors (e.g. network
+ * issues) or internally (by a timeout).
+ *
+ * @param event The nsIDOMEvent for the error
+ */
+ onFailXML: function(failure, event) {
+ let log = getScopedLogger("GMPInstallManager.onFailXML " + failure);
+ let request = event.target;
+ let status = this._getChannelStatus(request);
+ let message = "request.status: " + status + " (" + event.type + ")";
+ log.warn(message);
+ this._deferred.reject({
+ target: request,
+ status: status,
+ message: message
+ });
+ delete this._deferred;
+ },
+
+ /**
+ * Returns an array of GMPAddon objects discovered by the update check.
+ * Or returns an empty array if there were any problems with parsing.
+ * If there's an error, it will be logged if logging is enabled.
+ */
+ parseResponseXML: function() {
+ try {
+ let log = getScopedLogger("GMPInstallManager.parseResponseXML");
+ let updatesElement = this._request.responseXML.documentElement;
+ if (!updatesElement) {
+ let message = "empty updates document";
+ log.warn(message);
+ this._deferred.reject({
+ target: this._request,
+ message: message
+ });
+ delete this._deferred;
+ return;
+ }
+
+ if (updatesElement.nodeName != "updates") {
+ let message = "got node name: " + updatesElement.nodeName +
+ ", expected: updates";
+ log.warn(message);
+ this._deferred.reject({
+ target: this._request,
+ message: message
+ });
+ delete this._deferred;
+ return;
+ }
+
+ const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
+ let gmpResults = [];
+ for (let i = 0; i < updatesElement.childNodes.length; ++i) {
+ let updatesChildElement = updatesElement.childNodes.item(i);
+ if (updatesChildElement.nodeType != ELEMENT_NODE) {
+ continue;
+ }
+ if (updatesChildElement.localName == "addons") {
+ gmpResults = GMPAddon.parseGMPAddonsNode(updatesChildElement);
+ }
+ }
+ this._deferred.resolve(gmpResults);
+ delete this._deferred;
+ } catch (e) {
+ this._deferred.reject({
+ target: this._request,
+ message: e
+ });
+ delete this._deferred;
+ }
+ },
+};
+
+/**
+ * Used to construct a single GMP addon
+ * GMPAddon objects are returns from GMPInstallManager.checkForAddons
+ * GMPAddon objects can also be used in calls to GMPInstallManager.installAddon
+ *
+ * @param gmpAddon The AUS response XML's DOM element `addon`
+ */
+function GMPAddon(gmpAddon) {
+ let log = getScopedLogger("GMPAddon.constructor");
+ gmpAddon.QueryInterface(Ci.nsIDOMElement);
+ ["id", "URL", "hashFunction",
+ "hashValue", "version", "size"].forEach(name => {
+ if (gmpAddon.hasAttribute(name)) {
+ this[name] = gmpAddon.getAttribute(name);
+ }
+ });
+ this.size = Number(this.size) || undefined;
+ log.info ("Created new addon: " + this.toString());
+}
+/**
+ * Parses an XML GMP addons node from AUS into an array
+ * @param addonsElement An nsIDOMElement compatible node with XML from AUS
+ * @return An array of GMPAddon results
+ */
+GMPAddon.parseGMPAddonsNode = function(addonsElement) {
+ let log = getScopedLogger("GMPAddon.parseGMPAddonsNode");
+ let gmpResults = [];
+ if (addonsElement.localName !== "addons") {
+ return;
+ }
+
+ addonsElement.QueryInterface(Ci.nsIDOMElement);
+ let addonCount = addonsElement.childNodes.length;
+ for (let i = 0; i < addonCount; ++i) {
+ let addonElement = addonsElement.childNodes.item(i);
+ if (addonElement.localName !== "addon") {
+ continue;
+ }
+ addonElement.QueryInterface(Ci.nsIDOMElement);
+ try {
+ gmpResults.push(new GMPAddon(addonElement));
+ } catch (e) {
+ log.warn("invalid addon: " + e);
+ continue;
+ }
+ }
+ return gmpResults;
+};
+GMPAddon.prototype = {
+ /**
+ * Returns a string representation of the addon
+ */
+ toString: function() {
+ return this.id + " (" +
+ "isValid: " + this.isValid +
+ ", isInstalled: " + this.isInstalled +
+ ", hashFunction: " + this.hashFunction+
+ ", hashValue: " + this.hashValue +
+ (this.size !== undefined ? ", size: " + this.size : "" ) +
+ ")";
+ },
+ /**
+ * If all the fields aren't specified don't consider this addon valid
+ * @return true if the addon is parsed and valid
+ */
+ get isValid() {
+ return this.id && this.URL && this.version &&
+ this.hashFunction && !!this.hashValue;
+ },
+ get isInstalled() {
+ return this.version &&
+ GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, "", this.id) === this.version;
+ },
+ get isEME() {
+ return this.id == "gmp-widevinecdm" || this.id.indexOf("gmp-eme-") == 0;
+ },
+};
+/**
+ * Constructs a GMPExtractor object which is used to extract a GMP zip
+ * into the specified location. (Which typically leties per platform)
+ * @param zipPath The path on disk of the zip file to extract
+ */
+function GMPExtractor(zipPath, installToDirPath) {
+ this.zipPath = zipPath;
+ this.installToDirPath = installToDirPath;
+}
+GMPExtractor.prototype = {
+ /**
+ * Obtains a list of all the entries in a zipfile in the format of *.*.
+ * This also includes files inside directories.
+ *
+ * @param zipReader the nsIZipReader to check
+ * @return An array of string name entries which can be used
+ * in nsIZipReader.extract
+ */
+ _getZipEntries: function(zipReader) {
+ let entries = [];
+ let enumerator = zipReader.findEntries("*.*");
+ while (enumerator.hasMore()) {
+ entries.push(enumerator.getNext());
+ }
+ return entries;
+ },
+ /**
+ * Installs the this.zipPath contents into the directory used to store GMP
+ * addons for the current platform.
+ *
+ * @return a promise which will be resolved or rejected
+ * See GMPInstallManager.installAddon for resolve/rejected info
+ */
+ install: function() {
+ try {
+ let log = getScopedLogger("GMPExtractor.install");
+ this._deferred = Promise.defer();
+ log.info("Installing " + this.zipPath + "...");
+ // Get the input zip file
+ let zipFile = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsIFile);
+ zipFile.initWithPath(this.zipPath);
+
+ // Initialize a zipReader and obtain the entries
+ var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipReader.open(zipFile)
+ let entries = this._getZipEntries(zipReader);
+ let extractedPaths = [];
+
+ // Extract each of the entries
+ entries.forEach(entry => {
+ // We don't need these types of files
+ if (entry.includes("__MACOSX")) {
+ return;
+ }
+ let outFile = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsILocalFile);
+ outFile.initWithPath(this.installToDirPath);
+ outFile.appendRelativePath(entry);
+
+ // Make sure the directory hierarchy exists
+ if(!outFile.parent.exists()) {
+ outFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+ }
+ zipReader.extract(entry, outFile);
+ extractedPaths.push(outFile.path);
+ log.info(entry + " was successfully extracted to: " +
+ outFile.path);
+ });
+ zipReader.close();
+ if (!GMPInstallManager.overrideLeaveDownloadedZip) {
+ zipFile.remove(false);
+ }
+
+ log.info(this.zipPath + " was installed successfully");
+ this._deferred.resolve(extractedPaths);
+ } catch (e) {
+ if (zipReader) {
+ zipReader.close();
+ }
+ this._deferred.reject({
+ target: this,
+ status: e,
+ type: "exception"
+ });
+ }
+ return this._deferred.promise;
+ }
+};
+
+
+/**
+ * Constructs an object which downloads and initiates an install of
+ * the specified GMPAddon object.
+ * @param gmpAddon The addon to install.
+ */
+function GMPDownloader(gmpAddon)
+{
+ this._gmpAddon = gmpAddon;
+}
+/**
+ * Computes the file hash of fileToHash with the specified hash function
+ * @param hashFunctionName A hash function name such as sha512
+ * @param fileToHash An nsIFile to hash
+ * @return a promise which resolve to a digest in binary hex format
+ */
+GMPDownloader.computeHash = function(hashFunctionName, fileToHash) {
+ let log = getScopedLogger("GMPDownloader.computeHash");
+ let digest;
+ let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fileStream.init(fileToHash, FileUtils.MODE_RDONLY,
+ FileUtils.PERMS_FILE, 0);
+ try {
+ let hash = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ let hashFunction =
+ Ci.nsICryptoHash[hashFunctionName.toUpperCase()];
+ if (!hashFunction) {
+ log.error("could not get hash function");
+ return Promise.reject();
+ }
+ hash.init(hashFunction);
+ hash.updateFromStream(fileStream, -1);
+ digest = binaryToHex(hash.finish(false));
+ } catch (e) {
+ log.warn("failed to compute hash: " + e);
+ digest = "";
+ }
+ fileStream.close();
+ return Promise.resolve(digest);
+},
+GMPDownloader.prototype = {
+ /**
+ * Starts the download process for an addon.
+ * @return a promise which will be resolved or rejected
+ * See GMPInstallManager.installAddon for resolve/rejected info
+ */
+ start: function() {
+ let log = getScopedLogger("GMPDownloader.start");
+ this._deferred = Promise.defer();
+ if (!this._gmpAddon.isValid) {
+ log.info("gmpAddon is not valid, will not continue");
+ return Promise.reject({
+ target: this,
+ status: status,
+ type: "downloaderr"
+ });
+ }
+
+ let uri = Services.io.newURI(this._gmpAddon.URL, null, null);
+ this._request = Cc["@mozilla.org/network/incremental-download;1"].
+ createInstance(Ci.nsIIncrementalDownload);
+ let gmpFile = FileUtils.getFile("TmpD", [this._gmpAddon.id + ".zip"]);
+ if (gmpFile.exists()) {
+ gmpFile.remove(false);
+ }
+
+ log.info("downloading from " + uri.spec + " to " + gmpFile.path);
+ this._request.init(uri, gmpFile, DOWNLOAD_CHUNK_BYTES_SIZE,
+ DOWNLOAD_INTERVAL);
+ this._request.start(this, null);
+ return this._deferred.promise;
+ },
+ // For nsIRequestObserver
+ onStartRequest: function(request, context) {
+ },
+ // For nsIRequestObserver
+ // Called when the GMP addon zip file is downloaded
+ onStopRequest: function(request, context, status) {
+ let log = getScopedLogger("GMPDownloader.onStopRequest");
+ log.info("onStopRequest called");
+ if (!Components.isSuccessCode(status)) {
+ log.info("status failed: " + status);
+ this._deferred.reject({
+ target: this,
+ status: status,
+ type: "downloaderr"
+ });
+ return;
+ }
+
+ let promise = this._verifyDownload();
+ promise.then(() => {
+ log.info("GMP file is ready to unzip");
+ let destination = this._request.destination;
+
+ let zipPath = destination.path;
+ let gmpAddon = this._gmpAddon;
+ let installToDirPath = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsIFile);
+ let path = OS.Path.join(OS.Constants.Path.profileDir,
+ gmpAddon.id,
+ gmpAddon.version);
+ installToDirPath.initWithPath(path);
+ log.info("install to directory path: " + installToDirPath.path);
+ let gmpInstaller = new GMPExtractor(zipPath, installToDirPath.path);
+ let installPromise = gmpInstaller.install();
+ installPromise.then(extractedPaths => {
+ // Success, set the prefs
+ let now = Math.round(Date.now() / 1000);
+ GMPPrefs.set(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
+ // Setting the version pref signals installation completion to consumers,
+ // if you need to set other prefs etc. do it before this.
+ GMPPrefs.set(GMPPrefs.KEY_PLUGIN_VERSION, gmpAddon.version,
+ gmpAddon.id);
+ this._deferred.resolve(extractedPaths);
+ }, err => {
+ this._deferred.reject(err);
+ });
+ }, err => {
+ log.warn("verifyDownload check failed");
+ this._deferred.reject({
+ target: this,
+ status: 200,
+ type: "verifyerr"
+ });
+ });
+ },
+ /**
+ * Verifies that the downloaded zip file's hash matches the GMPAddon hash.
+ * @return a promise which resolves if the download verifies
+ */
+ _verifyDownload: function() {
+ let verifyDownloadDeferred = Promise.defer();
+ let log = getScopedLogger("GMPDownloader._verifyDownload");
+ log.info("_verifyDownload called");
+ if (!this._request) {
+ return Promise.reject();
+ }
+
+ let destination = this._request.destination;
+ log.info("for path: " + destination.path);
+
+ // Ensure that the file size matches the expected file size.
+ if (this._gmpAddon.size !== undefined &&
+ destination.fileSize != this._gmpAddon.size) {
+ log.warn("Downloader:_verifyDownload downloaded size " +
+ destination.fileSize + " != expected size " +
+ this._gmpAddon.size + ".");
+ return Promise.reject();
+ }
+
+ let promise = GMPDownloader.computeHash(this._gmpAddon.hashFunction, destination);
+ promise.then(digest => {
+ let expectedDigest = this._gmpAddon.hashValue.toLowerCase();
+ if (digest !== expectedDigest) {
+ log.warn("hashes do not match! Got: `" +
+ digest + "`, expected: `" + expectedDigest + "`");
+ this._deferred.reject();
+ return;
+ }
+
+ log.info("hashes match!");
+ verifyDownloadDeferred.resolve();
+ }, err => {
+ verifyDownloadDeferred.reject();
+ });
+ return verifyDownloadDeferred.promise;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver])
+};
+
+/**
+ * Convert a string containing binary values to hex.
+ */
+function binaryToHex(input) {
+ let result = "";
+ for (let i = 0; i < input.length; ++i) {
+ let hex = input.charCodeAt(i).toString(16);
+ if (hex.length == 1)
+ hex = "0" + hex;
+ result += hex;
+ }
+ return result;
+}
diff --git a/components/addons/src/GMPProvider.jsm b/components/addons/src/GMPProvider.jsm
new file mode 100644
index 000000000..c89427101
--- /dev/null
+++ b/components/addons/src/GMPProvider.jsm
@@ -0,0 +1,605 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/GMPUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(
+ this, "setTimeout", "resource://gre/modules/Timer.jsm");
+
+const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
+const STRING_TYPE_NAME = "type.%ID%.name";
+
+const SEC_IN_A_DAY = 24 * 60 * 60;
+// How long to wait after a user enabled EME before attempting to download CDMs.
+const GMP_CHECK_DELAY = 10 * 1000; // milliseconds
+
+const NS_GRE_DIR = "GreD";
+const CLEARKEY_PLUGIN_ID = "gmp-clearkey";
+const CLEARKEY_VERSION = "0.1";
+
+const GMP_LICENSE_INFO = "gmp_license_info";
+
+const GMP_PLUGINS = [
+ {
+ id: OPEN_H264_ID,
+ name: "openH264_name",
+ description: "openH264_description2",
+ // The following licenseURL is part of an awful hack to include the OpenH264
+ // license without having bug 624602 fixed yet, and intentionally ignores
+ // localisation.
+ licenseURL: "chrome://mozapps/content/extensions/OpenH264-license.txt",
+ homepageURL: "http://www.openh264.org/",
+ optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul"
+ },
+ {
+ id: WIDEVINE_ID,
+ name: "widevine_name",
+ // Describe the purpose of both CDMs in the same way.
+ description: "widevine_description2",
+ licenseURL: "https://www.google.com/policies/privacy/",
+ homepageURL: "https://www.widevine.com/",
+ optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul",
+ isEME: true
+ }];
+XPCOMUtils.defineConstant(this, "GMP_PLUGINS", GMP_PLUGINS);
+
+XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
+ () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
+XPCOMUtils.defineLazyGetter(this, "gmpService",
+ () => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginChromeService));
+
+var messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+var gLogger;
+var gLogAppenderDump = null;
+
+function configureLogging() {
+ if (!gLogger) {
+ gLogger = Log.repository.getLogger("Toolkit.GMP");
+ gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
+ }
+ gLogger.level = GMPPrefs.get(GMPPrefs.KEY_LOGGING_LEVEL, Log.Level.Warn);
+
+ let logDumping = GMPPrefs.get(GMPPrefs.KEY_LOGGING_DUMP, false);
+ if (logDumping != !!gLogAppenderDump) {
+ if (logDumping) {
+ gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
+ gLogger.addAppender(gLogAppenderDump);
+ } else {
+ gLogger.removeAppender(gLogAppenderDump);
+ gLogAppenderDump = null;
+ }
+ }
+}
+
+
+
+/**
+ * The GMPWrapper provides the info for the various GMP plugins to public
+ * callers through the API.
+ */
+function GMPWrapper(aPluginInfo) {
+ this._plugin = aPluginInfo;
+ this._log =
+ Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
+ "GMPWrapper(" +
+ this._plugin.id + ") ");
+ Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
+ this._plugin.id),
+ this.onPrefEnabledChanged, this);
+ Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
+ this._plugin.id),
+ this.onPrefVersionChanged, this);
+ if (this._plugin.isEME) {
+ Preferences.observe(GMPPrefs.KEY_EME_ENABLED,
+ this.onPrefEMEGlobalEnabledChanged, this);
+ messageManager.addMessageListener("EMEVideo:ContentMediaKeysRequest", this);
+ }
+}
+
+GMPWrapper.prototype = {
+ // An active task that checks for plugin updates and installs them.
+ _updateTask: null,
+ _gmpPath: null,
+ _isUpdateCheckPending: false,
+
+ optionsType: AddonManager.OPTIONS_TYPE_INLINE,
+ get optionsURL() { return this._plugin.optionsURL; },
+
+ set gmpPath(aPath) { this._gmpPath = aPath; },
+ get gmpPath() {
+ if (!this._gmpPath && this.isInstalled) {
+ this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
+ this._plugin.id,
+ GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
+ null, this._plugin.id));
+ }
+ return this._gmpPath;
+ },
+
+ get id() { return this._plugin.id; },
+ get type() { return "plugin"; },
+ get isGMPlugin() { return true; },
+ get name() { return this._plugin.name; },
+ get creator() { return null; },
+ get homepageURL() { return this._plugin.homepageURL; },
+
+ get description() { return this._plugin.description; },
+ get fullDescription() { return this._plugin.fullDescription; },
+
+ get version() { return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, null,
+ this._plugin.id); },
+
+ get isActive() { return !this.appDisabled && !this.userDisabled; },
+ get appDisabled() {
+ if (this._plugin.isEME && !GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
+ // If "media.eme.enabled" is false, all EME plugins are disabled.
+ return true;
+ }
+ return false;
+ },
+
+ get userDisabled() {
+ return !GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ENABLED, true, this._plugin.id);
+ },
+ set userDisabled(aVal) { GMPPrefs.set(GMPPrefs.KEY_PLUGIN_ENABLED,
+ aVal === false,
+ this._plugin.id); },
+
+ get blocklistState() { return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; },
+ get size() { return 0; },
+ get scope() { return AddonManager.SCOPE_APPLICATION; },
+ get pendingOperations() { return AddonManager.PENDING_NONE; },
+
+ get operationsRequiringRestart() { return AddonManager.OP_NEEDS_RESTART_NONE },
+
+ get permissions() {
+ let permissions = 0;
+ if (!this.appDisabled) {
+ permissions |= AddonManager.PERM_CAN_UPGRADE;
+ permissions |= this.userDisabled ? AddonManager.PERM_CAN_ENABLE :
+ AddonManager.PERM_CAN_DISABLE;
+ }
+ return permissions;
+ },
+
+ get updateDate() {
+ let time = Number(GMPPrefs.get(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, null,
+ this._plugin.id));
+ if (time !== NaN && this.isInstalled) {
+ return new Date(time * 1000)
+ }
+ return null;
+ },
+
+ get isCompatible() {
+ return true;
+ },
+
+ get isPlatformCompatible() {
+ return true;
+ },
+
+ get providesUpdatesSecurely() {
+ return true;
+ },
+
+ get foreignInstall() {
+ return false;
+ },
+
+ isCompatibleWith: function(aAppVersion, aPlatformVersion) {
+ return true;
+ },
+
+ get applyBackgroundUpdates() {
+ if (!GMPPrefs.isSet(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id)) {
+ return AddonManager.AUTOUPDATE_DEFAULT;
+ }
+
+ return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id) ?
+ AddonManager.AUTOUPDATE_ENABLE : AddonManager.AUTOUPDATE_DISABLE;
+ },
+
+ set applyBackgroundUpdates(aVal) {
+ if (aVal == AddonManager.AUTOUPDATE_DEFAULT) {
+ GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id);
+ } else if (aVal == AddonManager.AUTOUPDATE_ENABLE) {
+ GMPPrefs.set(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id);
+ } else if (aVal == AddonManager.AUTOUPDATE_DISABLE) {
+ GMPPrefs.set(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, false, this._plugin.id);
+ }
+ },
+
+ findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
+ this._log.trace("findUpdates() - " + this._plugin.id + " - reason=" +
+ aReason);
+
+ AddonManagerPrivate.callNoUpdateListeners(this, aListener);
+
+ if (aReason === AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
+ if (!AddonManager.shouldAutoUpdate(this)) {
+ this._log.trace("findUpdates() - " + this._plugin.id +
+ " - no autoupdate");
+ return Promise.resolve(false);
+ }
+
+ let secSinceLastCheck =
+ Date.now() / 1000 - Preferences.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
+ if (secSinceLastCheck <= SEC_IN_A_DAY) {
+ this._log.trace("findUpdates() - " + this._plugin.id +
+ " - last check was less then a day ago");
+ return Promise.resolve(false);
+ }
+ } else if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED) {
+ this._log.trace("findUpdates() - " + this._plugin.id +
+ " - the given reason to update is not supported");
+ return Promise.resolve(false);
+ }
+
+ if (this._updateTask !== null) {
+ this._log.trace("findUpdates() - " + this._plugin.id +
+ " - update task already running");
+ return this._updateTask;
+ }
+
+ this._updateTask = Task.spawn(function* GMPProvider_updateTask() {
+ this._log.trace("findUpdates() - updateTask");
+ try {
+ let installManager = new GMPInstallManager();
+ let gmpAddons = yield installManager.checkForAddons();
+ let update = gmpAddons.find(function(aAddon) {
+ return aAddon.id === this._plugin.id;
+ }, this);
+ if (update && update.isValid && !update.isInstalled) {
+ this._log.trace("findUpdates() - found update for " +
+ this._plugin.id + ", installing");
+ yield installManager.installAddon(update);
+ } else {
+ this._log.trace("findUpdates() - no updates for " + this._plugin.id);
+ }
+ this._log.info("findUpdates() - updateTask succeeded for " +
+ this._plugin.id);
+ } catch (e) {
+ this._log.error("findUpdates() - updateTask for " + this._plugin.id +
+ " threw", e);
+ throw e;
+ } finally {
+ this._updateTask = null;
+ return true;
+ }
+ }.bind(this));
+
+ return this._updateTask;
+ },
+
+ get pluginMimeTypes() { return []; },
+ get pluginLibraries() {
+ if (this.isInstalled) {
+ let path = this.version;
+ return [path];
+ }
+ return [];
+ },
+ get pluginFullpath() {
+ if (this.isInstalled) {
+ let path = OS.Path.join(OS.Constants.Path.profileDir,
+ this._plugin.id,
+ this.version);
+ return [path];
+ }
+ return [];
+ },
+
+ get isInstalled() {
+ return this.version && this.version.length > 0;
+ },
+
+ _handleEnabledChanged: function() {
+ AddonManagerPrivate.callAddonListeners(this.isActive ?
+ "onEnabling" : "onDisabling",
+ this, false);
+ if (this._gmpPath) {
+ if (this.isActive) {
+ this._log.info("onPrefEnabledChanged() - adding gmp directory " +
+ this._gmpPath);
+ gmpService.addPluginDirectory(this._gmpPath);
+ } else {
+ this._log.info("onPrefEnabledChanged() - removing gmp directory " +
+ this._gmpPath);
+ gmpService.removePluginDirectory(this._gmpPath);
+ }
+ }
+ AddonManagerPrivate.callAddonListeners(this.isActive ?
+ "onEnabled" : "onDisabled",
+ this);
+ },
+
+ onPrefEMEGlobalEnabledChanged: function() {
+ AddonManagerPrivate.callAddonListeners("onPropertyChanged", this,
+ ["appDisabled"]);
+ if (this.appDisabled) {
+ this.uninstallPlugin();
+ } else {
+ AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
+ null, false);
+ AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
+ AddonManagerPrivate.callAddonListeners("onInstalled", this);
+ this.checkForUpdates(GMP_CHECK_DELAY);
+ }
+ if (!this.userDisabled) {
+ this._handleEnabledChanged();
+ }
+ },
+
+ checkForUpdates: function(delay) {
+ if (this._isUpdateCheckPending) {
+ return;
+ }
+ this._isUpdateCheckPending = true;
+ GMPPrefs.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK, null);
+ // Delay this in case the user changes his mind and doesn't want to
+ // enable EME after all.
+ setTimeout(() => {
+ if (!this.appDisabled) {
+ let gmpInstallManager = new GMPInstallManager();
+ // We don't really care about the results, if someone is interested
+ // they can check the log.
+ gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
+ }
+ this._isUpdateCheckPending = false;
+ }, delay);
+ },
+
+ receiveMessage: function({target: browser, data: data}) {
+ this._log.trace("receiveMessage() data=" + data);
+ let parsedData;
+ try {
+ parsedData = JSON.parse(data);
+ } catch(ex) {
+ this._log.error("Malformed EME video message with data: " + data);
+ return;
+ }
+ let {status: status, keySystem: keySystem} = parsedData;
+ if (status == "cdm-not-installed" || status == "cdm-insufficient-version") {
+ this.checkForUpdates(0);
+ }
+ },
+
+ onPrefEnabledChanged: function() {
+ if (!this._plugin.isEME || !this.appDisabled) {
+ this._handleEnabledChanged();
+ }
+ },
+
+ onPrefVersionChanged: function() {
+ AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
+ if (this._gmpPath) {
+ this._log.info("onPrefVersionChanged() - unregistering gmp directory " +
+ this._gmpPath);
+ gmpService.removeAndDeletePluginDirectory(this._gmpPath, true /* can defer */);
+ }
+ AddonManagerPrivate.callAddonListeners("onUninstalled", this);
+
+ AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
+ null, false);
+ AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
+ this._gmpPath = null;
+ if (this.isInstalled) {
+ this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
+ this._plugin.id,
+ GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
+ null, this._plugin.id));
+ }
+ if (this._gmpPath && this.isActive) {
+ this._log.info("onPrefVersionChanged() - registering gmp directory " +
+ this._gmpPath);
+ gmpService.addPluginDirectory(this._gmpPath);
+ }
+ AddonManagerPrivate.callAddonListeners("onInstalled", this);
+ },
+
+ uninstallPlugin: function() {
+ AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
+ if (this.gmpPath) {
+ this._log.info("uninstallPlugin() - unregistering gmp directory " +
+ this.gmpPath);
+ gmpService.removeAndDeletePluginDirectory(this.gmpPath);
+ }
+ GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_VERSION, this.id);
+ AddonManagerPrivate.callAddonListeners("onUninstalled", this);
+ },
+
+ shutdown: function() {
+ Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
+ this._plugin.id),
+ this.onPrefEnabledChanged, this);
+ Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
+ this._plugin.id),
+ this.onPrefVersionChanged, this);
+ if (this._plugin.isEME) {
+ Preferences.ignore(GMPPrefs.KEY_EME_ENABLED,
+ this.onPrefEMEGlobalEnabledChanged, this);
+ messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", this);
+ }
+ return this._updateTask;
+ },
+};
+
+var GMPProvider = {
+ get name() { return "GMPProvider"; },
+
+ _plugins: null,
+
+ startup: function() {
+ configureLogging();
+ this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
+ "GMPProvider.");
+ this.buildPluginList();
+ this.ensureProperCDMInstallState();
+
+ Preferences.observe(GMPPrefs.KEY_LOG_BASE, configureLogging);
+
+ for (let [id, plugin] of this._plugins) {
+ let wrapper = plugin.wrapper;
+ let gmpPath = wrapper.gmpPath;
+ let isEnabled = wrapper.isActive;
+ this._log.trace("startup - enabled=" + isEnabled + ", gmpPath=" +
+ gmpPath);
+
+ if (gmpPath && isEnabled) {
+ this._log.info("startup - adding gmp directory " + gmpPath);
+ try {
+ gmpService.addPluginDirectory(gmpPath);
+ } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
+ this._log.warn("startup - adding gmp directory failed with " +
+ e.name + " - sandboxing not available?", e);
+ }
+ }
+ }
+
+ if (Preferences.get(GMPPrefs.KEY_EME_ENABLED, false)) {
+ try {
+ let greDir = Services.dirsvc.get(NS_GRE_DIR,
+ Ci.nsILocalFile);
+ let clearkeyPath = OS.Path.join(greDir.path,
+ CLEARKEY_PLUGIN_ID,
+ CLEARKEY_VERSION);
+ this._log.info("startup - adding clearkey CDM directory " +
+ clearkeyPath);
+ gmpService.addPluginDirectory(clearkeyPath);
+ } catch (e) {
+ this._log.warn("startup - adding clearkey CDM failed", e);
+ }
+ }
+ },
+
+ shutdown: function() {
+ this._log.trace("shutdown");
+ Preferences.ignore(GMPPrefs.KEY_LOG_BASE, configureLogging);
+
+ let shutdownTask = Task.spawn(function* GMPProvider_shutdownTask() {
+ this._log.trace("shutdown - shutdownTask");
+ let shutdownSucceeded = true;
+
+ for (let plugin of this._plugins.values()) {
+ try {
+ yield plugin.wrapper.shutdown();
+ } catch (e) {
+ shutdownSucceeded = false;
+ }
+ }
+
+ this._plugins = null;
+
+ if (!shutdownSucceeded) {
+ throw new Error("Shutdown failed");
+ }
+ }.bind(this));
+
+ return shutdownTask;
+ },
+
+ getAddonByID: function(aId, aCallback) {
+ if (!this.isEnabled) {
+ aCallback(null);
+ return;
+ }
+
+ let plugin = this._plugins.get(aId);
+ if (plugin && !GMPUtils.isPluginHidden(plugin)) {
+ aCallback(plugin.wrapper);
+ } else {
+ aCallback(null);
+ }
+ },
+
+ getAddonsByTypes: function(aTypes, aCallback) {
+ if (!this.isEnabled ||
+ (aTypes && aTypes.indexOf("plugin") < 0)) {
+ aCallback([]);
+ return;
+ }
+
+ // Tycho:
+ // let results = [p.wrapper for ([id, p] of this._plugins)
+ // if (!GMPUtils.isPluginHidden(p))];
+ let results = [];
+ for (let [id, p] of this._plugins) {
+ if (!GMPUtils.isPluginHidden(p)) {
+ results.push(p.wrapper);
+ }
+ }
+
+ aCallback(results);
+ },
+
+ get isEnabled() {
+ return GMPPrefs.get(GMPPrefs.KEY_PROVIDER_ENABLED, false);
+ },
+
+ generateFullDescription: function(aLicenseURL, aLicenseInfo) {
+ return "<xhtml:a href=\"" + aLicenseURL + "\" target=\"_blank\">" +
+ aLicenseInfo + "</xhtml:a>."
+ },
+
+ buildPluginList: function() {
+ let licenseInfo = pluginsBundle.GetStringFromName(GMP_LICENSE_INFO);
+
+ this._plugins = new Map();
+ for (let aPlugin of GMP_PLUGINS) {
+ let plugin = {
+ id: aPlugin.id,
+ name: pluginsBundle.GetStringFromName(aPlugin.name),
+ description: pluginsBundle.GetStringFromName(aPlugin.description),
+ homepageURL: aPlugin.homepageURL,
+ optionsURL: aPlugin.optionsURL,
+ wrapper: null,
+ isEME: aPlugin.isEME,
+ };
+ if (aPlugin.licenseURL) {
+ plugin.fullDescription =
+ this.generateFullDescription(aPlugin.licenseURL, licenseInfo);
+ }
+ plugin.wrapper = new GMPWrapper(plugin);
+ this._plugins.set(plugin.id, plugin);
+ }
+ },
+
+ ensureProperCDMInstallState: function() {
+ if (!GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
+ for (let [id, plugin] of this._plugins) {
+ if (plugin.isEME && plugin.wrapper.isInstalled) {
+ gmpService.addPluginDirectory(plugin.wrapper.gmpPath);
+ plugin.wrapper.uninstallPlugin();
+ }
+ }
+ }
+ },
+};
+
+AddonManagerPrivate.registerProvider(GMPProvider, [
+ new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 6000,
+ AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
+]);
diff --git a/components/addons/src/GMPUtils.jsm b/components/addons/src/GMPUtils.jsm
new file mode 100644
index 000000000..593fc3c8d
--- /dev/null
+++ b/components/addons/src/GMPUtils.jsm
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} =
+ Components;
+
+this.EXPORTED_SYMBOLS = [ "GMP_PLUGIN_IDS",
+ "GMPPrefs",
+ "GMPUtils",
+ "OPEN_H264_ID",
+ "WIDEVINE_ID" ];
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// GMP IDs
+const OPEN_H264_ID = "gmp-gmpopenh264";
+const WIDEVINE_ID = "gmp-widevinecdm";
+const GMP_PLUGIN_IDS = [ OPEN_H264_ID, WIDEVINE_ID ];
+
+var GMPPluginUnsupportedReason = {
+ NOT_WINDOWS: 1,
+ WINDOWS_VERSION: 2,
+};
+
+var GMPPluginHiddenReason = {
+ UNSUPPORTED: 1,
+ EME_DISABLED: 2,
+};
+
+this.GMPUtils = {
+ /**
+ * Checks whether or not a given plugin is hidden. Hidden plugins are neither
+ * downloaded nor displayed in the addons manager.
+ * @param aPlugin
+ * The plugin to check.
+ */
+ isPluginHidden: function(aPlugin) {
+ if (!aPlugin.isEME) {
+ return false;
+ }
+
+ if (!this._isPluginSupported(aPlugin) ||
+ !this._isPluginVisible(aPlugin)) {
+ return true;
+ }
+
+ if (!GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Checks whether or not a given plugin is supported by the current OS.
+ * @param aPlugin
+ * The plugin to check.
+ */
+ _isPluginSupported: function(aPlugin) {
+ if (this._isPluginForceSupported(aPlugin)) {
+ return true;
+ }
+ if (aPlugin.id == WIDEVINE_ID) {
+
+#if defined(XP_WIN) || defined(XP_LINUX)
+ // The Widevine plugin is available for Windows versions Vista and later,
+ // Mac OSX, and Linux.
+ return true;
+#else
+ return false;
+#endif
+ }
+
+ return true;
+ },
+
+ /**
+ * Checks whether or not a given plugin is visible in the addons manager
+ * UI and the "enable DRM" notification box. This can be used to test
+ * plugins that aren't yet turned on in the mozconfig.
+ * @param aPlugin
+ * The plugin to check.
+ */
+ _isPluginVisible: function(aPlugin) {
+ return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VISIBLE, false, aPlugin.id);
+ },
+
+ /**
+ * Checks whether or not a given plugin is forced-supported. This is used
+ * in automated tests to override the checks that prevent GMPs running on an
+ * unsupported platform.
+ * @param aPlugin
+ * The plugin to check.
+ */
+ _isPluginForceSupported: function(aPlugin) {
+ return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_FORCE_SUPPORTED, false, aPlugin.id);
+ },
+};
+
+/**
+ * Manages preferences for GMP addons
+ */
+this.GMPPrefs = {
+ KEY_EME_ENABLED: "media.eme.enabled",
+ KEY_PLUGIN_ENABLED: "media.{0}.enabled",
+ KEY_PLUGIN_LAST_UPDATE: "media.{0}.lastUpdate",
+ KEY_PLUGIN_VERSION: "media.{0}.version",
+ KEY_PLUGIN_AUTOUPDATE: "media.{0}.autoupdate",
+ KEY_PLUGIN_VISIBLE: "media.{0}.visible",
+ KEY_PLUGIN_ABI: "media.{0}.abi",
+ KEY_PLUGIN_FORCE_SUPPORTED: "media.{0}.forceSupported",
+ KEY_URL: "media.gmp-manager.url",
+ KEY_URL_OVERRIDE: "media.gmp-manager.url.override",
+ KEY_CERT_CHECKATTRS: "media.gmp-manager.cert.checkAttributes",
+ KEY_CERT_REQUIREBUILTIN: "media.gmp-manager.cert.requireBuiltIn",
+ KEY_UPDATE_LAST_CHECK: "media.gmp-manager.lastCheck",
+ KEY_SECONDS_BETWEEN_CHECKS: "media.gmp-manager.secondsBetweenChecks",
+ KEY_UPDATE_ENABLED: "media.gmp-manager.updateEnabled",
+ KEY_APP_DISTRIBUTION: "distribution.id",
+ KEY_APP_DISTRIBUTION_VERSION: "distribution.version",
+ KEY_BUILDID: "media.gmp-manager.buildID",
+ KEY_CERTS_BRANCH: "media.gmp-manager.certs.",
+ KEY_PROVIDER_ENABLED: "media.gmp-provider.enabled",
+ KEY_LOG_BASE: "media.gmp.log.",
+ KEY_LOGGING_LEVEL: "media.gmp.log.level",
+ KEY_LOGGING_DUMP: "media.gmp.log.dump",
+
+ /**
+ * Obtains the specified preference in relation to the specified plugin.
+ * @param aKey The preference key value to use.
+ * @param aDefaultValue The default value if no preference exists.
+ * @param aPlugin The plugin to scope the preference to.
+ * @return The obtained preference value, or the defaultValue if none exists.
+ */
+ get: function(aKey, aDefaultValue, aPlugin) {
+ if (aKey === this.KEY_APP_DISTRIBUTION ||
+ aKey === this.KEY_APP_DISTRIBUTION_VERSION) {
+ return Services.prefs.getDefaultBranch(null).getCharPref(aKey, "default");
+ }
+ return Preferences.get(this.getPrefKey(aKey, aPlugin), aDefaultValue);
+ },
+
+ /**
+ * Sets the specified preference in relation to the specified plugin.
+ * @param aKey The preference key value to use.
+ * @param aVal The value to set.
+ * @param aPlugin The plugin to scope the preference to.
+ */
+ set: function(aKey, aVal, aPlugin) {
+ Preferences.set(this.getPrefKey(aKey, aPlugin), aVal);
+ },
+
+ /**
+ * Checks whether or not the specified preference is set in relation to the
+ * specified plugin.
+ * @param aKey The preference key value to use.
+ * @param aPlugin The plugin to scope the preference to.
+ * @return true if the preference is set, false otherwise.
+ */
+ isSet: function(aKey, aPlugin) {
+ return Preferences.isSet(this.getPrefKey(aKey, aPlugin));
+ },
+
+ /**
+ * Resets the specified preference in relation to the specified plugin to its
+ * default.
+ * @param aKey The preference key value to use.
+ * @param aPlugin The plugin to scope the preference to.
+ */
+ reset: function(aKey, aPlugin) {
+ Preferences.reset(this.getPrefKey(aKey, aPlugin));
+ },
+
+ /**
+ * Scopes the specified preference key to the specified plugin.
+ * @param aKey The preference key value to use.
+ * @param aPlugin The plugin to scope the preference to.
+ * @return A preference key scoped to the specified plugin.
+ */
+ getPrefKey: function(aKey, aPlugin) {
+ return aKey.replace("{0}", aPlugin || "");
+ },
+};
diff --git a/components/addons/src/ProductAddonChecker.jsm b/components/addons/src/ProductAddonChecker.jsm
new file mode 100644
index 000000000..c6324da0a
--- /dev/null
+++ b/components/addons/src/ProductAddonChecker.jsm
@@ -0,0 +1,464 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const LOCAL_EME_SOURCES = [{
+ "id": "gmp-gmpopenh264",
+ "src": "chrome://global/content/gmp-sources/openh264.json"
+}, {
+ "id": "gmp-widevinecdm",
+ "src": "chrome://global/content/gmp-sources/widevinecdm.json"
+}];
+
+this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ];
+
+Cu.importGlobalProperties(["XMLHttpRequest"]);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/CertUtils.jsm");
+/* globals checkCert, BadCertHandler*/
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+/* globals GMPPrefs */
+XPCOMUtils.defineLazyModuleGetter(this, "GMPPrefs",
+ "resource://gre/modules/GMPUtils.jsm");
+
+/* globals OS */
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
+ "resource://gre/modules/ServiceRequest.jsm");
+
+// This exists so that tests can override the XHR behaviour for downloading
+// the addon update XML file.
+var CreateXHR = function() {
+ return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsISupports);
+}
+
+var logger = Log.repository.getLogger("addons.productaddons");
+
+/**
+ * Number of milliseconds after which we need to cancel `downloadXML`.
+ *
+ * Bug 1087674 suggests that the XHR we use in `downloadXML` may
+ * never terminate in presence of network nuisances (e.g. strange
+ * antivirus behavior). This timeout is a defensive measure to ensure
+ * that we fail cleanly in such case.
+ */
+const TIMEOUT_DELAY_MS = 20000;
+// Chunk size for the incremental downloader
+const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
+// Incremental downloader interval
+const DOWNLOAD_INTERVAL = 0;
+// How much of a file to read into memory at a time for hashing
+const HASH_CHUNK_SIZE = 8192;
+
+/**
+ * Gets the status of an XMLHttpRequest either directly or from its underlying
+ * channel.
+ *
+ * @param request
+ * The XMLHttpRequest.
+ * @return an integer status value.
+ */
+function getRequestStatus(request) {
+ let status = null;
+ try {
+ status = request.status;
+ }
+ catch (e) {
+ }
+
+ if (status != null) {
+ return status;
+ }
+
+ return request.channel.QueryInterface(Ci.nsIRequest).status;
+}
+
+/**
+ * Downloads an XML document from a URL optionally testing the SSL certificate
+ * for certain attributes.
+ *
+ * @param url
+ * The url to download from.
+ * @param allowNonBuiltIn
+ * Whether to trust SSL certificates without a built-in CA issuer.
+ * @param allowedCerts
+ * The list of certificate attributes to match the SSL certificate
+ * against or null to skip checks.
+ * @return a promise that resolves to the DOM document downloaded or rejects
+ * with a JS exception in case of error.
+ */
+function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) {
+ return new Promise((resolve, reject) => {
+ let request = CreateXHR();
+ // This is here to let unit test code override XHR
+ if (request.wrappedJSObject) {
+ request = request.wrappedJSObject;
+ }
+ request.open("GET", url, true);
+ request.channel.notificationCallbacks = new BadCertHandler(allowNonBuiltIn);
+ // Prevent the request from reading from the cache.
+ request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ // Prevent the request from writing to the cache.
+ request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ // Use conservative TLS settings. See bug 1325501.
+ // TODO move to ServiceRequest.
+ if (request.channel instanceof Ci.nsIHttpChannelInternal) {
+ request.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
+ }
+ request.timeout = TIMEOUT_DELAY_MS;
+
+ request.overrideMimeType("text/xml");
+ // The Cache-Control header is only interpreted by proxies and the
+ // final destination. It does not help if a resource is already
+ // cached locally.
+ request.setRequestHeader("Cache-Control", "no-cache");
+ // HTTP/1.0 servers might not implement Cache-Control and
+ // might only implement Pragma: no-cache
+ request.setRequestHeader("Pragma", "no-cache");
+
+ let fail = (event) => {
+ let request = event.target;
+ let status = getRequestStatus(request);
+ let message = "Failed downloading XML, status: " + status + ", reason: " + event.type;
+ logger.warn(message);
+ let ex = new Error(message);
+ ex.status = status;
+ reject(ex);
+ };
+
+ let success = (event) => {
+ logger.info("Completed downloading document");
+ let request = event.target;
+
+ try {
+ checkCert(request.channel, allowNonBuiltIn, allowedCerts);
+ } catch (ex) {
+ logger.error("Request failed certificate checks: " + ex);
+ ex.status = getRequestStatus(request);
+ reject(ex);
+ return;
+ }
+
+ resolve(request.responseXML);
+ };
+
+ request.addEventListener("error", fail, false);
+ request.addEventListener("abort", fail, false);
+ request.addEventListener("timeout", fail, false);
+ request.addEventListener("load", success, false);
+
+ logger.info("sending request to: " + url);
+ request.send(null);
+ });
+}
+
+function downloadJSON(uri) {
+ logger.info("fetching config from: " + uri);
+ return new Promise((resolve, reject) => {
+ let xmlHttp = new ServiceRequest({mozAnon: true});
+
+ xmlHttp.onload = function(aResponse) {
+ resolve(JSON.parse(this.responseText));
+ };
+
+ xmlHttp.onerror = function(e) {
+ reject("Fetching " + uri + " results in error code: " + e.target.status);
+ };
+
+ xmlHttp.open("GET", uri);
+ xmlHttp.overrideMimeType("application/json");
+ xmlHttp.send();
+ });
+}
+
+
+/**
+ * Parses a list of add-ons from a DOM document.
+ *
+ * @param document
+ * The DOM document to parse.
+ * @return null if there is no <addons> element otherwise an object containing
+ * an array of the addons listed and a field notifying whether the
+ * fallback was used.
+ */
+function parseXML(document) {
+ // Check that the root element is correct
+ if (document.documentElement.localName != "updates") {
+ throw new Error("got node name: " + document.documentElement.localName +
+ ", expected: updates");
+ }
+
+ // Check if there are any addons elements in the updates element
+ let addons = document.querySelector("updates:root > addons");
+ if (!addons) {
+ return null;
+ }
+
+ let results = [];
+ let addonList = document.querySelectorAll("updates:root > addons > addon");
+ for (let addonElement of addonList) {
+ let addon = {};
+
+ for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) {
+ if (addonElement.hasAttribute(name)) {
+ addon[name] = addonElement.getAttribute(name);
+ }
+ }
+ addon.size = Number(addon.size) || undefined;
+
+ results.push(addon);
+ }
+
+ return {
+ usedFallback: false,
+ gmpAddons: results
+ };
+}
+
+/**
+ * If downloading from the network fails (AUS server is down),
+ * load the sources from local build configuration.
+ */
+function downloadLocalConfig() {
+
+ if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
+ logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
+ return Promise.resolve({usedFallback: true, gmpAddons: []});
+ }
+
+ return Promise.all(LOCAL_EME_SOURCES.map(conf => {
+ return downloadJSON(conf.src).then(addons => {
+
+ let platforms = addons.vendors[conf.id].platforms;
+ let target = Services.appinfo.OS + "_" + UpdateUtils.ABI;
+ let details = null;
+
+ while (!details) {
+ if (!(target in platforms)) {
+ // There was no matching platform so return false, this addon
+ // will be filtered from the results below
+ logger.info("no details found for: " + target);
+ return false;
+ }
+ // Field either has the details of the binary or is an alias
+ // to another build target key that does
+ if (platforms[target].alias) {
+ target = platforms[target].alias;
+ } else {
+ details = platforms[target];
+ }
+ }
+
+ logger.info("found plugin: " + conf.id);
+ return {
+ "id": conf.id,
+ "URL": details.fileUrl,
+ "hashFunction": addons.hashFunction,
+ "hashValue": details.hashValue,
+ "version": addons.vendors[conf.id].version,
+ "size": details.filesize
+ };
+ });
+ })).then(addons => {
+
+ // Some filters may not match this platform so
+ // filter those out
+ addons = addons.filter(x => x !== false);
+
+ return {
+ usedFallback: true,
+ gmpAddons: addons
+ };
+ });
+}
+
+/**
+ * Downloads file from a URL using XHR.
+ *
+ * @param url
+ * The url to download from.
+ * @return a promise that resolves to the path of a temporary file or rejects
+ * with a JS exception in case of error.
+ */
+function downloadFile(url) {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.onload = function(response) {
+ logger.info("downloadXHR File download. status=" + xhr.status);
+ if (xhr.status != 200 && xhr.status != 206) {
+ reject(Components.Exception("File download failed", xhr.status));
+ return;
+ }
+ Task.spawn(function* () {
+ let f = yield OS.File.openUnique(OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon"));
+ let path = f.path;
+ logger.info(`Downloaded file will be saved to ${path}`);
+ yield f.file.close();
+ yield OS.File.writeAtomic(path, new Uint8Array(xhr.response));
+ return path;
+ }).then(resolve, reject);
+ };
+
+ let fail = (event) => {
+ let request = event.target;
+ let status = getRequestStatus(request);
+ let message = "Failed downloading via XHR, status: " + status + ", reason: " + event.type;
+ logger.warn(message);
+ let ex = new Error(message);
+ ex.status = status;
+ reject(ex);
+ };
+ xhr.addEventListener("error", fail);
+ xhr.addEventListener("abort", fail);
+
+ xhr.responseType = "arraybuffer";
+ try {
+ xhr.open("GET", url);
+ // Use conservative TLS settings. See bug 1325501.
+ // TODO move to ServiceRequest.
+ if (xhr.channel instanceof Ci.nsIHttpChannelInternal) {
+ xhr.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
+ }
+ xhr.send(null);
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+}
+
+/**
+ * Convert a string containing binary values to hex.
+ */
+function binaryToHex(input) {
+ let result = "";
+ for (let i = 0; i < input.length; ++i) {
+ let hex = input.charCodeAt(i).toString(16);
+ if (hex.length == 1) {
+ hex = "0" + hex;
+ }
+ result += hex;
+ }
+ return result;
+}
+
+/**
+ * Calculates the hash of a file.
+ *
+ * @param hashFunction
+ * The type of hash function to use, must be supported by nsICryptoHash.
+ * @param path
+ * The path of the file to hash.
+ * @return a promise that resolves to hash of the file or rejects with a JS
+ * exception in case of error.
+ */
+var computeHash = Task.async(function*(hashFunction, path) {
+ let file = yield OS.File.open(path, { existing: true, read: true });
+ try {
+ let hasher = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ hasher.initWithString(hashFunction);
+
+ let bytes;
+ do {
+ bytes = yield file.read(HASH_CHUNK_SIZE);
+ hasher.update(bytes, bytes.length);
+ } while (bytes.length == HASH_CHUNK_SIZE);
+
+ return binaryToHex(hasher.finish(false));
+ }
+ finally {
+ yield file.close();
+ }
+});
+
+/**
+ * Verifies that a downloaded file matches what was expected.
+ *
+ * @param properties
+ * The properties to check, `size` and `hashFunction` with `hashValue`
+ * are supported. Any properties missing won't be checked.
+ * @param path
+ * The path of the file to check.
+ * @return a promise that resolves if the file matched or rejects with a JS
+ * exception in case of error.
+ */
+var verifyFile = Task.async(function*(properties, path) {
+ if (properties.size !== undefined) {
+ let stat = yield OS.File.stat(path);
+ if (stat.size != properties.size) {
+ throw new Error("Downloaded file was " + stat.size + " bytes but expected " + properties.size + " bytes.");
+ }
+ }
+
+ if (properties.hashFunction !== undefined) {
+ let expectedDigest = properties.hashValue.toLowerCase();
+ let digest = yield computeHash(properties.hashFunction, path);
+ if (digest != expectedDigest) {
+ throw new Error("Hash was `" + digest + "` but expected `" + expectedDigest + "`.");
+ }
+ }
+});
+
+const ProductAddonChecker = {
+ /**
+ * Downloads a list of add-ons from a URL optionally testing the SSL
+ * certificate for certain attributes.
+ *
+ * @param url
+ * The url to download from.
+ * @param allowNonBuiltIn
+ * Whether to trust SSL certificates without a built-in CA issuer.
+ * @param allowedCerts
+ * The list of certificate attributes to match the SSL certificate
+ * against or null to skip checks.
+ * @return a promise that resolves to an object containing the list of add-ons
+ * and whether the local fallback was used, or rejects with a JS
+ * exception in case of error.
+ */
+ getProductAddonList: function(url, allowNonBuiltIn = false, allowedCerts = null) {
+ if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
+ logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
+ return Promise.resolve({usedFallback: true, gmpAddons: []});
+ }
+
+ return downloadXML(url, allowNonBuiltIn, allowedCerts)
+ .then(parseXML)
+ .catch(downloadLocalConfig);
+ },
+
+ /**
+ * Downloads an add-on to a local file and checks that it matches the expected
+ * file. The caller is responsible for deleting the temporary file returned.
+ *
+ * @param addon
+ * The addon to download.
+ * @return a promise that resolves to the temporary file downloaded or rejects
+ * with a JS exception in case of error.
+ */
+ downloadAddon: Task.async(function*(addon) {
+ let path = yield downloadFile(addon.URL);
+ try {
+ yield verifyFile(addon, path);
+ return path;
+ }
+ catch (e) {
+ yield OS.File.remove(path);
+ throw e;
+ }
+ })
+}
diff --git a/components/crashes/CrashManager.jsm b/components/crashes/CrashManager.jsm
index 199b9185a..537ab328d 100644
--- a/components/crashes/CrashManager.jsm
+++ b/components/crashes/CrashManager.jsm
@@ -128,6 +128,9 @@ this.CrashManager.prototype = Object.freeze({
// A crash in a plugin process.
PROCESS_TYPE_PLUGIN: "plugin",
+ // A crash in a Gecko media plugin process.
+ PROCESS_TYPE_GMPLUGIN: "gmplugin",
+
// A crash in the GPU process.
PROCESS_TYPE_GPU: "gpu",
diff --git a/components/crashes/CrashService.js b/components/crashes/CrashService.js
index ef955676c..56f8b69e7 100644
--- a/components/crashes/CrashService.js
+++ b/components/crashes/CrashService.js
@@ -34,6 +34,9 @@ CrashService.prototype = Object.freeze({
case Ci.nsICrashService.PROCESS_TYPE_PLUGIN:
processType = Services.crashmanager.PROCESS_TYPE_PLUGIN;
break;
+ case Ci.nsICrashService.PROCESS_TYPE_GMPLUGIN:
+ processType = Services.crashmanager.PROCESS_TYPE_GMPLUGIN;
+ break;
case Ci.nsICrashService.PROCESS_TYPE_GPU:
processType = Services.crashmanager.PROCESS_TYPE_GPU;
break;
diff --git a/components/crashes/nsICrashService.idl b/components/crashes/nsICrashService.idl
index d3ce8c19d..57a412804 100644
--- a/components/crashes/nsICrashService.idl
+++ b/components/crashes/nsICrashService.idl
@@ -22,6 +22,7 @@ interface nsICrashService : nsISupports
const long PROCESS_TYPE_MAIN = 0;
const long PROCESS_TYPE_CONTENT = 1;
const long PROCESS_TYPE_PLUGIN = 2;
+ const long PROCESS_TYPE_GMPLUGIN = 3;
const long PROCESS_TYPE_GPU = 4;
const long CRASH_TYPE_CRASH = 0;
diff --git a/components/global/content/gmp-sources/openh264.json b/components/global/content/gmp-sources/openh264.json
new file mode 100644
index 000000000..7c6eb0197
--- /dev/null
+++ b/components/global/content/gmp-sources/openh264.json
@@ -0,0 +1,57 @@
+{
+ "vendors": {
+ "gmp-gmpopenh264": {
+ "platforms": {
+ "WINNT_x86-msvc-x64": {
+ "alias": "WINNT_x86-msvc"
+ },
+ "WINNT_x86-msvc": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-win32-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "991e01c3b95fa13fac52e0512e1936f1edae42ecbbbcc55447a36915eb3ca8f836546cc780343751691e0188872e5bc56fe3ad5f23f3243e90b96a637561b89e",
+ "filesize": 356940
+ },
+ "WINNT_x86-msvc-x86": {
+ "alias": "WINNT_x86-msvc"
+ },
+ "Linux_x86_64-gcc3": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-linux64-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "e1086ee6e4fb60a1aa11b5626594b97695533a8e269d776877cebd5cf29088619e2c164e7bd1eba5486f772c943f2efec723f69cc48478ec84a11d7b61ca1865",
+ "filesize": 515722
+ },
+ "Darwin_x86-gcc3-u-i386-x86_64": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx32-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "64b0e13e6319b7a31ed35a46bea5abcfe6af04ba59a277db07677236cfb685813763731ff6b44b85e03e1489f3b15f8df0128a299a36720531b9f4ba6e1c1f58",
+ "filesize": 382435
+ },
+ "Darwin_x86_64-gcc3": {
+ "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"
+ },
+ "Linux_x86-gcc3": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-linux32-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "19084f0230218c584715861f4723e072b1af02e26995762f368105f670f60ecb4082531bc4e33065a4675dd1296f6872a6cb101547ef2d19ef3e25e2e16d4dc0",
+ "filesize": 515857
+ },
+ "Darwin_x86_64-gcc3-u-i386-x86_64": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx64-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "3b52343070a2f75e91b7b0d3bb33935352237c7e1d2fdc6a467d039ffbbda6a72087f9e0a369fe95e6c4c789ff3052f0c134af721d7273db9ba66d077d85b327",
+ "filesize": 390308
+ },
+ "Darwin_x86-gcc3": {
+ "alias": "Darwin_x86-gcc3-u-i386-x86_64"
+ },
+ "WINNT_x86_64-msvc-x64": {
+ "alias": "WINNT_x86_64-msvc"
+ },
+ "WINNT_x86_64-msvc": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-win64-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "5030b47065e817db5c40bca9c62ac27292bbf636e24698f45dc67f03fa6420b97bd2f792c1cb39df65776c1e7597c70122ac7abf36fb2ad0603734e9e8ec4ef3",
+ "filesize": 404355
+ }
+ },
+ "version": "1.6"
+ }
+ },
+ "hashFunction": "sha512",
+ "name": "OpenH264-1.6",
+ "schema_version": 1000
+}
diff --git a/components/global/content/gmp-sources/widevinecdm.json b/components/global/content/gmp-sources/widevinecdm.json
new file mode 100644
index 000000000..02ef7fee5
--- /dev/null
+++ b/components/global/content/gmp-sources/widevinecdm.json
@@ -0,0 +1,49 @@
+{
+ "vendors": {
+ "gmp-widevinecdm": {
+ "platforms": {
+ "WINNT_x86-msvc-x64": {
+ "alias": "WINNT_x86-msvc"
+ },
+ "WINNT_x86-msvc": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-win-ia32.zip",
+ "hashValue": "d7e10d09c87a157af865f8388ba70ae672bd9e38987bdd94077af52d6b1abaa745b3db92e9f93f607af6420c68210f7cfd518a9d2c99fecf79aed3385cbcbc0b",
+ "filesize": 2884452
+ },
+ "WINNT_x86-msvc-x86": {
+ "alias": "WINNT_x86-msvc"
+ },
+ "Linux_x86_64-gcc3": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-linux-x64.zip",
+ "hashValue": "1edfb58a44792d2a53694f46fcc698161edafb2a1fe0e5c31b50c1d52408b5e8918d9f33271c62a19a65017694ebeacb4f390fe914688ca7b1952cdb84ed55ec",
+ "filesize": 2975492
+ },
+ "Darwin_x86_64-gcc3-u-i386-x86_64": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-mac-x64.zip",
+ "hashValue": "1916805e84a49e04748204f4e4c48ae52c8312f7c04afedacacd7dfab2de424412bc988a8c3e5bcb0865f8844b569c0eb9589dae51e74d9bdfe46792c9d1631f",
+ "filesize": 2155607
+ },
+ "Darwin_x86_64-gcc3": {
+ "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"
+ },
+ "Linux_x86-gcc3": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-linux-ia32.zip",
+ "hashValue": "5c4beb72ea693740a013b60bc5491a042bb82fa5ca6845a2f450579e2e1e465263f19e7ab6d08d91deb8219b30f092ab6e6745300d5adda627f270c95e5a66e0",
+ "filesize": 3084582
+ },
+ "WINNT_x86_64-msvc-x64": {
+ "alias": "WINNT_x86_64-msvc"
+ },
+ "WINNT_x86_64-msvc": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-win-x64.zip",
+ "hashValue": "33497f3458846e11fa52413f6477bfe1a7f502da262c3a2ce9fe6d773a4a2d023c54228596eb162444b55c87fb126de01f60fa729d897ef5e6eec73b2dfbdc7a",
+ "filesize": 2853777
+ }
+ },
+ "version": "1.4.8.903"
+ }
+ },
+ "hashFunction": "sha512",
+ "name": "Widevine-1.4.8.903",
+ "schema_version": 1000
+}
diff --git a/components/global/content/process-content.js b/components/global/content/process-content.js
index 9d7e37100..2ff8f908a 100644
--- a/components/global/content/process-content.js
+++ b/components/global/content/process-content.js
@@ -14,6 +14,13 @@ Cu.import("resource://gre/modules/Services.jsm");
const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+Services.cpmm.addMessageListener("gmp-plugin-crash", msg => {
+ let gmpservice = Cc["@mozilla.org/gecko-media-plugin-service;1"]
+ .getService(Ci.mozIGeckoMediaPluginService);
+
+ gmpservice.RunPluginCrashCallbacks(msg.data.pluginID, msg.data.pluginName);
+});
+
if (gInContentProcess) {
let ProcessObserver = {
TOPICS: [
diff --git a/components/global/jar.mn b/components/global/jar.mn
index b02587ec4..5086663c2 100644
--- a/components/global/jar.mn
+++ b/components/global/jar.mn
@@ -1,6 +1,8 @@
toolkit.jar:
% content global %content/global/ contentaccessible=yes
% content global-platform %content/global-platform/ platform
+ content/global/gmp-sources/openh264.json (content/gmp-sources/openh264.json)
+ content/global/gmp-sources/widevinecdm.json (content/gmp-sources/widevinecdm.json)
content/global/XPCNativeWrapper.js (content/XPCNativeWrapper.js)
content/global/minimal-xul.css (content/minimal-xul.css)
* content/global/xul.css (content/xul.css)
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
index e72e95a61..11fe91b9a 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -88,6 +88,11 @@
#endif
#include "mozilla/dom/ContentChild.h"
+#ifdef MOZ_EME
+#include "mozilla/EMEUtils.h"
+#include "mozilla/DetailedPromise.h"
+#endif
+
namespace mozilla {
namespace dom {
@@ -190,6 +195,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+#ifdef MOZ_EME
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
+#endif
#ifdef MOZ_GAMEPAD
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
#endif
@@ -250,6 +258,13 @@ Navigator::Invalidate()
mServiceWorkerContainer = nullptr;
+#ifdef MOZ_EME
+ if (mMediaKeySystemAccessManager) {
+ mMediaKeySystemAccessManager->Shutdown();
+ mMediaKeySystemAccessManager = nullptr;
+ }
+#endif
+
#ifdef MOZ_GAMEPAD
if (mGamepadServiceTest) {
mGamepadServiceTest->Shutdown();
@@ -1483,5 +1498,131 @@ Navigator::GetUserAgent(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
return siteSpecificUA->GetUserAgentForURIAndWindow(aURI, aWindow, aUserAgent);
}
+#ifdef MOZ_EME
+static nsCString
+ToCString(const nsString& aString)
+{
+ nsCString str("'");
+ str.Append(NS_ConvertUTF16toUTF8(aString));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeysRequirement aValue)
+{
+ nsCString str("'");
+ str.Append(nsDependentCString(MediaKeysRequirementValues::strings[static_cast<uint32_t>(aValue)].value));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeySystemMediaCapability& aValue)
+{
+ nsCString str;
+ str.AppendLiteral("{contentType=");
+ str.Append(ToCString(aValue.mContentType));
+ str.AppendLiteral(", robustness=");
+ str.Append(ToCString(aValue.mRobustness));
+ str.AppendLiteral("}");
+ return str;
+}
+
+template<class Type>
+static nsCString
+ToCString(const Sequence<Type>& aSequence)
+{
+ nsCString str;
+ str.AppendLiteral("[");
+ for (size_t i = 0; i < aSequence.Length(); i++) {
+ if (i != 0) {
+ str.AppendLiteral(",");
+ }
+ str.Append(ToCString(aSequence[i]));
+ }
+ str.AppendLiteral("]");
+ return str;
+}
+
+template<class Type>
+static nsCString
+ToCString(const Optional<Sequence<Type>>& aOptional)
+{
+ nsCString str;
+ if (aOptional.WasPassed()) {
+ str.Append(ToCString(aOptional.Value()));
+ } else {
+ str.AppendLiteral("[]");
+ }
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeySystemConfiguration& aConfig)
+{
+ nsCString str;
+ str.AppendLiteral("{label=");
+ str.Append(ToCString(aConfig.mLabel));
+
+ str.AppendLiteral(", initDataTypes=");
+ str.Append(ToCString(aConfig.mInitDataTypes));
+
+ str.AppendLiteral(", audioCapabilities=");
+ str.Append(ToCString(aConfig.mAudioCapabilities));
+
+ str.AppendLiteral(", videoCapabilities=");
+ str.Append(ToCString(aConfig.mVideoCapabilities));
+
+ str.AppendLiteral(", distinctiveIdentifier=");
+ str.Append(ToCString(aConfig.mDistinctiveIdentifier));
+
+ str.AppendLiteral(", persistentState=");
+ str.Append(ToCString(aConfig.mPersistentState));
+
+ str.AppendLiteral(", sessionTypes=");
+ str.Append(ToCString(aConfig.mSessionTypes));
+
+ str.AppendLiteral("}");
+
+ return str;
+}
+
+static nsCString
+RequestKeySystemAccessLogString(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ nsCString str;
+ str.AppendPrintf("Navigator::RequestMediaKeySystemAccess(keySystem='%s' options=",
+ NS_ConvertUTF16toUTF8(aKeySystem).get());
+ str.Append(ToCString(aConfigs));
+ str.AppendLiteral(")");
+ return str;
+}
+
+already_AddRefed<Promise>
+Navigator::RequestMediaKeySystemAccess(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ ErrorResult& aRv)
+{
+ EME_LOG("%s", RequestKeySystemAccessLogString(aKeySystem, aConfigs).get());
+
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+ RefPtr<DetailedPromise> promise =
+ DetailedPromise::Create(go, aRv,
+ NS_LITERAL_CSTRING("navigator.requestMediaKeySystemAccess"));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!mMediaKeySystemAccessManager) {
+ mMediaKeySystemAccessManager = new MediaKeySystemAccessManager(mWindow);
+ }
+
+ mMediaKeySystemAccessManager->Request(promise, aKeySystem, aConfigs);
+ return promise.forget();
+}
+#endif
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
index a1c4794c4..2915b5069 100644
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -17,6 +17,9 @@
#include "nsString.h"
#include "nsTArray.h"
#include "nsWeakPtr.h"
+#ifdef MOZ_EME
+#include "mozilla/dom/MediaKeySystemAccessManager.h"
+#endif
class nsPluginArray;
class nsMimeTypeArray;
@@ -235,6 +238,15 @@ public:
// any, else null.
static already_AddRefed<nsPIDOMWindowInner> GetWindowFromGlobal(JSObject* aGlobal);
+#ifdef MOZ_EME
+ already_AddRefed<Promise>
+ RequestMediaKeySystemAccess(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig,
+ ErrorResult& aRv);
+private:
+ RefPtr<MediaKeySystemAccessManager> mMediaKeySystemAccessManager;
+#endif
+
private:
virtual ~Navigator();
diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
index 08c78c88d..52113f4d8 100644
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -4289,6 +4289,32 @@ nsDocument::SetScopeObject(nsIGlobalObject* aGlobal)
}
}
+#ifdef MOZ_EME
+static void
+CheckIfContainsEMEContent(nsISupports* aSupports, void* aContainsEME)
+{
+ nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aSupports));
+ if (domMediaElem) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(domMediaElem));
+ MOZ_ASSERT(content, "aSupports is not a content");
+ HTMLMediaElement* mediaElem = static_cast<HTMLMediaElement*>(content.get());
+ bool* contains = static_cast<bool*>(aContainsEME);
+ if (mediaElem->GetMediaKeys()) {
+ *contains = true;
+ }
+ }
+}
+
+bool
+nsDocument::ContainsEMEContent()
+{
+ bool containsEME = false;
+ EnumerateActivityObservers(CheckIfContainsEMEContent,
+ static_cast<void*>(&containsEME));
+ return containsEME;
+}
+#endif // MOZ_EME
+
static void
CheckIfContainsMSEContent(nsISupports* aSupports, void* aContainsMSE)
{
@@ -8065,6 +8091,14 @@ nsDocument::CanSavePresentation(nsIRequest *aNewRequest)
return false;
}
+#ifdef MOZ_EME
+ // Don't save presentations for documents containing EME content, so that
+ // CDMs reliably shutdown upon user navigation.
+ if (ContainsEMEContent()) {
+ return false;
+ }
+#endif
+
// Don't save presentations for documents containing MSE content, to
// reduce memory usage.
if (ContainsMSEContent()) {
diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h
index 024aaaa7e..010f95ae2 100644
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1115,6 +1115,10 @@ public:
js::ExpandoAndGeneration mExpandoAndGeneration;
+#ifdef MOZ_EME
+ bool ContainsEMEContent();
+#endif
+
bool ContainsMSEContent();
protected:
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index 7d00b2cb2..3965d40f8 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5008,6 +5008,13 @@ CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
return;
}
+#ifdef MOZ_EME
+ if (video->ContainsRestrictedContent()) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+#endif
+
uint16_t readyState;
if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp
index 9b3b725c6..6774504a4 100644
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -12,6 +12,9 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/AsyncEventDispatcher.h"
+#ifdef MOZ_EME
+#include "mozilla/dom/MediaEncryptedEvent.h"
+#endif
#include "base/basictypes.h"
#include "nsIDOMHTMLMediaElement.h"
@@ -874,6 +877,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTM
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
+#ifdef MOZ_EME
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
+#endif
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -899,6 +905,9 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLE
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
+#ifdef MOZ_EME
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
+#endif
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@@ -1151,6 +1160,9 @@ void HTMLMediaElement::AbortExistingLoads()
mDownloadSuspendedByCache = false;
mMediaInfo = MediaInfo();
mIsEncrypted = false;
+#ifdef MOZ_EME
+ mPendingEncryptedInitData.mInitDatas.Clear();
+#endif
mWaitingForKey = NOT_WAITING_FOR_KEY;
mSourcePointer = nullptr;
@@ -1839,6 +1851,20 @@ nsresult HTMLMediaElement::LoadResource()
// Set the media element's CORS mode only when loading a resource
mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
+#ifdef MOZ_EME
+ bool isBlob = false;
+ if (mMediaKeys &&
+ Preferences::GetBool("media.eme.mse-only", true) &&
+ // We only want mediaSource URLs, but they are BlobURL, so we have to
+ // check the schema and abort if they are not MediaStream or real Blob.
+ (NS_FAILED(mLoadingSrc->SchemeIs(BLOBURI_SCHEME, &isBlob)) ||
+ !isBlob ||
+ IsMediaStreamURI(mLoadingSrc) ||
+ IsBlobURI(mLoadingSrc))) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+#endif
+
HTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc);
if (other && other->mDecoder) {
// Clone it.
@@ -2754,6 +2780,11 @@ HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded,
if (!window) {
return nullptr;
}
+#ifdef MOZ_EME
+ if (ContainsRestrictedContent()) {
+ return nullptr;
+ }
+#endif
if (!mOutputStreams.IsEmpty() &&
aGraph != mOutputStreams[0].mStream->GetInputStream()->Graph()) {
@@ -3950,6 +3981,25 @@ nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
ms.mFinishWhenEnded);
}
+#ifdef MOZ_EME
+ if (mMediaKeys) {
+ if (mMediaKeys->GetCDMProxy()) {
+ mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
+ } else {
+ // CDM must have crashed.
+ ShutdownDecoder();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ MediaEventSource<void>* waitingForKeyProducer = mDecoder->WaitingForKeyEvent();
+ // Not every decoder will produce waitingForKey events, only add ones that can
+ if (waitingForKeyProducer) {
+ mWaitingForKeyListener = waitingForKeyProducer->Connect(
+ AbstractThread::MainThread(), this, &HTMLMediaElement::CannotDecryptWaitingForKey);
+ }
+#endif
+
if (mChannelLoader) {
mChannelLoader->Done();
mChannelLoader = nullptr;
@@ -4404,7 +4454,11 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
SetMediaInfo(*aInfo);
- mIsEncrypted = aInfo->IsEncrypted();
+ mIsEncrypted = aInfo->IsEncrypted()
+#ifdef MOZ_EME
+ || mPendingEncryptedInitData.IsEncrypted()
+#endif
+ ;
mTags = aTags.forget();
mLoadedDataFired = false;
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
@@ -4429,6 +4483,14 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
"Encrypted content not supported outside of MSE"));
return;
}
+
+#ifdef MOZ_EME
+ // Dispatch a distinct 'encrypted' event for each initData we have.
+ for (const auto& initData : mPendingEncryptedInitData.mInitDatas) {
+ DispatchEncrypted(initData.mInitData, initData.mType);
+ }
+ mPendingEncryptedInitData.mInitDatas.Clear();
+#endif
}
mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
@@ -5362,12 +5424,29 @@ void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendE
UpdateSrcMediaStreamPlaying();
UpdateAudioChannelPlayingState();
if (aPauseElement) {
+#ifdef MOZ_EME
+ // For EME content, we may force destruction of the CDM client (and CDM
+ // instance if this is the last client for that CDM instance) and
+ // the CDM's decoder. This ensures the CDM gets reliable and prompt
+ // shutdown notifications, as it may have book-keeping it needs
+ // to do on shutdown.
+ if (mMediaKeys) {
+ mMediaKeys->Shutdown();
+ mMediaKeys = nullptr;
+ if (mDecoder) {
+ ShutdownDecoder();
+ }
+ }
+#endif
if (mDecoder) {
mDecoder->Pause();
mDecoder->Suspend();
}
mEventDeliveryPaused = aSuspendEvents;
} else {
+#ifdef MOZ_EME
+ MOZ_ASSERT(!mMediaKeys);
+#endif
if (mDecoder) {
mDecoder->Resume();
if (!mPaused && !mDecoder->IsEnded()) {
@@ -5404,6 +5483,17 @@ void HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
bool pauseElement = ShouldElementBePaused();
SuspendOrResumeElement(pauseElement, !IsActive());
+#ifdef MOZ_EME
+ // If the owning document has become inactive we should shutdown the CDM.
+ if (!OwnerDoc()->IsCurrentActiveDocument() && mMediaKeys) {
+ mMediaKeys->Shutdown();
+ mMediaKeys = nullptr;
+ if (mDecoder) {
+ ShutdownDecoder();
+ }
+ }
+#endif
+
AddRemoveSelfReference();
}
@@ -6164,6 +6254,236 @@ HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility)
}
+#ifdef MOZ_EME
+MediaKeys*
+HTMLMediaElement::GetMediaKeys() const
+{
+ return mMediaKeys;
+}
+
+bool
+HTMLMediaElement::ContainsRestrictedContent()
+{
+ return GetMediaKeys() != nullptr;
+}
+
+already_AddRefed<Promise>
+HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
+ ErrorResult& aRv)
+{
+ LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p",
+ this, aMediaKeys, mMediaKeys.get(), mDecoder.get()));
+
+ if (MozAudioCaptured()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(OwnerDoc()->GetInnerWindow());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ RefPtr<DetailedPromise> promise = DetailedPromise::Create(global, aRv,
+ NS_LITERAL_CSTRING("HTMLMediaElement.setMediaKeys"));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // 1. If mediaKeys and the mediaKeys attribute are the same object,
+ // return a resolved promise.
+ if (mMediaKeys == aMediaKeys) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ // Note: Our attaching code is synchronous, so we can skip the following steps.
+
+ // 2. If this object's attaching media keys value is true, return a
+ // promise rejected with a new DOMException whose name is InvalidStateError.
+ // 3. Let this object's attaching media keys value be true.
+ // 4. Let promise be a new promise.
+ // 5. Run the following steps in parallel:
+
+ // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
+ // already in use by another media element, and the user agent is unable
+ // to use it with this element, let this object's attaching media keys
+ // value be false and reject promise with a new DOMException whose name
+ // is QuotaExceededError.
+ if (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) {
+ promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
+ NS_LITERAL_CSTRING("MediaKeys object is already bound to another HTMLMediaElement"));
+ return promise.forget();
+ }
+
+ // 5.2 If the mediaKeys attribute is not null, run the following steps:
+ if (mMediaKeys) {
+ // 5.2.1 If the user agent or CDM do not support removing the association,
+ // let this object's attaching media keys value be false and reject promise
+ // with a new DOMException whose name is NotSupportedError.
+
+ // 5.2.2 If the association cannot currently be removed, let this object's
+ // attaching media keys value be false and reject promise with a new
+ // DOMException whose name is InvalidStateError.
+ if (mDecoder) {
+ // We don't support swapping out the MediaKeys once we've started to
+ // setup the playback pipeline. Note this also means we don't need to worry
+ // about handling disassociating the MediaKeys from the MediaDecoder.
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Can't change MediaKeys on HTMLMediaElement after load has started"));
+ return promise.forget();
+ }
+
+ // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
+ // to decrypt media data and remove the association with the media element.
+ mMediaKeys->Unbind();
+ mMediaKeys = nullptr;
+
+ // 5.2.4 If the preceding step failed, let this object's attaching media
+ // keys value be false and reject promise with a new DOMException whose
+ // name is the appropriate error name.
+ }
+
+ // 5.3. If mediaKeys is not null, run the following steps:
+ if (aMediaKeys) {
+ if (!aMediaKeys->GetCDMProxy()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("CDM crashed before binding MediaKeys object to HTMLMediaElement"));
+ return promise.forget();
+ }
+
+ // 5.3.1 Associate the CDM instance represented by mediaKeys with the
+ // media element for decrypting media data.
+ if (NS_FAILED(aMediaKeys->Bind(this))) {
+ // 5.3.2 If the preceding step failed, run the following steps:
+ // 5.3.2.1 Set the mediaKeys attribute to null.
+ mMediaKeys = nullptr;
+ // 5.3.2.2 Let this object's attaching media keys value be false.
+ // 5.3.2.3 Reject promise with a new DOMException whose name is
+ // the appropriate error name.
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Failed to bind MediaKeys object to HTMLMediaElement"));
+ return promise.forget();
+ }
+ // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
+ // algorithm on the media element.
+ // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
+ if (mDecoder) {
+ mDecoder->SetCDMProxy(aMediaKeys->GetCDMProxy());
+ }
+ }
+
+ // 5.4 Set the mediaKeys attribute to mediaKeys.
+ mMediaKeys = aMediaKeys;
+
+ // 5.5 Let this object's attaching media keys value be false.
+
+ // 5.6 Resolve promise.
+ promise->MaybeResolveWithUndefined();
+
+ // 6. Return promise.
+ return promise.forget();
+}
+
+EventHandlerNonNull*
+HTMLMediaElement::GetOnencrypted()
+{
+ return EventTarget::GetEventHandler(nsGkAtoms::onencrypted, EmptyString());
+}
+
+void
+HTMLMediaElement::SetOnencrypted(EventHandlerNonNull* aCallback)
+{
+ EventTarget::SetEventHandler(nsGkAtoms::onencrypted, EmptyString(), aCallback);
+}
+
+EventHandlerNonNull*
+HTMLMediaElement::GetOnwaitingforkey()
+{
+ return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey, EmptyString());
+}
+
+void
+HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull* aCallback)
+{
+ EventTarget::SetEventHandler(nsGkAtoms::onwaitingforkey, EmptyString(), aCallback);
+}
+
+void
+HTMLMediaElement::DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+ const nsAString& aInitDataType)
+{
+ LOG(LogLevel::Debug,
+ ("%p DispatchEncrypted initDataType='%s'",
+ this, NS_ConvertUTF16toUTF8(aInitDataType).get()));
+
+ if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
+ // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now.
+ // Queueing for later dispatch in MetadataLoaded.
+ mPendingEncryptedInitData.AddInitData(aInitDataType, aInitData);
+ return;
+ }
+
+ RefPtr<MediaEncryptedEvent> event;
+ if (IsCORSSameOrigin()) {
+ event = MediaEncryptedEvent::Constructor(this, aInitDataType, aInitData);
+ } else {
+ event = MediaEncryptedEvent::Constructor(this);
+ }
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+bool
+HTMLMediaElement::IsEventAttributeName(nsIAtom* aName)
+{
+ return aName == nsGkAtoms::onencrypted ||
+ nsGenericHTMLElement::IsEventAttributeName(aName);
+}
+
+already_AddRefed<nsIPrincipal>
+HTMLMediaElement::GetTopLevelPrincipal()
+{
+ RefPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+ // XXXkhuey better hope we always have an outer ...
+ nsCOMPtr<nsPIDOMWindowOuter> top = window->GetOuterWindow()->GetTop();
+ if (!top) {
+ return nullptr;
+ }
+ nsIDocument* doc = top->GetExtantDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ principal = doc->NodePrincipal();
+ return principal.forget();
+}
+
+void
+HTMLMediaElement::CannotDecryptWaitingForKey()
+{
+ LOG(LogLevel::Debug, ("%p, CannotDecryptWaitingForKey()", this));
+
+ // http://w3c.github.io/encrypted-media/#wait-for-key
+ // 7.3.4 Queue a "waitingforkey" Event
+ // 1. Let the media element be the specified HTMLMediaElement object.
+ // 2. If the media element's waiting for key value is true, abort these steps.
+ if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
+ // 3. Set the media element's waiting for key value to true.
+ // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
+ // data enqueued in the MDSM is consumed.
+ mWaitingForKey = WAITING_FOR_KEY;
+ UpdateReadyStateInternal();
+ }
+}
+#endif //MOZ_EME
+
NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged(bool aCapture)
{
MOZ_ASSERT(mAudioChannelAgent);
diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h
index 23799b574..bda9924a6 100644
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -18,6 +18,9 @@
#include "mozilla/dom/TextTrackManager.h"
#include "mozilla/WeakPtr.h"
#include "MediaDecoder.h"
+#ifdef MOZ_EME
+#include "mozilla/dom/MediaKeys.h"
+#endif
#include "mozilla/StateWatching.h"
#include "nsGkAtoms.h"
#include "PrincipalChangeObserver.h"
@@ -613,6 +616,30 @@ public:
// XPCOM MozPreservesPitch() is OK
+#ifdef MOZ_EME
+ MediaKeys* GetMediaKeys() const;
+
+ already_AddRefed<Promise> SetMediaKeys(MediaKeys* mediaKeys,
+ ErrorResult& aRv);
+
+ mozilla::dom::EventHandlerNonNull* GetOnencrypted();
+ void SetOnencrypted(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ mozilla::dom::EventHandlerNonNull* GetOnwaitingforkey();
+ void SetOnwaitingforkey(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+ const nsAString& aInitDataType) override;
+
+ bool IsEventAttributeName(nsIAtom* aName) override;
+
+ // Returns the principal of the "top level" document; the origin displayed
+ // in the URL bar of the browser window.
+ already_AddRefed<nsIPrincipal> GetTopLevelPrincipal();
+
+ bool ContainsRestrictedContent();
+#endif // MOZ_EME
+
void CannotDecryptWaitingForKey();
bool MozAutoplayEnabled() const
@@ -1447,6 +1474,11 @@ protected:
// Timer used for updating progress events.
nsCOMPtr<nsITimer> mProgressTimer;
+#ifdef MOZ_EME
+ // Encrypted Media Extension media keys.
+ RefPtr<MediaKeys> mMediaKeys;
+#endif
+
// Stores the time at the start of the current 'played' range.
double mCurrentPlayRangeStart;
@@ -1600,6 +1632,11 @@ protected:
// Listens for waitingForKey events from the owned decoder.
MediaEventListener mWaitingForKeyListener;
+#ifdef MOZ_EME
+ // Init Data that needs to be sent in 'encrypted' events in MetadataLoaded().
+ EncryptionInfo mPendingEncryptedInitData;
+#endif
+
// True if the media's channel's download has been suspended.
Watchable<bool> mDownloadSuspendedByCache;
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
index f1e6c9bc1..8780fbf36 100644
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -59,6 +59,7 @@
#include "mozilla/BasePrincipal.h"
#include "mozilla/WebBrowserPersistDocumentChild.h"
#include "imgLoader.h"
+#include "GMPServiceChild.h"
#include "mozilla/Unused.h"
@@ -154,6 +155,7 @@
#include "mozilla/net/NeckoMessageUtils.h"
#include "mozilla/widget/PuppetBidiKeyboard.h"
#include "mozilla/RemoteSpellCheckEngineChild.h"
+#include "GMPServiceChild.h"
#include "gfxPlatform.h"
#include "nscore.h" // for NS_FREE_PERMANENT_DATA
@@ -163,6 +165,7 @@ using namespace mozilla::dom::ipc;
using namespace mozilla::dom::workers;
using namespace mozilla::media;
using namespace mozilla::embedding;
+using namespace mozilla::gmp;
using namespace mozilla::hal_sandbox;
using namespace mozilla::ipc;
using namespace mozilla::layers;
@@ -1114,6 +1117,20 @@ ContentChild::AllocPContentBridgeParent(mozilla::ipc::Transport* aTransport,
return mLastBridge;
}
+PGMPServiceChild*
+ContentChild::AllocPGMPServiceChild(mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherProcess)
+{
+ return GMPServiceChild::Create(aTransport, aOtherProcess);
+}
+
+bool
+ContentChild::RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities)
+{
+ GeckoMediaPluginServiceChild::UpdateGMPCapabilities(Move(capabilities));
+ return true;
+}
+
bool
ContentChild::RecvInitRendering(Endpoint<PCompositorBridgeChild>&& aCompositor,
Endpoint<PImageBridgeChild>&& aImageBridge,
diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h
index 5ca7a053b..7b2298feb 100644
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -140,6 +140,13 @@ public:
AllocPContentBridgeChild(mozilla::ipc::Transport* transport,
base::ProcessId otherProcess) override;
+ PGMPServiceChild*
+ AllocPGMPServiceChild(mozilla::ipc::Transport* transport,
+ base::ProcessId otherProcess) override;
+
+ bool
+ RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities) override;
+
bool
RecvInitRendering(
Endpoint<PCompositorBridgeChild>&& aCompositor,
diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
index 76ffc5ba6..f20312807 100644
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -21,6 +21,7 @@
#include "AppProcessChecker.h"
#include "AudioChannelService.h"
#include "BlobParent.h"
+#include "GMPServiceParent.h"
#include "HandlerServiceParent.h"
#include "IHistory.h"
#include "imgIContainer.h"
@@ -219,6 +220,7 @@ using namespace mozilla::dom::power;
using namespace mozilla::media;
using namespace mozilla::embedding;
using namespace mozilla::gfx;
+using namespace mozilla::gmp;
using namespace mozilla::hal;
using namespace mozilla::ipc;
using namespace mozilla::layers;
@@ -855,6 +857,12 @@ static nsIDocShell* GetOpenerDocShellHelper(Element* aFrameElement)
}
bool
+ContentParent::RecvCreateGMPService()
+{
+ return PGMPService::Open(this);
+}
+
+bool
ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID)
{
*aRv = NS_OK;
@@ -1249,6 +1257,9 @@ ContentParent::Init()
#endif
}
#endif
+
+ RefPtr<GeckoMediaPluginServiceParent> gmps(GeckoMediaPluginServiceParent::GetSingleton());
+ gmps->UpdateContentProcessGMPCapabilities();
}
void
@@ -2515,6 +2526,13 @@ ContentParent::Observe(nsISupports* aSubject,
return NS_OK;
}
+PGMPServiceParent*
+ContentParent::AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherProcess)
+{
+ return GMPServiceParent::Create(aTransport, aOtherProcess);
+}
+
PBackgroundParent*
ContentParent::AllocPBackgroundParent(Transport* aTransport,
ProcessId aOtherProcess)
diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h
index ab3258e4f..7a4d3d55a 100644
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -237,6 +237,8 @@ public:
virtual bool RecvBridgeToChildProcess(const ContentParentId& aCpId) override;
+ virtual bool RecvCreateGMPService() override;
+
virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
uint32_t* aRunID) override;
@@ -668,6 +670,10 @@ private:
TabParent* aTopLevel, const TabId& aTabId,
uint64_t* aId);
+ PGMPServiceParent*
+ AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherProcess) override;
+
PBackgroundParent*
AllocPBackgroundParent(Transport* aTransport, ProcessId aOtherProcess)
override;
diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl
index 991e90c23..70ef084cd 100644
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -21,7 +21,15 @@ include protocol PImageBridge;
include protocol PMedia;
include protocol PMemoryReportRequest;
include protocol PNecko;
+// FIXME This is pretty ridiculous, but we have to keep the order of the
+// following 4 includes, or the parser is confused about PGMPContent
+// bridging PContent and PGMP. As soon as it registers the bridge between
+// PContent and PPluginModule it seems to think that PContent's parent and
+// child live in the same process!
+include protocol PGMPContent;
+include protocol PGMPService;
include protocol PPluginModule;
+include protocol PGMP;
include protocol PPrinting;
include protocol PSendStream;
include protocol POfflineCacheUpdate;
@@ -202,11 +210,25 @@ struct BlobURLRegistrationData
Principal principal;
};
+struct GMPAPITags
+{
+ nsCString api;
+ nsCString[] tags;
+};
+
+struct GMPCapabilityData
+{
+ nsCString name;
+ nsCString version;
+ GMPAPITags[] capabilities;
+};
+
nested(upto inside_cpow) sync protocol PContent
{
parent spawns PPluginModule;
parent opens PProcessHangMonitor;
+ parent opens PGMPService;
child opens PBackground;
manages PBlob;
@@ -492,6 +514,9 @@ child:
async BlobURLUnregistration(nsCString aURI);
+
+ async GMPsChanged(GMPCapabilityData[] capabilities);
+
parent:
/**
* Tell the content process some attributes of itself. This is
@@ -523,6 +548,8 @@ parent:
returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId);
sync BridgeToChildProcess(ContentParentId cpId);
+ async CreateGMPService();
+
/**
* This call connects the content process to a plugin process. While this
* call runs, a new PluginModuleParent will be created in the ContentChild
diff --git a/dom/locales/en-US/chrome/plugins.properties b/dom/locales/en-US/chrome/plugins.properties
index d3c8464f6..d5e65fea7 100644
--- a/dom/locales/en-US/chrome/plugins.properties
+++ b/dom/locales/en-US/chrome/plugins.properties
@@ -20,3 +20,13 @@ mimetype_label=MIME Type
description_label=Description
suffixes_label=Suffixes
learn_more_label=Learn More
+
+# GMP Plugins
+gmp_license_info=License information
+gmp_privacy_info=Privacy Information
+
+openH264_name=OpenH264 Video Codec provided by Cisco Systems, Inc.
+openH264_description2=This plugin is automatically installed by Mozilla to comply with the WebRTC specification and to enable WebRTC calls with devices that require the H.264 video codec. Visit http://www.openh264.org/ to view the codec source code and learn more about the implementation.
+
+widevine_name=Widevine Content Decryption Module provided by Google Inc.
+widevine_description2=Play back protected web video.
diff --git a/dom/media/AbstractMediaDecoder.h b/dom/media/AbstractMediaDecoder.h
index 119e38bfb..6babcce17 100644
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -16,6 +17,8 @@
#include "nsDataHashtable.h"
#include "nsThreadUtils.h"
+class GMPCrashHelper;
+
namespace mozilla
{
@@ -28,6 +31,9 @@ class MediaResource;
class ReentrantMonitor;
class VideoFrameContainer;
class MediaDecoderOwner;
+#ifdef MOZ_EME
+class CDMProxy;
+#endif
typedef nsDataHashtable<nsCStringHashKey, nsCString> MetadataTags;
@@ -107,6 +113,8 @@ public:
// Set by Reader if the current audio track can be offloaded
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) {}
+ virtual already_AddRefed<GMPCrashHelper> GetCrashHelper() { return nullptr; }
+
// Stack based class to assist in notifying the frame statistics of
// parsed and decoded frames. Use inside video demux & decode functions
// to ensure all parsed and decoded frames are reported on all return paths.
diff --git a/dom/media/DecoderDoctorDiagnostics.cpp b/dom/media/DecoderDoctorDiagnostics.cpp
index 1ca33e14b..778e8c4c5 100644
--- a/dom/media/DecoderDoctorDiagnostics.cpp
+++ b/dom/media/DecoderDoctorDiagnostics.cpp
@@ -839,6 +839,13 @@ DecoderDoctorDiagnostics::GetDescription() const
if (mFFmpegFailedToLoad) {
s += ", Linux platform decoder failed to load";
}
+ if (mGMPPDMFailedToStartup) {
+ s += ", GMP PDM failed to startup";
+ } else if (!mGMP.IsEmpty()) {
+ s += ", Using GMP '";
+ s += mGMP;
+ s += "'";
+ }
break;
case eMediaKeySystemAccessRequest:
s = "key system='";
diff --git a/dom/media/DecoderDoctorDiagnostics.h b/dom/media/DecoderDoctorDiagnostics.h
index 45216b0ef..6ed04cfe3 100644
--- a/dom/media/DecoderDoctorDiagnostics.h
+++ b/dom/media/DecoderDoctorDiagnostics.h
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -78,9 +79,15 @@ public:
void SetFFmpegFailedToLoad() { mFFmpegFailedToLoad = true; }
bool DidFFmpegFailToLoad() const { return mFFmpegFailedToLoad; }
+ void SetGMPPDMFailedToStartup() { mGMPPDMFailedToStartup = true; }
+ bool DidGMPPDMFailToStartup() const { return mGMPPDMFailedToStartup; }
+
void SetVideoNotSupported() { mVideoNotSupported = true; }
void SetAudioNotSupported() { mAudioNotSupported = true; }
+ void SetGMP(const nsACString& aGMP) { mGMP = aGMP; }
+ const nsACString& GMP() const { return mGMP; }
+
const nsAString& KeySystem() const { return mKeySystem; }
bool IsKeySystemSupported() const { return mIsKeySystemSupported; }
enum KeySystemIssue {
@@ -113,8 +120,10 @@ private:
bool mWMFFailedToLoad = false;
bool mFFmpegFailedToLoad = false;
+ bool mGMPPDMFailedToStartup = false;
bool mVideoNotSupported = false;
bool mAudioNotSupported = false;
+ nsCString mGMP;
nsString mKeySystem;
bool mIsKeySystemSupported = false;
diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp
index 3988d37b1..10b97f7e6 100644
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -29,6 +29,7 @@
#include "mozilla/dom/VideoTrack.h"
#include "mozilla/dom/VideoTrackList.h"
#include "nsPrintfCString.h"
+#include "GMPService.h"
#include "Layers.h"
#include "mozilla/layers/ShadowLayers.h"
@@ -385,6 +386,9 @@ MediaDecoder::MediaDecoder(MediaDecoderOwner* aOwner)
, mLogicalPosition(0.0)
, mDuration(std::numeric_limits<double>::quiet_NaN())
, mResourceCallback(new ResourceCallback())
+#ifdef MOZ_EME
+ , mCDMProxyPromise(mCDMProxyPromiseHolder.Ensure(__func__))
+#endif
, mIgnoreProgressData(false)
, mInfiniteStream(false)
, mOwner(aOwner)
@@ -469,6 +473,10 @@ MediaDecoder::Shutdown()
mResourceCallback->Disconnect();
+#ifdef MOZ_EME
+ mCDMProxyPromiseHolder.RejectIfExists(true, __func__);
+#endif
+
DiscardOngoingSeekIfExists();
// This changes the decoder state to SHUTDOWN and does other things
@@ -925,6 +933,34 @@ MediaDecoder::OwnerHasError() const
return mOwner->HasError();
}
+class MediaElementGMPCrashHelper : public GMPCrashHelper
+{
+public:
+ explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement)
+ : mElement(aElement)
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ }
+ already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ if (!mElement) {
+ return nullptr;
+ }
+ return do_AddRef(mElement->OwnerDoc()->GetInnerWindow());
+ }
+private:
+ WeakPtr<HTMLMediaElement> mElement;
+};
+
+already_AddRefed<GMPCrashHelper>
+MediaDecoder::GetCrashHelper()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mOwner->GetMediaElement() ?
+ MakeAndAddRef<MediaElementGMPCrashHelper>(mOwner->GetMediaElement()) : nullptr;
+}
+
bool
MediaDecoder::IsEnded() const
{
@@ -1511,6 +1547,23 @@ MediaDecoder::CanPlayThrough()
return GetStatistics().CanPlayThrough();
}
+#ifdef MOZ_EME
+RefPtr<MediaDecoder::CDMProxyPromise>
+MediaDecoder::RequestCDMProxy() const
+{
+ return mCDMProxyPromise;
+}
+
+void
+MediaDecoder::SetCDMProxy(CDMProxy* aProxy)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aProxy);
+
+ mCDMProxyPromiseHolder.ResolveIfExists(aProxy, __func__);
+}
+#endif
+
bool
MediaDecoder::IsOpusEnabled()
{
diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h
index a0b14b6da..298552433 100644
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -8,6 +9,10 @@
#include "mozilla/Atomics.h"
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#endif
+
#include "mozilla/MozPromise.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/StateMirroring.h"
@@ -242,6 +247,8 @@ public:
// Must be called before Shutdown().
bool OwnerHasError() const;
+ already_AddRefed<GMPCrashHelper> GetCrashHelper() override;
+
protected:
// Updates the media duration. This is called while the media is being
// played, calls before the media has reached loaded metadata are ignored.
@@ -429,6 +436,16 @@ private:
MediaDecoderOwner* GetOwner() const override;
+#ifdef MOZ_EME
+ typedef MozPromise<RefPtr<CDMProxy>, bool /* aIgnored */, /* IsExclusive = */ true> CDMProxyPromise;
+
+ // Resolved when a CDMProxy is available and the capabilities are known or
+ // rejected when this decoder is about to shut down.
+ RefPtr<CDMProxyPromise> RequestCDMProxy() const;
+
+ void SetCDMProxy(CDMProxy* aProxy);
+#endif
+
static bool IsOggEnabled();
static bool IsOpusEnabled();
static bool IsWaveEnabled();
@@ -578,6 +595,11 @@ private:
RefPtr<ResourceCallback> mResourceCallback;
+#ifdef MOZ_EME
+ MozPromiseHolder<CDMProxyPromise> mCDMProxyPromiseHolder;
+ RefPtr<CDMProxyPromise> mCDMProxyPromise;
+#endif
+
protected:
// The promise resolving/rejection is queued as a "micro-task" which will be
// handled immediately after the current JS task and before any pending JS
diff --git a/dom/media/MediaDecoderOwner.h b/dom/media/MediaDecoderOwner.h
index 649d2996f..f993b4324 100644
--- a/dom/media/MediaDecoderOwner.h
+++ b/dom/media/MediaDecoderOwner.h
@@ -142,6 +142,14 @@ public:
// The decoder owner should call Shutdown() on the decoder and drop the
// reference to the decoder to prevent further calls into the decoder.
virtual void NotifyXPCOMShutdown() = 0;
+
+#ifdef MOZ_EME
+ // Dispatches a "encrypted" event to the HTMLMediaElement, with the
+ // provided init data. Actual dispatch may be delayed until HAVE_METADATA.
+ // Main thread only.
+ virtual void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+ const nsAString& aInitDataType) = 0;
+#endif
};
} // namespace mozilla
diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h
index 3a3e2ceb8..dd406ed90 100644
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -24,6 +24,9 @@
namespace mozilla {
+#ifdef MOZ_EME
+class CDMProxy;
+#endif
class MediaDecoderReader;
struct WaitForDataRejectValue
@@ -185,6 +188,10 @@ public:
// when to call SetIdle().
virtual void SetIdle() {}
+#ifdef MOZ_EME
+ virtual void SetCDMProxy(CDMProxy* aProxy) {}
+#endif
+
// Tell the reader that the data decoded are not for direct playback, so it
// can accept more files, in particular those which have more channels than
// available in the audio output.
diff --git a/dom/media/MediaDecoderReaderWrapper.h b/dom/media/MediaDecoderReaderWrapper.h
index c85c93d44..a0f845538 100644
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -112,6 +112,10 @@ public:
return mReader->CanonicalBuffered();
}
+#ifdef MOZ_EME
+ void SetCDMProxy(CDMProxy* aProxy) { mReader->SetCDMProxy(aProxy); }
+#endif
+
void SetVideoBlankDecode(bool aIsBlankDecode);
private:
diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp
index 04916d2f5..c5113bed4 100644
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -1227,7 +1228,33 @@ DecodeMetadataState::OnMetadataRead(MetadataHolder* aMetadata)
mMaster->GetAmpleVideoFrames());
}
- SetState<DecodingFirstFrameState>(SeekJob{});
+ // In general, we wait until we know the duration before notifying the decoder.
+ // However, we notify unconditionally in this case without waiting for the start
+ // time, since the caller might be waiting on metadataloaded to be fired before
+ // feeding in the CDM, which we need to decode the first frame (and
+ // thus get the metadata). We could fix this if we could compute the start
+ // time by demuxing without necessaring decoding.
+ bool waitingForCDM =
+#ifdef MOZ_EME
+ mMaster->Info().IsEncrypted() && !mMaster->mCDMProxy;
+#else
+ false;
+#endif
+
+ mMaster->mNotifyMetadataBeforeFirstFrame =
+ mMaster->mDuration.Ref().isSome() || waitingForCDM;
+
+ if (mMaster->mNotifyMetadataBeforeFirstFrame) {
+ mMaster->EnqueueLoadedMetadataEvent();
+ }
+
+ if (waitingForCDM) {
+ // Metadata parsing was successful but we're still waiting for CDM caps
+ // to become available so that we can build the correct decryptor/decoder.
+ SetState<WaitForCDMState>();
+ } else {
+ SetState<DecodingFirstFrameState>(SeekJob{});
+ }
}
void
@@ -1236,6 +1263,9 @@ DormantState::HandlePlayStateChanged(MediaDecoder::PlayState aPlayState)
{
if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
// Exit dormant when the user wants to play.
+#ifdef MOZ_EME
+ MOZ_ASSERT(!Info().IsEncrypted() || mMaster->mCDMProxy);
+#endif
MOZ_ASSERT(mMaster->mSentFirstFrameLoadedEvent);
SetState<SeekingState>(Move(mPendingSeek), EventVisibility::Suppressed);
}
@@ -1548,6 +1578,10 @@ ShutdownState::Enter()
// dispose of the timer.
master->mVideoDecodeSuspendTimer.Reset();
+#ifdef MOZ_EME
+ master->mCDMProxyPromise.DisconnectIfExists();
+#endif
+
if (master->IsPlaying()) {
master->StopPlayback();
}
@@ -2100,6 +2134,13 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoder* aDecoder)
mMediaSink = CreateMediaSink(mAudioCaptured);
+#ifdef MOZ_EME
+ mCDMProxyPromise.Begin(aDecoder->RequestCDMProxy()->Then(
+ OwnerThread(), __func__, this,
+ &MediaDecoderStateMachine::OnCDMProxyReady,
+ &MediaDecoderStateMachine::OnCDMProxyNotReady));
+#endif
+
nsresult rv = mReader->Init();
NS_ENSURE_SUCCESS(rv, rv);
@@ -3074,6 +3115,25 @@ void MediaDecoderStateMachine::OnMediaSinkAudioError(nsresult aResult)
DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, __func__));
}
+#ifdef MOZ_EME
+void
+MediaDecoderStateMachine::OnCDMProxyReady(RefPtr<CDMProxy> aProxy)
+{
+ MOZ_ASSERT(OnTaskQueue());
+ mCDMProxyPromise.Complete();
+ mCDMProxy = aProxy;
+ mReader->SetCDMProxy(aProxy);
+ mStateObj->HandleCDMProxyReady();
+}
+
+void
+MediaDecoderStateMachine::OnCDMProxyNotReady()
+{
+ MOZ_ASSERT(OnTaskQueue());
+ mCDMProxyPromise.Complete();
+}
+#endif
+
void
MediaDecoderStateMachine::SetAudioCaptured(bool aCaptured)
{
diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h
index 6391c8574..f04f34983 100644
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -767,6 +768,13 @@ private:
// Playback will not start when audio is offloading.
bool mAudioOffloading;
+#ifdef MOZ_EME
+ void OnCDMProxyReady(RefPtr<CDMProxy> aProxy);
+ void OnCDMProxyNotReady();
+ RefPtr<CDMProxy> mCDMProxy;
+ MozPromiseRequestHolder<MediaDecoder::CDMProxyPromise> mCDMProxyPromise;
+#endif
+
private:
// The buffered range. Mirrored from the decoder thread.
Mirror<media::TimeIntervals> mBuffered;
diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp
index 08505f306..98ba57a6e 100644
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -3,6 +3,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#endif
+
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/Preferences.h"
@@ -347,7 +351,12 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack)
if (!mOwner->mPlatform) {
mOwner->mPlatform = new PDMFactory();
if (mOwner->IsEncrypted()) {
+#ifdef MOZ_EME
+ MOZ_ASSERT(mOwner->mCDMProxy);
+ mOwner->mPlatform->SetCDMProxy(mOwner->mCDMProxy);
+#else
return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "EME not supported");
+#endif
}
}
@@ -359,6 +368,7 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack)
: *ownerData.mOriginalInfo->GetAsAudioInfo(),
ownerData.mTaskQueue,
ownerData.mCallback.get(),
+ mOwner->mCrashHelper,
ownerData.mIsBlankDecode,
&result
});
@@ -376,6 +386,7 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack)
ownerData.mCallback.get(),
mOwner->mKnowsCompositor,
mOwner->GetImageContainer(),
+ mOwner->mCrashHelper,
ownerData.mIsBlankDecode,
&result
});
@@ -568,13 +579,61 @@ MediaFormatReader::InitInternal()
mVideo.mTaskQueue =
new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER));
+ // Note: GMPCrashHelper must be created on main thread, as it may use
+ // weak references, which aren't threadsafe.
+ mCrashHelper = mDecoder->GetCrashHelper();
+
return NS_OK;
}
+#ifdef MOZ_EME
+class DispatchKeyNeededEvent : public Runnable {
+public:
+ DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
+ nsTArray<uint8_t>& aInitData,
+ const nsString& aInitDataType)
+ : mDecoder(aDecoder)
+ , mInitData(aInitData)
+ , mInitDataType(aInitDataType)
+ {
+ }
+ NS_IMETHOD Run() override {
+ // Note: Null check the owner, as the decoder could have been shutdown
+ // since this event was dispatched.
+ MediaDecoderOwner* owner = mDecoder->GetOwner();
+ if (owner) {
+ owner->DispatchEncrypted(mInitData, mInitDataType);
+ }
+ mDecoder = nullptr;
+ return NS_OK;
+ }
+private:
+ RefPtr<AbstractMediaDecoder> mDecoder;
+ nsTArray<uint8_t> mInitData;
+ nsString mInitDataType;
+};
+
+void
+MediaFormatReader::SetCDMProxy(CDMProxy* aProxy)
+{
+ RefPtr<CDMProxy> proxy = aProxy;
+ RefPtr<MediaFormatReader> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+ MOZ_ASSERT(self->OnTaskQueue());
+ self->mCDMProxy = proxy;
+ });
+ OwnerThread()->Dispatch(r.forget());
+}
+#endif // MOZ_EME
+
bool
MediaFormatReader::IsWaitingOnCDMResource() {
- /* STUB */
+ MOZ_ASSERT(OnTaskQueue());
+#ifdef MOZ_EME
+ return IsEncrypted() && !mCDMProxy;
+#else
return false;
+#endif
}
RefPtr<MediaDecoderReader::MetadataPromise>
@@ -681,6 +740,13 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
if (mDecoder && crypto && crypto->IsEncrypted()) {
+#ifdef MOZ_EME
+ // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
+ for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
+ NS_DispatchToMainThread(
+ new DispatchKeyNeededEvent(mDecoder, crypto->mInitDatas[i].mInitData, crypto->mInitDatas[i].mType));
+ }
+#endif
mInfo.mCrypto = *crypto;
}
diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h
index 4e084f415..be0b7cd17 100644
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -19,6 +20,10 @@
namespace mozilla {
+#ifdef MOZ_EME
+class CDMProxy;
+#endif
+
class MediaFormatReader final : public MediaDecoderReader
{
typedef TrackInfo::TrackType TrackType;
@@ -88,6 +93,10 @@ public:
return mTrackDemuxersMayBlock;
}
+#ifdef MOZ_EME
+ void SetCDMProxy(CDMProxy* aProxy) override;
+#endif
+
// Returns a string describing the state of the decoder data.
// Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString);
@@ -579,6 +588,12 @@ private:
RefPtr<VideoFrameContainer> mVideoFrameContainer;
layers::ImageContainer* GetImageContainer();
+#ifdef MOZ_EME
+ RefPtr<CDMProxy> mCDMProxy;
+#endif
+
+ RefPtr<GMPCrashHelper> mCrashHelper;
+
void SetBlankDecode(TrackType aTrack, bool aIsBlankDecode);
class DecoderFactory;
diff --git a/dom/media/MediaPrefs.h b/dom/media/MediaPrefs.h
index d05556b38..84163353a 100644
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -35,6 +35,7 @@ static StripAtomic<Type> Get##Name##PrefDefault() { return Default; } \
PrefTemplate<Type, Get##Name##PrefDefault, Get##Name##PrefName> mPref##Name
// Custom Definitions.
+#define GMP_DEFAULT_ASYNC_SHUTDOWN_TIMEOUT 3000
#define SUSPEND_BACKGROUND_VIDEO_DELAY_MS 10000
#define TEST_PREFERENCE_FAKE_RECOGNITION_SERVICE "media.webspeech.test.fake_recognition_service"
@@ -99,6 +100,9 @@ private:
// PlatformDecoderModule
DECL_MEDIA_PREF("media.apple.forcevda", AppleForceVDA, bool, false);
+ DECL_MEDIA_PREF("media.gmp.insecure.allow", GMPAllowInsecure, bool, false);
+ DECL_MEDIA_PREF("media.gmp.async-shutdown-timeout", GMPAsyncShutdownTimeout, uint32_t, GMP_DEFAULT_ASYNC_SHUTDOWN_TIMEOUT);
+ DECL_MEDIA_PREF("media.eme.enabled", EMEEnabled, bool, false);
DECL_MEDIA_PREF("media.use-blank-decoder", PDMUseBlankDecoder, bool, false);
DECL_MEDIA_PREF("media.gpu-process-decoder", PDMUseGPUDecoder, bool, false);
#ifdef MOZ_FFMPEG
@@ -121,6 +125,9 @@ private:
DECL_MEDIA_PREF("media.decoder.fuzzing.enabled", PDMFuzzingEnabled, bool, false);
DECL_MEDIA_PREF("media.decoder.fuzzing.video-output-minimum-interval-ms", PDMFuzzingInterval, uint32_t, 0);
DECL_MEDIA_PREF("media.decoder.fuzzing.dont-delay-inputexhausted", PDMFuzzingDelayInputExhausted, bool, true);
+ DECL_MEDIA_PREF("media.gmp.decoder.enabled", PDMGMPEnabled, bool, true);
+ DECL_MEDIA_PREF("media.gmp.decoder.aac", GMPAACPreferred, uint32_t, 0);
+ DECL_MEDIA_PREF("media.gmp.decoder.h264", GMPH264Preferred, uint32_t, 0);
// MediaDecoderStateMachine
DECL_MEDIA_PREF("media.suspend-bkgnd-video.enabled", MDSMSuspendBackgroundVideoEnabled, bool, false);
diff --git a/dom/media/eme/CDMCaps.cpp b/dom/media/eme/CDMCaps.cpp
new file mode 100644
index 000000000..a30fb59ac
--- /dev/null
+++ b/dom/media/eme/CDMCaps.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/CDMCaps.h"
+#include "mozilla/EMEUtils.h"
+#include "nsThreadUtils.h"
+#include "SamplesWaitingForKey.h"
+
+namespace mozilla {
+
+CDMCaps::CDMCaps()
+ : mMonitor("CDMCaps")
+{
+}
+
+CDMCaps::~CDMCaps()
+{
+}
+
+void
+CDMCaps::Lock()
+{
+ mMonitor.Lock();
+}
+
+void
+CDMCaps::Unlock()
+{
+ mMonitor.Unlock();
+}
+
+CDMCaps::AutoLock::AutoLock(CDMCaps& aInstance)
+ : mData(aInstance)
+{
+ mData.Lock();
+}
+
+CDMCaps::AutoLock::~AutoLock()
+{
+ mData.Unlock();
+}
+
+// Keys with MediaKeyStatus::Usable, MediaKeyStatus::Output_downscaled,
+// or MediaKeyStatus::Output_restricted status can be used by the CDM
+// to decrypt or decrypt-and-decode samples.
+static bool
+IsUsableStatus(dom::MediaKeyStatus aStatus)
+{
+ return aStatus == dom::MediaKeyStatus::Usable ||
+ aStatus == dom::MediaKeyStatus::Output_restricted ||
+ aStatus == dom::MediaKeyStatus::Output_downscaled;
+}
+
+bool
+CDMCaps::AutoLock::IsKeyUsable(const CencKeyId& aKeyId)
+{
+ mData.mMonitor.AssertCurrentThreadOwns();
+ for (const KeyStatus& keyStatus : mData.mKeyStatuses) {
+ if (keyStatus.mId == aKeyId) {
+ return IsUsableStatus(keyStatus.mStatus);
+ }
+ }
+ return false;
+}
+
+bool
+CDMCaps::AutoLock::SetKeyStatus(const CencKeyId& aKeyId,
+ const nsString& aSessionId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus)
+{
+ mData.mMonitor.AssertCurrentThreadOwns();
+
+ if (!aStatus.WasPassed()) {
+ // Called from ForgetKeyStatus.
+ // Return true if the element is found to notify key changes.
+ return mData.mKeyStatuses.RemoveElement(KeyStatus(aKeyId,
+ aSessionId,
+ dom::MediaKeyStatus::Internal_error));
+ }
+
+ KeyStatus key(aKeyId, aSessionId, aStatus.Value());
+ auto index = mData.mKeyStatuses.IndexOf(key);
+ if (index != mData.mKeyStatuses.NoIndex) {
+ if (mData.mKeyStatuses[index].mStatus == aStatus.Value()) {
+ // No change.
+ return false;
+ }
+ auto oldStatus = mData.mKeyStatuses[index].mStatus;
+ mData.mKeyStatuses[index].mStatus = aStatus.Value();
+ // The old key status was one for which we can decrypt media. We don't
+ // need to do the "notify usable" step below, as it should be impossible
+ // for us to have anything waiting on this key to become usable, since it
+ // was already usable.
+ if (IsUsableStatus(oldStatus)) {
+ return true;
+ }
+ } else {
+ mData.mKeyStatuses.AppendElement(key);
+ }
+
+ // Only call NotifyUsable() for a key when we are going from non-usable
+ // to usable state.
+ if (!IsUsableStatus(aStatus.Value())) {
+ return true;
+ }
+
+ auto& waiters = mData.mWaitForKeys;
+ size_t i = 0;
+ while (i < waiters.Length()) {
+ auto& w = waiters[i];
+ if (w.mKeyId == aKeyId) {
+ w.mListener->NotifyUsable(aKeyId);
+ waiters.RemoveElementAt(i);
+ } else {
+ i++;
+ }
+ }
+ return true;
+}
+
+void
+CDMCaps::AutoLock::NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+ SamplesWaitingForKey* aListener)
+{
+ mData.mMonitor.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!IsKeyUsable(aKey));
+ MOZ_ASSERT(aListener);
+ mData.mWaitForKeys.AppendElement(WaitForKeys(aKey, aListener));
+}
+
+void
+CDMCaps::AutoLock::GetKeyStatusesForSession(const nsAString& aSessionId,
+ nsTArray<KeyStatus>& aOutKeyStatuses)
+{
+ for (const KeyStatus& keyStatus : mData.mKeyStatuses) {
+ if (keyStatus.mSessionId.Equals(aSessionId)) {
+ aOutKeyStatuses.AppendElement(keyStatus);
+ }
+ }
+}
+
+void
+CDMCaps::AutoLock::GetSessionIdsForKeyId(const CencKeyId& aKeyId,
+ nsTArray<nsCString>& aOutSessionIds)
+{
+ for (const KeyStatus& keyStatus : mData.mKeyStatuses) {
+ if (keyStatus.mId == aKeyId) {
+ aOutSessionIds.AppendElement(NS_ConvertUTF16toUTF8(keyStatus.mSessionId));
+ }
+ }
+}
+
+bool
+CDMCaps::AutoLock::RemoveKeysForSession(const nsString& aSessionId)
+{
+ bool changed = false;
+ nsTArray<KeyStatus> statuses;
+ GetKeyStatusesForSession(aSessionId, statuses);
+ for (const KeyStatus& status : statuses) {
+ changed |= SetKeyStatus(status.mId,
+ aSessionId,
+ dom::Optional<dom::MediaKeyStatus>());
+ }
+ return changed;
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/CDMCaps.h b/dom/media/eme/CDMCaps.h
new file mode 100644
index 000000000..cb4b5e291
--- /dev/null
+++ b/dom/media/eme/CDMCaps.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CDMCaps_h_
+#define CDMCaps_h_
+
+#include "gmp-decryption.h"
+#include "nsIThread.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "SamplesWaitingForKey.h"
+
+#include "mozilla/Monitor.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/BindingDeclarations.h" // For Optional
+
+namespace mozilla {
+
+// CDM capabilities; what keys a CDMProxy can use.
+// Must be locked to access state.
+class CDMCaps {
+public:
+ CDMCaps();
+ ~CDMCaps();
+
+ struct KeyStatus {
+ KeyStatus(const CencKeyId& aId,
+ const nsString& aSessionId,
+ dom::MediaKeyStatus aStatus)
+ : mId(aId)
+ , mSessionId(aSessionId)
+ , mStatus(aStatus)
+ {}
+ KeyStatus(const KeyStatus& aOther)
+ : mId(aOther.mId)
+ , mSessionId(aOther.mSessionId)
+ , mStatus(aOther.mStatus)
+ {}
+ bool operator==(const KeyStatus& aOther) const {
+ return mId == aOther.mId &&
+ mSessionId == aOther.mSessionId;
+ };
+
+ CencKeyId mId;
+ nsString mSessionId;
+ dom::MediaKeyStatus mStatus;
+ };
+
+ // Locks the CDMCaps. It must be locked to access its shared state.
+ // Threadsafe when locked.
+ class MOZ_STACK_CLASS AutoLock {
+ public:
+ explicit AutoLock(CDMCaps& aKeyCaps);
+ ~AutoLock();
+
+ bool IsKeyUsable(const CencKeyId& aKeyId);
+
+ // Returns true if key status changed,
+ // i.e. the key status changed from usable to expired.
+ bool SetKeyStatus(const CencKeyId& aKeyId,
+ const nsString& aSessionId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus);
+
+ void GetKeyStatusesForSession(const nsAString& aSessionId,
+ nsTArray<KeyStatus>& aOutKeyStatuses);
+
+ void GetSessionIdsForKeyId(const CencKeyId& aKeyId,
+ nsTArray<nsCString>& aOutSessionIds);
+
+ // Ensures all keys for a session are marked as 'unknown', i.e. removed.
+ // Returns true if a key status was changed.
+ bool RemoveKeysForSession(const nsString& aSessionId);
+
+ // Notifies the SamplesWaitingForKey when key become usable.
+ void NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+ SamplesWaitingForKey* aSamplesWaiting);
+ private:
+ // Not taking a strong ref, since this should be allocated on the stack.
+ CDMCaps& mData;
+ };
+
+private:
+ void Lock();
+ void Unlock();
+
+ struct WaitForKeys {
+ WaitForKeys(const CencKeyId& aKeyId,
+ SamplesWaitingForKey* aListener)
+ : mKeyId(aKeyId)
+ , mListener(aListener)
+ {}
+ CencKeyId mKeyId;
+ RefPtr<SamplesWaitingForKey> mListener;
+ };
+
+ Monitor mMonitor;
+
+ nsTArray<KeyStatus> mKeyStatuses;
+
+ nsTArray<WaitForKeys> mWaitForKeys;
+
+ // It is not safe to copy this object.
+ CDMCaps(const CDMCaps&) = delete;
+ CDMCaps& operator=(const CDMCaps&) = delete;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/CDMProxy.h b/dom/media/eme/CDMProxy.h
new file mode 100644
index 000000000..a9e783f50
--- /dev/null
+++ b/dom/media/eme/CDMProxy.h
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CDMProxy_h_
+#define CDMProxy_h_
+
+#include "mozilla/CDMCaps.h"
+#include "mozilla/MozPromise.h"
+
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeys.h"
+
+#include "nsIThread.h"
+
+namespace mozilla {
+class MediaRawData;
+
+enum DecryptStatus {
+ Ok = 0,
+ GenericErr = 1,
+ NoKeyErr = 2,
+ AbortedErr = 3,
+};
+
+struct DecryptResult {
+ DecryptResult(DecryptStatus aStatus, MediaRawData* aSample)
+ : mStatus(aStatus)
+ , mSample(aSample)
+ {}
+ DecryptStatus mStatus;
+ RefPtr<MediaRawData> mSample;
+};
+
+class CDMKeyInfo {
+public:
+ explicit CDMKeyInfo(const nsTArray<uint8_t>& aKeyId)
+ : mKeyId(aKeyId)
+ , mStatus()
+ {}
+
+ CDMKeyInfo(const nsTArray<uint8_t>& aKeyId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus)
+ : mKeyId(aKeyId)
+ , mStatus(aStatus.Value())
+ {}
+
+ // The copy-ctor and copy-assignment operator for Optional<T> are declared as
+ // delete, so override CDMKeyInfo copy-ctor for nsTArray operations.
+ CDMKeyInfo(const CDMKeyInfo& aKeyInfo)
+ {
+ mKeyId = aKeyInfo.mKeyId;
+ if (aKeyInfo.mStatus.WasPassed()) {
+ mStatus.Construct(aKeyInfo.mStatus.Value());
+ }
+ }
+
+ nsTArray<uint8_t> mKeyId;
+ dom::Optional<dom::MediaKeyStatus> mStatus;
+};
+
+typedef int64_t UnixTime;
+
+// Proxies calls CDM, and proxies calls back.
+// Note: Promises are passed in via a PromiseId, so that the ID can be
+// passed via IPC to the CDM, which can then signal when to reject or
+// resolve the promise using its PromiseId.
+class CDMProxy {
+protected:
+ typedef dom::PromiseId PromiseId;
+ typedef dom::MediaKeySessionType MediaKeySessionType;
+public:
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
+
+ typedef MozPromise<DecryptResult, DecryptResult, /* IsExclusive = */ true> DecryptPromise;
+
+ // Main thread only.
+ CDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : mKeys(aKeys)
+ , mKeySystem(aKeySystem)
+ , mDistinctiveIdentifierRequired(aDistinctiveIdentifierRequired)
+ , mPersistentStateRequired(aPersistentStateRequired)
+ {}
+
+ // Main thread only.
+ // Loads the CDM corresponding to mKeySystem.
+ // Calls MediaKeys::OnCDMCreated() when the CDM is created.
+ virtual void Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aName,
+ bool aInPrivateBrowsing) = 0;
+
+ virtual void OnSetDecryptorId(uint32_t aId) {}
+
+ // Main thread only.
+ // Uses the CDM to create a key session.
+ // Calls MediaKeys::OnSessionActivated() when session is created.
+ // Assumes ownership of (Move()s) aInitData's contents.
+ virtual void CreateSession(uint32_t aCreateSessionToken,
+ MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) = 0;
+
+ // Main thread only.
+ // Uses the CDM to load a presistent session stored on disk.
+ // Calls MediaKeys::OnSessionActivated() when session is loaded.
+ virtual void LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId) = 0;
+
+ // Main thread only.
+ // Sends a new certificate to the CDM.
+ // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+ // processed the request.
+ // Assumes ownership of (Move()s) aCert's contents.
+ virtual void SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) = 0;
+
+ // Main thread only.
+ // Sends an update to the CDM.
+ // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+ // processed the request.
+ // Assumes ownership of (Move()s) aResponse's contents.
+ virtual void UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) = 0;
+
+ // Main thread only.
+ // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+ // processed the request.
+ // If processing this operation results in the session actually closing,
+ // we also call MediaKeySession::OnClosed(), which in turn calls
+ // MediaKeys::OnSessionClosed().
+ virtual void CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) = 0;
+
+ // Main thread only.
+ // Removes all data for a persisent session.
+ // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+ // processed the request.
+ virtual void RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) = 0;
+
+ // Main thread only.
+ virtual void Shutdown() = 0;
+
+ // Main thread only.
+ virtual void Terminated() = 0;
+
+ // Threadsafe.
+ virtual const nsCString& GetNodeId() const = 0;
+
+ // Main thread only.
+ virtual void OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) = 0;
+
+ // Main thread only.
+ virtual void OnResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) = 0;
+
+ // Main thread only.
+ virtual void OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ nsTArray<uint8_t>& aMessage) = 0;
+
+ // Main thread only.
+ virtual void OnExpirationChange(const nsAString& aSessionId,
+ UnixTime aExpiryTime) = 0;
+
+ // Main thread only.
+ virtual void OnSessionClosed(const nsAString& aSessionId) = 0;
+
+ // Main thread only.
+ virtual void OnSessionError(const nsAString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsAString& aMsg) = 0;
+
+ // Main thread only.
+ virtual void OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg) = 0;
+
+ virtual RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) = 0;
+
+ // Owner thread only.
+ virtual void OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) = 0;
+
+ // Reject promise with DOMException corresponding to aExceptionCode.
+ // Can be called from any thread.
+ virtual void RejectPromise(PromiseId aId,
+ nsresult aExceptionCode,
+ const nsCString& aReason) = 0;
+
+ // Resolves promise with "undefined".
+ // Can be called from any thread.
+ virtual void ResolvePromise(PromiseId aId) = 0;
+
+ // Threadsafe.
+ virtual const nsString& KeySystem() const = 0;
+
+ virtual CDMCaps& Capabilites() = 0;
+
+ // Main thread only.
+ virtual void OnKeyStatusesChange(const nsAString& aSessionId) = 0;
+
+ virtual void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds) = 0;
+
+#ifdef DEBUG
+ virtual bool IsOnOwnerThread() = 0;
+#endif
+
+ virtual uint32_t GetDecryptorId() { return 0; }
+
+protected:
+ virtual ~CDMProxy() {}
+
+ // Helper to enforce that a raw pointer is only accessed on the main thread.
+ template<class Type>
+ class MainThreadOnlyRawPtr {
+ public:
+ explicit MainThreadOnlyRawPtr(Type* aPtr)
+ : mPtr(aPtr)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool IsNull() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return !mPtr;
+ }
+
+ void Clear() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mPtr = nullptr;
+ }
+
+ Type* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mPtr;
+ }
+ private:
+ Type* mPtr;
+ };
+
+ // Our reference back to the MediaKeys object.
+ // WARNING: This is a non-owning reference that is cleared by MediaKeys
+ // destructor. only use on main thread, and always nullcheck before using!
+ MainThreadOnlyRawPtr<dom::MediaKeys> mKeys;
+
+ const nsString mKeySystem;
+
+ // Onwer specified thread. e.g. Gecko Media Plugin thread.
+ // All interactions with the out-of-process EME plugin must come from this thread.
+ RefPtr<nsIThread> mOwnerThread;
+
+ nsCString mNodeId;
+
+ CDMCaps mCapabilites;
+
+ const bool mDistinctiveIdentifierRequired;
+ const bool mPersistentStateRequired;
+};
+
+
+} // namespace mozilla
+
+#endif // CDMProxy_h_
diff --git a/dom/media/eme/DecryptorProxyCallback.h b/dom/media/eme/DecryptorProxyCallback.h
new file mode 100644
index 000000000..c1fcb49a4
--- /dev/null
+++ b/dom/media/eme/DecryptorProxyCallback.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DecryptorProxyCallback_h_
+#define DecryptorProxyCallback_h_
+
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/MediaKeyMessageEventBinding.h" // For MediaKeyMessageType
+#include "mozilla/CDMProxy.h"
+
+class DecryptorProxyCallback {
+public:
+
+ virtual ~DecryptorProxyCallback() {}
+
+ virtual void SetDecryptorId(uint32_t aId) = 0;
+
+ virtual void SetSessionId(uint32_t aCreateSessionId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) = 0;
+
+ virtual void ResolvePromise(uint32_t aPromiseId) = 0;
+
+ virtual void RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aSessionId) = 0;
+
+ virtual void SessionMessage(const nsCString& aSessionId,
+ mozilla::dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) = 0;
+
+ virtual void ExpirationChange(const nsCString& aSessionId,
+ mozilla::UnixTime aExpiryTime) = 0;
+
+ virtual void SessionClosed(const nsCString& aSessionId) = 0;
+
+ virtual void SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage) = 0;
+
+ virtual void Decrypted(uint32_t aId,
+ mozilla::DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) = 0;
+
+ virtual void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<mozilla::CDMKeyInfo>& aKeyInfos) = 0;
+};
+
+#endif
diff --git a/dom/media/eme/DetailedPromise.cpp b/dom/media/eme/DetailedPromise.cpp
new file mode 100644
index 000000000..1aa83bef5
--- /dev/null
+++ b/dom/media/eme/DetailedPromise.cpp
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DetailedPromise.h"
+#include "mozilla/dom/DOMException.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+namespace dom {
+
+DetailedPromise::DetailedPromise(nsIGlobalObject* aGlobal,
+ const nsACString& aName)
+ : Promise(aGlobal)
+ , mName(aName)
+ , mResponded(false)
+ , mStartTime(TimeStamp::Now())
+{
+}
+
+DetailedPromise::~DetailedPromise()
+{
+}
+
+void
+DetailedPromise::MaybeReject(nsresult aArg, const nsACString& aReason)
+{
+ nsPrintfCString msg("%s promise rejected 0x%x '%s'", mName.get(), aArg,
+ PromiseFlatCString(aReason).get());
+ EME_LOG(msg.get());
+
+ LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
+
+ ErrorResult rv;
+ rv.ThrowDOMException(aArg, aReason);
+ Promise::MaybeReject(rv);
+}
+
+void
+DetailedPromise::MaybeReject(ErrorResult&, const nsACString& aReason)
+{
+ NS_NOTREACHED("nsresult expected in MaybeReject()");
+}
+
+/* static */ already_AddRefed<DetailedPromise>
+DetailedPromise::Create(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv,
+ const nsACString& aName)
+{
+ RefPtr<DetailedPromise> promise = new DetailedPromise(aGlobal, aName);
+ promise->CreateWrapper(nullptr, aRv);
+ return aRv.Failed() ? nullptr : promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/DetailedPromise.h b/dom/media/eme/DetailedPromise.h
new file mode 100644
index 000000000..f7f10aa40
--- /dev/null
+++ b/dom/media/eme/DetailedPromise.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __DetailedPromise_h__
+#define __DetailedPromise_h__
+
+#include "mozilla/dom/Promise.h"
+#include "EMEUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * This is pretty horrible; bug 1160445.
+ * Extend Promise to add custom DOMException messages on rejection.
+ * Get rid of this once we've ironed out EME errors in the wild.
+ */
+class DetailedPromise : public Promise
+{
+public:
+ static already_AddRefed<DetailedPromise>
+ Create(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv,
+ const nsACString& aName);
+
+ template <typename T>
+ void MaybeResolve(const T& aArg)
+ {
+ EME_LOG("%s promise resolved", mName.get());
+ Promise::MaybeResolve<T>(aArg);
+ }
+
+ void MaybeReject(nsresult aArg) = delete;
+ void MaybeReject(nsresult aArg, const nsACString& aReason);
+
+ void MaybeReject(ErrorResult& aArg) = delete;
+ void MaybeReject(ErrorResult&, const nsACString& aReason);
+
+private:
+ explicit DetailedPromise(nsIGlobalObject* aGlobal,
+ const nsACString& aName);
+
+ virtual ~DetailedPromise();
+
+ enum Status { Succeeded, Failed };
+
+ nsCString mName;
+ bool mResponded;
+ TimeStamp mStartTime;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // __DetailedPromise_h__
diff --git a/dom/media/eme/EMEUtils.cpp b/dom/media/eme/EMEUtils.cpp
new file mode 100644
index 000000000..68ef52d83
--- /dev/null
+++ b/dom/media/eme/EMEUtils.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/EMEUtils.h"
+
+#include "jsfriendapi.h" // for AutoCheckCannotGC
+#include "mozilla/dom/UnionTypes.h"
+
+namespace mozilla {
+
+LogModule* GetEMELog() {
+ static LazyLogModule log("EME");
+ return log;
+}
+
+LogModule* GetEMEVerboseLog() {
+ static LazyLogModule log("EMEV");
+ return log;
+}
+
+ArrayData
+GetArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView)
+{
+ MOZ_ASSERT(aBufferOrView.IsArrayBuffer() || aBufferOrView.IsArrayBufferView());
+ JS::AutoCheckCannotGC nogc;
+ if (aBufferOrView.IsArrayBuffer()) {
+ const dom::ArrayBuffer& buffer = aBufferOrView.GetAsArrayBuffer();
+ buffer.ComputeLengthAndData();
+ return ArrayData(buffer.Data(), buffer.Length());
+ } else if (aBufferOrView.IsArrayBufferView()) {
+ const dom::ArrayBufferView& bufferview = aBufferOrView.GetAsArrayBufferView();
+ bufferview.ComputeLengthAndData();
+ return ArrayData(bufferview.Data(), bufferview.Length());
+ }
+ return ArrayData(nullptr, 0);
+}
+
+void
+CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
+ nsTArray<uint8_t>& aOutData)
+{
+ JS::AutoCheckCannotGC nogc;
+ ArrayData data = GetArrayBufferViewOrArrayBufferData(aBufferOrView);
+ aOutData.Clear();
+ if (!data.IsValid()) {
+ return;
+ }
+ aOutData.AppendElements(data.mData, data.mLength);
+}
+
+void CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBuffer& aBuffer,
+ nsTArray<uint8_t>& aOutData) {
+ JS::AutoCheckCannotGC nogc;
+ aBuffer.ComputeLengthAndData();
+ aOutData.Clear();
+ aOutData.AppendElements(aBuffer.Data(), aBuffer.Length());
+}
+
+bool
+IsClearkeyKeySystem(const nsAString& aKeySystem)
+{
+ return !CompareUTF8toUTF16(kEMEKeySystemClearkey, aKeySystem);
+}
+
+bool
+IsWidevineKeySystem(const nsAString& aKeySystem)
+{
+ return !CompareUTF8toUTF16(kEMEKeySystemWidevine, aKeySystem);
+}
+
+nsString
+KeySystemToGMPName(const nsAString& aKeySystem)
+{
+ if (IsClearkeyKeySystem(aKeySystem)) {
+ return NS_LITERAL_STRING("gmp-clearkey");
+ }
+ if (IsWidevineKeySystem(aKeySystem)) {
+ return NS_LITERAL_STRING("gmp-widevinecdm");
+ }
+ MOZ_ASSERT(false, "We should only call this for known GMPs");
+ return EmptyString();
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/EMEUtils.h b/dom/media/eme/EMEUtils.h
new file mode 100644
index 000000000..aef157482
--- /dev/null
+++ b/dom/media/eme/EMEUtils.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef EME_LOG_H_
+#define EME_LOG_H_
+
+#include "VideoUtils.h"
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/dom/TypedArray.h"
+
+namespace mozilla {
+
+namespace dom {
+class ArrayBufferViewOrArrayBuffer;
+}
+
+#ifndef EME_LOG
+ LogModule* GetEMELog();
+ #define EME_LOG(...) MOZ_LOG(GetEMELog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
+ #define EME_LOG_ENABLED() MOZ_LOG_TEST(GetEMELog(), mozilla::LogLevel::Debug)
+#endif
+
+#ifndef EME_VERBOSE_LOG
+ LogModule* GetEMEVerboseLog();
+ #define EME_VERBOSE_LOG(...) MOZ_LOG(GetEMEVerboseLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
+#else
+ #ifndef EME_LOG
+ #define EME_LOG(...)
+ #endif
+
+ #ifndef EME_VERBOSE_LOG
+ #define EME_VERBOSE_LOG(...)
+ #endif
+#endif
+
+// Helper function to extract a copy of data coming in from JS in an
+// (ArrayBuffer or ArrayBufferView) IDL typed function argument.
+//
+// Only call this on a properly initialized ArrayBufferViewOrArrayBuffer.
+void
+CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
+ nsTArray<uint8_t>& aOutData);
+
+// Overload for ArrayBuffer
+void CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBuffer& aBufferOrView,
+ nsTArray<uint8_t>& aOutData);
+
+struct ArrayData {
+ explicit ArrayData(const uint8_t* aData, size_t aLength)
+ : mData(aData)
+ , mLength(aLength)
+ {
+ }
+ const uint8_t* mData;
+ const size_t mLength;
+ bool IsValid() const {
+ return mData != nullptr && mLength != 0;
+ }
+ bool operator== (const nsTArray<uint8_t>& aOther) const {
+ return mLength == aOther.Length() &&
+ memcmp(mData, aOther.Elements(), mLength) == 0;
+ }
+};
+
+// Helper function to extract data coming in from JS in an
+// (ArrayBuffer or ArrayBufferView) IDL typed function argument.
+//
+// Be *very* careful with this!
+//
+// Only use returned ArrayData inside the lifetime of the
+// ArrayBufferViewOrArrayBuffer; the ArrayData struct does not contain
+// a copy of the data!
+//
+// And do *not* call out to anything that could call into JavaScript,
+// while the ArrayData is live, as then all bets about the data not changing
+// are off! No calls into JS, no calls into JS-implemented WebIDL or XPIDL,
+// nothing. Beware!
+//
+// Only call this on a properly initialized ArrayBufferViewOrArrayBuffer.
+ArrayData
+GetArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView);
+
+nsString
+KeySystemToGMPName(const nsAString& aKeySystem);
+
+bool
+IsClearkeyKeySystem(const nsAString& aKeySystem);
+
+bool
+IsWidevineKeySystem(const nsAString& aKeySystem);
+
+enum CDMType {
+ eClearKey = 0,
+ eWidevine = 2,
+ eUnknown = 3
+};
+
+} // namespace mozilla
+
+#endif // EME_LOG_H_
diff --git a/dom/media/eme/MediaEncryptedEvent.cpp b/dom/media/eme/MediaEncryptedEvent.cpp
new file mode 100644
index 000000000..a9c83291e
--- /dev/null
+++ b/dom/media/eme/MediaEncryptedEvent.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaEncryptedEvent.h"
+#include "mozilla/dom/MediaEncryptedEventBinding.h"
+#include "nsContentUtils.h"
+#include "jsfriendapi.h"
+#include "nsINode.h"
+#include "mozilla/dom/MediaKeys.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaEncryptedEvent)
+
+NS_IMPL_ADDREF_INHERITED(MediaEncryptedEvent, Event)
+NS_IMPL_RELEASE_INHERITED(MediaEncryptedEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaEncryptedEvent, Event)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MediaEncryptedEvent, Event)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mInitData)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaEncryptedEvent, Event)
+ tmp->mInitData = nullptr;
+ mozilla::DropJSObjects(this);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaEncryptedEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+MediaEncryptedEvent::MediaEncryptedEvent(EventTarget* aOwner)
+ : Event(aOwner, nullptr, nullptr)
+{
+ mozilla::HoldJSObjects(this);
+}
+
+MediaEncryptedEvent::~MediaEncryptedEvent()
+{
+ mInitData = nullptr;
+ mozilla::DropJSObjects(this);
+}
+
+JSObject*
+MediaEncryptedEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaEncryptedEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<MediaEncryptedEvent>
+MediaEncryptedEvent::Constructor(EventTarget* aOwner)
+{
+ RefPtr<MediaEncryptedEvent> e = new MediaEncryptedEvent(aOwner);
+ e->InitEvent(NS_LITERAL_STRING("encrypted"), false, false);
+ e->SetTrusted(true);
+ return e.forget();
+}
+
+already_AddRefed<MediaEncryptedEvent>
+MediaEncryptedEvent::Constructor(EventTarget* aOwner,
+ const nsAString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData)
+{
+ RefPtr<MediaEncryptedEvent> e = new MediaEncryptedEvent(aOwner);
+ e->InitEvent(NS_LITERAL_STRING("encrypted"), false, false);
+ e->mInitDataType = aInitDataType;
+ e->mRawInitData = aInitData;
+ e->SetTrusted(true);
+ return e.forget();
+}
+
+already_AddRefed<MediaEncryptedEvent>
+MediaEncryptedEvent::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const MediaKeyNeededEventInit& aEventInitDict,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<MediaEncryptedEvent> e = new MediaEncryptedEvent(owner);
+ bool trusted = e->Init(owner);
+ e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
+ e->mInitDataType = aEventInitDict.mInitDataType;
+ if (!aEventInitDict.mInitData.IsNull()) {
+ const auto& a = aEventInitDict.mInitData.Value();
+ nsTArray<uint8_t> initData;
+ CopyArrayBufferViewOrArrayBufferData(a, initData);
+ e->mInitData = ArrayBuffer::Create(aGlobal.Context(), initData.Length(),
+ initData.Elements());
+ if (!e->mInitData) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ }
+ e->SetTrusted(trusted);
+ return e.forget();
+}
+
+void
+MediaEncryptedEvent::GetInitDataType(nsString& aRetVal) const
+{
+ aRetVal = mInitDataType;
+}
+
+void
+MediaEncryptedEvent::GetInitData(JSContext* cx,
+ JS::MutableHandle<JSObject*> aData,
+ ErrorResult& aRv)
+{
+ if (mRawInitData.Length()) {
+ mInitData = ArrayBuffer::Create(cx,
+ this,
+ mRawInitData.Length(),
+ mRawInitData.Elements());
+ if (!mInitData) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ mRawInitData.Clear();
+ }
+ aData.set(mInitData);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaEncryptedEvent.h b/dom/media/eme/MediaEncryptedEvent.h
new file mode 100644
index 000000000..c2ac56061
--- /dev/null
+++ b/dom/media/eme/MediaEncryptedEvent.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeyNeededEvent_h__
+#define mozilla_dom_MediaKeyNeededEvent_h__
+
+#include "mozilla/dom/MediaEncryptedEventBinding.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaEncryptedEvent final : public Event
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MediaEncryptedEvent, Event)
+protected:
+ virtual ~MediaEncryptedEvent();
+ explicit MediaEncryptedEvent(EventTarget* aOwner);
+
+ nsString mInitDataType;
+ JS::Heap<JSObject*> mInitData;
+
+public:
+
+ JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<MediaEncryptedEvent>
+ Constructor(EventTarget* aOwner);
+
+ static already_AddRefed<MediaEncryptedEvent>
+ Constructor(EventTarget* aOwner,
+ const nsAString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData);
+
+ static already_AddRefed<MediaEncryptedEvent>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const MediaKeyNeededEventInit& aEventInitDict,
+ ErrorResult& aRv);
+
+ void GetInitDataType(nsString& aRetVal) const;
+
+ void GetInitData(JSContext* cx,
+ JS::MutableHandle<JSObject*> aData,
+ ErrorResult& aRv);
+private:
+ nsTArray<uint8_t> mRawInitData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaKeyNeededEvent_h__
diff --git a/dom/media/eme/MediaKeyError.cpp b/dom/media/eme/MediaKeyError.cpp
new file mode 100644
index 000000000..635c34364
--- /dev/null
+++ b/dom/media/eme/MediaKeyError.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaKeyError.h"
+#include "mozilla/dom/MediaKeyErrorBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+MediaKeyError::MediaKeyError(EventTarget* aOwner, uint32_t aSystemCode)
+ : Event(aOwner, nullptr, nullptr)
+ , mSystemCode(aSystemCode)
+{
+ InitEvent(NS_LITERAL_STRING("error"), false, false);
+}
+
+MediaKeyError::~MediaKeyError()
+{
+}
+
+uint32_t
+MediaKeyError::SystemCode() const
+{
+ return mSystemCode;
+}
+
+JSObject*
+MediaKeyError::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeyErrorBinding::Wrap(aCx, this, aGivenProto);
+}
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeyError.h b/dom/media/eme/MediaKeyError.h
new file mode 100644
index 000000000..df79b2c95
--- /dev/null
+++ b/dom/media/eme/MediaKeyError.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeyError_h
+#define mozilla_dom_MediaKeyError_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/Event.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaKeyError final : public Event
+{
+public:
+ NS_FORWARD_TO_EVENT
+
+ MediaKeyError(EventTarget* aOwner, uint32_t aSystemCode);
+ ~MediaKeyError();
+
+ JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ uint32_t SystemCode() const;
+
+private:
+ uint32_t mSystemCode;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/MediaKeyMessageEvent.cpp b/dom/media/eme/MediaKeyMessageEvent.cpp
new file mode 100644
index 000000000..ee13f8d26
--- /dev/null
+++ b/dom/media/eme/MediaKeyMessageEvent.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+#include "js/GCAPI.h"
+#include "jsfriendapi.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/MediaKeys.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeyMessageEvent)
+
+NS_IMPL_ADDREF_INHERITED(MediaKeyMessageEvent, Event)
+NS_IMPL_RELEASE_INHERITED(MediaKeyMessageEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMessage)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+ tmp->mMessage = nullptr;
+ mozilla::DropJSObjects(this);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeyMessageEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+MediaKeyMessageEvent::MediaKeyMessageEvent(EventTarget* aOwner)
+ : Event(aOwner, nullptr, nullptr)
+{
+ mozilla::HoldJSObjects(this);
+}
+
+MediaKeyMessageEvent::~MediaKeyMessageEvent()
+{
+ mMessage = nullptr;
+ mozilla::DropJSObjects(this);
+}
+
+MediaKeyMessageEvent*
+MediaKeyMessageEvent::AsMediaKeyMessageEvent()
+{
+ return this;
+}
+
+JSObject*
+MediaKeyMessageEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeyMessageEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<MediaKeyMessageEvent>
+MediaKeyMessageEvent::Constructor(EventTarget* aOwner,
+ MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
+{
+ RefPtr<MediaKeyMessageEvent> e = new MediaKeyMessageEvent(aOwner);
+ e->InitEvent(NS_LITERAL_STRING("message"), false, false);
+ e->mMessageType = aMessageType;
+ e->mRawMessage = aMessage;
+ e->SetTrusted(true);
+ return e.forget();
+}
+
+already_AddRefed<MediaKeyMessageEvent>
+MediaKeyMessageEvent::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const MediaKeyMessageEventInit& aEventInitDict,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<MediaKeyMessageEvent> e = new MediaKeyMessageEvent(owner);
+ bool trusted = e->Init(owner);
+ e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
+ nsTArray<uint8_t> initData;
+ CopyArrayBufferViewOrArrayBufferData(aEventInitDict.mMessage, initData);
+ e->mMessage = ArrayBuffer::Create(aGlobal.Context(),
+ initData.Length(),
+ initData.Elements());
+ if (!e->mMessage) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ e->mMessageType = aEventInitDict.mMessageType;
+ e->SetTrusted(trusted);
+ e->SetComposed(aEventInitDict.mComposed);
+ return e.forget();
+}
+
+void
+MediaKeyMessageEvent::GetMessage(JSContext* cx,
+ JS::MutableHandle<JSObject*> aMessage,
+ ErrorResult& aRv)
+{
+ if (!mMessage) {
+ mMessage = ArrayBuffer::Create(cx,
+ this,
+ mRawMessage.Length(),
+ mRawMessage.Elements());
+ if (!mMessage) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ mRawMessage.Clear();
+ }
+ aMessage.set(mMessage);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeyMessageEvent.h b/dom/media/eme/MediaKeyMessageEvent.h
new file mode 100644
index 000000000..2ec50f4f0
--- /dev/null
+++ b/dom/media/eme/MediaKeyMessageEvent.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeyMessageEvent_h__
+#define mozilla_dom_MediaKeyMessageEvent_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/TypedArray.h"
+#include "js/TypeDecls.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+struct MediaKeyMessageEventInit;
+
+class MediaKeyMessageEvent final : public Event
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MediaKeyMessageEvent, Event)
+protected:
+ virtual ~MediaKeyMessageEvent();
+ explicit MediaKeyMessageEvent(EventTarget* aOwner);
+
+ MediaKeyMessageType mMessageType;
+ JS::Heap<JSObject*> mMessage;
+
+public:
+ virtual MediaKeyMessageEvent* AsMediaKeyMessageEvent();
+
+ JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<MediaKeyMessageEvent>
+ Constructor(EventTarget* aOwner,
+ MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage);
+
+ static already_AddRefed<MediaKeyMessageEvent>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const MediaKeyMessageEventInit& aEventInitDict,
+ ErrorResult& aRv);
+
+ MediaKeyMessageType MessageType() const { return mMessageType; }
+
+ void GetMessage(JSContext* cx,
+ JS::MutableHandle<JSObject*> aMessage,
+ ErrorResult& aRv);
+
+private:
+ nsTArray<uint8_t> mRawMessage;
+};
+
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaKeyMessageEvent_h__
diff --git a/dom/media/eme/MediaKeySession.cpp b/dom/media/eme/MediaKeySession.cpp
new file mode 100644
index 000000000..b5f3fe498
--- /dev/null
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -0,0 +1,670 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/MediaKeyError.h"
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaEncryptedEvent.h"
+#include "mozilla/dom/MediaKeyStatusMap.h"
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "mozilla/dom/KeyIdsInitDataBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Move.h"
+#include "nsContentUtils.h"
+#include "mozilla/EMEUtils.h"
+#include "GMPUtils.h"
+#include "nsPrintfCString.h"
+#include "psshparser/PsshParser.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaKeySession,
+ DOMEventTargetHelper,
+ mMediaKeyError,
+ mKeys,
+ mKeyStatusMap,
+ mClosed)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeySession)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(MediaKeySession, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MediaKeySession, DOMEventTargetHelper)
+
+// Count of number of instances. Used to give each instance a
+// unique token.
+static uint32_t sMediaKeySessionNum = 0;
+
+// Max length of keyId in EME "keyIds" or WebM init data format, as enforced
+// by web platform tests.
+static const uint32_t MAX_KEY_ID_LENGTH = 512;
+
+// Max length of CENC PSSH init data tolerated, as enforced by web
+// platform tests.
+static const uint32_t MAX_CENC_INIT_DATA_LENGTH = 64 * 1024;
+
+
+MediaKeySession::MediaKeySession(JSContext* aCx,
+ nsPIDOMWindowInner* aParent,
+ MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ MediaKeySessionType aSessionType,
+ ErrorResult& aRv)
+ : DOMEventTargetHelper(aParent)
+ , mKeys(aKeys)
+ , mKeySystem(aKeySystem)
+ , mSessionType(aSessionType)
+ , mToken(sMediaKeySessionNum++)
+ , mIsClosed(false)
+ , mUninitialized(true)
+ , mKeyStatusMap(new MediaKeyStatusMap(aParent))
+ , mExpiration(JS::GenericNaN())
+{
+ EME_LOG("MediaKeySession[%p,''] ctor", this);
+
+ MOZ_ASSERT(aParent);
+ if (aRv.Failed()) {
+ return;
+ }
+ mClosed = MakePromise(aRv, NS_LITERAL_CSTRING("MediaKeys.createSession"));
+}
+
+void MediaKeySession::SetSessionId(const nsAString& aSessionId)
+{
+ EME_LOG("MediaKeySession[%p,'%s'] session Id set",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get());
+
+ if (NS_WARN_IF(!mSessionId.IsEmpty())) {
+ return;
+ }
+ mSessionId = aSessionId;
+ mKeys->OnSessionIdReady(this);
+}
+
+MediaKeySession::~MediaKeySession()
+{
+}
+
+MediaKeyError*
+MediaKeySession::GetError() const
+{
+ return mMediaKeyError;
+}
+
+void
+MediaKeySession::GetKeySystem(nsString& aOutKeySystem) const
+{
+ aOutKeySystem.Assign(mKeySystem);
+}
+
+void
+MediaKeySession::GetSessionId(nsString& aSessionId) const
+{
+ aSessionId = GetSessionId();
+}
+
+const nsString&
+MediaKeySession::GetSessionId() const
+{
+ return mSessionId;
+}
+
+JSObject*
+MediaKeySession::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeySessionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+double
+MediaKeySession::Expiration() const
+{
+ return mExpiration;
+}
+
+Promise*
+MediaKeySession::Closed() const
+{
+ return mClosed;
+}
+
+void
+MediaKeySession::UpdateKeyStatusMap()
+{
+ MOZ_ASSERT(!IsClosed());
+ if (!mKeys->GetCDMProxy()) {
+ return;
+ }
+
+ nsTArray<CDMCaps::KeyStatus> keyStatuses;
+ {
+ CDMCaps::AutoLock caps(mKeys->GetCDMProxy()->Capabilites());
+ caps.GetKeyStatusesForSession(mSessionId, keyStatuses);
+ }
+
+ mKeyStatusMap->Update(keyStatuses);
+
+ if (EME_LOG_ENABLED()) {
+ nsAutoCString message(
+ nsPrintfCString("MediaKeySession[%p,'%s'] key statuses change {",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get()));
+ using IntegerType = typename std::underlying_type<MediaKeyStatus>::type;
+ for (const CDMCaps::KeyStatus& status : keyStatuses) {
+ message.Append(nsPrintfCString(" (%s,%s)", ToBase64(status.mId).get(),
+ MediaKeyStatusValues::strings[static_cast<IntegerType>(status.mStatus)].value));
+ }
+ message.Append(" }");
+ EME_LOG(message.get());
+ }
+}
+
+MediaKeyStatusMap*
+MediaKeySession::KeyStatuses() const
+{
+ return mKeyStatusMap;
+}
+
+// The user agent MUST thoroughly validate the Initialization Data before
+// passing it to the CDM. This includes verifying that the length and
+// values of fields are reasonable, verifying that values are within
+// reasonable limits, and stripping irrelevant, unsupported, or unknown
+// data or fields. It is RECOMMENDED that user agents pre-parse, sanitize,
+// and/or generate a fully sanitized version of the Initialization Data.
+// If the Initialization Data format specified by initDataType supports
+// multiple entries, the user agent SHOULD remove entries that are not
+// needed by the CDM. The user agent MUST NOT re-order entries within
+// the Initialization Data.
+static bool
+ValidateInitData(const nsTArray<uint8_t>& aInitData, const nsAString& aInitDataType)
+{
+ if (aInitDataType.LowerCaseEqualsLiteral("webm")) {
+ // WebM initData consists of a single keyId. Ensure it's of reasonable length.
+ return aInitData.Length() <= MAX_KEY_ID_LENGTH;
+ } else if (aInitDataType.LowerCaseEqualsLiteral("cenc")) {
+ // Limit initData to less than 64KB.
+ if (aInitData.Length() > MAX_CENC_INIT_DATA_LENGTH) {
+ return false;
+ }
+ std::vector<std::vector<uint8_t>> keyIds;
+ return ParseCENCInitData(aInitData.Elements(), aInitData.Length(), keyIds);
+ } else if (aInitDataType.LowerCaseEqualsLiteral("keyids")) {
+ if (aInitData.Length() > MAX_KEY_ID_LENGTH) {
+ return false;
+ }
+ // Ensure that init data matches the expected JSON format.
+ mozilla::dom::KeyIdsInitData keyIds;
+ nsString json;
+ nsDependentCSubstring raw(reinterpret_cast<const char*>(aInitData.Elements()), aInitData.Length());
+ if (NS_FAILED(nsContentUtils::ConvertStringFromEncoding(NS_LITERAL_CSTRING("UTF-8"), raw, json))) {
+ return false;
+ }
+ if (!keyIds.Init(json)) {
+ return false;
+ }
+ if (keyIds.mKids.Length() == 0) {
+ return false;
+ }
+ for (const auto& kid : keyIds.mKids) {
+ if (kid.IsEmpty()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Generates a license request based on the initData. A message of type
+// "license-request" or "individualization-request" will always be queued
+// if the algorithm succeeds and the promise is resolved.
+already_AddRefed<Promise>
+MediaKeySession::GenerateRequest(const nsAString& aInitDataType,
+ const ArrayBufferViewOrArrayBuffer& aInitData,
+ ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.generateRequest")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // If this object is closed, return a promise rejected with an InvalidStateError.
+ if (IsClosed()) {
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, closed",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is closed in MediaKeySession.generateRequest()"));
+ return promise.forget();
+ }
+
+ // If this object's uninitialized value is false, return a promise rejected
+ // with an InvalidStateError.
+ if (!mUninitialized) {
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, uninitialized",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is already initialized in MediaKeySession.generateRequest()"));
+ return promise.forget();
+ }
+
+ // Let this object's uninitialized value be false.
+ mUninitialized = false;
+
+ // If initDataType is the empty string, return a promise rejected
+ // with a newly created TypeError.
+ if (aInitDataType.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Empty initDataType passed to MediaKeySession.generateRequest()"));
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, empty initDataType",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // If initData is an empty array, return a promise rejected with
+ // a newly created TypeError.
+ nsTArray<uint8_t> data;
+ CopyArrayBufferViewOrArrayBufferData(aInitData, data);
+ if (data.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Empty initData passed to MediaKeySession.generateRequest()"));
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, empty initData",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // If the Key System implementation represented by this object's
+ // cdm implementation value does not support initDataType as an
+ // Initialization Data Type, return a promise rejected with a
+ // NotSupportedError. String comparison is case-sensitive.
+ if (!MediaKeySystemAccess::KeySystemSupportsInitDataType(mKeySystem, aInitDataType)) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Unsupported initDataType passed to MediaKeySession.generateRequest()"));
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, unsupported initDataType",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // Let init data be a copy of the contents of the initData parameter.
+ // Note: Handled by the CopyArrayBufferViewOrArrayBufferData call above.
+
+ // Let session type be this object's session type.
+
+ // Let promise be a new promise.
+
+ // Run the following steps in parallel:
+
+ // If the init data is not valid for initDataType, reject promise with
+ // a newly created TypeError.
+ if (!ValidateInitData(data, aInitDataType)) {
+ // If the preceding step failed, reject promise with a newly created TypeError.
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("initData sanitization failed in MediaKeySession.generateRequest()"));
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() initData sanitization failed",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // Let sanitized init data be a validated and sanitized version of init data.
+
+ // If sanitized init data is empty, reject promise with a NotSupportedError.
+
+ // Note: Remaining steps of generateRequest method continue in CDM.
+
+ // Convert initData to base64 for easier logging.
+ // Note: CreateSession() Move()s the data out of the array, so we have
+ // to copy it here.
+ nsAutoCString base64InitData(ToBase64(data));
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->ConnectPendingPromiseIdWithToken(pid, Token());
+ mKeys->GetCDMProxy()->CreateSession(Token(),
+ mSessionType,
+ pid,
+ aInitDataType, data);
+
+ EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() sent, "
+ "promiseId=%d initData(base64)='%s' initDataType='%s'",
+ this,
+ NS_ConvertUTF16toUTF8(mSessionId).get(),
+ pid,
+ base64InitData.get(),
+ NS_ConvertUTF16toUTF8(aInitDataType).get());
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Load(const nsAString& aSessionId, ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.load")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // 1. If this object is closed, return a promise rejected with an InvalidStateError.
+ if (IsClosed()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is closed in MediaKeySession.load()"));
+ EME_LOG("MediaKeySession[%p,'%s'] Load() failed, closed",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get());
+ return promise.forget();
+ }
+
+ // 2.If this object's uninitialized value is false, return a promise rejected
+ // with an InvalidStateError.
+ if (!mUninitialized) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is already initialized in MediaKeySession.load()"));
+ EME_LOG("MediaKeySession[%p,'%s'] Load() failed, uninitialized",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get());
+ return promise.forget();
+ }
+
+ // 3.Let this object's uninitialized value be false.
+ mUninitialized = false;
+
+ // 4. If sessionId is the empty string, return a promise rejected with a newly created TypeError.
+ if (aSessionId.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Trying to load a session with empty session ID"));
+ // "The sessionId parameter is empty."
+ EME_LOG("MediaKeySession[%p,''] Load() failed, no sessionId", this);
+ return promise.forget();
+ }
+
+ // 5. If the result of running the Is persistent session type? algorithm
+ // on this object's session type is false, return a promise rejected with
+ // a newly created TypeError.
+ if (mSessionType == MediaKeySessionType::Temporary) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Trying to load() into a non-persistent session"));
+ EME_LOG("MediaKeySession[%p,''] Load() failed, can't load in a non-persistent session", this);
+ return promise.forget();
+ }
+
+ // Note: We don't support persistent sessions in any keysystem, so all calls
+ // to Load() should reject with a TypeError in the preceding check. Omitting
+ // implementing the rest of the specified MediaKeySession::Load() algorithm.
+
+ // We now know the sessionId being loaded into this session. Remove the
+ // session from its owning MediaKey's set of sessions awaiting a sessionId.
+ RefPtr<MediaKeySession> session(mKeys->GetPendingSession(Token()));
+ MOZ_ASSERT(session == this, "Session should be awaiting id on its own token");
+
+ // Associate with the known sessionId.
+ SetSessionId(aSessionId);
+
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->GetCDMProxy()->LoadSession(pid, aSessionId);
+
+ EME_LOG("MediaKeySession[%p,'%s'] Load() sent to CDM, promiseId=%d",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Update(const ArrayBufferViewOrArrayBuffer& aResponse, ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.update")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!IsCallable()) {
+ // If this object's callable value is false, return a promise rejected
+ // with a new DOMException whose name is InvalidStateError.
+ EME_LOG("MediaKeySession[%p,''] Update() called before sessionId set by CDM", this);
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySession.Update() called before sessionId set by CDM"));
+ return promise.forget();
+ }
+
+ nsTArray<uint8_t> data;
+ if (IsClosed() || !mKeys->GetCDMProxy()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Session is closed or was not properly initialized"));
+ EME_LOG("MediaKeySession[%p,'%s'] Update() failed, session is closed or was not properly initialised.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+ CopyArrayBufferViewOrArrayBufferData(aResponse, data);
+ if (data.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Empty response buffer passed to MediaKeySession.update()"));
+ EME_LOG("MediaKeySession[%p,'%s'] Update() failed, empty response buffer",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+
+ // Convert response to base64 for easier logging.
+ // Note: UpdateSession() Move()s the data out of the array, so we have
+ // to copy it here.
+ nsAutoCString base64Response(ToBase64(data));
+
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->GetCDMProxy()->UpdateSession(mSessionId,
+ pid,
+ data);
+
+ EME_LOG("MediaKeySession[%p,'%s'] Update() sent to CDM, "
+ "promiseId=%d Response(base64)='%s'",
+ this,
+ NS_ConvertUTF16toUTF8(mSessionId).get(),
+ pid,
+ base64Response.get());
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Close(ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.close")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ // 1. Let session be the associated MediaKeySession object.
+ // 2. If session is closed, return a resolved promise.
+ if (IsClosed()) {
+ EME_LOG("MediaKeySession[%p,'%s'] Close() already closed",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ // 3. If session's callable value is false, return a promise rejected
+ // with an InvalidStateError.
+ if (!IsCallable()) {
+ EME_LOG("MediaKeySession[%p,''] Close() called before sessionId set by CDM", this);
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySession.Close() called before sessionId set by CDM"));
+ return promise.forget();
+ }
+ if (!mKeys->GetCDMProxy()) {
+ EME_LOG("MediaKeySession[%p,'%s'] Close() null CDMProxy",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySession.Close() lost reference to CDM"));
+ return promise.forget();
+ }
+ // 4. Let promise be a new promise.
+ PromiseId pid = mKeys->StorePromise(promise);
+ // 5. Run the following steps in parallel:
+ // 5.1 Let cdm be the CDM instance represented by session's cdm instance value.
+ // 5.2 Use cdm to close the session associated with session.
+ mKeys->GetCDMProxy()->CloseSession(mSessionId, pid);
+
+ EME_LOG("MediaKeySession[%p,'%s'] Close() sent to CDM, promiseId=%d",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
+
+ // Session Closed algorithm is run when CDM causes us to run OnSessionClosed().
+
+ // 6. Return promise.
+ return promise.forget();
+}
+
+void
+MediaKeySession::OnClosed()
+{
+ if (IsClosed()) {
+ return;
+ }
+ EME_LOG("MediaKeySession[%p,'%s'] session close operation complete.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ mIsClosed = true;
+ mKeys->OnSessionClosed(this);
+ mKeys = nullptr;
+ mClosed->MaybeResolveWithUndefined();
+}
+
+bool
+MediaKeySession::IsClosed() const
+{
+ return mIsClosed;
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Remove(ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeySession.remove")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ if (!IsCallable()) {
+ // If this object's callable value is false, return a promise rejected
+ // with a new DOMException whose name is InvalidStateError.
+ EME_LOG("MediaKeySession[%p,''] Remove() called before sessionId set by CDM", this);
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySession.Remove() called before sessionId set by CDM"));
+ return promise.forget();
+ }
+ if (mSessionType != MediaKeySessionType::Persistent_license) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR,
+ NS_LITERAL_CSTRING("Calling MediaKeySession.remove() on non-persistent session"));
+ // "The operation is not supported on session type sessions."
+ EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, sesion not persisrtent.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+ if (IsClosed() || !mKeys->GetCDMProxy()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("MediaKeySesison.remove() called but session is not active"));
+ // "The session is closed."
+ EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, already session closed.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->GetCDMProxy()->RemoveSession(mSessionId, pid);
+ EME_LOG("MediaKeySession[%p,'%s'] Remove() sent to CDM, promiseId=%d.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
+
+ return promise.forget();
+}
+
+void
+MediaKeySession::DispatchKeyMessage(MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
+{
+ if (EME_LOG_ENABLED()) {
+ EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyMessage() type=%s message(base64)='%s'",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(),
+ MediaKeyMessageTypeValues::strings[uint32_t(aMessageType)].value,
+ ToBase64(aMessage).get());
+ }
+
+ RefPtr<MediaKeyMessageEvent> event(
+ MediaKeyMessageEvent::Constructor(this, aMessageType, aMessage));
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void
+MediaKeySession::DispatchKeyError(uint32_t aSystemCode)
+{
+ EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyError() systemCode=%u.",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), aSystemCode);
+
+ RefPtr<MediaKeyError> event(new MediaKeyError(this, aSystemCode));
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void
+MediaKeySession::DispatchKeyStatusesChange()
+{
+ if (IsClosed()) {
+ return;
+ }
+
+ UpdateKeyStatusMap();
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("keystatuseschange"), false);
+ asyncDispatcher->PostDOMEvent();
+}
+
+uint32_t
+MediaKeySession::Token() const
+{
+ return mToken;
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeySession::MakePromise(ErrorResult& aRv, const nsACString& aName)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ NS_WARNING("Passed non-global to MediaKeys ctor!");
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ return DetailedPromise::Create(global, aRv, aName);
+}
+
+void
+MediaKeySession::SetExpiration(double aExpiration)
+{
+ EME_LOG("MediaKeySession[%p,'%s'] SetExpiry(%lf)",
+ this,
+ NS_ConvertUTF16toUTF8(mSessionId).get(),
+ aExpiration);
+ mExpiration = aExpiration;
+}
+
+EventHandlerNonNull*
+MediaKeySession::GetOnkeystatuseschange()
+{
+ return GetEventHandler(nsGkAtoms::onkeystatuseschange, EmptyString());
+}
+
+void
+MediaKeySession::SetOnkeystatuseschange(EventHandlerNonNull* aCallback)
+{
+ SetEventHandler(nsGkAtoms::onkeystatuseschange, EmptyString(), aCallback);
+}
+
+EventHandlerNonNull*
+MediaKeySession::GetOnmessage()
+{
+ return GetEventHandler(nsGkAtoms::onmessage, EmptyString());
+}
+
+void
+MediaKeySession::SetOnmessage(EventHandlerNonNull* aCallback)
+{
+ SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeySession.h b/dom/media/eme/MediaKeySession.h
new file mode 100644
index 000000000..6b7137046
--- /dev/null
+++ b/dom/media/eme/MediaKeySession.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeySession_h
+#define mozilla_dom_MediaKeySession_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/dom/Date.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/DetailedPromise.h"
+#include "mozilla/dom/MediaKeySessionBinding.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class MediaKeyError;
+class MediaKeyStatusMap;
+
+class MediaKeySession final : public DOMEventTargetHelper
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaKeySession,
+ DOMEventTargetHelper)
+public:
+ MediaKeySession(JSContext* aCx,
+ nsPIDOMWindowInner* aParent,
+ MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ MediaKeySessionType aSessionType,
+ ErrorResult& aRv);
+
+ void SetSessionId(const nsAString& aSessionId);
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // Mark this as resultNotAddRefed to return raw pointers
+ MediaKeyError* GetError() const;
+
+ MediaKeyStatusMap* KeyStatuses() const;
+
+ void GetKeySystem(nsString& aRetval) const;
+
+ void GetSessionId(nsString& aRetval) const;
+
+ const nsString& GetSessionId() const;
+
+ // Number of ms since epoch at which expiration occurs, or NaN if unknown.
+ // TODO: The type of this attribute is still under contention.
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25902
+ double Expiration() const;
+
+ Promise* Closed() const;
+
+ already_AddRefed<Promise> GenerateRequest(const nsAString& aInitDataType,
+ const ArrayBufferViewOrArrayBuffer& aInitData,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Load(const nsAString& aSessionId,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Update(const ArrayBufferViewOrArrayBuffer& response,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Close(ErrorResult& aRv);
+
+ already_AddRefed<Promise> Remove(ErrorResult& aRv);
+
+ void DispatchKeyMessage(MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage);
+
+ void DispatchKeyError(uint32_t system_code);
+
+ void DispatchKeyStatusesChange();
+
+ void OnClosed();
+
+ bool IsClosed() const;
+
+ void SetExpiration(double aExpiry);
+
+ mozilla::dom::EventHandlerNonNull* GetOnkeystatuseschange();
+ void SetOnkeystatuseschange(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ mozilla::dom::EventHandlerNonNull* GetOnmessage();
+ void SetOnmessage(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ // Process-unique identifier.
+ uint32_t Token() const;
+
+private:
+ ~MediaKeySession();
+
+ void UpdateKeyStatusMap();
+
+ bool IsCallable() const {
+ // The EME spec sets the "callable value" to true whenever the CDM sets
+ // the sessionId. When the session is initialized, sessionId is empty and
+ // callable is thus false.
+ return !mSessionId.IsEmpty();
+ }
+
+ already_AddRefed<DetailedPromise> MakePromise(ErrorResult& aRv,
+ const nsACString& aName);
+
+ RefPtr<DetailedPromise> mClosed;
+
+ RefPtr<MediaKeyError> mMediaKeyError;
+ RefPtr<MediaKeys> mKeys;
+ const nsString mKeySystem;
+ nsString mSessionId;
+ const MediaKeySessionType mSessionType;
+ const uint32_t mToken;
+ bool mIsClosed;
+ bool mUninitialized;
+ RefPtr<MediaKeyStatusMap> mKeyStatusMap;
+ double mExpiration;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/MediaKeyStatusMap.cpp b/dom/media/eme/MediaKeyStatusMap.cpp
new file mode 100644
index 000000000..677fd0db2
--- /dev/null
+++ b/dom/media/eme/MediaKeyStatusMap.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MediaKeyStatusMap.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/EMEUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeyStatusMap)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeyStatusMap)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeyStatusMap)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeyStatusMap, mParent)
+
+MediaKeyStatusMap::MediaKeyStatusMap(nsPIDOMWindowInner* aParent)
+ : mParent(aParent)
+{
+}
+
+MediaKeyStatusMap::~MediaKeyStatusMap()
+{
+}
+
+JSObject*
+MediaKeyStatusMap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeyStatusMapBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner*
+MediaKeyStatusMap::GetParentObject() const
+{
+ return mParent;
+}
+
+void
+MediaKeyStatusMap::Get(JSContext* aCx,
+ const ArrayBufferViewOrArrayBuffer& aKey,
+ JS::MutableHandle<JS::Value> aOutValue,
+ ErrorResult& aOutRv) const
+{
+ ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey);
+ if (!keyId.IsValid()) {
+ aOutValue.setUndefined();
+ return;
+ }
+ for (const KeyStatus& status : mStatuses) {
+ if (keyId == status.mKeyId) {
+ bool ok = ToJSValue(aCx, status.mStatus, aOutValue);
+ if (!ok) {
+ aOutRv.NoteJSContextException(aCx);
+ }
+ return;
+ }
+ }
+ aOutValue.setUndefined();
+}
+
+bool
+MediaKeyStatusMap::Has(const ArrayBufferViewOrArrayBuffer& aKey) const
+{
+ ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey);
+ if (!keyId.IsValid()) {
+ return false;
+ }
+
+ for (const KeyStatus& status : mStatuses) {
+ if (keyId == status.mKeyId) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+uint32_t
+MediaKeyStatusMap::GetIterableLength() const
+{
+ return mStatuses.Length();
+}
+
+TypedArrayCreator<ArrayBuffer>
+MediaKeyStatusMap::GetKeyAtIndex(uint32_t aIndex) const
+{
+ MOZ_ASSERT(aIndex < GetIterableLength());
+ return TypedArrayCreator<ArrayBuffer>(mStatuses[aIndex].mKeyId);
+}
+
+MediaKeyStatus
+MediaKeyStatusMap::GetValueAtIndex(uint32_t aIndex) const
+{
+ MOZ_ASSERT(aIndex < GetIterableLength());
+ return mStatuses[aIndex].mStatus;
+}
+
+uint32_t
+MediaKeyStatusMap::Size() const
+{
+ return mStatuses.Length();
+}
+
+void
+MediaKeyStatusMap::Update(const nsTArray<CDMCaps::KeyStatus>& aKeys)
+{
+ mStatuses.Clear();
+ for (const auto& key : aKeys) {
+ mStatuses.InsertElementSorted(KeyStatus(key.mId, key.mStatus));
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeyStatusMap.h b/dom/media/eme/MediaKeyStatusMap.h
new file mode 100644
index 000000000..8bfbd4d07
--- /dev/null
+++ b/dom/media/eme/MediaKeyStatusMap.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeyStatuses_h
+#define mozilla_dom_MediaKeyStatuses_h
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h"
+#include "mozilla/CDMCaps.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+
+// The MediaKeyStatusMap WebIDL interface; maps a keyId to its status.
+// Note that the underlying "map" is stored in an array, since we assume
+// that a MediaKeySession won't have many key statuses to report.
+class MediaKeyStatusMap final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeyStatusMap)
+
+public:
+ explicit MediaKeyStatusMap(nsPIDOMWindowInner* aParent);
+
+protected:
+ ~MediaKeyStatusMap();
+
+public:
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void Get(JSContext* aCx,
+ const ArrayBufferViewOrArrayBuffer& aKey,
+ JS::MutableHandle<JS::Value> aOutValue,
+ ErrorResult& aOutRv) const;
+ bool Has(const ArrayBufferViewOrArrayBuffer& aKey) const;
+ uint32_t Size() const;
+
+ uint32_t GetIterableLength() const;
+ TypedArrayCreator<ArrayBuffer> GetKeyAtIndex(uint32_t aIndex) const;
+ MediaKeyStatus GetValueAtIndex(uint32_t aIndex) const;
+
+ void Update(const nsTArray<CDMCaps::KeyStatus>& keys);
+
+private:
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+
+ struct KeyStatus {
+ KeyStatus(const nsTArray<uint8_t>& aKeyId,
+ MediaKeyStatus aStatus)
+ : mKeyId(aKeyId)
+ , mStatus(aStatus)
+ {
+ }
+ bool operator== (const KeyStatus& aOther) const {
+ return aOther.mKeyId == mKeyId;
+ }
+ bool operator<(const KeyStatus& aOther) const {
+ // Copy chromium and compare keys' bytes.
+ // Update once https://github.com/w3c/encrypted-media/issues/69
+ // is resolved.
+ const nsTArray<uint8_t>& other = aOther.mKeyId;
+ const nsTArray<uint8_t>& self = mKeyId;
+ size_t length = std::min<size_t>(other.Length(), self.Length());
+ int cmp = memcmp(self.Elements(), other.Elements(), length);
+ if (cmp != 0) {
+ return cmp < 0;
+ }
+ return self.Length() <= other.Length();
+ }
+ nsTArray<uint8_t> mKeyId;
+ MediaKeyStatus mStatus;
+ };
+
+ nsTArray<KeyStatus> mStatuses;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/MediaKeySystemAccess.cpp b/dom/media/eme/MediaKeySystemAccess.cpp
new file mode 100644
index 000000000..c65c57ed0
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -0,0 +1,1042 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/Preferences.h"
+#include "MediaPrefs.h"
+#include "nsContentTypeParser.h"
+#ifdef MOZ_FMP4
+#include "MP4Decoder.h"
+#endif
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#include "WMFDecoderModule.h"
+#endif
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "VideoUtils.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "mozilla/EMEUtils.h"
+#include "GMPUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsXULAppAPI.h"
+#include "gmp-audio-decode.h"
+#include "gmp-video-decode.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "WebMDecoder.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/dom/MediaSource.h"
+#include "DecoderTraits.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
+ mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+MediaKeySystemAccess::MediaKeySystemAccess(nsPIDOMWindowInner* aParent,
+ const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig)
+ : mParent(aParent)
+ , mKeySystem(aKeySystem)
+ , mConfig(aConfig)
+{
+}
+
+MediaKeySystemAccess::~MediaKeySystemAccess()
+{
+}
+
+JSObject*
+MediaKeySystemAccess::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeySystemAccessBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner*
+MediaKeySystemAccess::GetParentObject() const
+{
+ return mParent;
+}
+
+void
+MediaKeySystemAccess::GetKeySystem(nsString& aOutKeySystem) const
+{
+ aOutKeySystem.Assign(mKeySystem);
+}
+
+void
+MediaKeySystemAccess::GetConfiguration(MediaKeySystemConfiguration& aConfig)
+{
+ aConfig = mConfig;
+}
+
+already_AddRefed<Promise>
+MediaKeySystemAccess::CreateMediaKeys(ErrorResult& aRv)
+{
+ RefPtr<MediaKeys> keys(new MediaKeys(mParent,
+ mKeySystem,
+ mConfig));
+ return keys->Init(aRv);
+}
+
+static bool
+HavePluginForKeySystem(const nsCString& aKeySystem)
+{
+ bool havePlugin = HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
+ { aKeySystem });
+ return havePlugin;
+}
+
+static MediaKeySystemStatus
+EnsureCDMInstalled(const nsAString& aKeySystem,
+ nsACString& aOutMessage)
+{
+ if (!HavePluginForKeySystem(NS_ConvertUTF16toUTF8(aKeySystem))) {
+ aOutMessage = NS_LITERAL_CSTRING("CDM is not installed");
+ return MediaKeySystemStatus::Cdm_not_installed;
+ }
+
+ return MediaKeySystemStatus::Available;
+}
+
+/* static */
+MediaKeySystemStatus
+MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem,
+ nsACString& aOutMessage)
+{
+ MOZ_ASSERT(MediaPrefs::EMEEnabled() || IsClearkeyKeySystem(aKeySystem));
+
+ if (IsClearkeyKeySystem(aKeySystem)) {
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+ }
+
+ if (IsWidevineKeySystem(aKeySystem)) {
+ if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
+ if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
+ aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled");
+ return MediaKeySystemStatus::Cdm_disabled;
+ }
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+ }
+ }
+
+ return MediaKeySystemStatus::Cdm_not_supported;
+}
+
+typedef nsCString EMECodecString;
+
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_AAC, "aac");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_OPUS, "opus");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VORBIS, "vorbis");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_H264, "h264");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP8, "vp8");
+static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP9, "vp9");
+
+EMECodecString
+ToEMEAPICodecString(const nsString& aCodec)
+{
+ if (IsAACCodecString(aCodec)) {
+ return EME_CODEC_AAC;
+ }
+ if (aCodec.EqualsLiteral("opus")) {
+ return EME_CODEC_OPUS;
+ }
+ if (aCodec.EqualsLiteral("vorbis")) {
+ return EME_CODEC_VORBIS;
+ }
+ if (IsH264CodecString(aCodec)) {
+ return EME_CODEC_H264;
+ }
+ if (IsVP8CodecString(aCodec)) {
+ return EME_CODEC_VP8;
+ }
+ if (IsVP9CodecString(aCodec)) {
+ return EME_CODEC_VP9;
+ }
+ return EmptyCString();
+}
+
+// A codec can be decrypted-and-decoded by the CDM, or only decrypted
+// by the CDM and decoded by Gecko. Not both.
+struct KeySystemContainerSupport
+{
+ bool IsSupported() const
+ {
+ return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty();
+ }
+
+ // CDM decrypts and decodes using a DRM robust decoder, and passes decoded
+ // samples back to Gecko for rendering.
+ bool DecryptsAndDecodes(EMECodecString aCodec) const
+ {
+ return mCodecsDecoded.Contains(aCodec);
+ }
+
+ // CDM decrypts and passes the decrypted samples back to Gecko for decoding.
+ bool Decrypts(EMECodecString aCodec) const
+ {
+ return mCodecsDecrypted.Contains(aCodec);
+ }
+
+ void SetCanDecryptAndDecode(EMECodecString aCodec)
+ {
+ // Can't both decrypt and decrypt-and-decode a codec.
+ MOZ_ASSERT(!Decrypts(aCodec));
+ // Prevent duplicates.
+ MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
+ mCodecsDecoded.AppendElement(aCodec);
+ }
+
+ void SetCanDecrypt(EMECodecString aCodec)
+ {
+ // Prevent duplicates.
+ MOZ_ASSERT(!Decrypts(aCodec));
+ // Can't both decrypt and decrypt-and-decode a codec.
+ MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
+ mCodecsDecrypted.AppendElement(aCodec);
+ }
+
+private:
+ nsTArray<EMECodecString> mCodecsDecoded;
+ nsTArray<EMECodecString> mCodecsDecrypted;
+};
+
+enum class KeySystemFeatureSupport
+{
+ Prohibited = 1,
+ Requestable = 2,
+ Required = 3,
+};
+
+struct KeySystemConfig
+{
+ nsString mKeySystem;
+ nsTArray<nsString> mInitDataTypes;
+ KeySystemFeatureSupport mPersistentState = KeySystemFeatureSupport::Prohibited;
+ KeySystemFeatureSupport mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+ nsTArray<MediaKeySessionType> mSessionTypes;
+ nsTArray<nsString> mVideoRobustness;
+ nsTArray<nsString> mAudioRobustness;
+ KeySystemContainerSupport mMP4;
+ KeySystemContainerSupport mWebM;
+};
+
+static nsTArray<KeySystemConfig>
+GetSupportedKeySystems()
+{
+ nsTArray<KeySystemConfig> keySystemConfigs;
+
+ {
+ if (HavePluginForKeySystem(kEMEKeySystemClearkey)) {
+ KeySystemConfig clearkey;
+ clearkey.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemClearkey);
+ clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+ clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
+ clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
+ clearkey.mPersistentState = KeySystemFeatureSupport::Requestable;
+ clearkey.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+ clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+ if (MediaPrefs::ClearKeyPersistentLicenseEnabled()) {
+ clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
+ }
+#if defined(XP_WIN)
+ // Clearkey CDM uses WMF decoders on Windows.
+ if (WMFDecoderModule::HasAAC()) {
+ clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_AAC);
+ } else {
+ clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+ }
+ if (WMFDecoderModule::HasH264()) {
+ clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+ } else {
+ clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
+ }
+#else
+ clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+ clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
+#endif
+ clearkey.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
+ clearkey.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
+ clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP8);
+ clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP9);
+ keySystemConfigs.AppendElement(Move(clearkey));
+ }
+ }
+ {
+ if (HavePluginForKeySystem(kEMEKeySystemWidevine)) {
+ KeySystemConfig widevine;
+ widevine.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemWidevine);
+ widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+ widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
+ widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
+ widevine.mPersistentState = KeySystemFeatureSupport::Requestable;
+ widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+ widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+ widevine.mAudioRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
+ widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_DECODE"));
+#if defined(XP_WIN)
+ // Widevine CDM doesn't include an AAC decoder. So if WMF can't
+ // decode AAC, and a codec wasn't specified, be conservative
+ // and reject the MediaKeys request, since our policy is to prevent
+ // the Adobe GMP's unencrypted AAC decoding path being used to
+ // decode content decrypted by the Widevine CDM.
+ if (WMFDecoderModule::HasAAC()) {
+ widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+ }
+#endif
+
+ widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+ widevine.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
+ widevine.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
+ widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8);
+ widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9);
+ keySystemConfigs.AppendElement(Move(widevine));
+ }
+ }
+
+ return keySystemConfigs;
+}
+
+static bool
+GetKeySystemConfig(const nsAString& aKeySystem, KeySystemConfig& aOutKeySystemConfig)
+{
+ for (auto&& config : GetSupportedKeySystems()) {
+ if (config.mKeySystem.Equals(aKeySystem)) {
+ aOutKeySystemConfig = mozilla::Move(config);
+ return true;
+ }
+ }
+ // No matching key system found.
+ return false;
+}
+
+/* static */
+bool
+MediaKeySystemAccess::KeySystemSupportsInitDataType(const nsAString& aKeySystem,
+ const nsAString& aInitDataType)
+{
+ KeySystemConfig implementation;
+ return GetKeySystemConfig(aKeySystem, implementation) &&
+ implementation.mInitDataTypes.Contains(aInitDataType);
+}
+
+enum CodecType
+{
+ Audio,
+ Video,
+ Invalid
+};
+
+static bool
+CanDecryptAndDecode(const nsString& aKeySystem,
+ const nsString& aContentType,
+ CodecType aCodecType,
+ const KeySystemContainerSupport& aContainerSupport,
+ const nsTArray<EMECodecString>& aCodecs,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ MOZ_ASSERT(aCodecType != Invalid);
+ for (const EMECodecString& codec : aCodecs) {
+ MOZ_ASSERT(!codec.IsEmpty());
+
+ if (aContainerSupport.DecryptsAndDecodes(codec)) {
+ // GMP can decrypt-and-decode this codec.
+ continue;
+ }
+
+ if (aContainerSupport.Decrypts(codec) &&
+ NS_SUCCEEDED(MediaSource::IsTypeSupported(aContentType, aDiagnostics))) {
+ // GMP can decrypt and is allowed to return compressed samples to
+ // Gecko to decode, and Gecko has a decoder.
+ continue;
+ }
+
+ // Neither the GMP nor Gecko can both decrypt and decode. We don't
+ // support this codec.
+
+#if defined(XP_WIN)
+ // Widevine CDM doesn't include an AAC decoder. So if WMF can't
+ // decode AAC, and a codec wasn't specified, be conservative
+ // and reject the MediaKeys request, since our policy is to prevent
+ // the Adobe GMP's unencrypted AAC decoding path being used to
+ // decode content decrypted by the Widevine CDM.
+ if (codec == EME_CODEC_AAC &&
+ IsWidevineKeySystem(aKeySystem) &&
+ !WMFDecoderModule::HasAAC()) {
+ if (aDiagnostics) {
+ aDiagnostics->SetKeySystemIssue(
+ DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+ }
+ }
+#endif
+ return false;
+ }
+ return true;
+}
+
+static bool
+ToSessionType(const nsAString& aSessionType, MediaKeySessionType& aOutType)
+{
+ using MediaKeySessionTypeValues::strings;
+ const char* temporary =
+ strings[static_cast<uint32_t>(MediaKeySessionType::Temporary)].value;
+ if (aSessionType.EqualsASCII(temporary)) {
+ aOutType = MediaKeySessionType::Temporary;
+ return true;
+ }
+ const char* persistentLicense =
+ strings[static_cast<uint32_t>(MediaKeySessionType::Persistent_license)].value;
+ if (aSessionType.EqualsASCII(persistentLicense)) {
+ aOutType = MediaKeySessionType::Persistent_license;
+ return true;
+ }
+ return false;
+}
+
+// 5.2.1 Is persistent session type?
+static bool
+IsPersistentSessionType(MediaKeySessionType aSessionType)
+{
+ return aSessionType == MediaKeySessionType::Persistent_license;
+}
+
+CodecType
+GetMajorType(const nsAString& aContentType)
+{
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("audio/"), aContentType)) {
+ return Audio;
+ }
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("video/"), aContentType)) {
+ return Video;
+ }
+ return Invalid;
+}
+
+static CodecType
+GetCodecType(const EMECodecString& aCodec)
+{
+ if (aCodec.Equals(EME_CODEC_AAC) ||
+ aCodec.Equals(EME_CODEC_OPUS) ||
+ aCodec.Equals(EME_CODEC_VORBIS)) {
+ return Audio;
+ }
+ if (aCodec.Equals(EME_CODEC_H264) ||
+ aCodec.Equals(EME_CODEC_VP8) ||
+ aCodec.Equals(EME_CODEC_VP9)) {
+ return Video;
+ }
+ return Invalid;
+}
+
+static bool
+AllCodecsOfType(const nsTArray<EMECodecString>& aCodecs, const CodecType aCodecType)
+{
+ for (const EMECodecString& codec : aCodecs) {
+ if (GetCodecType(codec) != aCodecType) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+IsParameterUnrecognized(const nsAString& aContentType)
+{
+ nsAutoString contentType(aContentType);
+ contentType.StripWhitespace();
+
+ nsTArray<nsString> params;
+ nsAString::const_iterator start, end, semicolon, equalSign;
+ contentType.BeginReading(start);
+ contentType.EndReading(end);
+ semicolon = start;
+ // Find any substring between ';' & '='.
+ while (semicolon != end) {
+ if (FindCharInReadable(';', semicolon, end)) {
+ equalSign = ++semicolon;
+ if (FindCharInReadable('=', equalSign, end)) {
+ params.AppendElement(Substring(semicolon, equalSign));
+ semicolon = equalSign;
+ }
+ }
+ }
+
+ for (auto param : params) {
+ if (!param.LowerCaseEqualsLiteral("codecs") &&
+ !param.LowerCaseEqualsLiteral("profiles")) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// 3.1.2.3 Get Supported Capabilities for Audio/Video Type
+static Sequence<MediaKeySystemMediaCapability>
+GetSupportedCapabilities(const CodecType aCodecType,
+ const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
+ const MediaKeySystemConfiguration& aPartialConfig,
+ const KeySystemConfig& aKeySystem,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ // Let local accumulated configuration be a local copy of partial configuration.
+ // (Note: It's not necessary for us to maintain a local copy, as we don't need
+ // to test whether capabilites from previous calls to this algorithm work with
+ // the capabilities currently being considered in this call. )
+
+ // Let supported media capabilities be an empty sequence of
+ // MediaKeySystemMediaCapability dictionaries.
+ Sequence<MediaKeySystemMediaCapability> supportedCapabilities;
+
+ // For each requested media capability in requested media capabilities:
+ for (const MediaKeySystemMediaCapability& capabilities : aRequestedCapabilities) {
+ // Let content type be requested media capability's contentType member.
+ const nsString& contentType = capabilities.mContentType;
+ // Let robustness be requested media capability's robustness member.
+ const nsString& robustness = capabilities.mRobustness;
+ // If content type is the empty string, return null.
+ if (contentType.IsEmpty()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') rejected; "
+ "audio or video capability has empty contentType.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ return Sequence<MediaKeySystemMediaCapability>();
+ }
+ // If content type is an invalid or unrecognized MIME type, continue
+ // to the next iteration.
+ nsAutoString container;
+ nsTArray<nsString> codecStrings;
+ if (!ParseMIMETypeString(contentType, container, codecStrings)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "failed to parse contentType as MIME type.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ bool invalid = false;
+ nsTArray<EMECodecString> codecs;
+ for (const nsString& codecString : codecStrings) {
+ EMECodecString emeCodec = ToEMEAPICodecString(codecString);
+ if (emeCodec.IsEmpty()) {
+ invalid = true;
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "'%s' is an invalid codec string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(codecString).get());
+ break;
+ }
+ codecs.AppendElement(emeCodec);
+ }
+ if (invalid) {
+ continue;
+ }
+
+ // If the user agent does not support container, continue to the next iteration.
+ // The case-sensitivity of string comparisons is determined by the appropriate RFC.
+ // (Note: Per RFC 6838 [RFC6838], "Both top-level type and subtype names are
+ // case-insensitive."'. We're using nsContentTypeParser and that is
+ // case-insensitive and converts all its parameter outputs to lower case.)
+ NS_ConvertUTF16toUTF8 container_utf8(container);
+ const bool isMP4 = DecoderTraits::IsMP4TypeAndEnabled(container_utf8, aDiagnostics);
+ if (isMP4 && !aKeySystem.mMP4.IsSupported()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "MP4 requested but unsupported.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ const bool isWebM = DecoderTraits::IsWebMTypeAndEnabled(container_utf8);
+ if (isWebM && !aKeySystem.mWebM.IsSupported()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "WebM requested but unsupported.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ if (!isMP4 && !isWebM) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "Unsupported or unrecognized container requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+
+ // Let parameters be the RFC 6381[RFC6381] parameters, if any, specified by
+ // content type.
+ // If the user agent does not recognize one or more parameters, continue to
+ // the next iteration.
+ if (IsParameterUnrecognized(contentType)) {
+ continue;
+ }
+
+ // Let media types be the set of codecs and codec constraints specified by
+ // parameters. The case-sensitivity of string comparisons is determined by
+ // the appropriate RFC or other specification.
+ // (Note: codecs array is 'parameter').
+
+ // If media types is empty:
+ if (codecs.IsEmpty()) {
+ // If container normatively implies a specific set of codecs and codec constraints:
+ // Let parameters be that set.
+ if (isMP4) {
+ if (aCodecType == Audio) {
+ codecs.AppendElement(EME_CODEC_AAC);
+ } else if (aCodecType == Video) {
+ codecs.AppendElement(EME_CODEC_H264);
+ }
+ } else if (isWebM) {
+ if (aCodecType == Audio) {
+ codecs.AppendElement(EME_CODEC_VORBIS);
+ } else if (aCodecType == Video) {
+ codecs.AppendElement(EME_CODEC_VP8);
+ }
+ }
+ // Otherwise: Continue to the next iteration.
+ // (Note: all containers we support have implied codecs, so don't continue here.)
+ }
+
+ // If content type is not strictly a audio/video type, continue to the next iteration.
+ const auto majorType = GetMajorType(container);
+ if (majorType == Invalid) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "MIME type is not an audio or video MIME type.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "MIME type mixes audio codecs in video capabilities "
+ "or video codecs in audio capabilities.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ // If robustness is not the empty string and contains an unrecognized
+ // value or a value not supported by implementation, continue to the
+ // next iteration. String comparison is case-sensitive.
+ if (!robustness.IsEmpty()) {
+ if (majorType == Audio && !aKeySystem.mAudioRobustness.Contains(robustness)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "unsupported robustness string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ if (majorType == Video && !aKeySystem.mVideoRobustness.Contains(robustness)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "unsupported robustness string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+ // Note: specified robustness requirements are satisfied.
+ }
+
+ // If the user agent and implementation definitely support playback of
+ // encrypted media data for the combination of container, media types,
+ // robustness and local accumulated configuration in combination with
+ // restrictions...
+ const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
+ if (!CanDecryptAndDecode(aKeySystem.mKeySystem,
+ contentType,
+ majorType,
+ containerSupport,
+ codecs,
+ aDiagnostics)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+ "codec unsupported by CDM requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentType).get(),
+ NS_ConvertUTF16toUTF8(robustness).get());
+ continue;
+ }
+
+ // ... add requested media capability to supported media capabilities.
+ if (!supportedCapabilities.AppendElement(capabilities, mozilla::fallible)) {
+ NS_WARNING("GetSupportedCapabilities: Malloc failure");
+ return Sequence<MediaKeySystemMediaCapability>();
+ }
+
+ // Note: omitting steps 3.13.2, our robustness is not sophisticated enough
+ // to require considering all requirements together.
+ }
+ return Move(supportedCapabilities);
+}
+
+// "Get Supported Configuration and Consent" algorithm, steps 4-7 for
+// distinctive identifier, and steps 8-11 for persistent state. The steps
+// are the same for both requirements/features, so we factor them out into
+// a single function.
+static bool
+CheckRequirement(const MediaKeysRequirement aRequirement,
+ const KeySystemFeatureSupport aFeatureSupport,
+ MediaKeysRequirement& aOutRequirement)
+{
+ // Let requirement be the value of candidate configuration's member.
+ MediaKeysRequirement requirement = aRequirement;
+ // If requirement is "optional" and feature is not allowed according to
+ // restrictions, set requirement to "not-allowed".
+ if (aRequirement == MediaKeysRequirement::Optional &&
+ aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
+ requirement = MediaKeysRequirement::Not_allowed;
+ }
+
+ // Follow the steps for requirement from the following list:
+ switch (requirement) {
+ case MediaKeysRequirement::Required: {
+ // If the implementation does not support use of requirement in combination
+ // with accumulated configuration and restrictions, return NotSupported.
+ if (aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
+ return false;
+ }
+ break;
+ }
+ case MediaKeysRequirement::Optional: {
+ // Continue with the following steps.
+ break;
+ }
+ case MediaKeysRequirement::Not_allowed: {
+ // If the implementation requires use of feature in combination with
+ // accumulated configuration and restrictions, return NotSupported.
+ if (aFeatureSupport == KeySystemFeatureSupport::Required) {
+ return false;
+ }
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+
+ // Set the requirement member of accumulated configuration to equal
+ // calculated requirement.
+ aOutRequirement = requirement;
+
+ return true;
+}
+
+// 3.1.2.2, step 12
+// Follow the steps for the first matching condition from the following list:
+// If the sessionTypes member is present in candidate configuration.
+// Let session types be candidate configuration's sessionTypes member.
+// Otherwise let session types be ["temporary"].
+// Note: This returns an empty array on malloc failure.
+static Sequence<nsString>
+UnboxSessionTypes(const Optional<Sequence<nsString>>& aSessionTypes)
+{
+ Sequence<nsString> sessionTypes;
+ if (aSessionTypes.WasPassed()) {
+ sessionTypes = aSessionTypes.Value();
+ } else {
+ using MediaKeySessionTypeValues::strings;
+ const char* temporary = strings[static_cast<uint32_t>(MediaKeySessionType::Temporary)].value;
+ // Note: fallible. Results in an empty array.
+ sessionTypes.AppendElement(NS_ConvertUTF8toUTF16(nsDependentCString(temporary)), mozilla::fallible);
+ }
+ return sessionTypes;
+}
+
+// 3.1.2.2 Get Supported Configuration and Consent
+static bool
+GetSupportedConfig(const KeySystemConfig& aKeySystem,
+ const MediaKeySystemConfiguration& aCandidate,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ // Let accumulated configuration be a new MediaKeySystemConfiguration dictionary.
+ MediaKeySystemConfiguration config;
+ // Set the label member of accumulated configuration to equal the label member of
+ // candidate configuration.
+ config.mLabel = aCandidate.mLabel;
+ // If the initDataTypes member of candidate configuration is non-empty, run the
+ // following steps:
+ if (!aCandidate.mInitDataTypes.IsEmpty()) {
+ // Let supported types be an empty sequence of DOMStrings.
+ nsTArray<nsString> supportedTypes;
+ // For each value in candidate configuration's initDataTypes member:
+ for (const nsString& initDataType : aCandidate.mInitDataTypes) {
+ // Let initDataType be the value.
+ // If the implementation supports generating requests based on initDataType,
+ // add initDataType to supported types. String comparison is case-sensitive.
+ // The empty string is never supported.
+ if (aKeySystem.mInitDataTypes.Contains(initDataType)) {
+ supportedTypes.AppendElement(initDataType);
+ }
+ }
+ // If supported types is empty, return NotSupported.
+ if (supportedTypes.IsEmpty()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported initDataTypes provided.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the initDataTypes member of accumulated configuration to supported types.
+ if (!config.mInitDataTypes.Assign(supportedTypes)) {
+ return false;
+ }
+ }
+
+ if (!CheckRequirement(aCandidate.mDistinctiveIdentifier,
+ aKeySystem.mDistinctiveIdentifier,
+ config.mDistinctiveIdentifier)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "distinctiveIdentifier requirement not satisfied.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+
+ if (!CheckRequirement(aCandidate.mPersistentState,
+ aKeySystem.mPersistentState,
+ config.mPersistentState)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "persistentState requirement not satisfied.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+
+ Sequence<nsString> sessionTypes(UnboxSessionTypes(aCandidate.mSessionTypes));
+ if (sessionTypes.IsEmpty()) {
+ // Malloc failure.
+ return false;
+ }
+
+ // For each value in session types:
+ for (const auto& sessionTypeString : sessionTypes) {
+ // Let session type be the value.
+ MediaKeySessionType sessionType;
+ if (!ToSessionType(sessionTypeString, sessionType)) {
+ // (Assume invalid sessionType is unsupported as per steps below).
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "invalid session type specified.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // If accumulated configuration's persistentState value is "not-allowed"
+ // and the Is persistent session type? algorithm returns true for session
+ // type return NotSupported.
+ if (config.mPersistentState == MediaKeysRequirement::Not_allowed &&
+ IsPersistentSessionType(sessionType)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "persistent session requested but keysystem doesn't"
+ "support persistent state.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // If the implementation does not support session type in combination
+ // with accumulated configuration and restrictions for other reasons,
+ // return NotSupported.
+ if (!aKeySystem.mSessionTypes.Contains(sessionType)) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "session type '%s' unsupported by keySystem.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(),
+ NS_ConvertUTF16toUTF8(sessionTypeString).get());
+ return false;
+ }
+ // If accumulated configuration's persistentState value is "optional"
+ // and the result of running the Is persistent session type? algorithm
+ // on session type is true, change accumulated configuration's
+ // persistentState value to "required".
+ if (config.mPersistentState == MediaKeysRequirement::Optional &&
+ IsPersistentSessionType(sessionType)) {
+ config.mPersistentState = MediaKeysRequirement::Required;
+ }
+ }
+ // Set the sessionTypes member of accumulated configuration to session types.
+ config.mSessionTypes.Construct(Move(sessionTypes));
+
+ // If the videoCapabilities and audioCapabilities members in candidate
+ // configuration are both empty, return NotSupported.
+ // TODO: Most sites using EME still don't pass capabilities, so we
+ // can't reject on it yet without breaking them. So add this later.
+
+ // If the videoCapabilities member in candidate configuration is non-empty:
+ if (!aCandidate.mVideoCapabilities.IsEmpty()) {
+ // Let video capabilities be the result of executing the Get Supported
+ // Capabilities for Audio/Video Type algorithm on Video, candidate
+ // configuration's videoCapabilities member, accumulated configuration,
+ // and restrictions.
+ Sequence<MediaKeySystemMediaCapability> caps =
+ GetSupportedCapabilities(Video,
+ aCandidate.mVideoCapabilities,
+ config,
+ aKeySystem,
+ aDiagnostics);
+ // If video capabilities is null, return NotSupported.
+ if (caps.IsEmpty()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported video capabilities.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the videoCapabilities member of accumulated configuration to video capabilities.
+ config.mVideoCapabilities = Move(caps);
+ } else {
+ // Otherwise:
+ // Set the videoCapabilities member of accumulated configuration to an empty sequence.
+ }
+
+ // If the audioCapabilities member in candidate configuration is non-empty:
+ if (!aCandidate.mAudioCapabilities.IsEmpty()) {
+ // Let audio capabilities be the result of executing the Get Supported Capabilities
+ // for Audio/Video Type algorithm on Audio, candidate configuration's audioCapabilities
+ // member, accumulated configuration, and restrictions.
+ Sequence<MediaKeySystemMediaCapability> caps =
+ GetSupportedCapabilities(Audio,
+ aCandidate.mAudioCapabilities,
+ config,
+ aKeySystem,
+ aDiagnostics);
+ // If audio capabilities is null, return NotSupported.
+ if (caps.IsEmpty()) {
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported audio capabilities.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the audioCapabilities member of accumulated configuration to audio capabilities.
+ config.mAudioCapabilities = Move(caps);
+ } else {
+ // Otherwise:
+ // Set the audioCapabilities member of accumulated configuration to an empty sequence.
+ }
+
+ // If accumulated configuration's distinctiveIdentifier value is "optional", follow the
+ // steps for the first matching condition from the following list:
+ if (config.mDistinctiveIdentifier == MediaKeysRequirement::Optional) {
+ // If the implementation requires use Distinctive Identifier(s) or
+ // Distinctive Permanent Identifier(s) for any of the combinations
+ // in accumulated configuration
+ if (aKeySystem.mDistinctiveIdentifier == KeySystemFeatureSupport::Required) {
+ // Change accumulated configuration's distinctiveIdentifier value to "required".
+ config.mDistinctiveIdentifier = MediaKeysRequirement::Required;
+ } else {
+ // Otherwise, change accumulated configuration's distinctiveIdentifier
+ // value to "not-allowed".
+ config.mDistinctiveIdentifier = MediaKeysRequirement::Not_allowed;
+ }
+ }
+
+ // If accumulated configuration's persistentState value is "optional", follow the
+ // steps for the first matching condition from the following list:
+ if (config.mPersistentState == MediaKeysRequirement::Optional) {
+ // If the implementation requires persisting state for any of the combinations
+ // in accumulated configuration
+ if (aKeySystem.mPersistentState == KeySystemFeatureSupport::Required) {
+ // Change accumulated configuration's persistentState value to "required".
+ config.mPersistentState = MediaKeysRequirement::Required;
+ } else {
+ // Otherwise, change accumulated configuration's persistentState
+ // value to "not-allowed".
+ config.mPersistentState = MediaKeysRequirement::Not_allowed;
+ }
+ }
+
+ // Note: Omitting steps 20-22. We don't ask for consent.
+
+#if defined(XP_WIN)
+ // Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC,
+ // and a codec wasn't specified, be conservative and reject the MediaKeys request.
+ if (IsWidevineKeySystem(aKeySystem.mKeySystem) &&
+ (aCandidate.mAudioCapabilities.IsEmpty() ||
+ aCandidate.mVideoCapabilities.IsEmpty()) &&
+ !WMFDecoderModule::HasAAC()) {
+ if (aDiagnostics) {
+ aDiagnostics->SetKeySystemIssue(
+ DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+ }
+ EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+ "WMF required for Widevine decoding, but it's not available.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+#endif
+
+ // Return accumulated configuration.
+ aOutConfig = config;
+
+ return true;
+}
+
+/* static */
+bool
+MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ KeySystemConfig implementation;
+ if (!GetKeySystemConfig(aKeySystem, implementation)) {
+ return false;
+ }
+ for (const MediaKeySystemConfiguration& candidate : aConfigs) {
+ if (mozilla::dom::GetSupportedConfig(implementation,
+ candidate,
+ aOutConfig,
+ aDiagnostics)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/* static */
+void
+MediaKeySystemAccess::NotifyObservers(nsPIDOMWindowInner* aWindow,
+ const nsAString& aKeySystem,
+ MediaKeySystemStatus aStatus)
+{
+ RequestMediaKeySystemAccessNotification data;
+ data.mKeySystem = aKeySystem;
+ data.mStatus = aStatus;
+ nsAutoString json;
+ data.ToJSON(json);
+ EME_LOG("MediaKeySystemAccess::NotifyObservers() %s", NS_ConvertUTF16toUTF8(json).get());
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(aWindow, "mediakeys-request", json.get());
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeySystemAccess.h b/dom/media/eme/MediaKeySystemAccess.h
new file mode 100644
index 000000000..a166ac22e
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccess.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeySystemAccess_h
+#define mozilla_dom_MediaKeySystemAccess_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/dom/MediaKeysRequestStatusBinding.h"
+
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+
+class DecoderDoctorDiagnostics;
+
+namespace dom {
+
+class MediaKeySystemAccess final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeySystemAccess)
+
+public:
+ explicit MediaKeySystemAccess(nsPIDOMWindowInner* aParent,
+ const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig);
+
+protected:
+ ~MediaKeySystemAccess();
+
+public:
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetKeySystem(nsString& aRetVal) const;
+
+ void GetConfiguration(MediaKeySystemConfiguration& aConfig);
+
+ already_AddRefed<Promise> CreateMediaKeys(ErrorResult& aRv);
+
+ static MediaKeySystemStatus GetKeySystemStatus(const nsAString& aKeySystem,
+ nsACString& aOutExceptionMessage);
+
+ static bool IsSupported(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ DecoderDoctorDiagnostics* aDiagnostics);
+
+ static void NotifyObservers(nsPIDOMWindowInner* aWindow,
+ const nsAString& aKeySystem,
+ MediaKeySystemStatus aStatus);
+
+ static bool GetSupportedConfig(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics);
+
+ static bool KeySystemSupportsInitDataType(const nsAString& aKeySystem,
+ const nsAString& aInitDataType);
+
+private:
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ const nsString mKeySystem;
+ const MediaKeySystemConfiguration mConfig;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaKeySystemAccess_h
diff --git a/dom/media/eme/MediaKeySystemAccessManager.cpp b/dom/media/eme/MediaKeySystemAccessManager.cpp
new file mode 100644
index 000000000..a1e1254ad
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -0,0 +1,336 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaKeySystemAccessManager.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "MediaPrefs.h"
+#include "mozilla/EMEUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/DetailedPromise.h"
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#endif
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccessManager)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccessManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccessManager)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeySystemAccessManager)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeySystemAccessManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ for (size_t i = 0; i < tmp->mRequests.Length(); i++) {
+ tmp->mRequests[i].RejectPromise(NS_LITERAL_CSTRING("Promise still outstanding at MediaKeySystemAccessManager GC"));
+ tmp->mRequests[i].CancelTimer();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequests[i].mPromise)
+ }
+ tmp->mRequests.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeySystemAccessManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ for (size_t i = 0; i < tmp->mRequests.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequests[i].mPromise)
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+MediaKeySystemAccessManager::MediaKeySystemAccessManager(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+ , mAddedObservers(false)
+{
+}
+
+MediaKeySystemAccessManager::~MediaKeySystemAccessManager()
+{
+ Shutdown();
+}
+
+void
+MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ Request(aPromise, aKeySystem, aConfigs, RequestType::Initial);
+}
+
+void
+MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ RequestType aType)
+{
+ EME_LOG("MediaKeySystemAccessManager::Request %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
+
+ if (aKeySystem.IsEmpty()) {
+ aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Key system string is empty"));
+ // Don't notify DecoderDoctor, as there's nothing we or the user can
+ // do to fix this situation; the site is using the API wrong.
+ return;
+ }
+ if (aConfigs.IsEmpty()) {
+ aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Candidate MediaKeySystemConfigs is empty"));
+ // Don't notify DecoderDoctor, as there's nothing we or the user can
+ // do to fix this situation; the site is using the API wrong.
+ return;
+ }
+
+ DecoderDoctorDiagnostics diagnostics;
+
+ // Ensure keysystem is supported.
+ if (!IsWidevineKeySystem(aKeySystem) &&
+ !IsClearkeyKeySystem(aKeySystem)) {
+ // Not to inform user, because nothing to do if the keySystem is not
+ // supported.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Key system is unsupported"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+ return;
+ }
+
+ if (!MediaPrefs::EMEEnabled() && !IsClearkeyKeySystem(aKeySystem)) {
+ // EME disabled by user, send notification to chrome so UI can inform user.
+ // Clearkey is allowed even when EME is disabled because we want the pref
+ // "media.eme.enabled" only taking effect on proprietary DRMs.
+ MediaKeySystemAccess::NotifyObservers(mWindow,
+ aKeySystem,
+ MediaKeySystemStatus::Api_disabled);
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("EME has been preffed off"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+ return;
+ }
+
+ nsAutoCString message;
+ MediaKeySystemStatus status =
+ MediaKeySystemAccess::GetKeySystemStatus(aKeySystem, message);
+
+ nsPrintfCString msg("MediaKeySystemAccess::GetKeySystemStatus(%s) "
+ "result=%s msg='%s'",
+ NS_ConvertUTF16toUTF8(aKeySystem).get(),
+ MediaKeySystemStatusValues::strings[(size_t)status].value,
+ message.get());
+ LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
+
+ if (status == MediaKeySystemStatus::Cdm_not_installed &&
+ IsWidevineKeySystem(aKeySystem)) {
+ // These are cases which could be resolved by downloading a new(er) CDM.
+ // When we send the status to chrome, chrome's GMPProvider will attempt to
+ // download or update the CDM. In AwaitInstall() we add listeners to wait
+ // for the update to complete, and we'll call this function again with
+ // aType==Subsequent once the download has completed and the GMPService
+ // has had a new plugin added. AwaitInstall() sets a timer to fail if the
+ // update/download takes too long or fails.
+ if (aType == RequestType::Initial &&
+ AwaitInstall(aPromise, aKeySystem, aConfigs)) {
+ // Notify chrome that we're going to wait for the CDM to download/update.
+ // Note: If we're re-trying, we don't re-send the notification,
+ // as chrome is already displaying the "we can't play, updating"
+ // notification.
+ MediaKeySystemAccess::NotifyObservers(mWindow, aKeySystem, status);
+ } else {
+ // We waited or can't wait for an update and we still can't service
+ // the request. Give up. Chrome will still be showing a "I can't play,
+ // updating" notification.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Gave up while waiting for a CDM update"));
+ }
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+ return;
+ }
+ if (status != MediaKeySystemStatus::Available) {
+ // Failed due to user disabling something, send a notification to
+ // chrome, so we can show some UI to explain how the user can rectify
+ // the situation.
+ MediaKeySystemAccess::NotifyObservers(mWindow, aKeySystem, status);
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, message);
+ return;
+ }
+
+ MediaKeySystemConfiguration config;
+ if (MediaKeySystemAccess::GetSupportedConfig(aKeySystem, aConfigs, config, &diagnostics)) {
+ RefPtr<MediaKeySystemAccess> access(
+ new MediaKeySystemAccess(mWindow, aKeySystem, config));
+ aPromise->MaybeResolve(access);
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, true, __func__);
+ return;
+ }
+ // Not to inform user, because nothing to do if the corresponding keySystem
+ // configuration is not supported.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Key system configuration is not supported"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+}
+
+MediaKeySystemAccessManager::PendingRequest::PendingRequest(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ nsITimer* aTimer)
+ : mPromise(aPromise)
+ , mKeySystem(aKeySystem)
+ , mConfigs(aConfigs)
+ , mTimer(aTimer)
+{
+ MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+MediaKeySystemAccessManager::PendingRequest::PendingRequest(const PendingRequest& aOther)
+ : mPromise(aOther.mPromise)
+ , mKeySystem(aOther.mKeySystem)
+ , mConfigs(aOther.mConfigs)
+ , mTimer(aOther.mTimer)
+{
+ MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+MediaKeySystemAccessManager::PendingRequest::~PendingRequest()
+{
+ MOZ_COUNT_DTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+void
+MediaKeySystemAccessManager::PendingRequest::CancelTimer()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+}
+
+void
+MediaKeySystemAccessManager::PendingRequest::RejectPromise(const nsCString& aReason)
+{
+ if (mPromise) {
+ mPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR, aReason);
+ }
+}
+
+bool
+MediaKeySystemAccessManager::AwaitInstall(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ EME_LOG("MediaKeySystemAccessManager::AwaitInstall %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
+
+ if (!EnsureObserversAdded()) {
+ NS_WARNING("Failed to add pref observer");
+ return false;
+ }
+
+ nsCOMPtr<nsITimer> timer(do_CreateInstance("@mozilla.org/timer;1"));
+ if (!timer || NS_FAILED(timer->Init(this, 60 * 1000, nsITimer::TYPE_ONE_SHOT))) {
+ NS_WARNING("Failed to create timer to await CDM install.");
+ return false;
+ }
+
+ mRequests.AppendElement(PendingRequest(aPromise, aKeySystem, aConfigs, timer));
+ return true;
+}
+
+void
+MediaKeySystemAccessManager::RetryRequest(PendingRequest& aRequest)
+{
+ aRequest.CancelTimer();
+ Request(aRequest.mPromise, aRequest.mKeySystem, aRequest.mConfigs, RequestType::Subsequent);
+}
+
+nsresult
+MediaKeySystemAccessManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ EME_LOG("MediaKeySystemAccessManager::Observe %s", aTopic);
+
+ if (!strcmp(aTopic, "gmp-changed")) {
+ // Filter out the requests where the CDM's install-status is no longer
+ // "unavailable". This will be the CDMs which have downloaded since the
+ // initial request.
+ // Note: We don't have a way to communicate from chrome that the CDM has
+ // failed to download, so we'll just let the timeout fail us in that case.
+ nsTArray<PendingRequest> requests;
+ for (size_t i = mRequests.Length(); i-- > 0; ) {
+ PendingRequest& request = mRequests[i];
+ nsAutoCString message;
+ MediaKeySystemStatus status =
+ MediaKeySystemAccess::GetKeySystemStatus(request.mKeySystem, message);
+ if (status == MediaKeySystemStatus::Cdm_not_installed) {
+ // Not yet installed, don't retry. Keep waiting until timeout.
+ continue;
+ }
+ // Status has changed, retry request.
+ requests.AppendElement(Move(request));
+ mRequests.RemoveElementAt(i);
+ }
+ // Retry all pending requests, but this time fail if the CDM is not installed.
+ for (PendingRequest& request : requests) {
+ RetryRequest(request);
+ }
+ } else if (!strcmp(aTopic, "timer-callback")) {
+ // Find the timer that expired and re-run the request for it.
+ nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
+ for (size_t i = 0; i < mRequests.Length(); i++) {
+ if (mRequests[i].mTimer == timer) {
+ EME_LOG("MediaKeySystemAccessManager::AwaitInstall resuming request");
+ PendingRequest request = mRequests[i];
+ mRequests.RemoveElementAt(i);
+ RetryRequest(request);
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+bool
+MediaKeySystemAccessManager::EnsureObserversAdded()
+{
+ if (mAddedObservers) {
+ return true;
+ }
+
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obsService)) {
+ return false;
+ }
+ mAddedObservers = NS_SUCCEEDED(obsService->AddObserver(this, "gmp-changed", false));
+ return mAddedObservers;
+}
+
+void
+MediaKeySystemAccessManager::Shutdown()
+{
+ EME_LOG("MediaKeySystemAccessManager::Shutdown");
+ nsTArray<PendingRequest> requests(Move(mRequests));
+ for (PendingRequest& request : requests) {
+ // Cancel all requests; we're shutting down.
+ request.CancelTimer();
+ request.RejectPromise(NS_LITERAL_CSTRING("Promise still outstanding at MediaKeySystemAccessManager shutdown"));
+ }
+ if (mAddedObservers) {
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->RemoveObserver(this, "gmp-changed");
+ mAddedObservers = false;
+ }
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeySystemAccessManager.h b/dom/media/eme/MediaKeySystemAccessManager.h
new file mode 100644
index 000000000..9c092248e
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessManager.h
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeySystemAccessManager_h
+#define mozilla_dom_MediaKeySystemAccessManager_h
+
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "nsIObserver.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace dom {
+
+class DetailedPromise;
+class TestGMPVideoDecoder;
+
+class MediaKeySystemAccessManager final : public nsIObserver
+{
+public:
+
+ explicit MediaKeySystemAccessManager(nsPIDOMWindowInner* aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(MediaKeySystemAccessManager, nsIObserver)
+ NS_DECL_NSIOBSERVER
+
+ void Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig);
+
+ void Shutdown();
+
+ struct PendingRequest {
+ PendingRequest(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig,
+ nsITimer* aTimer);
+ PendingRequest(const PendingRequest& aOther);
+ ~PendingRequest();
+ void CancelTimer();
+ void RejectPromise(const nsCString& aReason);
+
+ RefPtr<DetailedPromise> mPromise;
+ const nsString mKeySystem;
+ const Sequence<MediaKeySystemConfiguration> mConfigs;
+ nsCOMPtr<nsITimer> mTimer;
+ };
+
+private:
+
+ enum RequestType {
+ Initial,
+ Subsequent
+ };
+
+ void Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig,
+ RequestType aType);
+
+ ~MediaKeySystemAccessManager();
+
+ bool EnsureObserversAdded();
+
+ bool AwaitInstall(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig);
+
+ void RetryRequest(PendingRequest& aRequest);
+
+ nsTArray<PendingRequest> mRequests;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ bool mAddedObservers;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/eme/MediaKeys.cpp b/dom/media/eme/MediaKeys.cpp
new file mode 100644
index 000000000..862100757
--- /dev/null
+++ b/dom/media/eme/MediaKeys.cpp
@@ -0,0 +1,579 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MediaKeys.h"
+#include "GMPService.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeyError.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "GMPCDMProxy.h"
+#include "mozilla/EMEUtils.h"
+#include "nsContentUtils.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsContentTypeParser.h"
+#ifdef MOZ_FMP4
+#include "MP4Decoder.h"
+#endif
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#endif
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys,
+ mElement,
+ mParent,
+ mKeySessions,
+ mPromises,
+ mPendingSessions);
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeys)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeys)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+MediaKeys::MediaKeys(nsPIDOMWindowInner* aParent,
+ const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig)
+ : mParent(aParent)
+ , mKeySystem(aKeySystem)
+ , mCreatePromiseId(0)
+ , mConfig(aConfig)
+{
+ EME_LOG("MediaKeys[%p] constructed keySystem=%s",
+ this, NS_ConvertUTF16toUTF8(mKeySystem).get());
+}
+
+MediaKeys::~MediaKeys()
+{
+ Shutdown();
+ EME_LOG("MediaKeys[%p] destroyed", this);
+}
+
+void
+MediaKeys::Terminated()
+{
+ EME_LOG("MediaKeys[%p] CDM crashed unexpectedly", this);
+
+ KeySessionHashMap keySessions;
+ // Remove entries during iteration will screw it. Make a copy first.
+ for (auto iter = mKeySessions.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<MediaKeySession>& session = iter.Data();
+ keySessions.Put(session->GetSessionId(), session);
+ }
+ for (auto iter = keySessions.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<MediaKeySession>& session = iter.Data();
+ session->OnClosed();
+ }
+ keySessions.Clear();
+ MOZ_ASSERT(mKeySessions.Count() == 0);
+
+ // Notify the element about that CDM has terminated.
+ if (mElement) {
+ mElement->DecodeError(NS_ERROR_DOM_MEDIA_CDM_ERR);
+ }
+
+ Shutdown();
+}
+
+void
+MediaKeys::Shutdown()
+{
+ if (mProxy) {
+ mProxy->Shutdown();
+ mProxy = nullptr;
+ }
+
+ RefPtr<MediaKeys> kungFuDeathGrip = this;
+
+ for (auto iter = mPromises.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<dom::DetailedPromise>& promise = iter.Data();
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Promise still outstanding at MediaKeys shutdown"));
+ Release();
+ }
+ mPromises.Clear();
+}
+
+nsPIDOMWindowInner*
+MediaKeys::GetParentObject() const
+{
+ return mParent;
+}
+
+JSObject*
+MediaKeys::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaKeysBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+MediaKeys::GetKeySystem(nsString& aOutKeySystem) const
+{
+ aOutKeySystem.Assign(mKeySystem);
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeys::SetServerCertificate(const ArrayBufferViewOrArrayBuffer& aCert, ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeys.setServerCertificate")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!mProxy) {
+ NS_WARNING("Tried to use a MediaKeys without a CDM");
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in MediaKeys.setServerCertificate()"));
+ return promise.forget();
+ }
+
+ nsTArray<uint8_t> data;
+ CopyArrayBufferViewOrArrayBufferData(aCert, data);
+ if (data.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("Empty certificate passed to MediaKeys.setServerCertificate()"));
+ return promise.forget();
+ }
+
+ mProxy->SetServerCertificate(StorePromise(promise), data);
+ return promise.forget();
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeys::MakePromise(ErrorResult& aRv, const nsACString& aName)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ NS_WARNING("Passed non-global to MediaKeys ctor!");
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ return DetailedPromise::Create(global, aRv, aName);
+}
+
+PromiseId
+MediaKeys::StorePromise(DetailedPromise* aPromise)
+{
+ static uint32_t sEMEPromiseCount = 1;
+ MOZ_ASSERT(aPromise);
+ uint32_t id = sEMEPromiseCount++;
+
+ EME_LOG("MediaKeys[%p]::StorePromise() id=%d", this, id);
+
+ // Keep MediaKeys alive for the lifetime of its promises. Any still-pending
+ // promises are rejected in Shutdown().
+ AddRef();
+
+#ifdef DEBUG
+ // We should not have already stored this promise!
+ for (auto iter = mPromises.ConstIter(); !iter.Done(); iter.Next()) {
+ MOZ_ASSERT(iter.Data() != aPromise);
+ }
+#endif
+
+ mPromises.Put(id, aPromise);
+ return id;
+}
+
+void
+MediaKeys::ConnectPendingPromiseIdWithToken(PromiseId aId, uint32_t aToken)
+{
+ // Should only be called from MediaKeySession::GenerateRequest.
+ mPromiseIdToken.Put(aId, aToken);
+ EME_LOG("MediaKeys[%p]::ConnectPendingPromiseIdWithToken() id=%u => token(%u)",
+ this, aId, aToken);
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeys::RetrievePromise(PromiseId aId)
+{
+ if (!mPromises.Contains(aId)) {
+ NS_WARNING(nsPrintfCString("Tried to retrieve a non-existent promise id=%d", aId).get());
+ return nullptr;
+ }
+ RefPtr<DetailedPromise> promise;
+ mPromises.Remove(aId, getter_AddRefs(promise));
+ Release();
+ return promise.forget();
+}
+
+void
+MediaKeys::RejectPromise(PromiseId aId, nsresult aExceptionCode,
+ const nsCString& aReason)
+{
+ EME_LOG("MediaKeys[%p]::RejectPromise(%d, 0x%x)", this, aId, aExceptionCode);
+
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+
+ // This promise could be a createSession or loadSession promise,
+ // so we might have a pending session waiting to be resolved into
+ // the promise on success. We've been directed to reject to promise,
+ // so we can throw away the corresponding session object.
+ uint32_t token = 0;
+ if (mPromiseIdToken.Get(aId, &token)) {
+ MOZ_ASSERT(mPendingSessions.Contains(token));
+ mPendingSessions.Remove(token);
+ mPromiseIdToken.Remove(aId);
+ }
+
+ MOZ_ASSERT(NS_FAILED(aExceptionCode));
+ promise->MaybeReject(aExceptionCode, aReason);
+
+ if (mCreatePromiseId == aId) {
+ // Note: This will probably destroy the MediaKeys object!
+ Release();
+ }
+}
+
+void
+MediaKeys::OnSessionIdReady(MediaKeySession* aSession)
+{
+ if (!aSession) {
+ NS_WARNING("Invalid MediaKeySession passed to OnSessionIdReady()");
+ return;
+ }
+ if (mKeySessions.Contains(aSession->GetSessionId())) {
+ NS_WARNING("MediaKeySession's made ready multiple times!");
+ return;
+ }
+ if (mPendingSessions.Contains(aSession->Token())) {
+ NS_WARNING("MediaKeySession made ready when it wasn't waiting to be ready!");
+ return;
+ }
+ if (aSession->GetSessionId().IsEmpty()) {
+ NS_WARNING("MediaKeySession with invalid sessionId passed to OnSessionIdReady()");
+ return;
+ }
+ mKeySessions.Put(aSession->GetSessionId(), aSession);
+}
+
+void
+MediaKeys::ResolvePromise(PromiseId aId)
+{
+ EME_LOG("MediaKeys[%p]::ResolvePromise(%d)", this, aId);
+
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ MOZ_ASSERT(!mPromises.Contains(aId));
+ if (!promise) {
+ return;
+ }
+
+ uint32_t token = 0;
+ if (!mPromiseIdToken.Get(aId, &token)) {
+ promise->MaybeResolveWithUndefined();
+ return;
+ } else if (!mPendingSessions.Contains(token)) {
+ // Pending session for CreateSession() should be removed when sessionId
+ // is ready.
+ promise->MaybeResolveWithUndefined();
+ mPromiseIdToken.Remove(aId);
+ return;
+ }
+ mPromiseIdToken.Remove(aId);
+
+ // We should only resolve LoadSession calls via this path,
+ // not CreateSession() promises.
+ RefPtr<MediaKeySession> session;
+ mPendingSessions.Remove(token, getter_AddRefs(session));
+ if (!session || session->GetSessionId().IsEmpty()) {
+ NS_WARNING("Received activation for non-existent session!");
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR,
+ NS_LITERAL_CSTRING("CDM LoadSession() returned a different session ID than requested"));
+ return;
+ }
+ mKeySessions.Put(session->GetSessionId(), session);
+ promise->MaybeResolve(session);
+}
+
+class MediaKeysGMPCrashHelper : public GMPCrashHelper
+{
+public:
+ explicit MediaKeysGMPCrashHelper(MediaKeys* aMediaKeys)
+ : mMediaKeys(aMediaKeys)
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ }
+ already_AddRefed<nsPIDOMWindowInner>
+ GetPluginCrashedEventTarget() override
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ EME_LOG("MediaKeysGMPCrashHelper::GetPluginCrashedEventTarget()");
+ return (mMediaKeys && mMediaKeys->GetParentObject()) ?
+ do_AddRef(mMediaKeys->GetParentObject()) : nullptr;
+ }
+private:
+ WeakPtr<MediaKeys> mMediaKeys;
+};
+
+already_AddRefed<CDMProxy>
+MediaKeys::CreateCDMProxy()
+{
+ RefPtr<CDMProxy> proxy;
+ {
+ proxy = new GMPCDMProxy(this,
+ mKeySystem,
+ new MediaKeysGMPCrashHelper(this),
+ mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
+ mConfig.mPersistentState == MediaKeysRequirement::Required);
+ }
+ return proxy.forget();
+}
+
+already_AddRefed<DetailedPromise>
+MediaKeys::Init(ErrorResult& aRv)
+{
+ RefPtr<DetailedPromise> promise(MakePromise(aRv,
+ NS_LITERAL_CSTRING("MediaKeys::Init()")));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ mProxy = CreateCDMProxy();
+
+ // Determine principal (at creation time) of the MediaKeys object.
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject());
+ if (!sop) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get script principal in MediaKeys::Init"));
+ return promise.forget();
+ }
+ mPrincipal = sop->GetPrincipal();
+
+ // Determine principal of the "top-level" window; the principal of the
+ // page that will display in the URL bar.
+ nsCOMPtr<nsPIDOMWindowInner> window = GetParentObject();
+ if (!window) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get top-level window in MediaKeys::Init"));
+ return promise.forget();
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> top = window->GetOuterWindow()->GetTop();
+ if (!top || !top->GetExtantDoc()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get document in MediaKeys::Init"));
+ return promise.forget();
+ }
+
+ mTopLevelPrincipal = top->GetExtantDoc()->NodePrincipal();
+
+ if (!mPrincipal || !mTopLevelPrincipal) {
+ NS_WARNING("Failed to get principals when creating MediaKeys");
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get principal(s) in MediaKeys::Init"));
+ return promise.forget();
+ }
+
+ nsAutoCString origin;
+ nsresult rv = mPrincipal->GetOrigin(origin);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get principal origin string in MediaKeys::Init"));
+ return promise.forget();
+ }
+ nsAutoCString topLevelOrigin;
+ rv = mTopLevelPrincipal->GetOrigin(topLevelOrigin);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get top-level principal origin string in MediaKeys::Init"));
+ return promise.forget();
+ }
+
+ nsIDocument* doc = window->GetExtantDoc();
+ const bool inPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(doc);
+
+ EME_LOG("MediaKeys[%p]::Create() (%s, %s), %s",
+ this,
+ origin.get(),
+ topLevelOrigin.get(),
+ (inPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
+
+ // The CDMProxy's initialization is asynchronous. The MediaKeys is
+ // refcounted, and its instance is returned to JS by promise once
+ // it's been initialized. No external refs exist to the MediaKeys while
+ // we're waiting for the promise to be resolved, so we must hold a
+ // reference to the new MediaKeys object until it's been created,
+ // or its creation has failed. Store the id of the promise returned
+ // here, and hold a self-reference until that promise is resolved or
+ // rejected.
+ MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!");
+ mCreatePromiseId = StorePromise(promise);
+ AddRef();
+ mProxy->Init(mCreatePromiseId,
+ NS_ConvertUTF8toUTF16(origin),
+ NS_ConvertUTF8toUTF16(topLevelOrigin),
+ KeySystemToGMPName(mKeySystem),
+ inPrivateBrowsing);
+
+ return promise.forget();
+}
+
+void
+MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const uint32_t aPluginId)
+{
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+ mNodeId = aNodeId;
+ RefPtr<MediaKeys> keys(this);
+ EME_LOG("MediaKeys[%p]::OnCDMCreated() resolve promise id=%d", this, aId);
+ promise->MaybeResolve(keys);
+ if (mCreatePromiseId == aId) {
+ Release();
+ }
+
+ MediaKeySystemAccess::NotifyObservers(mParent,
+ mKeySystem,
+ MediaKeySystemStatus::Cdm_created);
+
+}
+
+static bool
+IsSessionTypeSupported(const MediaKeySessionType aSessionType,
+ const MediaKeySystemConfiguration& aConfig)
+{
+ if (aSessionType == MediaKeySessionType::Temporary) {
+ // Temporary is always supported.
+ return true;
+ }
+ if (!aConfig.mSessionTypes.WasPassed()) {
+ // No other session types supported.
+ return false;
+ }
+ using MediaKeySessionTypeValues::strings;
+ const char* sessionType = strings[static_cast<uint32_t>(aSessionType)].value;
+ for (const nsString& s : aConfig.mSessionTypes.Value()) {
+ if (s.EqualsASCII(sessionType)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+already_AddRefed<MediaKeySession>
+MediaKeys::CreateSession(JSContext* aCx,
+ MediaKeySessionType aSessionType,
+ ErrorResult& aRv)
+{
+ if (!IsSessionTypeSupported(aSessionType, mConfig)) {
+ EME_LOG("MediaKeys[%p,'%s'] CreateSession() failed, unsupported session type", this);
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (!mProxy) {
+ NS_WARNING("Tried to use a MediaKeys which lost its CDM");
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ EME_LOG("MediaKeys[%p] Creating session", this);
+
+ RefPtr<MediaKeySession> session = new MediaKeySession(aCx,
+ GetParentObject(),
+ this,
+ mKeySystem,
+ aSessionType,
+ aRv);
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Add session to the set of sessions awaiting their sessionId being ready.
+ mPendingSessions.Put(session->Token(), session);
+
+ return session.forget();
+}
+
+void
+MediaKeys::OnSessionLoaded(PromiseId aId, bool aSuccess)
+{
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+ EME_LOG("MediaKeys[%p]::OnSessionLoaded() resolve promise id=%d", this, aId);
+
+ promise->MaybeResolve(aSuccess);
+}
+
+void
+MediaKeys::OnSessionClosed(MediaKeySession* aSession)
+{
+ nsAutoString id;
+ aSession->GetSessionId(id);
+ mKeySessions.Remove(id);
+}
+
+already_AddRefed<MediaKeySession>
+MediaKeys::GetSession(const nsAString& aSessionId)
+{
+ RefPtr<MediaKeySession> session;
+ mKeySessions.Get(aSessionId, getter_AddRefs(session));
+ return session.forget();
+}
+
+already_AddRefed<MediaKeySession>
+MediaKeys::GetPendingSession(uint32_t aToken)
+{
+ RefPtr<MediaKeySession> session;
+ mPendingSessions.Get(aToken, getter_AddRefs(session));
+ mPendingSessions.Remove(aToken);
+ return session.forget();
+}
+
+const nsCString&
+MediaKeys::GetNodeId() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mNodeId;
+}
+
+bool
+MediaKeys::IsBoundToMediaElement() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mElement != nullptr;
+}
+
+nsresult
+MediaKeys::Bind(HTMLMediaElement* aElement)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsBoundToMediaElement()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mElement = aElement;
+
+ return NS_OK;
+}
+
+void
+MediaKeys::Unbind()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mElement = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/MediaKeys.h b/dom/media/eme/MediaKeys.h
new file mode 100644
index 000000000..491963934
--- /dev/null
+++ b/dom/media/eme/MediaKeys.h
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_mediakeys_h__
+#define mozilla_dom_mediakeys_h__
+
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/DetailedPromise.h"
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla {
+
+class CDMProxy;
+
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class MediaKeySession;
+class HTMLMediaElement;
+
+typedef nsRefPtrHashtable<nsStringHashKey, MediaKeySession> KeySessionHashMap;
+typedef nsRefPtrHashtable<nsUint32HashKey, dom::DetailedPromise> PromiseHashMap;
+typedef nsRefPtrHashtable<nsUint32HashKey, MediaKeySession> PendingKeySessionsHashMap;
+typedef nsDataHashtable<nsUint32HashKey, uint32_t> PendingPromiseIdTokenHashMap;
+typedef uint32_t PromiseId;
+
+// This class is used on the main thread only.
+// Note: its addref/release is not (and can't be) thread safe!
+class MediaKeys final : public nsISupports,
+ public nsWrapperCache,
+ public SupportsWeakPtr<MediaKeys>
+{
+ ~MediaKeys();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeys)
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MediaKeys)
+
+ MediaKeys(nsPIDOMWindowInner* aParentWindow,
+ const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig);
+
+ already_AddRefed<DetailedPromise> Init(ErrorResult& aRv);
+
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsresult Bind(HTMLMediaElement* aElement);
+ void Unbind();
+
+ // Javascript: readonly attribute DOMString keySystem;
+ void GetKeySystem(nsString& retval) const;
+
+ // JavaScript: MediaKeys.createSession()
+ already_AddRefed<MediaKeySession> CreateSession(JSContext* aCx,
+ MediaKeySessionType aSessionType,
+ ErrorResult& aRv);
+
+ // JavaScript: MediaKeys.SetServerCertificate()
+ already_AddRefed<DetailedPromise>
+ SetServerCertificate(const ArrayBufferViewOrArrayBuffer& aServerCertificate,
+ ErrorResult& aRv);
+
+ already_AddRefed<MediaKeySession> GetSession(const nsAString& aSessionId);
+
+ // Removes and returns MediaKeySession from the set of sessions awaiting
+ // their sessionId to be assigned.
+ already_AddRefed<MediaKeySession> GetPendingSession(uint32_t aToken);
+
+ // Called once a Init() operation succeeds.
+ void OnCDMCreated(PromiseId aId,
+ const nsACString& aNodeId, const uint32_t aPluginId);
+
+ // Called once the CDM generates a sessionId while servicing a
+ // MediaKeySession.generateRequest() or MediaKeySession.load() call,
+ // once the sessionId of a MediaKeySession is known.
+ void OnSessionIdReady(MediaKeySession* aSession);
+
+ // Called once a LoadSession succeeds.
+ void OnSessionLoaded(PromiseId aId, bool aSuccess);
+
+ // Called once a session has closed.
+ void OnSessionClosed(MediaKeySession* aSession);
+
+ CDMProxy* GetCDMProxy() { return mProxy; }
+
+ // Makes a new promise, or nullptr on failure.
+ already_AddRefed<DetailedPromise> MakePromise(ErrorResult& aRv,
+ const nsACString& aName);
+ // Stores promise in mPromises, returning an ID that can be used to retrieve
+ // it later. The ID is passed to the CDM, so that it can signal specific
+ // promises to be resolved.
+ PromiseId StorePromise(DetailedPromise* aPromise);
+
+ // Stores a map from promise id to pending session token. Using this
+ // mapping, when a promise is rejected via its ID, we can check if the
+ // promise corresponds to a pending session and retrieve that session
+ // via the mapped-to token, and remove the pending session from the
+ // list of sessions awaiting a session id.
+ void ConnectPendingPromiseIdWithToken(PromiseId aId, uint32_t aToken);
+
+ // Reject promise with DOMException corresponding to aExceptionCode.
+ void RejectPromise(PromiseId aId, nsresult aExceptionCode,
+ const nsCString& aReason);
+ // Resolves promise with "undefined".
+ void ResolvePromise(PromiseId aId);
+
+ const nsCString& GetNodeId() const;
+
+ void Shutdown();
+
+ // Called by CDMProxy when CDM crashes or shuts down. It is different from
+ // Shutdown which is called from the script/dom side.
+ void Terminated();
+
+ // Returns true if this MediaKeys has been bound to a media element.
+ bool IsBoundToMediaElement() const;
+
+private:
+
+ // Instantiate CDMProxy instance.
+ // It could be MediaDrmCDMProxy (Widevine on Fennec) or GMPCDMProxy (the rest).
+ already_AddRefed<CDMProxy> CreateCDMProxy();
+
+ // Removes promise from mPromises, and returns it.
+ already_AddRefed<DetailedPromise> RetrievePromise(PromiseId aId);
+
+ // Owning ref to proxy. The proxy has a weak reference back to the MediaKeys,
+ // and the MediaKeys destructor clears the proxy's reference to the MediaKeys.
+ RefPtr<CDMProxy> mProxy;
+
+ RefPtr<HTMLMediaElement> mElement;
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ const nsString mKeySystem;
+ nsCString mNodeId;
+ KeySessionHashMap mKeySessions;
+ PromiseHashMap mPromises;
+ PendingKeySessionsHashMap mPendingSessions;
+ PromiseId mCreatePromiseId;
+
+ RefPtr<nsIPrincipal> mPrincipal;
+ RefPtr<nsIPrincipal> mTopLevelPrincipal;
+
+ const MediaKeySystemConfiguration mConfig;
+
+ PendingPromiseIdTokenHashMap mPromiseIdToken;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_mediakeys_h__
diff --git a/dom/media/eme/moz.build b/dom/media/eme/moz.build
new file mode 100644
index 000000000..7b1ad9d84
--- /dev/null
+++ b/dom/media/eme/moz.build
@@ -0,0 +1,41 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom += [
+ 'MediaEncryptedEvent.h',
+ 'MediaKeyError.h',
+ 'MediaKeyMessageEvent.h',
+ 'MediaKeys.h',
+ 'MediaKeySession.h',
+ 'MediaKeyStatusMap.h',
+ 'MediaKeySystemAccess.h',
+ 'MediaKeySystemAccessManager.h',
+]
+
+EXPORTS.mozilla += [
+ 'CDMCaps.h',
+ 'CDMProxy.h',
+ 'DecryptorProxyCallback.h',
+ 'DetailedPromise.h',
+ 'EMEUtils.h',
+]
+
+SOURCES += [
+ 'CDMCaps.cpp',
+ 'DetailedPromise.cpp',
+ 'EMEUtils.cpp',
+ 'MediaEncryptedEvent.cpp',
+ 'MediaKeyError.cpp',
+ 'MediaKeyMessageEvent.cpp',
+ 'MediaKeys.cpp',
+ 'MediaKeySession.cpp',
+ 'MediaKeyStatusMap.cpp',
+ 'MediaKeySystemAccess.cpp',
+ 'MediaKeySystemAccessManager.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/media/fmp4/MP4Decoder.cpp b/dom/media/fmp4/MP4Decoder.cpp
index 5ad690625..7a340d829 100644
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -9,6 +10,9 @@
#include "MP4Demuxer.h"
#include "mozilla/Preferences.h"
#include "nsCharSeparatedTokenizer.h"
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#endif
#include "mozilla/Logging.h"
#include "mozilla/SharedThreadPool.h"
#include "nsMimeTypes.h"
diff --git a/dom/media/gmp-plugin-openh264/fakeopenh264.info b/dom/media/gmp-plugin-openh264/fakeopenh264.info
new file mode 100644
index 000000000..814a96be3
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/fakeopenh264.info
@@ -0,0 +1,4 @@
+Name: fakeopenh264
+Description: Fake GMP Plugin
+Version: 1.0
+APIs: encode-video[h264:fake], decode-video[h264:fake]
diff --git a/dom/media/gmp-plugin-openh264/fakeopenh264.voucher b/dom/media/gmp-plugin-openh264/fakeopenh264.voucher
new file mode 100644
index 000000000..2104d73c7
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/fakeopenh264.voucher
@@ -0,0 +1 @@
+gmp-fakeopenh264 placeholder voucher
diff --git a/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
new file mode 100644
index 000000000..7dbc85251
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
@@ -0,0 +1,429 @@
+/*!
+ * \copy
+ * Copyright (c) 2009-2014, Cisco Systems
+ * Copyright (c) 2014, Mozilla
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ *************************************************************************************
+ */
+
+#include <stdint.h>
+#include <time.h>
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+#include <string>
+#include <memory>
+#include <assert.h>
+#include <limits.h>
+
+#include "gmp-platform.h"
+#include "gmp-video-host.h"
+#include "gmp-video-encode.h"
+#include "gmp-video-decode.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+#include "gmp-decryption.h"
+#include "gmp-test-decryptor.h"
+#include "gmp-test-storage.h"
+#endif
+
+#if defined(_MSC_VER)
+#define PUBLIC_FUNC __declspec(dllexport)
+#else
+#define PUBLIC_FUNC
+#endif
+
+#define BIG_FRAME 10000
+
+static int g_log_level = 0;
+
+#define GMPLOG(l, x) do { \
+ if (l <= g_log_level) { \
+ const char *log_string = "unknown"; \
+ if ((l >= 0) && (l <= 3)) { \
+ log_string = kLogStrings[l]; \
+ } \
+ std::cerr << log_string << ": " << x << std::endl; \
+ } \
+ } while(0)
+
+#define GL_CRIT 0
+#define GL_ERROR 1
+#define GL_INFO 2
+#define GL_DEBUG 3
+
+const char* kLogStrings[] = {
+ "Critical",
+ "Error",
+ "Info",
+ "Debug"
+};
+
+
+GMPPlatformAPI* g_platform_api = NULL;
+
+class FakeVideoEncoder;
+class FakeVideoDecoder;
+
+struct EncodedFrame {
+ uint32_t length_;
+ uint8_t h264_compat_;
+ uint32_t magic_;
+ uint32_t width_;
+ uint32_t height_;
+ uint8_t y_;
+ uint8_t u_;
+ uint8_t v_;
+ uint32_t timestamp_;
+};
+
+#define ENCODED_FRAME_MAGIC 0x4652414d
+
+class FakeEncoderTask : public GMPTask {
+ public:
+ FakeEncoderTask(FakeVideoEncoder* encoder,
+ GMPVideoi420Frame* frame,
+ GMPVideoFrameType type)
+ : encoder_(encoder), frame_(frame), type_(type) {}
+
+ virtual void Run();
+ virtual void Destroy() { delete this; }
+
+ FakeVideoEncoder* encoder_;
+ GMPVideoi420Frame* frame_;
+ GMPVideoFrameType type_;
+};
+
+class FakeVideoEncoder : public GMPVideoEncoder {
+ public:
+ explicit FakeVideoEncoder (GMPVideoHost* hostAPI) :
+ host_ (hostAPI),
+ callback_ (NULL) {}
+
+ virtual void InitEncode (const GMPVideoCodec& codecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificSize,
+ GMPVideoEncoderCallback* callback,
+ int32_t numberOfCores,
+ uint32_t maxPayloadSize) {
+ callback_ = callback;
+
+ GMPLOG (GL_INFO, "Initialized encoder");
+ }
+
+ virtual void Encode (GMPVideoi420Frame* inputImage,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ const GMPVideoFrameType* aFrameTypes,
+ uint32_t aFrameTypesLength) {
+ GMPLOG (GL_DEBUG,
+ __FUNCTION__
+ << " size="
+ << inputImage->Width() << "x" << inputImage->Height());
+
+ assert (aFrameTypesLength != 0);
+
+ g_platform_api->runonmainthread(new FakeEncoderTask(this,
+ inputImage,
+ aFrameTypes[0]));
+ }
+
+ void Encode_m (GMPVideoi420Frame* inputImage,
+ GMPVideoFrameType frame_type) {
+ if (frame_type == kGMPKeyFrame) {
+ if (!inputImage)
+ return;
+ }
+ if (!inputImage) {
+ GMPLOG (GL_ERROR, "no input image");
+ return;
+ }
+
+ // Now return the encoded data back to the parent.
+ GMPVideoFrame* ftmp;
+ GMPErr err = host_->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+ if (err != GMPNoErr) {
+ GMPLOG (GL_ERROR, "Error creating encoded frame");
+ return;
+ }
+
+ GMPVideoEncodedFrame* f = static_cast<GMPVideoEncodedFrame*> (ftmp);
+
+ // Encode this in a frame that looks a little bit like H.264.
+ // Note that we don't do PPS or SPS.
+ // Copy the data. This really should convert this to network byte order.
+ EncodedFrame eframe;
+ eframe.length_ = sizeof(eframe) - sizeof(uint32_t);
+ eframe.h264_compat_ = 5; // Emulate a H.264 IDR NAL.
+ eframe.magic_ = ENCODED_FRAME_MAGIC;
+ eframe.width_ = inputImage->Width();
+ eframe.height_ = inputImage->Height();
+ eframe.y_ = AveragePlane(inputImage->Buffer(kGMPYPlane),
+ inputImage->AllocatedSize(kGMPYPlane));
+ eframe.u_ = AveragePlane(inputImage->Buffer(kGMPUPlane),
+ inputImage->AllocatedSize(kGMPUPlane));
+ eframe.v_ = AveragePlane(inputImage->Buffer(kGMPVPlane),
+ inputImage->AllocatedSize(kGMPVPlane));
+
+ eframe.timestamp_ = inputImage->Timestamp();
+
+ err = f->CreateEmptyFrame (sizeof(eframe) +
+ (frame_type == kGMPKeyFrame ? sizeof(uint32_t) + BIG_FRAME : 0));
+ if (err != GMPNoErr) {
+ GMPLOG (GL_ERROR, "Error allocating frame data");
+ f->Destroy();
+ return;
+ }
+ memcpy(f->Buffer(), &eframe, sizeof(eframe));
+ if (frame_type == kGMPKeyFrame) {
+ *((uint32_t*) f->Buffer() + sizeof(eframe)) = BIG_FRAME;
+ }
+
+ f->SetEncodedWidth (inputImage->Width());
+ f->SetEncodedHeight (inputImage->Height());
+ f->SetTimeStamp (inputImage->Timestamp());
+ f->SetFrameType (frame_type);
+ f->SetCompleteFrame (true);
+ f->SetBufferType(GMP_BufferLength32);
+
+ GMPLOG (GL_DEBUG, "Encoding complete. type= "
+ << f->FrameType()
+ << " length="
+ << f->Size()
+ << " timestamp="
+ << f->TimeStamp());
+
+ // Return the encoded frame.
+ GMPCodecSpecificInfo info;
+ memset (&info, 0, sizeof (info));
+ info.mCodecType = kGMPVideoCodecH264;
+ info.mBufferType = GMP_BufferLength32;
+ info.mCodecSpecific.mH264.mSimulcastIdx = 0;
+ GMPLOG (GL_DEBUG, "Calling callback");
+ callback_->Encoded (f, reinterpret_cast<uint8_t*> (&info), sizeof(info));
+ GMPLOG (GL_DEBUG, "Callback called");
+ }
+
+ virtual void SetChannelParameters (uint32_t aPacketLoss, uint32_t aRTT) {
+ }
+
+ virtual void SetRates (uint32_t aNewBitRate, uint32_t aFrameRate) {
+ }
+
+ virtual void SetPeriodicKeyFrames (bool aEnable) {
+ }
+
+ virtual void EncodingComplete() {
+ delete this;
+ }
+
+ private:
+ uint8_t AveragePlane(uint8_t* ptr, size_t len) {
+ uint64_t val = 0;
+
+ for (size_t i=0; i<len; ++i) {
+ val += ptr[i];
+ }
+
+ return (val / len) % 0xff;
+ }
+
+ GMPVideoHost* host_;
+ GMPVideoEncoderCallback* callback_;
+};
+
+void FakeEncoderTask::Run() {
+ encoder_->Encode_m(frame_, type_);
+ frame_->Destroy();
+}
+
+class FakeDecoderTask : public GMPTask {
+ public:
+ FakeDecoderTask(FakeVideoDecoder* decoder,
+ GMPVideoEncodedFrame* frame,
+ int64_t time)
+ : decoder_(decoder), frame_(frame), time_(time) {}
+
+ virtual void Run();
+ virtual void Destroy() { delete this; }
+
+ FakeVideoDecoder* decoder_;
+ GMPVideoEncodedFrame* frame_;
+ int64_t time_;
+};
+
+class FakeVideoDecoder : public GMPVideoDecoder {
+ public:
+ explicit FakeVideoDecoder (GMPVideoHost* hostAPI) :
+ host_ (hostAPI),
+ callback_ (NULL) {}
+
+ virtual ~FakeVideoDecoder() {
+ }
+
+ virtual void InitDecode (const GMPVideoCodec& codecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificSize,
+ GMPVideoDecoderCallback* callback,
+ int32_t coreCount) {
+ GMPLOG (GL_INFO, "InitDecode");
+
+ callback_ = callback;
+ }
+
+ virtual void Decode (GMPVideoEncodedFrame* inputFrame,
+ bool missingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t renderTimeMs = -1) {
+ GMPLOG (GL_DEBUG, __FUNCTION__
+ << "Decoding frame size=" << inputFrame->Size()
+ << " timestamp=" << inputFrame->TimeStamp());
+ g_platform_api->runonmainthread(new FakeDecoderTask(this, inputFrame, renderTimeMs));
+ }
+
+ virtual void Reset() {
+ }
+
+ virtual void Drain() {
+ }
+
+ virtual void DecodingComplete() {
+ delete this;
+ }
+
+ // Return the decoded data back to the parent.
+ void Decode_m (GMPVideoEncodedFrame* inputFrame,
+ int64_t renderTimeMs) {
+ EncodedFrame *eframe;
+ if (inputFrame->Size() != (sizeof(*eframe))) {
+ GMPLOG (GL_ERROR, "Couldn't decode frame. Size=" << inputFrame->Size());
+ return;
+ }
+ eframe = reinterpret_cast<EncodedFrame*>(inputFrame->Buffer());
+
+ if (eframe->magic_ != ENCODED_FRAME_MAGIC) {
+ GMPLOG (GL_ERROR, "Couldn't decode frame. Magic=" << eframe->magic_);
+ return;
+ }
+
+ int width = eframe->width_;
+ int height = eframe->height_;
+ int ystride = eframe->width_;
+ int uvstride = eframe->width_/2;
+
+ GMPLOG (GL_DEBUG, "Video frame ready for display "
+ << width
+ << "x"
+ << height
+ << " timestamp="
+ << inputFrame->TimeStamp());
+
+ GMPVideoFrame* ftmp = NULL;
+
+ // Translate the image.
+ GMPErr err = host_->CreateFrame (kGMPI420VideoFrame, &ftmp);
+ if (err != GMPNoErr) {
+ GMPLOG (GL_ERROR, "Couldn't allocate empty I420 frame");
+ return;
+ }
+
+ GMPVideoi420Frame* frame = static_cast<GMPVideoi420Frame*> (ftmp);
+ err = frame->CreateEmptyFrame (
+ width, height,
+ ystride, uvstride, uvstride);
+ if (err != GMPNoErr) {
+ GMPLOG (GL_ERROR, "Couldn't make decoded frame");
+ return;
+ }
+
+ memset(frame->Buffer(kGMPYPlane),
+ eframe->y_,
+ frame->AllocatedSize(kGMPYPlane));
+ memset(frame->Buffer(kGMPUPlane),
+ eframe->u_,
+ frame->AllocatedSize(kGMPUPlane));
+ memset(frame->Buffer(kGMPVPlane),
+ eframe->v_,
+ frame->AllocatedSize(kGMPVPlane));
+
+ GMPLOG (GL_DEBUG, "Allocated size = "
+ << frame->AllocatedSize (kGMPYPlane));
+ frame->SetTimestamp (inputFrame->TimeStamp());
+ frame->SetDuration (inputFrame->Duration());
+ callback_->Decoded (frame);
+
+ }
+
+ GMPVideoHost* host_;
+ GMPVideoDecoderCallback* callback_;
+};
+
+void FakeDecoderTask::Run() {
+ decoder_->Decode_m(frame_, time_);
+ frame_->Destroy();
+}
+
+extern "C" {
+
+ PUBLIC_FUNC GMPErr
+ GMPInit (GMPPlatformAPI* aPlatformAPI) {
+ g_platform_api = aPlatformAPI;
+ return GMPNoErr;
+ }
+
+ PUBLIC_FUNC GMPErr
+ GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
+ if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
+ *aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI));
+ return GMPNoErr;
+ } else if (!strcmp (aApiName, GMP_API_VIDEO_ENCODER)) {
+ *aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI));
+ return GMPNoErr;
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+ } else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) {
+ *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
+ return GMPNoErr;
+ } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) {
+ *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
+ return GMPNoErr;
+#endif
+ }
+ return GMPGenericErr;
+ }
+
+ PUBLIC_FUNC void
+ GMPShutdown (void) {
+ g_platform_api = NULL;
+ }
+
+} // extern "C"
diff --git a/dom/media/gmp-plugin-openh264/moz.build b/dom/media/gmp-plugin-openh264/moz.build
new file mode 100644
index 000000000..d47a1d197
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# largely a copy of dom/media/gmp-fake/moz.build
+
+FINAL_TARGET = 'dist/bin/gmp-fakeopenh264/1.0'
+
+FINAL_TARGET_FILES += [
+ 'fakeopenh264.info',
+ 'fakeopenh264.voucher',
+]
+
+SOURCES += [
+ 'gmp-fake-openh264.cpp',
+]
+
+SharedLibrary("fakeopenh264")
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ OS_LIBS += [
+ 'ole32',
+ ]
+
+USE_STATIC_LIBS = True
+NO_VISIBILITY_FLAGS = True
+# Don't use STL wrappers; this isn't Gecko code
+DISABLE_STL_WRAPPING = True
diff --git a/dom/media/gmp-plugin/fake.info b/dom/media/gmp-plugin/fake.info
new file mode 100644
index 000000000..b0592ff63
--- /dev/null
+++ b/dom/media/gmp-plugin/fake.info
@@ -0,0 +1,5 @@
+Name: fake
+Description: Fake GMP Plugin, which deliberately uses GMP_API_DECRYPTOR_BACKWARDS_COMPAT for its decryptor.
+Version: 1.0
+APIs: decode-video[h264:broken], eme-decrypt-v7[fake]
+Libraries: dxva2.dll
diff --git a/dom/media/gmp-plugin/fake.voucher b/dom/media/gmp-plugin/fake.voucher
new file mode 100644
index 000000000..bb133701c
--- /dev/null
+++ b/dom/media/gmp-plugin/fake.voucher
@@ -0,0 +1 @@
+gmp-fake placeholder voucher \ No newline at end of file
diff --git a/dom/media/gmp-plugin/gmp-fake.cpp b/dom/media/gmp-plugin/gmp-fake.cpp
new file mode 100644
index 000000000..b67efe251
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-fake.cpp
@@ -0,0 +1,98 @@
+/*!
+ * \copy
+ * Copyright (c) 2009-2014, Cisco Systems
+ * Copyright (c) 2014, Mozilla
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ *************************************************************************************
+ */
+
+#include <stdint.h>
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include <memory>
+
+#include "gmp-platform.h"
+#include "gmp-video-decode.h"
+
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+#include "gmp-decryption.h"
+#include "gmp-test-decryptor.h"
+#include "gmp-test-storage.h"
+#endif
+
+#if defined(_MSC_VER)
+#define PUBLIC_FUNC __declspec(dllexport)
+#else
+#define PUBLIC_FUNC
+#endif
+
+GMPPlatformAPI* g_platform_api = NULL;
+
+extern "C" {
+
+ PUBLIC_FUNC GMPErr
+ GMPInit (GMPPlatformAPI* aPlatformAPI) {
+ g_platform_api = aPlatformAPI;
+ return GMPNoErr;
+ }
+
+ PUBLIC_FUNC GMPErr
+ GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
+ if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
+ // Note: Deliberately advertise in our .info file that we support
+ // video-decode, but we fail the "get" call here to simulate what
+ // happens when decoder init fails.
+ return GMPGenericErr;
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+ } else if (!strcmp (aApiName, GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) {
+ *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
+ return GMPNoErr;
+ } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) {
+ *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
+ return GMPNoErr;
+#endif
+ }
+ return GMPGenericErr;
+ }
+
+ PUBLIC_FUNC void
+ GMPShutdown (void) {
+ g_platform_api = NULL;
+ }
+
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+ PUBLIC_FUNC void
+ GMPSetNodeId(const char* aNodeId, uint32_t aLength) {
+ FakeDecryptor::SetNodeId(aNodeId, aLength);
+ }
+#endif
+
+} // extern "C"
diff --git a/dom/media/gmp-plugin/gmp-test-decryptor.cpp b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
new file mode 100644
index 000000000..afa809602
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
@@ -0,0 +1,608 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gmp-test-decryptor.h"
+#include "gmp-test-storage.h"
+#include "gmp-test-output-protection.h"
+
+#include <string>
+#include <vector>
+#include <iostream>
+#include <istream>
+#include <iterator>
+#include <sstream>
+#include <set>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+using namespace std;
+
+std::string FakeDecryptor::sNodeId;
+
+FakeDecryptor* FakeDecryptor::sInstance = nullptr;
+extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp
+
+class GMPMutexAutoLock
+{
+public:
+ explicit GMPMutexAutoLock(GMPMutex* aMutex) : mMutex(aMutex) {
+ mMutex->Acquire();
+ }
+ ~GMPMutexAutoLock() {
+ mMutex->Release();
+ }
+private:
+ GMPMutex* const mMutex;
+};
+
+class TestManager {
+public:
+ TestManager() : mMutex(CreateMutex()) {}
+
+ // Register a test with the test manager.
+ void BeginTest(const string& aTestID) {
+ GMPMutexAutoLock lock(mMutex);
+ auto found = mTestIDs.find(aTestID);
+ if (found == mTestIDs.end()) {
+ mTestIDs.insert(aTestID);
+ } else {
+ Error("FAIL BeginTest test already existed: " + aTestID);
+ }
+ }
+
+ // Notify the test manager that the test is finished. If all tests are done,
+ // test manager will send "test-storage complete" to notify the parent that
+ // all tests are finished and also delete itself.
+ void EndTest(const string& aTestID) {
+ bool isEmpty = false;
+ {
+ GMPMutexAutoLock lock(mMutex);
+ auto found = mTestIDs.find(aTestID);
+ if (found != mTestIDs.end()) {
+ mTestIDs.erase(aTestID);
+ isEmpty = mTestIDs.empty();
+ } else {
+ Error("FAIL EndTest test not existed: " + aTestID);
+ return;
+ }
+ }
+ if (isEmpty) {
+ Finish();
+ delete this;
+ }
+ }
+
+private:
+ ~TestManager() {
+ mMutex->Destroy();
+ }
+
+ static void Error(const string& msg) {
+ FakeDecryptor::Message(msg);
+ }
+
+ static void Finish() {
+ FakeDecryptor::Message("test-storage complete");
+ }
+
+ static GMPMutex* CreateMutex() {
+ GMPMutex* mutex = nullptr;
+ g_platform_api->createmutex(&mutex);
+ return mutex;
+ }
+
+ GMPMutex* const mMutex;
+ set<string> mTestIDs;
+};
+
+FakeDecryptor::FakeDecryptor(GMPDecryptorHost* aHost)
+ : mCallback(nullptr)
+ , mHost(aHost)
+{
+ MOZ_ASSERT(!sInstance);
+ sInstance = this;
+}
+
+void FakeDecryptor::DecryptingComplete()
+{
+ sInstance = nullptr;
+ delete this;
+}
+
+void
+FakeDecryptor::Message(const std::string& aMessage)
+{
+ MOZ_ASSERT(sInstance);
+ const static std::string sid("fake-session-id");
+ sInstance->mCallback->SessionMessage(sid.c_str(), sid.size(),
+ kGMPLicenseRequest,
+ (const uint8_t*)aMessage.c_str(), aMessage.size());
+}
+
+std::vector<std::string>
+Tokenize(const std::string& aString)
+{
+ std::stringstream strstr(aString);
+ std::istream_iterator<std::string> it(strstr), end;
+ return std::vector<std::string>(it, end);
+}
+
+static const string TruncateRecordId = "truncate-record-id";
+static const string TruncateRecordData = "I will soon be truncated";
+
+class ReadThenTask : public GMPTask {
+public:
+ ReadThenTask(string aId, ReadContinuation* aThen)
+ : mId(aId)
+ , mThen(aThen)
+ {}
+ void Run() override {
+ ReadRecord(mId, mThen);
+ }
+ void Destroy() override {
+ delete this;
+ }
+ string mId;
+ ReadContinuation* mThen;
+};
+
+class SendMessageTask : public GMPTask {
+public:
+ explicit SendMessageTask(const string& aMessage,
+ TestManager* aTestManager = nullptr,
+ const string& aTestID = "")
+ : mMessage(aMessage), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void Run() override {
+ FakeDecryptor::Message(mMessage);
+ if (mTestmanager) {
+ mTestmanager->EndTest(mTestID);
+ }
+ }
+
+ void Destroy() override {
+ delete this;
+ }
+
+private:
+ string mMessage;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class TestEmptyContinuation : public ReadContinuation {
+public:
+ TestEmptyContinuation(TestManager* aTestManager, const string& aTestID)
+ : mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (aData != "") {
+ FakeDecryptor::Message("FAIL TestEmptyContinuation record was not truncated");
+ }
+ mTestmanager->EndTest(mTestID);
+ delete this;
+ }
+
+private:
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class TruncateContinuation : public ReadContinuation {
+public:
+ TruncateContinuation(const string& aID,
+ TestManager* aTestManager,
+ const string& aTestID)
+ : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (aData != TruncateRecordData) {
+ FakeDecryptor::Message("FAIL TruncateContinuation read data doesn't match written data");
+ }
+ auto cont = new TestEmptyContinuation(mTestmanager, mTestID);
+ auto msg = "FAIL in TruncateContinuation write.";
+ auto failTask = new SendMessageTask(msg, mTestmanager, mTestID);
+ WriteRecord(mID, nullptr, 0, new ReadThenTask(mID, cont), failTask);
+ delete this;
+ }
+
+private:
+ const string mID;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class VerifyAndFinishContinuation : public ReadContinuation {
+public:
+ explicit VerifyAndFinishContinuation(string aValue,
+ TestManager* aTestManager,
+ const string& aTestID)
+ : mValue(aValue), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (aData != mValue) {
+ FakeDecryptor::Message("FAIL VerifyAndFinishContinuation read data doesn't match expected data");
+ }
+ mTestmanager->EndTest(mTestID);
+ delete this;
+ }
+
+private:
+ string mValue;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class VerifyAndOverwriteContinuation : public ReadContinuation {
+public:
+ VerifyAndOverwriteContinuation(string aId, string aValue, string aOverwrite,
+ TestManager* aTestManager, const string& aTestID)
+ : mId(aId)
+ , mValue(aValue)
+ , mOverwrite(aOverwrite)
+ , mTestmanager(aTestManager)
+ , mTestID(aTestID)
+ {}
+
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (aData != mValue) {
+ FakeDecryptor::Message("FAIL VerifyAndOverwriteContinuation read data doesn't match expected data");
+ }
+ auto cont = new VerifyAndFinishContinuation(mOverwrite, mTestmanager, mTestID);
+ auto msg = "FAIL in VerifyAndOverwriteContinuation write.";
+ auto failTask = new SendMessageTask(msg, mTestmanager, mTestID);
+ WriteRecord(mId, mOverwrite, new ReadThenTask(mId, cont), failTask);
+ delete this;
+ }
+
+private:
+ string mId;
+ string mValue;
+ string mOverwrite;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+static const string OpenAgainRecordId = "open-again-record-id";
+
+class OpenedSecondTimeContinuation : public OpenContinuation {
+public:
+ explicit OpenedSecondTimeContinuation(GMPRecord* aRecord,
+ TestManager* aTestManager,
+ const string& aTestID)
+ : mRecord(aRecord), mTestmanager(aTestManager), mTestID(aTestID) {
+ MOZ_ASSERT(aRecord);
+ }
+
+ virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) override {
+ if (GMP_SUCCEEDED(aStatus)) {
+ FakeDecryptor::Message("FAIL OpenSecondTimeContinuation should not be able to re-open record.");
+ }
+ if (aRecord) {
+ aRecord->Close();
+ }
+ // Succeeded, open should have failed.
+ mTestmanager->EndTest(mTestID);
+ mRecord->Close();
+ }
+
+private:
+ GMPRecord* mRecord;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class OpenedFirstTimeContinuation : public OpenContinuation {
+public:
+ OpenedFirstTimeContinuation(const string& aID,
+ TestManager* aTestManager,
+ const string& aTestID)
+ : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) override {
+ if (GMP_FAILED(aStatus)) {
+ FakeDecryptor::Message("FAIL OpenAgainContinuation to open record initially.");
+ mTestmanager->EndTest(mTestID);
+ if (aRecord) {
+ aRecord->Close();
+ }
+ return;
+ }
+
+ auto cont = new OpenedSecondTimeContinuation(aRecord, mTestmanager, mTestID);
+ GMPOpenRecord(mID, cont);
+ }
+
+private:
+ const string mID;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+static void
+DoTestStorage(const string& aPrefix, TestManager* aTestManager)
+{
+ // Basic I/O tests. We run three cases concurrently. The tests, like
+ // GMPStorage run asynchronously. When they've all passed, we send
+ // a message back to the parent process, or a failure message if not.
+
+ // Test 1: Basic I/O test, and test that writing 0 bytes in a record
+ // deletes record.
+ //
+ // Write data to truncate record, then
+ // read data, verify that we read what we wrote, then
+ // write 0 bytes to truncate record, then
+ // read data, verify that 0 bytes was read
+ const string id1 = aPrefix + TruncateRecordId;
+ const string testID1 = aPrefix + "write-test-1";
+ aTestManager->BeginTest(testID1);
+ auto cont1 = new TruncateContinuation(id1, aTestManager, testID1);
+ auto msg1 = "FAIL in TestStorage writing TruncateRecord.";
+ auto failTask1 = new SendMessageTask(msg1, aTestManager, testID1);
+ WriteRecord(id1, TruncateRecordData,
+ new ReadThenTask(id1, cont1), failTask1);
+
+ // Test 2: Test that overwriting a record with a shorter record truncates
+ // the record to the shorter record.
+ //
+ // Write record, then
+ // read and verify record, then
+ // write a shorter record to same record.
+ // read and verify
+ string id2 = aPrefix + "record1";
+ string record1 = "This is the first write to a record.";
+ string overwrite = "A shorter record";
+ const string testID2 = aPrefix + "write-test-2";
+ aTestManager->BeginTest(testID2);
+ auto task2 = new VerifyAndOverwriteContinuation(id2, record1, overwrite,
+ aTestManager, testID2);
+ auto msg2 = "FAIL in TestStorage writing record1.";
+ auto failTask2 = new SendMessageTask(msg2, aTestManager, testID2);
+ WriteRecord(id2, record1, new ReadThenTask(id2, task2), failTask2);
+
+ // Test 3: Test that opening a record while it's already open fails.
+ //
+ // Open record1, then
+ // open record1, should fail.
+ // close record1
+ const string id3 = aPrefix + OpenAgainRecordId;
+ const string testID3 = aPrefix + "open-test-1";
+ aTestManager->BeginTest(testID3);
+ auto task3 = new OpenedFirstTimeContinuation(id3, aTestManager, testID3);
+ GMPOpenRecord(id3, task3);
+}
+
+class TestStorageTask : public GMPTask {
+public:
+ TestStorageTask(const string& aPrefix, TestManager* aTestManager)
+ : mPrefix(aPrefix), mTestManager(aTestManager) {}
+ virtual void Destroy() { delete this; }
+ virtual void Run() {
+ DoTestStorage(mPrefix, mTestManager);
+ }
+private:
+ const string mPrefix;
+ TestManager* const mTestManager;
+};
+
+void
+FakeDecryptor::TestStorage()
+{
+ TestManager* testManager = new TestManager();
+ GMPThread* thread1 = nullptr;
+ GMPThread* thread2 = nullptr;
+
+ // Main thread tests.
+ DoTestStorage("mt1-", testManager);
+ DoTestStorage("mt2-", testManager);
+
+ // Off-main-thread tests.
+ if (GMP_SUCCEEDED(g_platform_api->createthread(&thread1))) {
+ thread1->Post(new TestStorageTask("thread1-", testManager));
+ } else {
+ FakeDecryptor::Message("FAIL to create thread1 for storage tests");
+ }
+
+ if (GMP_SUCCEEDED(g_platform_api->createthread(&thread2))) {
+ thread2->Post(new TestStorageTask("thread2-", testManager));
+ } else {
+ FakeDecryptor::Message("FAIL to create thread2 for storage tests");
+ }
+
+ if (thread1) {
+ thread1->Join();
+ }
+
+ if (thread2) {
+ thread2->Join();
+ }
+
+ // Note: Once all tests finish, TestManager will dispatch "test-pass" message,
+ // which ends the test for the parent.
+}
+
+class ReportWritten : public GMPTask {
+public:
+ ReportWritten(const string& aRecordId, const string& aValue)
+ : mRecordId(aRecordId)
+ , mValue(aValue)
+ {}
+ void Run() override {
+ FakeDecryptor::Message("stored " + mRecordId + " " + mValue);
+ }
+ void Destroy() override {
+ delete this;
+ }
+ const string mRecordId;
+ const string mValue;
+};
+
+class ReportReadStatusContinuation : public ReadContinuation {
+public:
+ explicit ReportReadStatusContinuation(const string& aRecordId)
+ : mRecordId(aRecordId)
+ {}
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (GMP_FAILED(aErr)) {
+ FakeDecryptor::Message("retrieve " + mRecordId + " failed");
+ } else {
+ stringstream ss;
+ ss << aData.size();
+ string len;
+ ss >> len;
+ FakeDecryptor::Message("retrieve " + mRecordId + " succeeded (length " +
+ len + " bytes)");
+ }
+ delete this;
+ }
+ string mRecordId;
+};
+
+class ReportReadRecordContinuation : public ReadContinuation {
+public:
+ explicit ReportReadRecordContinuation(const string& aRecordId)
+ : mRecordId(aRecordId)
+ {}
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (GMP_FAILED(aErr)) {
+ FakeDecryptor::Message("retrieved " + mRecordId + " failed");
+ } else {
+ FakeDecryptor::Message("retrieved " + mRecordId + " " + aData);
+ }
+ delete this;
+ }
+ string mRecordId;
+};
+
+static void
+RecvGMPRecordIterator(GMPRecordIterator* aRecordIterator,
+ void* aUserArg,
+ GMPErr aStatus)
+{
+ FakeDecryptor* decryptor = reinterpret_cast<FakeDecryptor*>(aUserArg);
+ decryptor->ProcessRecordNames(aRecordIterator, aStatus);
+}
+
+void
+FakeDecryptor::ProcessRecordNames(GMPRecordIterator* aRecordIterator,
+ GMPErr aStatus)
+{
+ if (sInstance != this) {
+ FakeDecryptor::Message("Error aUserArg was not passed through GetRecordIterator");
+ return;
+ }
+ if (GMP_FAILED(aStatus)) {
+ FakeDecryptor::Message("Error GetRecordIterator failed");
+ return;
+ }
+ std::string response("record-names ");
+ bool first = true;
+ const char* name = nullptr;
+ uint32_t len = 0;
+ while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
+ std::string s(name, name+len);
+ if (!first) {
+ response += ",";
+ } else {
+ first = false;
+ }
+ response += s;
+ aRecordIterator->NextRecord();
+ }
+ aRecordIterator->Close();
+ FakeDecryptor::Message(response);
+}
+
+enum ShutdownMode {
+ ShutdownNormal,
+ ShutdownTimeout,
+ ShutdownStoreToken
+};
+
+static ShutdownMode sShutdownMode = ShutdownNormal;
+static string sShutdownToken = "";
+
+void
+FakeDecryptor::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize)
+{
+ std::string response((const char*)aResponse, (const char*)(aResponse)+aResponseSize);
+ std::vector<std::string> tokens = Tokenize(response);
+ const string& task = tokens[0];
+ if (task == "test-storage") {
+ TestStorage();
+ } else if (task == "store") {
+ // send "stored record" message on complete.
+ const string& id = tokens[1];
+ const string& value = tokens[2];
+ WriteRecord(id,
+ value,
+ new ReportWritten(id, value),
+ new SendMessageTask("FAIL in writing record."));
+ } else if (task == "retrieve") {
+ const string& id = tokens[1];
+ ReadRecord(id, new ReportReadStatusContinuation(id));
+ } else if (task == "shutdown-mode") {
+ const string& mode = tokens[1];
+ if (mode == "timeout") {
+ sShutdownMode = ShutdownTimeout;
+ } else if (mode == "token") {
+ sShutdownMode = ShutdownStoreToken;
+ sShutdownToken = tokens[2];
+ Message("shutdown-token received " + sShutdownToken);
+ }
+ } else if (task == "retrieve-shutdown-token") {
+ ReadRecord("shutdown-token", new ReportReadRecordContinuation("shutdown-token"));
+ } else if (task == "test-op-apis") {
+ mozilla::gmptest::TestOuputProtectionAPIs();
+ } else if (task == "retrieve-plugin-voucher") {
+ const uint8_t* rawVoucher = nullptr;
+ uint32_t length = 0;
+ mHost->GetPluginVoucher(&rawVoucher, &length);
+ std::string voucher((const char*)rawVoucher, (const char*)(rawVoucher + length));
+ Message("retrieved plugin-voucher: " + voucher);
+ } else if (task == "retrieve-record-names") {
+ GMPEnumRecordNames(&RecvGMPRecordIterator, this);
+ } else if (task == "retrieve-node-id") {
+ Message("node-id " + sNodeId);
+ }
+}
+
+class CompleteShutdownTask : public GMPTask {
+public:
+ explicit CompleteShutdownTask(GMPAsyncShutdownHost* aHost)
+ : mHost(aHost)
+ {
+ }
+ virtual void Run() {
+ mHost->ShutdownComplete();
+ }
+ virtual void Destroy() { delete this; }
+ GMPAsyncShutdownHost* mHost;
+};
+
+void
+TestAsyncShutdown::BeginShutdown() {
+ switch (sShutdownMode) {
+ case ShutdownNormal:
+ mHost->ShutdownComplete();
+ break;
+ case ShutdownTimeout:
+ // Don't do anything; wait for timeout, Gecko should kill
+ // the plugin and recover.
+ break;
+ case ShutdownStoreToken:
+ // Store message, then shutdown.
+ WriteRecord("shutdown-token",
+ sShutdownToken,
+ new CompleteShutdownTask(mHost),
+ new SendMessageTask("FAIL writing shutdown-token."));
+ break;
+ }
+}
diff --git a/dom/media/gmp-plugin/gmp-test-decryptor.h b/dom/media/gmp-plugin/gmp-test-decryptor.h
new file mode 100644
index 000000000..3b17e42c5
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FAKE_DECRYPTOR_H__
+#define FAKE_DECRYPTOR_H__
+
+#include "gmp-decryption.h"
+#include "gmp-async-shutdown.h"
+#include <string>
+#include "mozilla/Attributes.h"
+
+class FakeDecryptor : public GMPDecryptor7 {
+public:
+
+ explicit FakeDecryptor(GMPDecryptorHost* aHost);
+
+ void Init(GMPDecryptorCallback* aCallback) override {
+ mCallback = aCallback;
+ }
+
+ 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 LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ }
+
+ void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) override;
+
+ void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ }
+
+ void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ }
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) override
+ {
+ }
+
+ void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) override
+ {
+ }
+
+ void DecryptingComplete() override;
+
+ static void Message(const std::string& aMessage);
+
+ void ProcessRecordNames(GMPRecordIterator* aRecordIterator,
+ GMPErr aStatus);
+
+ static void SetNodeId(const char* aNodeId, uint32_t aLength) {
+ sNodeId = std::string(aNodeId, aNodeId + aLength);
+ }
+
+private:
+
+ virtual ~FakeDecryptor() {}
+ static FakeDecryptor* sInstance;
+ static std::string sNodeId;
+
+ void TestStorage();
+
+ GMPDecryptorCallback* mCallback;
+ GMPDecryptorHost* mHost;
+};
+
+class TestAsyncShutdown : public GMPAsyncShutdown {
+public:
+ explicit TestAsyncShutdown(GMPAsyncShutdownHost* aHost)
+ : mHost(aHost)
+ {
+ }
+ void BeginShutdown() override;
+private:
+ GMPAsyncShutdownHost* mHost;
+};
+
+#endif
diff --git a/dom/media/gmp-plugin/gmp-test-output-protection.h b/dom/media/gmp-plugin/gmp-test-output-protection.h
new file mode 100644
index 000000000..93adf299c
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-output-protection.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(XP_WIN)
+#include <d3d9.h> // needed to prevent re-definition of enums
+#include <stdio.h>
+#include <string>
+#include <vector>
+#include <windows.h>
+
+#include "opmapi.h"
+#endif
+
+namespace mozilla {
+namespace gmptest {
+
+#if defined(XP_WIN)
+typedef HRESULT(STDAPICALLTYPE * OPMGetVideoOutputsFromHMONITORProc)
+ (HMONITOR, OPM_VIDEO_OUTPUT_SEMANTICS, ULONG*, IOPMVideoOutput***);
+
+static OPMGetVideoOutputsFromHMONITORProc sOPMGetVideoOutputsFromHMONITORProc = nullptr;
+
+static BOOL CALLBACK EnumDisplayMonitorsCallback(HMONITOR hMonitor, HDC hdc,
+ LPRECT lprc, LPARAM pData)
+{
+ std::vector<std::string>* failureMsgs = (std::vector<std::string>*)pData;
+
+ MONITORINFOEXA miex;
+ ZeroMemory(&miex, sizeof(miex));
+ miex.cbSize = sizeof(miex);
+ if (!GetMonitorInfoA(hMonitor, &miex)) {
+ failureMsgs->push_back("FAIL GetMonitorInfoA call failed");
+ }
+
+ ULONG numVideoOutputs = 0;
+ IOPMVideoOutput** opmVideoOutputArray = nullptr;
+ HRESULT hr = sOPMGetVideoOutputsFromHMONITORProc(hMonitor,
+ OPM_VOS_OPM_SEMANTICS,
+ &numVideoOutputs,
+ &opmVideoOutputArray);
+ if (S_OK != hr) {
+ if (0x8007001f != hr && 0x80070032 != hr && 0xc02625e5 != hr) {
+ char msg[100];
+ sprintf(msg, "FAIL OPMGetVideoOutputsFromHMONITOR call failed: HRESULT=0x%08x", hr);
+ failureMsgs->push_back(msg);
+ }
+ return true;
+ }
+
+ DISPLAY_DEVICEA dd;
+ ZeroMemory(&dd, sizeof(dd));
+ dd.cb = sizeof(dd);
+ if (!EnumDisplayDevicesA(miex.szDevice, 0, &dd, 1)) {
+ failureMsgs->push_back("FAIL EnumDisplayDevicesA call failed");
+ }
+
+ for (ULONG i = 0; i < numVideoOutputs; ++i) {
+ OPM_RANDOM_NUMBER opmRandomNumber;
+ BYTE* certificate = nullptr;
+ ULONG certificateLength = 0;
+ hr = opmVideoOutputArray[i]->StartInitialization(&opmRandomNumber,
+ &certificate,
+ &certificateLength);
+ if (S_OK != hr) {
+ char msg[100];
+ sprintf(msg, "FAIL StartInitialization call failed: HRESULT=0x%08x", hr);
+ failureMsgs->push_back(msg);
+ }
+
+ if (certificate) {
+ CoTaskMemFree(certificate);
+ }
+
+ opmVideoOutputArray[i]->Release();
+ }
+
+ if (opmVideoOutputArray) {
+ CoTaskMemFree(opmVideoOutputArray);
+ }
+
+ return true;
+}
+#endif
+
+static void
+RunOutputProtectionAPITests()
+{
+#if defined(XP_WIN)
+ // Get hold of OPMGetVideoOutputsFromHMONITOR function.
+ HMODULE hDvax2DLL = GetModuleHandleW(L"dxva2.dll");
+ if (!hDvax2DLL) {
+ FakeDecryptor::Message("FAIL GetModuleHandleW call failed for dxva2.dll");
+ return;
+ }
+
+ sOPMGetVideoOutputsFromHMONITORProc = (OPMGetVideoOutputsFromHMONITORProc)
+ GetProcAddress(hDvax2DLL, "OPMGetVideoOutputsFromHMONITOR");
+ if (!sOPMGetVideoOutputsFromHMONITORProc) {
+ FakeDecryptor::Message("FAIL GetProcAddress call failed for OPMGetVideoOutputsFromHMONITOR");
+ return;
+ }
+
+ // Test EnumDisplayMonitors.
+ // Other APIs are tested in the callback function.
+ std::vector<std::string> failureMsgs;
+ if (!EnumDisplayMonitors(NULL, NULL, EnumDisplayMonitorsCallback,
+ (LPARAM) &failureMsgs)) {
+ FakeDecryptor::Message("FAIL EnumDisplayMonitors call failed");
+ }
+
+ // Report any failures in the callback function.
+ for (size_t i = 0; i < failureMsgs.size(); i++) {
+ FakeDecryptor::Message(failureMsgs[i]);
+ }
+#endif
+}
+
+static void
+TestOuputProtectionAPIs()
+{
+ RunOutputProtectionAPITests();
+ FakeDecryptor::Message("OP tests completed");
+ return;
+}
+
+} // namespace gmptest
+} // namespace mozilla
diff --git a/dom/media/gmp-plugin/gmp-test-storage.cpp b/dom/media/gmp-plugin/gmp-test-storage.cpp
new file mode 100644
index 000000000..e75b49f42
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-storage.cpp
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gmp-test-storage.h"
+#include <vector>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+class WriteRecordClient : public GMPRecordClient {
+public:
+ GMPErr Init(GMPRecord* aRecord,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure,
+ const uint8_t* aData,
+ uint32_t aDataSize) {
+ mRecord = aRecord;
+ mOnSuccess = aOnSuccess;
+ mOnFailure = aOnFailure;
+ mData.insert(mData.end(), aData, aData + aDataSize);
+ return mRecord->Open();
+ }
+
+ void OpenComplete(GMPErr aStatus) override {
+ if (GMP_SUCCEEDED(aStatus)) {
+ mRecord->Write(mData.size() ? &mData.front() : nullptr, mData.size());
+ } else {
+ GMPRunOnMainThread(mOnFailure);
+ mOnSuccess->Destroy();
+ }
+ }
+
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override {}
+
+ void WriteComplete(GMPErr aStatus) override {
+ // 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.
+ mRecord->Close();
+ if (GMP_SUCCEEDED(aStatus)) {
+ GMPRunOnMainThread(mOnSuccess);
+ mOnFailure->Destroy();
+ } else {
+ GMPRunOnMainThread(mOnFailure);
+ mOnSuccess->Destroy();
+ }
+ delete this;
+ }
+
+private:
+ GMPRecord* mRecord;
+ GMPTask* mOnSuccess;
+ GMPTask* mOnFailure;
+ std::vector<uint8_t> mData;
+};
+
+GMPErr
+WriteRecord(const std::string& aRecordName,
+ const uint8_t* aData,
+ uint32_t aNumBytes,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure)
+{
+ GMPRecord* record;
+ WriteRecordClient* client = new WriteRecordClient();
+ auto err = GMPOpenRecord(aRecordName.c_str(),
+ aRecordName.size(),
+ &record,
+ client);
+ if (GMP_FAILED(err)) {
+ GMPRunOnMainThread(aOnFailure);
+ aOnSuccess->Destroy();
+ return err;
+ }
+ return client->Init(record, aOnSuccess, aOnFailure, aData, aNumBytes);
+}
+
+GMPErr
+WriteRecord(const std::string& aRecordName,
+ const std::string& aData,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure)
+{
+ return WriteRecord(aRecordName,
+ (const uint8_t*)aData.c_str(),
+ aData.size(),
+ aOnSuccess,
+ aOnFailure);
+}
+
+class ReadRecordClient : public GMPRecordClient {
+public:
+ GMPErr Init(GMPRecord* aRecord,
+ ReadContinuation* aContinuation) {
+ mRecord = aRecord;
+ mContinuation = aContinuation;
+ return mRecord->Open();
+ }
+
+ void OpenComplete(GMPErr aStatus) override {
+ auto err = mRecord->Read();
+ if (GMP_FAILED(err)) {
+ mContinuation->ReadComplete(err, "");
+ delete this;
+ }
+ }
+
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override {
+ // 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.
+ mRecord->Close();
+ std::string data((const char*)aData, aDataSize);
+ mContinuation->ReadComplete(GMPNoErr, data);
+ delete this;
+ }
+
+ void WriteComplete(GMPErr aStatus) override {
+ }
+
+private:
+ GMPRecord* mRecord;
+ ReadContinuation* mContinuation;
+};
+
+GMPErr
+ReadRecord(const std::string& aRecordName,
+ ReadContinuation* aContinuation)
+{
+ MOZ_ASSERT(aContinuation);
+ GMPRecord* record;
+ ReadRecordClient* client = new ReadRecordClient();
+ auto err = GMPOpenRecord(aRecordName.c_str(),
+ aRecordName.size(),
+ &record,
+ client);
+ if (GMP_FAILED(err)) {
+ return err;
+ }
+ return client->Init(record, aContinuation);
+}
+
+extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp
+
+GMPErr
+GMPOpenRecord(const char* aName,
+ uint32_t aNameLength,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ MOZ_ASSERT(g_platform_api);
+ return g_platform_api->createrecord(aName, aNameLength, aOutRecord, aClient);
+}
+
+GMPErr
+GMPRunOnMainThread(GMPTask* aTask)
+{
+ MOZ_ASSERT(g_platform_api);
+ return g_platform_api->runonmainthread(aTask);
+}
+
+class OpenRecordClient : public GMPRecordClient {
+public:
+ /*
+ * This function will take the memory ownership of the parameters and
+ * delete them when done.
+ */
+ static void Open(const std::string& aRecordName,
+ OpenContinuation* aContinuation) {
+ MOZ_ASSERT(aContinuation);
+ (new OpenRecordClient(aContinuation))->Do(aRecordName);
+ }
+
+ void OpenComplete(GMPErr aStatus) override {
+ Done(aStatus);
+ }
+
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override {
+ MOZ_CRASH("Should not reach here.");
+ }
+
+ void WriteComplete(GMPErr aStatus) override {
+ MOZ_CRASH("Should not reach here.");
+ }
+
+private:
+ explicit OpenRecordClient(OpenContinuation* aContinuation)
+ : mRecord(nullptr), mContinuation(aContinuation) {}
+
+ void Do(const std::string& aName) {
+ auto err = GMPOpenRecord(aName.c_str(), aName.size(), &mRecord, this);
+ if (GMP_FAILED(err) ||
+ GMP_FAILED(err = mRecord->Open())) {
+ Done(err);
+ }
+ }
+
+ void Done(GMPErr err) {
+ // mContinuation is responsible for closing mRecord.
+ mContinuation->OpenComplete(err, mRecord);
+ delete mContinuation;
+ delete this;
+ }
+
+ GMPRecord* mRecord;
+ OpenContinuation* mContinuation;
+};
+
+void
+GMPOpenRecord(const std::string& aRecordName,
+ OpenContinuation* aContinuation)
+{
+ OpenRecordClient::Open(aRecordName, aContinuation);
+}
+
+GMPErr
+GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg)
+{
+ return g_platform_api->getrecordenumerator(aRecvIteratorFunc, aUserArg);
+}
diff --git a/dom/media/gmp-plugin/gmp-test-storage.h b/dom/media/gmp-plugin/gmp-test-storage.h
new file mode 100644
index 000000000..d77f8ebb3
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-storage.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TEST_GMP_STORAGE_H__
+#define TEST_GMP_STORAGE_H__
+
+#include "gmp-errors.h"
+#include "gmp-platform.h"
+#include <string>
+
+class ReadContinuation {
+public:
+ virtual ~ReadContinuation() {}
+ virtual void ReadComplete(GMPErr aErr, const std::string& aData) = 0;
+};
+
+// Reads a record to storage using GMPRecord.
+// Calls ReadContinuation with read data.
+GMPErr
+ReadRecord(const std::string& aRecordName,
+ ReadContinuation* aContinuation);
+
+// Writes a record to storage using GMPRecord.
+// Runs continuation when data is written.
+GMPErr
+WriteRecord(const std::string& aRecordName,
+ const std::string& aData,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure);
+
+GMPErr
+WriteRecord(const std::string& aRecordName,
+ const uint8_t* aData,
+ uint32_t aNumBytes,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure);
+
+GMPErr
+GMPOpenRecord(const char* aName,
+ uint32_t aNameLength,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+GMPErr
+GMPRunOnMainThread(GMPTask* aTask);
+
+class OpenContinuation {
+public:
+ virtual ~OpenContinuation() {}
+ virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) = 0;
+};
+
+void
+GMPOpenRecord(const std::string& aRecordName,
+ OpenContinuation* aContinuation);
+
+GMPErr
+GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+#endif // TEST_GMP_STORAGE_H__
diff --git a/dom/media/gmp-plugin/moz.build b/dom/media/gmp-plugin/moz.build
new file mode 100644
index 000000000..39061d0c9
--- /dev/null
+++ b/dom/media/gmp-plugin/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_TARGET = 'dist/bin/gmp-fake/1.0'
+
+FINAL_TARGET_FILES += [
+ 'fake.info',
+ 'fake.voucher',
+]
+
+SOURCES += [
+ 'gmp-fake.cpp',
+ 'gmp-test-decryptor.cpp',
+ 'gmp-test-storage.cpp',
+]
+
+DEFINES['GMP_FAKE_SUPPORT_DECRYPT'] = True
+
+SharedLibrary("fake")
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ OS_LIBS += [
+ 'ole32',
+ ]
+
+USE_STATIC_LIBS = True
+NO_VISIBILITY_FLAGS = True
+# Don't use STL wrappers; this isn't Gecko code
+DISABLE_STL_WRAPPING = True
diff --git a/dom/media/gmp/GMPAudioDecoderChild.cpp b/dom/media/gmp/GMPAudioDecoderChild.cpp
new file mode 100644
index 000000000..53550d4a1
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderChild.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPAudioDecoderChild.h"
+#include "GMPContentChild.h"
+#include "GMPAudioHost.h"
+#include "mozilla/Unused.h"
+#include <stdio.h>
+
+namespace mozilla {
+namespace gmp {
+
+GMPAudioDecoderChild::GMPAudioDecoderChild(GMPContentChild* aPlugin)
+ : mPlugin(aPlugin)
+ , mAudioDecoder(nullptr)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPAudioDecoderChild::~GMPAudioDecoderChild()
+{
+}
+
+void
+GMPAudioDecoderChild::Init(GMPAudioDecoder* aDecoder)
+{
+ MOZ_ASSERT(aDecoder, "Cannot initialize Audio decoder child without a Audio decoder!");
+ mAudioDecoder = aDecoder;
+}
+
+GMPAudioHostImpl&
+GMPAudioDecoderChild::Host()
+{
+ return mAudioHost;
+}
+
+void
+GMPAudioDecoderChild::Decoded(GMPAudioSamples* aDecodedSamples)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ if (!aDecodedSamples) {
+ MOZ_CRASH("Not given decoded audio samples!");
+ }
+
+ GMPAudioDecodedSampleData samples;
+ samples.mData().AppendElements((int16_t*)aDecodedSamples->Buffer(),
+ aDecodedSamples->Size() / sizeof(int16_t));
+ samples.mTimeStamp() = aDecodedSamples->TimeStamp();
+ samples.mChannelCount() = aDecodedSamples->Channels();
+ samples.mSamplesPerSecond() = aDecodedSamples->Rate();
+
+ Unused << SendDecoded(samples);
+
+ aDecodedSamples->Destroy();
+}
+
+void
+GMPAudioDecoderChild::InputDataExhausted()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ Unused << SendInputDataExhausted();
+}
+
+void
+GMPAudioDecoderChild::DrainComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ Unused << SendDrainComplete();
+}
+
+void
+GMPAudioDecoderChild::ResetComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ Unused << SendResetComplete();
+}
+
+void
+GMPAudioDecoderChild::Error(GMPErr aError)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ Unused << SendError(aError);
+}
+
+bool
+GMPAudioDecoderChild::RecvInitDecode(const GMPAudioCodecData& a)
+{
+ MOZ_ASSERT(mAudioDecoder);
+ if (!mAudioDecoder) {
+ return false;
+ }
+
+ GMPAudioCodec codec;
+ codec.mCodecType = a.mCodecType();
+ codec.mChannelCount = a.mChannelCount();
+ codec.mBitsPerChannel = a.mBitsPerChannel();
+ codec.mSamplesPerSecond = a.mSamplesPerSecond();
+ codec.mExtraData = a.mExtraData().Elements();
+ codec.mExtraDataLen = a.mExtraData().Length();
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->InitDecode(codec, this);
+
+ return true;
+}
+
+bool
+GMPAudioDecoderChild::RecvDecode(const GMPAudioEncodedSampleData& aEncodedSamples)
+{
+ if (!mAudioDecoder) {
+ return false;
+ }
+
+ GMPAudioSamples* samples = new GMPAudioSamplesImpl(aEncodedSamples);
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->Decode(samples);
+
+ return true;
+}
+
+bool
+GMPAudioDecoderChild::RecvReset()
+{
+ if (!mAudioDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->Reset();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderChild::RecvDrain()
+{
+ if (!mAudioDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->Drain();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderChild::RecvDecodingComplete()
+{
+ if (mAudioDecoder) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->DecodingComplete();
+ mAudioDecoder = nullptr;
+ }
+
+ mPlugin = nullptr;
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPAudioDecoderChild.h b/dom/media/gmp/GMPAudioDecoderChild.h
new file mode 100644
index 000000000..0393fa573
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderChild.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPAudioDecoderChild_h_
+#define GMPAudioDecoderChild_h_
+
+#include "mozilla/gmp/PGMPAudioDecoderChild.h"
+#include "gmp-audio-decode.h"
+#include "GMPAudioHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPAudioDecoderChild : public PGMPAudioDecoderChild,
+ public GMPAudioDecoderCallback
+{
+public:
+ explicit GMPAudioDecoderChild(GMPContentChild* aPlugin);
+ virtual ~GMPAudioDecoderChild();
+
+ void Init(GMPAudioDecoder* aDecoder);
+ GMPAudioHostImpl& Host();
+
+ // GMPAudioDecoderCallback
+ void Decoded(GMPAudioSamples* aEncodedSamples) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aError) override;
+
+private:
+ // PGMPAudioDecoderChild
+ bool RecvInitDecode(const GMPAudioCodecData& codecSettings) override;
+ bool RecvDecode(const GMPAudioEncodedSampleData& input) override;
+ bool RecvReset() override;
+ bool RecvDrain() override;
+ bool RecvDecodingComplete() override;
+
+ GMPContentChild* mPlugin;
+ GMPAudioDecoder* mAudioDecoder;
+ GMPAudioHostImpl mAudioHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPAudioDecoderChild_h_
diff --git a/dom/media/gmp/GMPAudioDecoderParent.cpp b/dom/media/gmp/GMPAudioDecoderParent.cpp
new file mode 100644
index 000000000..592c7719b
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderParent.cpp
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPAudioDecoderParent.h"
+#include "GMPContentParent.h"
+#include <stdio.h>
+#include "mozilla/Unused.h"
+#include "GMPMessageUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+GMPAudioDecoderParent::GMPAudioDecoderParent(GMPContentParent* aPlugin)
+ : mIsOpen(false)
+ , mShuttingDown(false)
+ , mActorDestroyed(false)
+ , mIsAwaitingResetComplete(false)
+ , mIsAwaitingDrainComplete(false)
+ , mPlugin(aPlugin)
+ , mCallback(nullptr)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPAudioDecoderParent::~GMPAudioDecoderParent()
+{
+}
+
+nsresult
+GMPAudioDecoderParent::InitDecode(GMPAudioCodecType aCodecType,
+ uint32_t aChannelCount,
+ uint32_t aBitsPerChannel,
+ uint32_t aSamplesPerSecond,
+ nsTArray<uint8_t>& aExtraData,
+ GMPAudioDecoderCallbackProxy* aCallback)
+{
+ LOGD(("GMPAudioDecoderParent[%p]::InitDecode()", this));
+
+ if (mIsOpen) {
+ NS_WARNING("Trying to re-init an in-use GMP audio decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!aCallback) {
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aCallback;
+
+ GMPAudioCodecData data;
+ data.mCodecType() = aCodecType;
+ data.mChannelCount() = aChannelCount;
+ data.mBitsPerChannel() = aBitsPerChannel;
+ data.mSamplesPerSecond() = aSamplesPerSecond;
+ data.mExtraData() = aExtraData;
+ if (!SendInitDecode(data)) {
+ return NS_ERROR_FAILURE;
+ }
+ mIsOpen = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPAudioDecoderParent::Decode(GMPAudioSamplesImpl& aEncodedSamples)
+{
+ LOGV(("GMPAudioDecoderParent[%p]::Decode() timestamp=%lld",
+ this, aEncodedSamples.TimeStamp()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP Audio decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ GMPAudioEncodedSampleData samples;
+ aEncodedSamples.RelinquishData(samples);
+
+ if (!SendDecode(samples)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPAudioDecoderParent::Reset()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Reset()", this));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP Audio decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendReset()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingResetComplete = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPAudioDecoderParent::Drain()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Drain()", this));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP Audio decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendDrain()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingDrainComplete = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+nsresult
+GMPAudioDecoderParent::Close()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Close()", this));
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ // Ensure if we've received a Close while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the close. This seems unlikely to happen, but better to be careful.
+ UnblockResetAndDrain();
+
+ // Consumer is done with us; we can shut down. No more callbacks should
+ // be made to mCallback. Note: do this before Shutdown()!
+ mCallback = nullptr;
+ // Let Shutdown mark us as dead so it knows if we had been alive
+
+ // In case this is the last reference
+ RefPtr<GMPAudioDecoderParent> kungfudeathgrip(this);
+ Release();
+ Shutdown();
+
+ return NS_OK;
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+nsresult
+GMPAudioDecoderParent::Shutdown()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Shutdown()", this));
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+ mShuttingDown = true;
+
+ // Ensure if we've received a shutdown while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the shutdown.
+ UnblockResetAndDrain();
+
+ // Notify client we're gone! Won't occur after Close()
+ if (mCallback) {
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+
+ mIsOpen = false;
+ if (!mActorDestroyed) {
+ Unused << SendDecodingComplete();
+ }
+
+ return NS_OK;
+}
+
+// Note: Keep this sync'd up with DecodingComplete
+void
+GMPAudioDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("GMPAudioDecoderParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
+
+ mIsOpen = false;
+ mActorDestroyed = true;
+
+ // Ensure if we've received a destroy while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the error.
+ UnblockResetAndDrain();
+
+ if (mCallback) {
+ // May call Close() (and Shutdown()) immediately or with a delay
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->AudioDecoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+ MaybeDisconnect(aWhy == AbnormalShutdown);
+}
+
+bool
+GMPAudioDecoderParent::RecvDecoded(const GMPAudioDecodedSampleData& aDecoded)
+{
+ LOGV(("GMPAudioDecoderParent[%p]::RecvDecoded() timestamp=%lld",
+ this, aDecoded.mTimeStamp()));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ mCallback->Decoded(aDecoded.mData(),
+ aDecoded.mTimeStamp(),
+ aDecoded.mChannelCount(),
+ aDecoded.mSamplesPerSecond());
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvInputDataExhausted()
+{
+ LOGV(("GMPAudioDecoderParent[%p]::RecvInputDataExhausted()", this));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->InputDataExhausted();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvDrainComplete()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::RecvDrainComplete()", this));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!mIsAwaitingDrainComplete) {
+ return true;
+ }
+ mIsAwaitingDrainComplete = false;
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->DrainComplete();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvResetComplete()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::RecvResetComplete()", this));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!mIsAwaitingResetComplete) {
+ return true;
+ }
+ mIsAwaitingResetComplete = false;
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->ResetComplete();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvError(const GMPErr& aError)
+{
+ LOGD(("GMPAudioDecoderParent[%p]::RecvError(error=%d)", this, aError));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ensure if we've received an error while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the error.
+ UnblockResetAndDrain();
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->Error(aError);
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvShutdown()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::RecvShutdown()", this));
+
+ Shutdown();
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::Recv__delete__()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Recv__delete__()", this));
+
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->AudioDecoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+
+ return true;
+}
+
+void
+GMPAudioDecoderParent::UnblockResetAndDrain()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::UnblockResetAndDrain()", this));
+
+ if (!mCallback) {
+ MOZ_ASSERT(!mIsAwaitingResetComplete);
+ MOZ_ASSERT(!mIsAwaitingDrainComplete);
+ return;
+ }
+ if (mIsAwaitingResetComplete) {
+ mIsAwaitingResetComplete = false;
+ mCallback->ResetComplete();
+ }
+ if (mIsAwaitingDrainComplete) {
+ mIsAwaitingDrainComplete = false;
+ mCallback->DrainComplete();
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPAudioDecoderParent.h b/dom/media/gmp/GMPAudioDecoderParent.h
new file mode 100644
index 000000000..5ced5ca61
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderParent.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPAudioDecoderParent_h_
+#define GMPAudioDecoderParent_h_
+
+#include "mozilla/RefPtr.h"
+#include "gmp-audio-decode.h"
+#include "gmp-audio-codec.h"
+#include "mozilla/gmp/PGMPAudioDecoderParent.h"
+#include "GMPMessageUtils.h"
+#include "GMPAudioDecoderProxy.h"
+#include "GMPCrashHelperHolder.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentParent;
+
+class GMPAudioDecoderParent final : public GMPAudioDecoderProxy
+ , public PGMPAudioDecoderParent
+ , public GMPCrashHelperHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPAudioDecoderParent)
+
+ explicit GMPAudioDecoderParent(GMPContentParent *aPlugin);
+
+ nsresult Shutdown();
+
+ // GMPAudioDecoderProxy
+ nsresult InitDecode(GMPAudioCodecType aCodecType,
+ uint32_t aChannelCount,
+ uint32_t aBitsPerChannel,
+ uint32_t aSamplesPerSecond,
+ nsTArray<uint8_t>& aExtraData,
+ GMPAudioDecoderCallbackProxy* aCallback) override;
+ nsresult Decode(GMPAudioSamplesImpl& aInput) override;
+ nsresult Reset() override;
+ nsresult Drain() override;
+ nsresult Close() override;
+
+private:
+ ~GMPAudioDecoderParent();
+
+ // PGMPAudioDecoderParent
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool RecvDecoded(const GMPAudioDecodedSampleData& aDecoded) override;
+ bool RecvInputDataExhausted() override;
+ bool RecvDrainComplete() override;
+ bool RecvResetComplete() override;
+ bool RecvError(const GMPErr& aError) override;
+ bool RecvShutdown() override;
+ bool Recv__delete__() override;
+
+ void UnblockResetAndDrain();
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ bool mIsAwaitingResetComplete;
+ bool mIsAwaitingDrainComplete;
+ RefPtr<GMPContentParent> mPlugin;
+ GMPAudioDecoderCallbackProxy* mCallback;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPAudioDecoderParent_h_
diff --git a/dom/media/gmp/GMPAudioDecoderProxy.h b/dom/media/gmp/GMPAudioDecoderProxy.h
new file mode 100644
index 000000000..6d71ba089
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderProxy.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPAudioDecoderProxy_h_
+#define GMPAudioDecoderProxy_h_
+
+#include "GMPCallbackBase.h"
+#include "gmp-audio-codec.h"
+#include "GMPAudioHost.h"
+#include "nsTArray.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+class GMPAudioDecoderCallbackProxy : public GMPCallbackBase {
+public:
+ virtual ~GMPAudioDecoderCallbackProxy() {}
+ // Note: aChannelCount and aSamplesPerSecond may not be consistent from
+ // one invocation to the next.
+ virtual void Decoded(const nsTArray<int16_t>& aPCM,
+ uint64_t aTimeStamp,
+ uint32_t aChannelCount,
+ uint32_t aSamplesPerSecond) = 0;
+ virtual void InputDataExhausted() = 0;
+ virtual void DrainComplete() = 0;
+ virtual void ResetComplete() = 0;
+ virtual void Error(GMPErr aError) = 0;
+};
+
+class GMPAudioDecoderProxy {
+public:
+ virtual ~GMPAudioDecoderProxy() {}
+
+ virtual nsresult InitDecode(GMPAudioCodecType aCodecType,
+ uint32_t aChannelCount,
+ uint32_t aBitsPerChannel,
+ uint32_t aSamplesPerSecond,
+ nsTArray<uint8_t>& aExtraData,
+ GMPAudioDecoderCallbackProxy* aCallback) = 0;
+ virtual nsresult Decode(mozilla::gmp::GMPAudioSamplesImpl& aSamples) = 0;
+ virtual nsresult Reset() = 0;
+ virtual nsresult Drain() = 0;
+ // Call to tell GMP/plugin the consumer will no longer use this
+ // interface/codec.
+ virtual nsresult Close() = 0;
+};
+
+#endif // GMPAudioDecoderProxy_h_
diff --git a/dom/media/gmp/GMPAudioHost.cpp b/dom/media/gmp/GMPAudioHost.cpp
new file mode 100644
index 000000000..4e14fed0b
--- /dev/null
+++ b/dom/media/gmp/GMPAudioHost.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPAudioHost.h"
+#include "gmp-audio-samples.h"
+#include "gmp-errors.h"
+#include "GMPEncryptedBufferDataImpl.h"
+#include "MediaData.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPAudioSamplesImpl::GMPAudioSamplesImpl(GMPAudioFormat aFormat)
+ : mFormat(aFormat)
+ , mTimeStamp(0)
+ , mChannels(0)
+ , mRate(0)
+{
+}
+
+GMPAudioSamplesImpl::GMPAudioSamplesImpl(const GMPAudioEncodedSampleData& aData)
+ : mFormat(kGMPAudioEncodedSamples)
+ , mBuffer(aData.mData())
+ , mTimeStamp(aData.mTimeStamp())
+ , mChannels(aData.mChannelCount())
+ , mRate(aData.mSamplesPerSecond())
+{
+ if (aData.mDecryptionData().mKeyId().Length() > 0) {
+ mCrypto = new GMPEncryptedBufferDataImpl(aData.mDecryptionData());
+ }
+}
+
+GMPAudioSamplesImpl::GMPAudioSamplesImpl(MediaRawData* aSample,
+ uint32_t aChannels,
+ uint32_t aRate)
+ : mFormat(kGMPAudioEncodedSamples)
+ , mTimeStamp(aSample->mTime)
+ , mChannels(aChannels)
+ , mRate(aRate)
+{
+ mBuffer.AppendElements(aSample->Data(), aSample->Size());
+ if (aSample->mCrypto.mValid) {
+ mCrypto = new GMPEncryptedBufferDataImpl(aSample->mCrypto);
+ }
+}
+
+GMPAudioSamplesImpl::~GMPAudioSamplesImpl()
+{
+}
+
+GMPAudioFormat
+GMPAudioSamplesImpl::GetFormat()
+{
+ return mFormat;
+}
+
+void
+GMPAudioSamplesImpl::Destroy()
+{
+ delete this;
+}
+
+GMPErr
+GMPAudioSamplesImpl::SetBufferSize(uint32_t aSize)
+{
+ mBuffer.SetLength(aSize);
+ return GMPNoErr;
+}
+
+uint32_t
+GMPAudioSamplesImpl::Size()
+{
+ return mBuffer.Length();
+}
+
+void
+GMPAudioSamplesImpl::SetTimeStamp(uint64_t aTimeStamp)
+{
+ mTimeStamp = aTimeStamp;
+}
+
+uint64_t
+GMPAudioSamplesImpl::TimeStamp()
+{
+ return mTimeStamp;
+}
+
+const uint8_t*
+GMPAudioSamplesImpl::Buffer() const
+{
+ return mBuffer.Elements();
+}
+
+uint8_t*
+GMPAudioSamplesImpl::Buffer()
+{
+ return mBuffer.Elements();
+}
+
+const GMPEncryptedBufferMetadata*
+GMPAudioSamplesImpl::GetDecryptionData() const
+{
+ return mCrypto;
+}
+
+void
+GMPAudioSamplesImpl::InitCrypto(const CryptoSample& aCrypto)
+{
+ if (!aCrypto.mValid) {
+ return;
+ }
+ mCrypto = new GMPEncryptedBufferDataImpl(aCrypto);
+}
+
+void
+GMPAudioSamplesImpl::RelinquishData(GMPAudioEncodedSampleData& aData)
+{
+ aData.mData() = Move(mBuffer);
+ aData.mTimeStamp() = TimeStamp();
+ if (mCrypto) {
+ mCrypto->RelinquishData(aData.mDecryptionData());
+ }
+}
+
+uint32_t
+GMPAudioSamplesImpl::Channels() const
+{
+ return mChannels;
+}
+
+void
+GMPAudioSamplesImpl::SetChannels(uint32_t aChannels)
+{
+ mChannels = aChannels;
+}
+
+uint32_t
+GMPAudioSamplesImpl::Rate() const
+{
+ return mRate;
+}
+
+void
+GMPAudioSamplesImpl::SetRate(uint32_t aRate)
+{
+ mRate = aRate;
+}
+
+
+GMPErr
+GMPAudioHostImpl::CreateSamples(GMPAudioFormat aFormat,
+ GMPAudioSamples** aSamples)
+{
+
+ *aSamples = new GMPAudioSamplesImpl(aFormat);
+ return GMPNoErr;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPAudioHost.h b/dom/media/gmp/GMPAudioHost.h
new file mode 100644
index 000000000..ed829b9c5
--- /dev/null
+++ b/dom/media/gmp/GMPAudioHost.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPAudioHost_h_
+#define GMPAudioHost_h_
+
+#include "gmp-audio-host.h"
+#include "gmp-audio-samples.h"
+#include "nsTArray.h"
+#include "gmp-decryption.h"
+#include "nsAutoPtr.h"
+#include "GMPEncryptedBufferDataImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+class CryptoSample;
+class MediaRawData;
+
+namespace gmp {
+
+class GMPAudioSamplesImpl : public GMPAudioSamples {
+public:
+ explicit GMPAudioSamplesImpl(GMPAudioFormat aFormat);
+ explicit GMPAudioSamplesImpl(const GMPAudioEncodedSampleData& aData);
+ GMPAudioSamplesImpl(MediaRawData* aSample,
+ uint32_t aChannels,
+ uint32_t aRate);
+ virtual ~GMPAudioSamplesImpl();
+
+ GMPAudioFormat GetFormat() override;
+ void Destroy() override;
+ GMPErr SetBufferSize(uint32_t aSize) override;
+ uint32_t Size() override;
+ void SetTimeStamp(uint64_t aTimeStamp) override;
+ uint64_t TimeStamp() override;
+ const uint8_t* Buffer() const override;
+ uint8_t* Buffer() override;
+ const GMPEncryptedBufferMetadata* GetDecryptionData() const override;
+
+ void InitCrypto(const CryptoSample& aCrypto);
+
+ void RelinquishData(GMPAudioEncodedSampleData& aData);
+
+ uint32_t Channels() const override;
+ void SetChannels(uint32_t aChannels) override;
+ uint32_t Rate() const override;
+ void SetRate(uint32_t aRate) override;
+
+private:
+ GMPAudioFormat mFormat;
+ nsTArray<uint8_t> mBuffer;
+ int64_t mTimeStamp;
+ nsAutoPtr<GMPEncryptedBufferDataImpl> mCrypto;
+ uint32_t mChannels;
+ uint32_t mRate;
+};
+
+class GMPAudioHostImpl : public GMPAudioHost
+{
+public:
+ GMPErr CreateSamples(GMPAudioFormat aFormat,
+ GMPAudioSamples** aSamples) override;
+private:
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPAudioHost_h_
diff --git a/dom/media/gmp/GMPCDMCallbackProxy.cpp b/dom/media/gmp/GMPCDMCallbackProxy.cpp
new file mode 100644
index 000000000..a0b490849
--- /dev/null
+++ b/dom/media/gmp/GMPCDMCallbackProxy.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPCDMCallbackProxy.h"
+#include "mozilla/CDMProxy.h"
+#include "nsString.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "MainThreadUtils.h"
+#include "mozilla/EMEUtils.h"
+
+namespace mozilla {
+
+GMPCDMCallbackProxy::GMPCDMCallbackProxy(CDMProxy* aProxy)
+ : mProxy(aProxy)
+{}
+
+void
+GMPCDMCallbackProxy::SetDecryptorId(uint32_t aId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, aId] ()
+ {
+ proxy->OnSetDecryptorId(aId);
+ })
+ );}
+
+void
+GMPCDMCallbackProxy::SetSessionId(uint32_t aToken,
+ const nsCString& aSessionId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ aToken,
+ sid] ()
+ {
+ proxy->OnSetSessionId(aToken, sid);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, aPromiseId, aSuccess] ()
+ {
+ proxy->OnResolveLoadSessionPromise(aPromiseId, aSuccess);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ // Note: CDMProxy proxies this from non-main threads to main thread.
+ mProxy->ResolvePromise(aPromiseId);
+}
+
+void
+GMPCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ aPromiseId,
+ aException,
+ aMessage] ()
+ {
+ proxy->OnRejectPromise(aPromiseId, aException, aMessage);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ nsTArray<uint8_t> msg(aMessage);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aMessageType,
+ msg] () mutable
+ {
+ proxy->OnSessionMessage(sid, aMessageType, msg);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+ GMPTimestamp aExpiryTime)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aExpiryTime] ()
+ {
+ proxy->OnExpirationChange(sid, aExpiryTime);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ bool keyStatusesChange = false;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ {
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ keyStatusesChange = caps.RemoveKeysForSession(NS_ConvertUTF8toUTF16(aSessionId));
+ }
+ if (keyStatusesChange) {
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnKeyStatusesChange(sid);
+ })
+ );
+ }
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnSessionClosed(sid);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ auto msg = NS_ConvertUTF8toUTF16(aMessage);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aException,
+ aSystemCode,
+ msg] ()
+ {
+ proxy->OnSessionError(sid,
+ aException,
+ aSystemCode,
+ msg);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+ BatchedKeyStatusChangedInternal(aSessionId, aKeyInfos);
+}
+
+void
+GMPCDMCallbackProxy::BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ bool keyStatusesChange = false;
+ {
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ for (size_t i = 0; i < aKeyInfos.Length(); i++) {
+ keyStatusesChange |=
+ caps.SetKeyStatus(aKeyInfos[i].mKeyId,
+ NS_ConvertUTF8toUTF16(aSessionId),
+ aKeyInfos[i].mStatus);
+ }
+ }
+ if (keyStatusesChange) {
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnKeyStatusesChange(sid);
+ })
+ );
+ }
+}
+
+void
+GMPCDMCallbackProxy::Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ mProxy->OnDecrypted(aId, aResult, aDecryptedData);
+}
+
+void
+GMPCDMCallbackProxy::Terminated()
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy] ()
+ {
+ proxy->Terminated();
+ })
+ );
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPCDMCallbackProxy.h b/dom/media/gmp/GMPCDMCallbackProxy.h
new file mode 100644
index 000000000..d2cc80682
--- /dev/null
+++ b/dom/media/gmp/GMPCDMCallbackProxy.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPCDMCallbackProxy_h_
+#define GMPCDMCallbackProxy_h_
+
+#include "mozilla/CDMProxy.h"
+#include "gmp-decryption.h"
+#include "GMPDecryptorProxy.h"
+
+namespace mozilla {
+
+// Proxies call backs from the CDM on the GMP thread back to the MediaKeys
+// object on the main thread.
+class GMPCDMCallbackProxy : public GMPDecryptorProxyCallback {
+public:
+
+ void SetDecryptorId(uint32_t aId) override;
+
+ void SetSessionId(uint32_t aCreateSessionToken,
+ const nsCString& aSessionId) override;
+
+ void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) override;
+
+ void ResolvePromise(uint32_t aPromiseId) override;
+
+ void RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aSessionId) override;
+
+ void SessionMessage(const nsCString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) override;
+
+ void ExpirationChange(const nsCString& aSessionId,
+ UnixTime aExpiryTime) override;
+
+ void SessionClosed(const nsCString& aSessionId) override;
+
+ void SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage) override;
+
+ void Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override;
+
+ void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos) override;
+
+ void Terminated() override;
+
+ ~GMPCDMCallbackProxy() {}
+
+private:
+ friend class GMPCDMProxy;
+ explicit GMPCDMCallbackProxy(CDMProxy* aProxy);
+
+ void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos);
+ // Warning: Weak ref.
+ CDMProxy* mProxy;
+};
+
+} // namespace mozilla
+
+#endif // GMPCDMCallbackProxy_h_
diff --git a/dom/media/gmp/GMPCDMProxy.cpp b/dom/media/gmp/GMPCDMProxy.cpp
new file mode 100644
index 000000000..0f1958632
--- /dev/null
+++ b/dom/media/gmp/GMPCDMProxy.cpp
@@ -0,0 +1,798 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPCDMProxy.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/PodOperations.h"
+
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsIConsoleService.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "prenv.h"
+#include "GMPCDMCallbackProxy.h"
+#include "GMPService.h"
+#include "MainThreadUtils.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+GMPCDMProxy::GMPCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ GMPCrashHelper* aCrashHelper,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : CDMProxy(aKeys,
+ aKeySystem,
+ aDistinctiveIdentifierRequired,
+ aPersistentStateRequired)
+ , mCrashHelper(aCrashHelper)
+ , mCDM(nullptr)
+ , mDecryptionJobCount(0)
+ , mShutdownCalled(false)
+ , mDecryptorId(0)
+ , mCreatePromiseId(0)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(GMPCDMProxy);
+}
+
+GMPCDMProxy::~GMPCDMProxy()
+{
+ MOZ_COUNT_DTOR(GMPCDMProxy);
+}
+
+void
+GMPCDMProxy::Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ EME_LOG("GMPCDMProxy::Init (%s, %s) %s",
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
+
+ nsCString pluginVersion;
+ if (!mOwnerThread) {
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::Init"));
+ return;
+ }
+ mps->GetThread(getter_AddRefs(mOwnerThread));
+ if (!mOwnerThread) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get GMP thread GMPCDMProxy::Init"));
+ return;
+ }
+ }
+
+ if (aGMPName.IsEmpty()) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ nsPrintfCString("Unknown GMP for keysystem '%s'", NS_ConvertUTF16toUTF8(mKeySystem).get()));
+ return;
+ }
+
+ nsAutoPtr<InitData> data(new InitData());
+ data->mPromiseId = aPromiseId;
+ data->mOrigin = aOrigin;
+ data->mTopLevelOrigin = aTopLevelOrigin;
+ data->mGMPName = aGMPName;
+ data->mInPrivateBrowsing = aInPrivateBrowsing;
+ data->mCrashHelper = mCrashHelper;
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<InitData>>(this,
+ &GMPCDMProxy::gmp_Init,
+ Move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+#ifdef DEBUG
+bool
+GMPCDMProxy::IsOnOwnerThread()
+{
+ return NS_GetCurrentThread() == mOwnerThread;
+}
+#endif
+
+void
+GMPCDMProxy::gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData)
+{
+ EME_LOG("GMPCDMProxy::gmp_InitDone");
+ if (mShutdownCalled) {
+ if (aCDM) {
+ aCDM->Close();
+ }
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GMPCDMProxy was shut down before init could complete"));
+ return;
+ }
+ if (!aCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GetGMPDecryptor failed to return a CDM"));
+ return;
+ }
+
+ mCDM = aCDM;
+ mCallback = new GMPCDMCallbackProxy(this);
+ mCDM->Init(mCallback,
+ mDistinctiveIdentifierRequired,
+ mPersistentStateRequired);
+
+ // Await the OnSetDecryptorId callback.
+ mCreatePromiseId = aData->mPromiseId;
+}
+
+void GMPCDMProxy::OnSetDecryptorId(uint32_t aId)
+{
+ MOZ_ASSERT(mCreatePromiseId);
+ mDecryptorId = aId;
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<uint32_t>(this,
+ &GMPCDMProxy::OnCDMCreated,
+ mCreatePromiseId));
+ NS_DispatchToMainThread(task);
+}
+
+class gmp_InitDoneCallback : public GetGMPDecryptorCallback
+{
+public:
+ gmp_InitDoneCallback(GMPCDMProxy* aGMPCDMProxy,
+ nsAutoPtr<GMPCDMProxy::InitData>&& aData)
+ : mGMPCDMProxy(aGMPCDMProxy),
+ mData(Move(aData))
+ {
+ }
+
+ void Done(GMPDecryptorProxy* aCDM)
+ {
+ mGMPCDMProxy->gmp_InitDone(aCDM, Move(mData));
+ }
+
+private:
+ RefPtr<GMPCDMProxy> mGMPCDMProxy;
+ nsAutoPtr<GMPCDMProxy::InitData> mData;
+};
+
+class gmp_InitGetGMPDecryptorCallback : public GetNodeIdCallback
+{
+public:
+ gmp_InitGetGMPDecryptorCallback(GMPCDMProxy* aGMPCDMProxy,
+ nsAutoPtr<GMPCDMProxy::InitData>&& aData)
+ : mGMPCDMProxy(aGMPCDMProxy),
+ mData(aData)
+ {
+ }
+
+ void Done(nsresult aResult, const nsACString& aNodeId)
+ {
+ mGMPCDMProxy->gmp_InitGetGMPDecryptor(aResult, aNodeId, Move(mData));
+ }
+
+private:
+ RefPtr<GMPCDMProxy> mGMPCDMProxy;
+ nsAutoPtr<GMPCDMProxy::InitData> mData;
+};
+
+void
+GMPCDMProxy::gmp_Init(nsAutoPtr<InitData>&& aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::gmp_Init"));
+ return;
+ }
+
+ // Make a copy before we transfer ownership of aData to the
+ // gmp_InitGetGMPDecryptorCallback.
+ InitData data(*aData);
+ UniquePtr<GetNodeIdCallback> callback(
+ new gmp_InitGetGMPDecryptorCallback(this, Move(aData)));
+ nsresult rv = mps->GetNodeId(data.mOrigin,
+ data.mTopLevelOrigin,
+ data.mGMPName,
+ data.mInPrivateBrowsing,
+ Move(callback));
+ if (NS_FAILED(rv)) {
+ RejectPromise(data.mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Call to GetNodeId() failed early"));
+ }
+}
+
+void
+GMPCDMProxy::gmp_InitGetGMPDecryptor(nsresult aResult,
+ const nsACString& aNodeId,
+ nsAutoPtr<InitData>&& aData)
+{
+ uint32_t promiseID = aData->mPromiseId;
+ if (NS_FAILED(aResult)) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GetNodeId() called back, but with a failure result"));
+ return;
+ }
+
+ mNodeId = aNodeId;
+ MOZ_ASSERT(!GetNodeId().IsEmpty());
+
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::gmp_InitGetGMPDecryptor"));
+ return;
+ }
+
+ EME_LOG("GMPCDMProxy::gmp_Init (%s, %s) %s NodeId=%s",
+ NS_ConvertUTF16toUTF8(aData->mOrigin).get(),
+ NS_ConvertUTF16toUTF8(aData->mTopLevelOrigin).get(),
+ (aData->mInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"),
+ GetNodeId().get());
+
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
+
+ // Note: must capture helper refptr here, before the Move()
+ // when we create the GetGMPDecryptorCallback below.
+ RefPtr<GMPCrashHelper> crashHelper = Move(aData->mCrashHelper);
+ UniquePtr<GetGMPDecryptorCallback> callback(new gmp_InitDoneCallback(this,
+ Move(aData)));
+ nsresult rv = mps->GetGMPDecryptor(crashHelper,
+ &tags,
+ GetNodeId(),
+ Move(callback));
+ if (NS_FAILED(rv)) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Call to GetGMPDecryptor() failed early"));
+ }
+}
+
+void
+GMPCDMProxy::OnCDMCreated(uint32_t aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ MOZ_ASSERT(!GetNodeId().IsEmpty());
+ if (mCDM) {
+ mKeys->OnCDMCreated(aPromiseId, GetNodeId(), mCDM->GetPluginId());
+ } else {
+ // No CDM? Just reject the promise.
+ mKeys->RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in OnCDMCreated()"));
+ }
+}
+
+void
+GMPCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+ dom::MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<CreateSessionData> data(new CreateSessionData());
+ data->mSessionType = aSessionType;
+ data->mCreateSessionToken = aCreateSessionToken;
+ data->mPromiseId = aPromiseId;
+ data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
+ data->mInitData = Move(aInitData);
+
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<CreateSessionData>>(this, &GMPCDMProxy::gmp_CreateSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+GMPSessionType
+ToGMPSessionType(dom::MediaKeySessionType aSessionType) {
+ switch (aSessionType) {
+ case dom::MediaKeySessionType::Temporary: return kGMPTemporySession;
+ case dom::MediaKeySessionType::Persistent_license: return kGMPPersistentSession;
+ default: return kGMPTemporySession;
+ };
+};
+
+void
+GMPCDMProxy::gmp_CreateSession(nsAutoPtr<CreateSessionData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_CreateSession"));
+ return;
+ }
+ mCDM->CreateSession(aData->mCreateSessionToken,
+ aData->mPromiseId,
+ aData->mInitDataType,
+ aData->mInitData,
+ ToGMPSessionType(aData->mSessionType));
+}
+
+void
+GMPCDMProxy::LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_LoadSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_LoadSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_LoadSession"));
+ return;
+ }
+ mCDM->LoadSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<SetServerCertificateData> data(new SetServerCertificateData());
+ data->mPromiseId = aPromiseId;
+ data->mCert = Move(aCert);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SetServerCertificateData>>(this, &GMPCDMProxy::gmp_SetServerCertificate, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_SetServerCertificate(nsAutoPtr<SetServerCertificateData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_SetServerCertificate"));
+ return;
+ }
+ mCDM->SetServerCertificate(aData->mPromiseId, aData->mCert);
+}
+
+void
+GMPCDMProxy::UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<UpdateSessionData> data(new UpdateSessionData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ data->mResponse = Move(aResponse);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<UpdateSessionData>>(this, &GMPCDMProxy::gmp_UpdateSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_UpdateSession(nsAutoPtr<UpdateSessionData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_UpdateSession"));
+ return;
+ }
+ mCDM->UpdateSession(aData->mPromiseId,
+ aData->mSessionId,
+ aData->mResponse);
+}
+
+void
+GMPCDMProxy::CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_CloseSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_CloseSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_CloseSession"));
+ return;
+ }
+ mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_RemoveSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_RemoveSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_RemoveSession"));
+ return;
+ }
+ mCDM->RemoveSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mKeys.Clear();
+ // Note: This may end up being the last owning reference to the GMPCDMProxy.
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(this, &GMPCDMProxy::gmp_Shutdown));
+ if (mOwnerThread) {
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+}
+
+void
+GMPCDMProxy::gmp_Shutdown()
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ mShutdownCalled = true;
+
+ // Abort any pending decrypt jobs, to awaken any clients waiting on a job.
+ for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
+ DecryptJob* job = mDecryptionJobs[i];
+ job->PostResult(AbortedErr);
+ }
+ mDecryptionJobs.Clear();
+
+ if (mCDM) {
+ mCDM->Close();
+ mCDM = nullptr;
+ }
+}
+
+void
+GMPCDMProxy::RejectPromise(PromiseId aId, nsresult aCode,
+ const nsCString& aReason)
+{
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->RejectPromise(aId, aCode, aReason);
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode,
+ aReason));
+ NS_DispatchToMainThread(task);
+ }
+}
+
+void
+GMPCDMProxy::ResolvePromise(PromiseId aId)
+{
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromise(aId);
+ } else {
+ NS_WARNING("GMPCDMProxy unable to resolve promise!");
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task;
+ task = NewRunnableMethod<PromiseId>(this,
+ &GMPCDMProxy::ResolvePromise,
+ aId);
+ NS_DispatchToMainThread(task);
+ }
+}
+
+const nsCString&
+GMPCDMProxy::GetNodeId() const
+{
+ return mNodeId;
+}
+
+void
+GMPCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+
+ RefPtr<dom::MediaKeySession> session(mKeys->GetPendingSession(aCreateSessionToken));
+ if (session) {
+ session->SetSessionId(aSessionId);
+ }
+}
+
+void
+GMPCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ mKeys->OnSessionLoaded(aPromiseId, aSuccess);
+}
+
+void
+GMPCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ nsTArray<uint8_t>& aMessage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyMessage(aMessageType, aMessage);
+ }
+}
+
+void
+GMPCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyStatusesChange();
+ }
+}
+
+void
+GMPCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+ GMPTimestamp aExpiryTime)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->SetExpiration(static_cast<double>(aExpiryTime));
+ }
+}
+
+void
+GMPCDMProxy::OnSessionClosed(const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->OnClosed();
+ }
+}
+
+void
+GMPCDMProxy::OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ gmp_Decrypted(aId, aResult, aDecryptedData);
+}
+
+static void
+LogToConsole(const nsAString& aMsg)
+{
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+ nsAutoString msg(aMsg);
+ console->LogStringMessage(msg.get());
+}
+
+void
+GMPCDMProxy::OnSessionError(const nsAString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsAString& aMsg)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyError(aSystemCode);
+ }
+ LogToConsole(aMsg);
+}
+
+void
+GMPCDMProxy::OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RejectPromise(aPromiseId, aDOMException, aMsg);
+}
+
+const nsString&
+GMPCDMProxy::KeySystem() const
+{
+ return mKeySystem;
+}
+
+CDMCaps&
+GMPCDMProxy::Capabilites() {
+ return mCapabilites;
+}
+
+RefPtr<GMPCDMProxy::DecryptPromise>
+GMPCDMProxy::Decrypt(MediaRawData* aSample)
+{
+ RefPtr<DecryptJob> job(new DecryptJob(aSample));
+ RefPtr<DecryptPromise> promise(job->Ensure());
+
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<RefPtr<DecryptJob>>(this, &GMPCDMProxy::gmp_Decrypt, job));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ return promise;
+}
+
+void
+GMPCDMProxy::gmp_Decrypt(RefPtr<DecryptJob> aJob)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ aJob->PostResult(AbortedErr);
+ return;
+ }
+
+ aJob->mId = ++mDecryptionJobCount;
+ nsTArray<uint8_t> data;
+ data.AppendElements(aJob->mSample->Data(), aJob->mSample->Size());
+ mCDM->Decrypt(aJob->mId, aJob->mSample->mCrypto, data);
+ mDecryptionJobs.AppendElement(aJob.forget());
+}
+
+void
+GMPCDMProxy::gmp_Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+#ifdef DEBUG
+ bool jobIdFound = false;
+#endif
+ for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
+ DecryptJob* job = mDecryptionJobs[i];
+ if (job->mId == aId) {
+#ifdef DEBUG
+ jobIdFound = true;
+#endif
+ job->PostResult(aResult, aDecryptedData);
+ mDecryptionJobs.RemoveElementAt(i);
+ }
+ }
+#ifdef DEBUG
+ if (!jobIdFound) {
+ NS_WARNING("GMPDecryptorChild returned incorrect job ID");
+ }
+#endif
+}
+
+void
+GMPCDMProxy::DecryptJob::PostResult(DecryptStatus aResult)
+{
+ nsTArray<uint8_t> empty;
+ PostResult(aResult, empty);
+}
+
+void
+GMPCDMProxy::DecryptJob::PostResult(DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ if (aDecryptedData.Length() != mSample->Size()) {
+ NS_WARNING("CDM returned incorrect number of decrypted bytes");
+ }
+ if (aResult == Ok) {
+ nsAutoPtr<MediaRawDataWriter> writer(mSample->CreateWriter());
+ PodCopy(writer->Data(),
+ aDecryptedData.Elements(),
+ std::min<size_t>(aDecryptedData.Length(), mSample->Size()));
+ } else if (aResult == NoKeyErr) {
+ NS_WARNING("CDM returned NoKeyErr");
+ // We still have the encrypted sample, so we can re-enqueue it to be
+ // decrypted again once the key is usable again.
+ } else {
+ nsAutoCString str("CDM returned decode failure DecryptStatus=");
+ str.AppendInt(aResult);
+ NS_WARNING(str.get());
+ }
+ mPromise.Resolve(DecryptResult(aResult, mSample), __func__);
+}
+
+void
+GMPCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds)
+{
+ CDMCaps::AutoLock caps(Capabilites());
+ caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
+}
+
+void
+GMPCDMProxy::Terminated()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_WARNING("CDM terminated");
+ if (mCreatePromiseId) {
+ RejectPromise(mCreatePromiseId,
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ NS_LITERAL_CSTRING("Crashed waiting for CDM to initialize"));
+ mCreatePromiseId = 0;
+ }
+ if (!mKeys.IsNull()) {
+ mKeys->Terminated();
+ }
+}
+
+uint32_t
+GMPCDMProxy::GetDecryptorId()
+{
+ return mDecryptorId;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPCDMProxy.h b/dom/media/gmp/GMPCDMProxy.h
new file mode 100644
index 000000000..a7fae235b
--- /dev/null
+++ b/dom/media/gmp/GMPCDMProxy.h
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPCDMProxy_h_
+#define GMPCDMProxy_h_
+
+#include "mozilla/CDMProxy.h"
+#include "GMPCDMCallbackProxy.h"
+#include "GMPDecryptorProxy.h"
+
+namespace mozilla {
+class MediaRawData;
+
+// Implementation of CDMProxy which is based on GMP architecture.
+class GMPCDMProxy : public CDMProxy {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPCDMProxy, override)
+
+ typedef MozPromise<DecryptResult, DecryptResult, /* IsExclusive = */ true> DecryptPromise;
+
+ GMPCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ GMPCrashHelper* aCrashHelper,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired);
+
+ void Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing) override;
+
+ void OnSetDecryptorId(uint32_t aId) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ dom::MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) override;
+
+ void LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId) override;
+
+ void SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) override;
+
+ void UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) override;
+
+ void CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) override;
+
+ void RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) override;
+
+ void Shutdown() override;
+
+ void Terminated() override;
+
+ const nsCString& GetNodeId() const override;
+
+ void OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) override;
+
+ void OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess) override;
+
+ void OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ nsTArray<uint8_t>& aMessage) override;
+
+ void OnExpirationChange(const nsAString& aSessionId,
+ GMPTimestamp aExpiryTime) override;
+
+ void OnSessionClosed(const nsAString& aSessionId) override;
+
+ void OnSessionError(const nsAString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsAString& aMsg) override;
+
+ void OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg) override;
+
+ RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) override;
+
+ void OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override;
+
+ void RejectPromise(PromiseId aId, nsresult aExceptionCode,
+ const nsCString& aReason) override;
+
+ void ResolvePromise(PromiseId aId) override;
+
+ const nsString& KeySystem() const override;
+
+ CDMCaps& Capabilites() override;
+
+ void OnKeyStatusesChange(const nsAString& aSessionId) override;
+
+ void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds) override;
+
+#ifdef DEBUG
+ bool IsOnOwnerThread() override;
+#endif
+
+ uint32_t GetDecryptorId() override;
+
+private:
+ friend class gmp_InitDoneCallback;
+ friend class gmp_InitGetGMPDecryptorCallback;
+
+ struct InitData {
+ uint32_t mPromiseId;
+ nsString mOrigin;
+ nsString mTopLevelOrigin;
+ nsString mGMPName;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+ bool mInPrivateBrowsing;
+ };
+
+ // GMP thread only.
+ void gmp_Init(nsAutoPtr<InitData>&& aData);
+ void gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData);
+ void gmp_InitGetGMPDecryptor(nsresult aResult,
+ const nsACString& aNodeId,
+ nsAutoPtr<InitData>&& aData);
+
+ // GMP thread only.
+ void gmp_Shutdown();
+
+ // Main thread only.
+ void OnCDMCreated(uint32_t aPromiseId);
+
+ struct CreateSessionData {
+ dom::MediaKeySessionType mSessionType;
+ uint32_t mCreateSessionToken;
+ PromiseId mPromiseId;
+ nsCString mInitDataType;
+ nsTArray<uint8_t> mInitData;
+ };
+ // GMP thread only.
+ void gmp_CreateSession(nsAutoPtr<CreateSessionData> aData);
+
+ struct SessionOpData {
+ PromiseId mPromiseId;
+ nsCString mSessionId;
+ };
+ // GMP thread only.
+ void gmp_LoadSession(nsAutoPtr<SessionOpData> aData);
+
+ struct SetServerCertificateData {
+ PromiseId mPromiseId;
+ nsTArray<uint8_t> mCert;
+ };
+ // GMP thread only.
+ void gmp_SetServerCertificate(nsAutoPtr<SetServerCertificateData> aData);
+
+ struct UpdateSessionData {
+ PromiseId mPromiseId;
+ nsCString mSessionId;
+ nsTArray<uint8_t> mResponse;
+ };
+ // GMP thread only.
+ void gmp_UpdateSession(nsAutoPtr<UpdateSessionData> aData);
+
+ // GMP thread only.
+ void gmp_CloseSession(nsAutoPtr<SessionOpData> aData);
+
+ // GMP thread only.
+ void gmp_RemoveSession(nsAutoPtr<SessionOpData> aData);
+
+ class DecryptJob {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecryptJob)
+
+ explicit DecryptJob(MediaRawData* aSample)
+ : mId(0)
+ , mSample(aSample)
+ {
+ }
+
+ void PostResult(DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData);
+ void PostResult(DecryptStatus aResult);
+
+ RefPtr<DecryptPromise> Ensure() {
+ return mPromise.Ensure(__func__);
+ }
+
+ uint32_t mId;
+ RefPtr<MediaRawData> mSample;
+ private:
+ ~DecryptJob() {}
+ MozPromiseHolder<DecryptPromise> mPromise;
+ };
+ // GMP thread only.
+ void gmp_Decrypt(RefPtr<DecryptJob> aJob);
+
+ // GMP thread only.
+ void gmp_Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData);
+
+ class RejectPromiseTask : public Runnable {
+ public:
+ RejectPromiseTask(GMPCDMProxy* aProxy,
+ PromiseId aId,
+ nsresult aCode,
+ const nsCString& aReason)
+ : mProxy(aProxy)
+ , mId(aId)
+ , mCode(aCode)
+ , mReason(aReason)
+ {
+ }
+ NS_IMETHOD Run() override {
+ mProxy->RejectPromise(mId, mCode, mReason);
+ return NS_OK;
+ }
+ private:
+ RefPtr<GMPCDMProxy> mProxy;
+ PromiseId mId;
+ nsresult mCode;
+ nsCString mReason;
+ };
+
+ ~GMPCDMProxy();
+
+ GMPCrashHelper* mCrashHelper;
+
+ GMPDecryptorProxy* mCDM;
+
+ nsAutoPtr<GMPCDMCallbackProxy> mCallback;
+
+ // Decryption jobs sent to CDM, awaiting result.
+ // GMP thread only.
+ nsTArray<RefPtr<DecryptJob>> mDecryptionJobs;
+
+ // Number of buffers we've decrypted. Used to uniquely identify
+ // decryption jobs sent to CDM. Note we can't just use the length of
+ // mDecryptionJobs as that shrinks as jobs are completed and removed
+ // from it.
+ // GMP thread only.
+ uint32_t mDecryptionJobCount;
+
+ // True if GMPCDMProxy::gmp_Shutdown was called.
+ // GMP thread only.
+ bool mShutdownCalled;
+
+ uint32_t mDecryptorId;
+
+ PromiseId mCreatePromiseId;
+};
+
+
+} // namespace mozilla
+
+#endif // GMPCDMProxy_h_
diff --git a/dom/media/gmp/GMPCallbackBase.h b/dom/media/gmp/GMPCallbackBase.h
new file mode 100644
index 000000000..3d96629ef
--- /dev/null
+++ b/dom/media/gmp/GMPCallbackBase.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPCallbackBase_h_
+#define GMPCallbackBase_h_
+
+class GMPCallbackBase
+{
+public:
+ virtual ~GMPCallbackBase() {}
+
+ // The GMP code will call this if the codec crashes or shuts down. It's
+ // expected that the consumer (destination of this callback) will respond
+ // by dropping their reference to the proxy, allowing the proxy/parent to
+ // be destroyed.
+ virtual void Terminated() = 0;
+};
+
+#endif
diff --git a/dom/media/gmp/GMPChild.cpp b/dom/media/gmp/GMPChild.cpp
new file mode 100644
index 000000000..eb1803736
--- /dev/null
+++ b/dom/media/gmp/GMPChild.cpp
@@ -0,0 +1,500 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPChild.h"
+#include "GMPContentChild.h"
+#include "GMPProcessChild.h"
+#include "GMPLoader.h"
+#include "GMPVideoDecoderChild.h"
+#include "GMPVideoEncoderChild.h"
+#include "GMPAudioDecoderChild.h"
+#include "GMPDecryptorChild.h"
+#include "GMPVideoHost.h"
+#include "nsDebugImpl.h"
+#include "nsIFile.h"
+#include "nsXULAppAPI.h"
+#include "gmp-video-decode.h"
+#include "gmp-video-encode.h"
+#include "GMPPlatform.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "GMPUtils.h"
+#include "prio.h"
+#include "base/task.h"
+#ifdef MOZ_EME
+#include "widevine-adapter/WidevineAdapter.h"
+#endif
+
+using namespace mozilla::ipc;
+
+static const int MAX_VOUCHER_LENGTH = 500000;
+
+#ifdef XP_WIN
+#include <stdlib.h> // for _exit()
+#else
+#include <unistd.h> // for _exit()
+#endif
+
+namespace mozilla {
+
+#undef LOG
+#undef LOGD
+
+extern LogModule* GetGMPLog();
+#define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__))
+#define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPChild[pid=%d] " x, (int)base::GetCurrentProcId(), ##__VA_ARGS__)
+
+namespace gmp {
+
+GMPChild::GMPChild()
+ : mAsyncShutdown(nullptr)
+ , mGMPMessageLoop(MessageLoop::current())
+ , mGMPLoader(nullptr)
+{
+ LOGD("GMPChild ctor");
+ nsDebugImpl::SetMultiprocessMode("GMP");
+}
+
+GMPChild::~GMPChild()
+{
+ LOGD("GMPChild dtor");
+}
+
+static bool
+GetFileBase(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aLibDirectory,
+ nsCOMPtr<nsIFile>& aFileBase,
+ nsAutoString& aBaseName)
+{
+ nsresult rv = NS_NewLocalFile(aPluginPath,
+ true, getter_AddRefs(aFileBase));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (NS_FAILED(aFileBase->Clone(getter_AddRefs(aLibDirectory)))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIFile> parent;
+ rv = aFileBase->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoString parentLeafName;
+ rv = parent->GetLeafName(parentLeafName);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ aBaseName = Substring(parentLeafName,
+ 4,
+ parentLeafName.Length() - 1);
+ return true;
+}
+
+static bool
+GetFileBase(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aFileBase,
+ nsAutoString& aBaseName)
+{
+ nsCOMPtr<nsIFile> unusedLibDir;
+ return GetFileBase(aPluginPath, unusedLibDir, aFileBase, aBaseName);
+}
+
+static bool
+GetPluginFile(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aLibDirectory,
+ nsCOMPtr<nsIFile>& aLibFile)
+{
+ nsAutoString baseName;
+ GetFileBase(aPluginPath, aLibDirectory, aLibFile, baseName);
+
+#if defined(OS_POSIX)
+ nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".so");
+#elif defined(XP_WIN)
+ nsAutoString binaryName = baseName + NS_LITERAL_STRING(".dll");
+#else
+#error Unsupported O.S.
+#endif
+ aLibFile->AppendRelativePath(binaryName);
+ return true;
+}
+
+static bool
+GetPluginFile(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aLibFile)
+{
+ nsCOMPtr<nsIFile> unusedlibDir;
+ return GetPluginFile(aPluginPath, unusedlibDir, aLibFile);
+}
+
+bool
+GMPChild::Init(const nsAString& aPluginPath,
+ const nsAString& aVoucherPath,
+ base::ProcessId aParentPid,
+ MessageLoop* aIOLoop,
+ IPC::Channel* aChannel)
+{
+ LOGD("%s pluginPath=%s", __FUNCTION__, NS_ConvertUTF16toUTF8(aPluginPath).get());
+
+ if (NS_WARN_IF(!Open(aChannel, aParentPid, aIOLoop))) {
+ return false;
+ }
+
+ mPluginPath = aPluginPath;
+ mSandboxVoucherPath = aVoucherPath;
+
+ return true;
+}
+
+bool
+GMPChild::RecvSetNodeId(const nsCString& aNodeId)
+{
+ LOGD("%s nodeId=%s", __FUNCTION__, aNodeId.Data());
+
+ // Store the per origin salt for the node id. Note: we do this in a
+ // separate message than RecvStartPlugin() so that the string is not
+ // sitting in a string on the IPC code's call stack.
+ mNodeId = aNodeId;
+ return true;
+}
+
+GMPErr
+GMPChild::GetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId)
+{
+ if (!mGMPLoader) {
+ return GMPGenericErr;
+ }
+ return mGMPLoader->GetAPI(aAPIName, aHostAPI, aPluginAPI, aDecryptorId);
+}
+
+bool
+GMPChild::RecvPreloadLibs(const nsCString& aLibs)
+{
+#ifdef XP_WIN
+ // Pre-load DLLs that need to be used by the EME plugin but that can't be
+ // loaded after the sandbox has started
+ // Items in this must be lowercase!
+ static const char* whitelist[] = {
+ "d3d9.dll", // Create an `IDirect3D9` to get adapter information
+ "dxva2.dll", // Get monitor information
+ "evr.dll", // MFGetStrideForBitmapInfoHeader
+ "mfh264dec.dll", // H.264 decoder (on Windows Vista)
+ "mfheaacdec.dll", // AAC decoder (on Windows Vista)
+ "mfplat.dll", // MFCreateSample, MFCreateAlignedMemoryBuffer, MFCreateMediaType
+ "msauddecmft.dll", // AAC decoder (on Windows 8)
+ "msmpeg2adec.dll", // AAC decoder (on Windows 7)
+ "msmpeg2vdec.dll", // H.264 decoder
+ };
+
+ nsTArray<nsCString> libs;
+ SplitAt(", ", aLibs, libs);
+ for (nsCString lib : libs) {
+ ToLowerCase(lib);
+ for (const char* whiteListedLib : whitelist) {
+ if (lib.EqualsASCII(whiteListedLib)) {
+ LoadLibraryA(lib.get());
+ break;
+ }
+ }
+ }
+#endif
+ return true;
+}
+
+bool
+GMPChild::GetUTF8LibPath(nsACString& aOutLibPath)
+{
+ nsCOMPtr<nsIFile> libFile;
+ if (!GetPluginFile(mPluginPath, libFile)) {
+ return false;
+ }
+
+ if (!FileExists(libFile)) {
+ NS_WARNING("Can't find GMP library file!");
+ return false;
+ }
+
+ nsAutoString path;
+ libFile->GetPath(path);
+ aOutLibPath = NS_ConvertUTF16toUTF8(path);
+
+ return true;
+}
+
+bool
+GMPChild::AnswerStartPlugin(const nsString& aAdapter)
+{
+ LOGD("%s", __FUNCTION__);
+
+ if (!PreLoadPluginVoucher()) {
+ NS_WARNING("Plugin voucher failed to load!");
+ return false;
+ }
+ PreLoadSandboxVoucher();
+
+ nsCString libPath;
+ if (!GetUTF8LibPath(libPath)) {
+ return false;
+ }
+
+ auto platformAPI = new GMPPlatformAPI();
+ InitPlatformAPI(*platformAPI, this);
+
+ mGMPLoader = GMPProcessChild::GetGMPLoader();
+ if (!mGMPLoader) {
+ NS_WARNING("Failed to get GMPLoader");
+ delete platformAPI;
+ return false;
+ }
+
+#ifdef MOZ_EME
+ bool isWidevine = aAdapter.EqualsLiteral("widevine");
+
+ GMPAdapter* adapter = (isWidevine) ? new WidevineAdapter() : nullptr;
+#else
+ GMPAdapter* adapter = nullptr;
+#endif
+ if (!mGMPLoader->Load(libPath.get(),
+ libPath.Length(),
+ mNodeId.BeginWriting(),
+ mNodeId.Length(),
+ platformAPI,
+ adapter)) {
+ NS_WARNING("Failed to load GMP");
+ delete platformAPI;
+ return false;
+ }
+
+ void* sh = nullptr;
+ GMPAsyncShutdownHost* host = static_cast<GMPAsyncShutdownHost*>(this);
+ GMPErr err = GetAPI(GMP_API_ASYNC_SHUTDOWN, host, &sh);
+ if (err == GMPNoErr && sh) {
+ mAsyncShutdown = reinterpret_cast<GMPAsyncShutdown*>(sh);
+ SendAsyncShutdownRequired();
+ }
+
+ return true;
+}
+
+MessageLoop*
+GMPChild::GMPMessageLoop()
+{
+ return mGMPMessageLoop;
+}
+
+void
+GMPChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD("%s reason=%d", __FUNCTION__, aWhy);
+
+ for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) {
+ MOZ_ASSERT_IF(aWhy == NormalShutdown, !mGMPContentChildren[i - 1]->IsUsed());
+ mGMPContentChildren[i - 1]->Close();
+ }
+
+ if (mGMPLoader) {
+ mGMPLoader->Shutdown();
+ }
+ if (AbnormalShutdown == aWhy) {
+ NS_WARNING("Abnormal shutdown of GMP process!");
+ ProcessChild::QuickExit();
+ }
+
+ XRE_ShutdownChildProcess();
+}
+
+void
+GMPChild::ProcessingError(Result aCode, const char* aReason)
+{
+ switch (aCode) {
+ case MsgDropped:
+ _exit(0); // Don't trigger a crash report.
+ case MsgNotKnown:
+ MOZ_CRASH("aborting because of MsgNotKnown");
+ case MsgNotAllowed:
+ MOZ_CRASH("aborting because of MsgNotAllowed");
+ case MsgPayloadError:
+ MOZ_CRASH("aborting because of MsgPayloadError");
+ case MsgProcessingError:
+ MOZ_CRASH("aborting because of MsgProcessingError");
+ case MsgRouteError:
+ MOZ_CRASH("aborting because of MsgRouteError");
+ case MsgValueError:
+ MOZ_CRASH("aborting because of MsgValueError");
+ default:
+ MOZ_CRASH("not reached");
+ }
+}
+
+PGMPTimerChild*
+GMPChild::AllocPGMPTimerChild()
+{
+ return new GMPTimerChild(this);
+}
+
+bool
+GMPChild::DeallocPGMPTimerChild(PGMPTimerChild* aActor)
+{
+ MOZ_ASSERT(mTimerChild == static_cast<GMPTimerChild*>(aActor));
+ mTimerChild = nullptr;
+ return true;
+}
+
+GMPTimerChild*
+GMPChild::GetGMPTimers()
+{
+ if (!mTimerChild) {
+ PGMPTimerChild* sc = SendPGMPTimerConstructor();
+ if (!sc) {
+ return nullptr;
+ }
+ mTimerChild = static_cast<GMPTimerChild*>(sc);
+ }
+ return mTimerChild;
+}
+
+PGMPStorageChild*
+GMPChild::AllocPGMPStorageChild()
+{
+ return new GMPStorageChild(this);
+}
+
+bool
+GMPChild::DeallocPGMPStorageChild(PGMPStorageChild* aActor)
+{
+ mStorage = nullptr;
+ return true;
+}
+
+GMPStorageChild*
+GMPChild::GetGMPStorage()
+{
+ if (!mStorage) {
+ PGMPStorageChild* sc = SendPGMPStorageConstructor();
+ if (!sc) {
+ return nullptr;
+ }
+ mStorage = static_cast<GMPStorageChild*>(sc);
+ }
+ return mStorage;
+}
+
+bool
+GMPChild::RecvCrashPluginNow()
+{
+ MOZ_CRASH();
+ return true;
+}
+
+bool
+GMPChild::RecvBeginAsyncShutdown()
+{
+ LOGD("%s AsyncShutdown=%d", __FUNCTION__, mAsyncShutdown!=nullptr);
+
+ MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
+ if (mAsyncShutdown) {
+ mAsyncShutdown->BeginShutdown();
+ } else {
+ ShutdownComplete();
+ }
+ return true;
+}
+
+bool
+GMPChild::RecvCloseActive()
+{
+ for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) {
+ mGMPContentChildren[i - 1]->CloseActive();
+ }
+ return true;
+}
+
+void
+GMPChild::ShutdownComplete()
+{
+ LOGD("%s", __FUNCTION__);
+ MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
+ mAsyncShutdown = nullptr;
+ SendAsyncShutdownComplete();
+}
+
+static void
+GetPluginVoucherFile(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aOutVoucherFile)
+{
+ nsAutoString baseName;
+ GetFileBase(aPluginPath, aOutVoucherFile, baseName);
+ nsAutoString infoFileName = baseName + NS_LITERAL_STRING(".voucher");
+ aOutVoucherFile->AppendRelativePath(infoFileName);
+}
+
+bool
+GMPChild::PreLoadPluginVoucher()
+{
+ nsCOMPtr<nsIFile> voucherFile;
+ GetPluginVoucherFile(mPluginPath, voucherFile);
+ if (!FileExists(voucherFile)) {
+ // Assume missing file is not fatal; that would break OpenH264.
+ return true;
+ }
+ return ReadIntoArray(voucherFile, mPluginVoucher, MAX_VOUCHER_LENGTH);
+}
+
+void
+GMPChild::PreLoadSandboxVoucher()
+{
+ nsCOMPtr<nsIFile> f;
+ nsresult rv = NS_NewLocalFile(mSandboxVoucherPath, true, getter_AddRefs(f));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create nsIFile for sandbox voucher");
+ return;
+ }
+ if (!FileExists(f)) {
+ // Assume missing file is not fatal; that would break OpenH264.
+ return;
+ }
+
+ if (!ReadIntoArray(f, mSandboxVoucher, MAX_VOUCHER_LENGTH)) {
+ NS_WARNING("Failed to read sandbox voucher");
+ }
+}
+
+PGMPContentChild*
+GMPChild::AllocPGMPContentChild(Transport* aTransport,
+ ProcessId aOtherPid)
+{
+ GMPContentChild* child =
+ mGMPContentChildren.AppendElement(new GMPContentChild(this))->get();
+ child->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(), ipc::ChildSide);
+
+ return child;
+}
+
+void
+GMPChild::GMPContentChildActorDestroy(GMPContentChild* aGMPContentChild)
+{
+ for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) {
+ UniquePtr<GMPContentChild>& toDestroy = mGMPContentChildren[i - 1];
+ if (toDestroy.get() == aGMPContentChild) {
+ SendPGMPContentChildDestroyed();
+ RefPtr<DeleteTask<GMPContentChild>> task =
+ new DeleteTask<GMPContentChild>(toDestroy.release());
+ MessageLoop::current()->PostTask(task.forget());
+ mGMPContentChildren.RemoveElementAt(i - 1);
+ break;
+ }
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+#undef LOG
+#undef LOGD
diff --git a/dom/media/gmp/GMPChild.h b/dom/media/gmp/GMPChild.h
new file mode 100644
index 000000000..722e4c7a9
--- /dev/null
+++ b/dom/media/gmp/GMPChild.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPChild_h_
+#define GMPChild_h_
+
+#include "mozilla/gmp/PGMPChild.h"
+#include "GMPTimerChild.h"
+#include "GMPStorageChild.h"
+#include "GMPLoader.h"
+#include "gmp-async-shutdown.h"
+#include "gmp-entrypoints.h"
+#include "prlink.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPChild : public PGMPChild
+ , public GMPAsyncShutdownHost
+{
+public:
+ GMPChild();
+ virtual ~GMPChild();
+
+ bool Init(const nsAString& aPluginPath,
+ const nsAString& aVoucherPath,
+ base::ProcessId aParentPid,
+ MessageLoop* aIOLoop,
+ IPC::Channel* aChannel);
+ MessageLoop* GMPMessageLoop();
+
+ // Main thread only.
+ GMPTimerChild* GetGMPTimers();
+ GMPStorageChild* GetGMPStorage();
+
+ // GMPAsyncShutdownHost
+ void ShutdownComplete() override;
+
+private:
+ friend class GMPContentChild;
+
+ bool PreLoadPluginVoucher();
+ void PreLoadSandboxVoucher();
+
+ bool GetUTF8LibPath(nsACString& aOutLibPath);
+
+ bool RecvSetNodeId(const nsCString& aNodeId) override;
+ bool AnswerStartPlugin(const nsString& aAdapter) override;
+ bool RecvPreloadLibs(const nsCString& aLibs) override;
+
+ PGMPTimerChild* AllocPGMPTimerChild() override;
+ bool DeallocPGMPTimerChild(PGMPTimerChild* aActor) override;
+
+ PGMPStorageChild* AllocPGMPStorageChild() override;
+ bool DeallocPGMPStorageChild(PGMPStorageChild* aActor) override;
+
+ PGMPContentChild* AllocPGMPContentChild(Transport* aTransport,
+ ProcessId aOtherPid) override;
+ void GMPContentChildActorDestroy(GMPContentChild* aGMPContentChild);
+
+ bool RecvCrashPluginNow() override;
+ bool RecvBeginAsyncShutdown() override;
+ bool RecvCloseActive() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void ProcessingError(Result aCode, const char* aReason) override;
+
+ GMPErr GetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI, uint32_t aDecryptorId = 0);
+
+ nsTArray<UniquePtr<GMPContentChild>> mGMPContentChildren;
+
+ GMPAsyncShutdown* mAsyncShutdown;
+ RefPtr<GMPTimerChild> mTimerChild;
+ RefPtr<GMPStorageChild> mStorage;
+
+ MessageLoop* mGMPMessageLoop;
+ nsString mPluginPath;
+ nsString mSandboxVoucherPath;
+ nsCString mNodeId;
+ GMPLoader* mGMPLoader;
+ nsTArray<uint8_t> mPluginVoucher;
+ nsTArray<uint8_t> mSandboxVoucher;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPChild_h_
diff --git a/dom/media/gmp/GMPContentChild.cpp b/dom/media/gmp/GMPContentChild.cpp
new file mode 100644
index 000000000..415736e11
--- /dev/null
+++ b/dom/media/gmp/GMPContentChild.cpp
@@ -0,0 +1,320 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPContentChild.h"
+#include "GMPChild.h"
+#include "GMPAudioDecoderChild.h"
+#include "GMPDecryptorChild.h"
+#include "GMPVideoDecoderChild.h"
+#include "GMPVideoEncoderChild.h"
+#include "base/task.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPContentChild::GMPContentChild(GMPChild* aChild)
+ : mGMPChild(aChild)
+{
+ MOZ_COUNT_CTOR(GMPContentChild);
+}
+
+GMPContentChild::~GMPContentChild()
+{
+ MOZ_COUNT_DTOR(GMPContentChild);
+}
+
+MessageLoop*
+GMPContentChild::GMPMessageLoop()
+{
+ return mGMPChild->GMPMessageLoop();
+}
+
+void
+GMPContentChild::CheckThread()
+{
+ MOZ_ASSERT(mGMPChild->mGMPMessageLoop == MessageLoop::current());
+}
+
+void
+GMPContentChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ mGMPChild->GMPContentChildActorDestroy(this);
+}
+
+void
+GMPContentChild::ProcessingError(Result aCode, const char* aReason)
+{
+ mGMPChild->ProcessingError(aCode, aReason);
+}
+
+PGMPAudioDecoderChild*
+GMPContentChild::AllocPGMPAudioDecoderChild()
+{
+ return new GMPAudioDecoderChild(this);
+}
+
+bool
+GMPContentChild::DeallocPGMPAudioDecoderChild(PGMPAudioDecoderChild* aActor)
+{
+ delete aActor;
+ return true;
+}
+
+PGMPDecryptorChild*
+GMPContentChild::AllocPGMPDecryptorChild()
+{
+ GMPDecryptorChild* actor = new GMPDecryptorChild(this,
+ mGMPChild->mPluginVoucher,
+ mGMPChild->mSandboxVoucher);
+ actor->AddRef();
+ return actor;
+}
+
+bool
+GMPContentChild::DeallocPGMPDecryptorChild(PGMPDecryptorChild* aActor)
+{
+ static_cast<GMPDecryptorChild*>(aActor)->Release();
+ return true;
+}
+
+PGMPVideoDecoderChild*
+GMPContentChild::AllocPGMPVideoDecoderChild(const uint32_t& aDecryptorId)
+{
+ GMPVideoDecoderChild* actor = new GMPVideoDecoderChild(this);
+ actor->AddRef();
+ return actor;
+}
+
+bool
+GMPContentChild::DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor)
+{
+ static_cast<GMPVideoDecoderChild*>(aActor)->Release();
+ return true;
+}
+
+PGMPVideoEncoderChild*
+GMPContentChild::AllocPGMPVideoEncoderChild()
+{
+ GMPVideoEncoderChild* actor = new GMPVideoEncoderChild(this);
+ actor->AddRef();
+ return actor;
+}
+
+bool
+GMPContentChild::DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor)
+{
+ static_cast<GMPVideoEncoderChild*>(aActor)->Release();
+ return true;
+}
+
+// Adapts GMPDecryptor7 to the current GMPDecryptor version.
+class GMPDecryptor7BackwardsCompat : public GMPDecryptor {
+public:
+ explicit GMPDecryptor7BackwardsCompat(GMPDecryptor7* aDecryptorV7)
+ : mDecryptorV7(aDecryptorV7)
+ {
+ }
+
+ void Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) override
+ {
+ // Distinctive identifier and persistent state arguments not present
+ // in v7 interface.
+ mDecryptorV7->Init(aCallback);
+ }
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) override
+ {
+ mDecryptorV7->CreateSession(aCreateSessionToken,
+ aPromiseId,
+ aInitDataType,
+ aInitDataTypeSize,
+ aInitData,
+ aInitDataSize,
+ aSessionType);
+ }
+
+ void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ mDecryptorV7->LoadSession(aPromiseId, aSessionId, aSessionIdLength);
+ }
+
+ void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) override
+ {
+ mDecryptorV7->UpdateSession(aPromiseId,
+ aSessionId,
+ aSessionIdLength,
+ aResponse,
+ aResponseSize);
+ }
+
+ void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ mDecryptorV7->CloseSession(aPromiseId, aSessionId, aSessionIdLength);
+ }
+
+ void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ mDecryptorV7->RemoveSession(aPromiseId, aSessionId, aSessionIdLength);
+ }
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) override
+ {
+ mDecryptorV7->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize);
+ }
+
+ void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) override
+ {
+ mDecryptorV7->Decrypt(aBuffer, aMetadata);
+ }
+
+ void DecryptingComplete() override
+ {
+ mDecryptorV7->DecryptingComplete();
+ delete this;
+ }
+private:
+ GMPDecryptor7* mDecryptorV7;
+};
+
+bool
+GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
+{
+ GMPDecryptorChild* child = static_cast<GMPDecryptorChild*>(aActor);
+ GMPDecryptorHost* host = static_cast<GMPDecryptorHost*>(child);
+
+ void* ptr = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, host, &ptr, aActor->Id());
+ GMPDecryptor* decryptor = nullptr;
+ if (GMP_SUCCEEDED(err) && ptr) {
+ decryptor = static_cast<GMPDecryptor*>(ptr);
+ } else if (err != GMPNoErr) {
+ // We Adapt the previous GMPDecryptor version to the current, so that
+ // Gecko thinks it's only talking to the current version. v7 differs
+ // from v9 in its Init() function arguments, and v9 has extra enumeration
+ // members at the end of the key status enumerations.
+ err = mGMPChild->GetAPI(GMP_API_DECRYPTOR_BACKWARDS_COMPAT, host, &ptr);
+ if (err != GMPNoErr || !ptr) {
+ return false;
+ }
+ decryptor = new GMPDecryptor7BackwardsCompat(static_cast<GMPDecryptor7*>(ptr));
+ }
+
+ child->Init(decryptor);
+
+ return true;
+}
+
+bool
+GMPContentChild::RecvPGMPAudioDecoderConstructor(PGMPAudioDecoderChild* aActor)
+{
+ auto vdc = static_cast<GMPAudioDecoderChild*>(aActor);
+
+ void* vd = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_AUDIO_DECODER, &vdc->Host(), &vd);
+ if (err != GMPNoErr || !vd) {
+ return false;
+ }
+
+ vdc->Init(static_cast<GMPAudioDecoder*>(vd));
+
+ return true;
+}
+
+bool
+GMPContentChild::RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor,
+ const uint32_t& aDecryptorId)
+{
+ auto vdc = static_cast<GMPVideoDecoderChild*>(aActor);
+
+ void* vd = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_VIDEO_DECODER, &vdc->Host(), &vd, aDecryptorId);
+ if (err != GMPNoErr || !vd) {
+ NS_WARNING("GMPGetAPI call failed trying to construct decoder.");
+ return false;
+ }
+
+ vdc->Init(static_cast<GMPVideoDecoder*>(vd));
+
+ return true;
+}
+
+bool
+GMPContentChild::RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor)
+{
+ auto vec = static_cast<GMPVideoEncoderChild*>(aActor);
+
+ void* ve = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_VIDEO_ENCODER, &vec->Host(), &ve);
+ if (err != GMPNoErr || !ve) {
+ NS_WARNING("GMPGetAPI call failed trying to construct encoder.");
+ return false;
+ }
+
+ vec->Init(static_cast<GMPVideoEncoder*>(ve));
+
+ return true;
+}
+
+void
+GMPContentChild::CloseActive()
+{
+ // Invalidate and remove any remaining API objects.
+ const ManagedContainer<PGMPAudioDecoderChild>& audioDecoders =
+ ManagedPGMPAudioDecoderChild();
+ for (auto iter = audioDecoders.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->SendShutdown();
+ }
+
+ const ManagedContainer<PGMPDecryptorChild>& decryptors =
+ ManagedPGMPDecryptorChild();
+ for (auto iter = decryptors.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->SendShutdown();
+ }
+
+ const ManagedContainer<PGMPVideoDecoderChild>& videoDecoders =
+ ManagedPGMPVideoDecoderChild();
+ for (auto iter = videoDecoders.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->SendShutdown();
+ }
+
+ const ManagedContainer<PGMPVideoEncoderChild>& videoEncoders =
+ ManagedPGMPVideoEncoderChild();
+ for (auto iter = videoEncoders.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->SendShutdown();
+ }
+}
+
+bool
+GMPContentChild::IsUsed()
+{
+ return !ManagedPGMPAudioDecoderChild().IsEmpty() ||
+ !ManagedPGMPDecryptorChild().IsEmpty() ||
+ !ManagedPGMPVideoDecoderChild().IsEmpty() ||
+ !ManagedPGMPVideoEncoderChild().IsEmpty();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPContentChild.h b/dom/media/gmp/GMPContentChild.h
new file mode 100644
index 000000000..714207608
--- /dev/null
+++ b/dom/media/gmp/GMPContentChild.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPContentChild_h_
+#define GMPContentChild_h_
+
+#include "mozilla/gmp/PGMPContentChild.h"
+#include "GMPSharedMemManager.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+
+class GMPContentChild : public PGMPContentChild
+ , public GMPSharedMem
+{
+public:
+ explicit GMPContentChild(GMPChild* aChild);
+ virtual ~GMPContentChild();
+
+ MessageLoop* GMPMessageLoop();
+
+ bool RecvPGMPAudioDecoderConstructor(PGMPAudioDecoderChild* aActor) override;
+ bool RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor) override;
+ bool RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor, const uint32_t& aDecryptorId) override;
+ bool RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor) override;
+
+ PGMPAudioDecoderChild* AllocPGMPAudioDecoderChild() override;
+ bool DeallocPGMPAudioDecoderChild(PGMPAudioDecoderChild* aActor) override;
+
+ PGMPDecryptorChild* AllocPGMPDecryptorChild() override;
+ bool DeallocPGMPDecryptorChild(PGMPDecryptorChild* aActor) override;
+
+ PGMPVideoDecoderChild* AllocPGMPVideoDecoderChild(const uint32_t& aDecryptorId) override;
+ bool DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor) override;
+
+ PGMPVideoEncoderChild* AllocPGMPVideoEncoderChild() override;
+ bool DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor) override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void ProcessingError(Result aCode, const char* aReason) override;
+
+ // GMPSharedMem
+ void CheckThread() override;
+
+ void CloseActive();
+ bool IsUsed();
+
+ GMPChild* mGMPChild;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPContentChild_h_
diff --git a/dom/media/gmp/GMPContentParent.cpp b/dom/media/gmp/GMPContentParent.cpp
new file mode 100644
index 000000000..12f6f4c48
--- /dev/null
+++ b/dom/media/gmp/GMPContentParent.cpp
@@ -0,0 +1,298 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPContentParent.h"
+#include "GMPAudioDecoderParent.h"
+#include "GMPDecryptorParent.h"
+#include "GMPParent.h"
+#include "GMPServiceChild.h"
+#include "GMPVideoDecoderParent.h"
+#include "GMPVideoEncoderParent.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Unused.h"
+#include "base/task.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPContentParent"
+
+namespace gmp {
+
+GMPContentParent::GMPContentParent(GMPParent* aParent)
+ : mParent(aParent)
+{
+ if (mParent) {
+ SetDisplayName(mParent->GetDisplayName());
+ SetPluginId(mParent->GetPluginId());
+ }
+}
+
+GMPContentParent::~GMPContentParent()
+{
+}
+
+class ReleaseGMPContentParent : public Runnable
+{
+public:
+ explicit ReleaseGMPContentParent(GMPContentParent* aToRelease)
+ : mToRelease(aToRelease)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ return NS_OK;
+ }
+
+private:
+ RefPtr<GMPContentParent> mToRelease;
+};
+
+void
+GMPContentParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ MOZ_ASSERT(mAudioDecoders.IsEmpty() &&
+ mDecryptors.IsEmpty() &&
+ mVideoDecoders.IsEmpty() &&
+ mVideoEncoders.IsEmpty());
+ NS_DispatchToCurrentThread(new ReleaseGMPContentParent(this));
+}
+
+void
+GMPContentParent::CheckThread()
+{
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+}
+
+void
+GMPContentParent::AudioDecoderDestroyed(GMPAudioDecoderParent* aDecoder)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ MOZ_ALWAYS_TRUE(mAudioDecoders.RemoveElement(aDecoder));
+ CloseIfUnused();
+}
+
+void
+GMPContentParent::VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ // If the constructor fails, we'll get called before it's added
+ Unused << NS_WARN_IF(!mVideoDecoders.RemoveElement(aDecoder));
+ CloseIfUnused();
+}
+
+void
+GMPContentParent::VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ // If the constructor fails, we'll get called before it's added
+ Unused << NS_WARN_IF(!mVideoEncoders.RemoveElement(aEncoder));
+ CloseIfUnused();
+}
+
+void
+GMPContentParent::DecryptorDestroyed(GMPDecryptorParent* aSession)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ MOZ_ALWAYS_TRUE(mDecryptors.RemoveElement(aSession));
+ CloseIfUnused();
+}
+
+void
+GMPContentParent::CloseIfUnused()
+{
+ if (mAudioDecoders.IsEmpty() &&
+ mDecryptors.IsEmpty() &&
+ mVideoDecoders.IsEmpty() &&
+ mVideoEncoders.IsEmpty()) {
+ RefPtr<GMPContentParent> toClose;
+ if (mParent) {
+ toClose = mParent->ForgetGMPContentParent();
+ } else {
+ toClose = this;
+ RefPtr<GeckoMediaPluginServiceChild> gmp(
+ GeckoMediaPluginServiceChild::GetSingleton());
+ gmp->RemoveGMPContentParent(toClose);
+ }
+ NS_DispatchToCurrentThread(NewRunnableMethod(toClose,
+ &GMPContentParent::Close));
+ }
+}
+
+nsresult
+GMPContentParent::GetGMPDecryptor(GMPDecryptorParent** aGMPDP)
+{
+ PGMPDecryptorParent* pdp = SendPGMPDecryptorConstructor();
+ if (!pdp) {
+ return NS_ERROR_FAILURE;
+ }
+ GMPDecryptorParent* dp = static_cast<GMPDecryptorParent*>(pdp);
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ NS_ADDREF(dp);
+ mDecryptors.AppendElement(dp);
+ *aGMPDP = dp;
+
+ return NS_OK;
+}
+
+nsIThread*
+GMPContentParent::GMPThread()
+{
+ if (!mGMPThread) {
+ nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mps);
+ if (!mps) {
+ return nullptr;
+ }
+ // Not really safe if we just grab to the mGMPThread, as we don't know
+ // what thread we're running on and other threads may be trying to
+ // access this without locks! However, debug only, and primary failure
+ // mode outside of compiler-helped TSAN is a leak. But better would be
+ // to use swap() under a lock.
+ mps->GetThread(getter_AddRefs(mGMPThread));
+ MOZ_ASSERT(mGMPThread);
+ }
+
+ return mGMPThread;
+}
+
+nsresult
+GMPContentParent::GetGMPAudioDecoder(GMPAudioDecoderParent** aGMPAD)
+{
+ PGMPAudioDecoderParent* pvap = SendPGMPAudioDecoderConstructor();
+ if (!pvap) {
+ return NS_ERROR_FAILURE;
+ }
+ GMPAudioDecoderParent* vap = static_cast<GMPAudioDecoderParent*>(pvap);
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ NS_ADDREF(vap);
+ *aGMPAD = vap;
+ mAudioDecoders.AppendElement(vap);
+
+ return NS_OK;
+}
+
+nsresult
+GMPContentParent::GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD,
+ uint32_t aDecryptorId)
+{
+ // returned with one anonymous AddRef that locks it until Destroy
+ PGMPVideoDecoderParent* pvdp = SendPGMPVideoDecoderConstructor(aDecryptorId);
+ if (!pvdp) {
+ return NS_ERROR_FAILURE;
+ }
+ GMPVideoDecoderParent *vdp = static_cast<GMPVideoDecoderParent*>(pvdp);
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ NS_ADDREF(vdp);
+ *aGMPVD = vdp;
+ mVideoDecoders.AppendElement(vdp);
+
+ return NS_OK;
+}
+
+nsresult
+GMPContentParent::GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE)
+{
+ // returned with one anonymous AddRef that locks it until Destroy
+ PGMPVideoEncoderParent* pvep = SendPGMPVideoEncoderConstructor();
+ if (!pvep) {
+ return NS_ERROR_FAILURE;
+ }
+ GMPVideoEncoderParent *vep = static_cast<GMPVideoEncoderParent*>(pvep);
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ NS_ADDREF(vep);
+ *aGMPVE = vep;
+ mVideoEncoders.AppendElement(vep);
+
+ return NS_OK;
+}
+
+PGMPVideoDecoderParent*
+GMPContentParent::AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId)
+{
+ GMPVideoDecoderParent* vdp = new GMPVideoDecoderParent(this);
+ NS_ADDREF(vdp);
+ return vdp;
+}
+
+bool
+GMPContentParent::DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor)
+{
+ GMPVideoDecoderParent* vdp = static_cast<GMPVideoDecoderParent*>(aActor);
+ NS_RELEASE(vdp);
+ return true;
+}
+
+PGMPVideoEncoderParent*
+GMPContentParent::AllocPGMPVideoEncoderParent()
+{
+ GMPVideoEncoderParent* vep = new GMPVideoEncoderParent(this);
+ NS_ADDREF(vep);
+ return vep;
+}
+
+bool
+GMPContentParent::DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor)
+{
+ GMPVideoEncoderParent* vep = static_cast<GMPVideoEncoderParent*>(aActor);
+ NS_RELEASE(vep);
+ return true;
+}
+
+PGMPDecryptorParent*
+GMPContentParent::AllocPGMPDecryptorParent()
+{
+ GMPDecryptorParent* ksp = new GMPDecryptorParent(this);
+ NS_ADDREF(ksp);
+ return ksp;
+}
+
+bool
+GMPContentParent::DeallocPGMPDecryptorParent(PGMPDecryptorParent* aActor)
+{
+ GMPDecryptorParent* ksp = static_cast<GMPDecryptorParent*>(aActor);
+ NS_RELEASE(ksp);
+ return true;
+}
+
+PGMPAudioDecoderParent*
+GMPContentParent::AllocPGMPAudioDecoderParent()
+{
+ GMPAudioDecoderParent* vdp = new GMPAudioDecoderParent(this);
+ NS_ADDREF(vdp);
+ return vdp;
+}
+
+bool
+GMPContentParent::DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor)
+{
+ GMPAudioDecoderParent* vdp = static_cast<GMPAudioDecoderParent*>(aActor);
+ NS_RELEASE(vdp);
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPContentParent.h b/dom/media/gmp/GMPContentParent.h
new file mode 100644
index 000000000..81f79bc73
--- /dev/null
+++ b/dom/media/gmp/GMPContentParent.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPContentParent_h_
+#define GMPContentParent_h_
+
+#include "mozilla/gmp/PGMPContentParent.h"
+#include "GMPSharedMemManager.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPAudioDecoderParent;
+class GMPDecryptorParent;
+class GMPParent;
+class GMPVideoDecoderParent;
+class GMPVideoEncoderParent;
+
+class GMPContentParent final : public PGMPContentParent,
+ public GMPSharedMem
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPContentParent)
+
+ explicit GMPContentParent(GMPParent* aParent = nullptr);
+
+ nsresult GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD,
+ uint32_t aDecryptorId);
+ void VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder);
+
+ nsresult GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE);
+ void VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder);
+
+ nsresult GetGMPDecryptor(GMPDecryptorParent** aGMPKS);
+ void DecryptorDestroyed(GMPDecryptorParent* aSession);
+
+ nsresult GetGMPAudioDecoder(GMPAudioDecoderParent** aGMPAD);
+ void AudioDecoderDestroyed(GMPAudioDecoderParent* aDecoder);
+
+ nsIThread* GMPThread();
+
+ // GMPSharedMem
+ void CheckThread() override;
+
+ void SetDisplayName(const nsCString& aDisplayName)
+ {
+ mDisplayName = aDisplayName;
+ }
+ const nsCString& GetDisplayName()
+ {
+ return mDisplayName;
+ }
+ void SetPluginId(const uint32_t aPluginId)
+ {
+ mPluginId = aPluginId;
+ }
+ uint32_t GetPluginId() const
+ {
+ return mPluginId;
+ }
+
+private:
+ ~GMPContentParent();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ PGMPVideoDecoderParent* AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId) override;
+ bool DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor) override;
+
+ PGMPVideoEncoderParent* AllocPGMPVideoEncoderParent() override;
+ bool DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor) override;
+
+ PGMPDecryptorParent* AllocPGMPDecryptorParent() override;
+ bool DeallocPGMPDecryptorParent(PGMPDecryptorParent* aActor) override;
+
+ PGMPAudioDecoderParent* AllocPGMPAudioDecoderParent() override;
+ bool DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor) override;
+
+ void CloseIfUnused();
+ // Needed because NewRunnableMethod tried to use the class that the method
+ // lives on to store the receiver, but PGMPContentParent isn't refcounted.
+ void Close()
+ {
+ PGMPContentParent::Close();
+ }
+
+ nsTArray<RefPtr<GMPVideoDecoderParent>> mVideoDecoders;
+ nsTArray<RefPtr<GMPVideoEncoderParent>> mVideoEncoders;
+ nsTArray<RefPtr<GMPDecryptorParent>> mDecryptors;
+ nsTArray<RefPtr<GMPAudioDecoderParent>> mAudioDecoders;
+ nsCOMPtr<nsIThread> mGMPThread;
+ RefPtr<GMPParent> mParent;
+ nsCString mDisplayName;
+ uint32_t mPluginId;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPParent_h_
diff --git a/dom/media/gmp/GMPCrashHelperHolder.h b/dom/media/gmp/GMPCrashHelperHolder.h
new file mode 100644
index 000000000..ea6f36c83
--- /dev/null
+++ b/dom/media/gmp/GMPCrashHelperHolder.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPCrashHelperHolder_h_
+#define GMPCrashHelperHolder_h_
+
+#include "GMPService.h"
+#include "mozilla/RefPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+class GMPCrashHelper;
+
+namespace mozilla {
+
+// Disconnecting the GMPCrashHelpers at the right time is hard. We need to
+// ensure that in the crashing case that we stay connected until the
+// "gmp-plugin-crashed" message is processed in the content process.
+//
+// We have two channels connecting to the GMP; PGMP which connects from
+// chrome to GMP process, and PGMPContent, which bridges between the content
+// and GMP process. If the GMP crashes both PGMP and PGMPContent receive
+// ActorDestroy messages and begin to shutdown at the same time.
+//
+// However the crash report mini dump must be generated in the chrome
+// process' ActorDestroy, before the "gmp-plugin-crashed" message can be sent
+// to the content process. We fire the "PluginCrashed" event when we handle
+// the "gmp-plugin-crashed" message in the content process, and we need the
+// crash helpers to do that.
+//
+// The PGMPContent's managed actors' ActorDestroy messages are the only shutdown
+// notification we get in the content process, but we can't disconnect the
+// crash handlers there in the crashing case, as ActorDestroy happens before
+// we've received the "gmp-plugin-crashed" message and had a chance for the
+// crash helpers to generate the window to dispatch PluginCrashed to initiate
+// the crash report submission notification box.
+//
+// So we need to ensure that in the content process, the GMPCrashHelpers stay
+// connected to the GMPService until after ActorDestroy messages are received
+// if there's an abnormal shutdown. In the case where the GMP doesn't crash,
+// we do actually want to disconnect GMPCrashHandlers in ActorDestroy, since
+// we don't have any other signal that a GMP actor is shutting down. If we don't
+// disconnect the crash helper there in the normal shutdown case, the helper
+// will stick around forever and leak.
+//
+// In the crashing case, the GMPCrashHelpers are deallocated when the crash
+// report is processed in GeckoMediaPluginService::RunPluginCrashCallbacks().
+//
+// It's a bit yuck that we have to have two paths for disconnecting the crash
+// helpers, but there aren't really any better options.
+class GMPCrashHelperHolder
+{
+public:
+
+ void SetCrashHelper(GMPCrashHelper* aHelper)
+ {
+ mCrashHelper = aHelper;
+ }
+
+ GMPCrashHelper* GetCrashHelper()
+ {
+ return mCrashHelper;
+ }
+
+ void MaybeDisconnect(bool aAbnormalShutdown)
+ {
+ if (!aAbnormalShutdown) {
+ RefPtr<gmp::GeckoMediaPluginService> service(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ service->DisconnectCrashHelper(GetCrashHelper());
+ }
+ }
+
+private:
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+}
+
+#endif // GMPCrashHelperHolder_h_
diff --git a/dom/media/gmp/GMPDecryptorChild.cpp b/dom/media/gmp/GMPDecryptorChild.cpp
new file mode 100644
index 000000000..6da3c6c43
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorChild.cpp
@@ -0,0 +1,403 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPDecryptorChild.h"
+#include "GMPContentChild.h"
+#include "GMPChild.h"
+#include "base/task.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "runnable_utils.h"
+#include <ctime>
+
+#define ON_GMP_THREAD() (mPlugin->GMPMessageLoop() == MessageLoop::current())
+
+#define CALL_ON_GMP_THREAD(_func, ...) \
+ CallOnGMPThread(&GMPDecryptorChild::_func, __VA_ARGS__)
+
+namespace mozilla {
+namespace gmp {
+
+GMPDecryptorChild::GMPDecryptorChild(GMPContentChild* aPlugin,
+ const nsTArray<uint8_t>& aPluginVoucher,
+ const nsTArray<uint8_t>& aSandboxVoucher)
+ : mSession(nullptr)
+ , mPlugin(aPlugin)
+ , mPluginVoucher(aPluginVoucher)
+ , mSandboxVoucher(aSandboxVoucher)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPDecryptorChild::~GMPDecryptorChild()
+{
+}
+
+template <typename MethodType, typename... ParamType>
+void
+GMPDecryptorChild::CallMethod(MethodType aMethod, ParamType&&... aParams)
+{
+ MOZ_ASSERT(ON_GMP_THREAD());
+ // Don't send IPC messages after tear-down.
+ if (mSession) {
+ (this->*aMethod)(Forward<ParamType>(aParams)...);
+ }
+}
+
+template<typename T>
+struct AddConstReference {
+ typedef const typename RemoveReference<T>::Type& Type;
+};
+
+template<typename MethodType, typename... ParamType>
+void
+GMPDecryptorChild::CallOnGMPThread(MethodType aMethod, ParamType&&... aParams)
+{
+ if (ON_GMP_THREAD()) {
+ // Use forwarding reference when we can.
+ CallMethod(aMethod, Forward<ParamType>(aParams)...);
+ } else {
+ // Use const reference when we have to.
+ auto m = &GMPDecryptorChild::CallMethod<
+ decltype(aMethod), typename AddConstReference<ParamType>::Type...>;
+ RefPtr<mozilla::Runnable> t =
+ dont_add_new_uses_of_this::NewRunnableMethod(this, m, aMethod, Forward<ParamType>(aParams)...);
+ mPlugin->GMPMessageLoop()->PostTask(t.forget());
+ }
+}
+
+void
+GMPDecryptorChild::Init(GMPDecryptor* aSession)
+{
+ MOZ_ASSERT(aSession);
+ mSession = aSession;
+ // The ID of this decryptor is the IPDL actor ID. Note it's unique inside
+ // the child process, but not necessarily across all gecko processes. However,
+ // since GMPDecryptors are segregated by node ID/origin, we shouldn't end up
+ // with clashes in the content process.
+ SendSetDecryptorId(Id());
+}
+
+void
+GMPDecryptorChild::SetSessionId(uint32_t aCreateSessionToken,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CALL_ON_GMP_THREAD(SendSetSessionId,
+ aCreateSessionToken, nsCString(aSessionId, aSessionIdLength));
+}
+
+void
+GMPDecryptorChild::ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess)
+{
+ CALL_ON_GMP_THREAD(SendResolveLoadSessionPromise, aPromiseId, aSuccess);
+}
+
+void
+GMPDecryptorChild::ResolvePromise(uint32_t aPromiseId)
+{
+ CALL_ON_GMP_THREAD(SendResolvePromise, aPromiseId);
+}
+
+void
+GMPDecryptorChild::RejectPromise(uint32_t aPromiseId,
+ GMPDOMException aException,
+ const char* aMessage,
+ uint32_t aMessageLength)
+{
+ CALL_ON_GMP_THREAD(SendRejectPromise,
+ aPromiseId, aException, nsCString(aMessage, aMessageLength));
+}
+
+void
+GMPDecryptorChild::SessionMessage(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPSessionMessageType aMessageType,
+ const uint8_t* aMessage,
+ uint32_t aMessageLength)
+{
+ nsTArray<uint8_t> msg;
+ msg.AppendElements(aMessage, aMessageLength);
+ CALL_ON_GMP_THREAD(SendSessionMessage,
+ nsCString(aSessionId, aSessionIdLength),
+ aMessageType, Move(msg));
+}
+
+void
+GMPDecryptorChild::ExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPTimestamp aExpiryTime)
+{
+ CALL_ON_GMP_THREAD(SendExpirationChange,
+ nsCString(aSessionId, aSessionIdLength), aExpiryTime);
+}
+
+void
+GMPDecryptorChild::SessionClosed(const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CALL_ON_GMP_THREAD(SendSessionClosed,
+ nsCString(aSessionId, aSessionIdLength));
+}
+
+void
+GMPDecryptorChild::SessionError(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPDOMException aException,
+ uint32_t aSystemCode,
+ const char* aMessage,
+ uint32_t aMessageLength)
+{
+ CALL_ON_GMP_THREAD(SendSessionError,
+ nsCString(aSessionId, aSessionIdLength),
+ aException, aSystemCode,
+ nsCString(aMessage, aMessageLength));
+}
+
+void
+GMPDecryptorChild::KeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aKeyId,
+ uint32_t aKeyIdLength,
+ GMPMediaKeyStatus aStatus)
+{
+ AutoTArray<uint8_t, 16> kid;
+ kid.AppendElements(aKeyId, aKeyIdLength);
+
+ nsTArray<GMPKeyInformation> keyInfos;
+ keyInfos.AppendElement(GMPKeyInformation(kid, aStatus));
+ CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
+ nsCString(aSessionId, aSessionIdLength),
+ keyInfos);
+}
+
+void
+GMPDecryptorChild::BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength)
+{
+ nsTArray<GMPKeyInformation> keyInfos;
+ for (uint32_t i = 0; i < aKeyInfosLength; i++) {
+ nsTArray<uint8_t> keyId;
+ keyId.AppendElements(aKeyInfos[i].keyid, aKeyInfos[i].keyid_size);
+ keyInfos.AppendElement(GMPKeyInformation(keyId, aKeyInfos[i].status));
+ }
+ CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
+ nsCString(aSessionId, aSessionIdLength),
+ keyInfos);
+}
+
+void
+GMPDecryptorChild::Decrypted(GMPBuffer* aBuffer, GMPErr aResult)
+{
+ if (!ON_GMP_THREAD()) {
+ // We should run this whole method on the GMP thread since the buffer needs
+ // to be deleted after the SendDecrypted call.
+ mPlugin->GMPMessageLoop()->PostTask(NewRunnableMethod
+ <GMPBuffer*, GMPErr>(this,
+ &GMPDecryptorChild::Decrypted,
+ aBuffer, aResult));
+ return;
+ }
+
+ if (!aBuffer) {
+ NS_WARNING("GMPDecryptorCallback passed bull GMPBuffer");
+ return;
+ }
+
+ auto buffer = static_cast<GMPBufferImpl*>(aBuffer);
+ if (mSession) {
+ SendDecrypted(buffer->mId, aResult, buffer->mData);
+ }
+ delete buffer;
+}
+
+void
+GMPDecryptorChild::SetCapabilities(uint64_t aCaps)
+{
+ // Deprecated.
+}
+
+void
+GMPDecryptorChild::GetSandboxVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength)
+{
+ if (!aVoucher || !aVoucherLength) {
+ return;
+ }
+ *aVoucher = mSandboxVoucher.Elements();
+ *aVoucherLength = mSandboxVoucher.Length();
+}
+
+void
+GMPDecryptorChild::GetPluginVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength)
+{
+ if (!aVoucher || !aVoucherLength) {
+ return;
+ }
+ *aVoucher = mPluginVoucher.Elements();
+ *aVoucherLength = mPluginVoucher.Length();
+}
+
+bool
+GMPDecryptorChild::RecvInit(const bool& aDistinctiveIdentifierRequired,
+ const bool& aPersistentStateRequired)
+{
+ if (!mSession) {
+ return false;
+ }
+ mSession->Init(this, aDistinctiveIdentifierRequired, aPersistentStateRequired);
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvCreateSession(const uint32_t& aCreateSessionToken,
+ const uint32_t& aPromiseId,
+ const nsCString& aInitDataType,
+ InfallibleTArray<uint8_t>&& aInitData,
+ const GMPSessionType& aSessionType)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->CreateSession(aCreateSessionToken,
+ aPromiseId,
+ aInitDataType.get(),
+ aInitDataType.Length(),
+ aInitData.Elements(),
+ aInitData.Length(),
+ aSessionType);
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvLoadSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->LoadSession(aPromiseId,
+ aSessionId.get(),
+ aSessionId.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvUpdateSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId,
+ InfallibleTArray<uint8_t>&& aResponse)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->UpdateSession(aPromiseId,
+ aSessionId.get(),
+ aSessionId.Length(),
+ aResponse.Elements(),
+ aResponse.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvCloseSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->CloseSession(aPromiseId,
+ aSessionId.get(),
+ aSessionId.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvRemoveSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->RemoveSession(aPromiseId,
+ aSessionId.get(),
+ aSessionId.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvSetServerCertificate(const uint32_t& aPromiseId,
+ InfallibleTArray<uint8_t>&& aServerCert)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->SetServerCertificate(aPromiseId,
+ aServerCert.Elements(),
+ aServerCert.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvDecrypt(const uint32_t& aId,
+ InfallibleTArray<uint8_t>&& aBuffer,
+ const GMPDecryptionData& aMetadata)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ // Note: the GMPBufferImpl created here is deleted when the GMP passes
+ // it back in the Decrypted() callback above.
+ GMPBufferImpl* buffer = new GMPBufferImpl(aId, aBuffer);
+
+ // |metadata| lifetime is managed by |buffer|.
+ GMPEncryptedBufferDataImpl* metadata = new GMPEncryptedBufferDataImpl(aMetadata);
+ buffer->SetMetadata(metadata);
+
+ mSession->Decrypt(buffer, metadata);
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvDecryptingComplete()
+{
+ // Reset |mSession| before calling DecryptingComplete(). We should not send
+ // any IPC messages during tear-down.
+ auto session = mSession;
+ mSession = nullptr;
+
+ if (!session) {
+ return false;
+ }
+
+ session->DecryptingComplete();
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+// avoid redefined macro in unified build
+#undef ON_GMP_THREAD
+#undef CALL_ON_GMP_THREAD
diff --git a/dom/media/gmp/GMPDecryptorChild.h b/dom/media/gmp/GMPDecryptorChild.h
new file mode 100644
index 000000000..434da774f
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorChild.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPDecryptorChild_h_
+#define GMPDecryptorChild_h_
+
+#include "mozilla/gmp/PGMPDecryptorChild.h"
+#include "gmp-decryption.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPEncryptedBufferDataImpl.h"
+#include <string>
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPDecryptorChild : public GMPDecryptorCallback
+ , public GMPDecryptorHost
+ , public PGMPDecryptorChild
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPDecryptorChild);
+
+ explicit GMPDecryptorChild(GMPContentChild* aPlugin,
+ const nsTArray<uint8_t>& aPluginVoucher,
+ const nsTArray<uint8_t>& aSandboxVoucher);
+
+ void Init(GMPDecryptor* aSession);
+
+ // GMPDecryptorCallback
+ void SetSessionId(uint32_t aCreateSessionToken,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+ void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) override;
+ void ResolvePromise(uint32_t aPromiseId) override;
+
+ void RejectPromise(uint32_t aPromiseId,
+ GMPDOMException aException,
+ const char* aMessage,
+ uint32_t aMessageLength) override;
+
+ void SessionMessage(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPSessionMessageType aMessageType,
+ const uint8_t* aMessage,
+ uint32_t aMessageLength) override;
+
+ void ExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPTimestamp aExpiryTime) override;
+
+ void SessionClosed(const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ void SessionError(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPDOMException aException,
+ uint32_t aSystemCode,
+ const char* aMessage,
+ uint32_t aMessageLength) override;
+
+ void KeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aKeyId,
+ uint32_t aKeyIdLength,
+ GMPMediaKeyStatus aStatus) override;
+
+ void SetCapabilities(uint64_t aCaps) override;
+
+ void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) override;
+
+ void BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength) override;
+
+ // GMPDecryptorHost
+ void GetSandboxVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength) override;
+
+ void GetPluginVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength) override;
+private:
+ ~GMPDecryptorChild();
+
+ // GMPDecryptorChild
+ bool RecvInit(const bool& aDistinctiveIdentifierRequired,
+ const bool& aPersistentStateRequired) override;
+
+ bool RecvCreateSession(const uint32_t& aCreateSessionToken,
+ const uint32_t& aPromiseId,
+ const nsCString& aInitDataType,
+ InfallibleTArray<uint8_t>&& aInitData,
+ const GMPSessionType& aSessionType) override;
+
+ bool RecvLoadSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId) override;
+
+ bool RecvUpdateSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId,
+ InfallibleTArray<uint8_t>&& aResponse) override;
+
+ bool RecvCloseSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId) override;
+
+ bool RecvRemoveSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId) override;
+
+ bool RecvDecrypt(const uint32_t& aId,
+ InfallibleTArray<uint8_t>&& aBuffer,
+ const GMPDecryptionData& aMetadata) override;
+
+ // Resolve/reject promise on completion.
+ bool RecvSetServerCertificate(const uint32_t& aPromiseId,
+ InfallibleTArray<uint8_t>&& aServerCert) override;
+
+ bool RecvDecryptingComplete() override;
+
+ template <typename MethodType, typename... ParamType>
+ void CallMethod(MethodType, ParamType&&...);
+
+ template<typename MethodType, typename... ParamType>
+ void CallOnGMPThread(MethodType, ParamType&&...);
+
+ // GMP's GMPDecryptor implementation.
+ // Only call into this on the (GMP process) main thread.
+ GMPDecryptor* mSession;
+ GMPContentChild* mPlugin;
+
+ // Reference to the vouchers owned by the GMPChild.
+ const nsTArray<uint8_t>& mPluginVoucher;
+ const nsTArray<uint8_t>& mSandboxVoucher;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPDecryptorChild_h_
diff --git a/dom/media/gmp/GMPDecryptorParent.cpp b/dom/media/gmp/GMPDecryptorParent.cpp
new file mode 100644
index 000000000..56474e973
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -0,0 +1,537 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPDecryptorParent.h"
+
+#include "GMPContentParent.h"
+#include "GMPUtils.h"
+#include "MediaData.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+GMPDecryptorParent::GMPDecryptorParent(GMPContentParent* aPlugin)
+ : mIsOpen(false)
+ , mShuttingDown(false)
+ , mActorDestroyed(false)
+ , mPlugin(aPlugin)
+ , mPluginId(aPlugin->GetPluginId())
+ , mCallback(nullptr)
+#ifdef DEBUG
+ , mGMPThread(aPlugin->GMPThread())
+#endif
+{
+ MOZ_ASSERT(mPlugin && mGMPThread);
+}
+
+GMPDecryptorParent::~GMPDecryptorParent()
+{
+}
+
+bool
+GMPDecryptorParent::RecvSetDecryptorId(const uint32_t& aId)
+{
+#ifdef MOZ_EME
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SetDecryptorId(aId);
+#endif
+ return true;
+}
+
+nsresult
+GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+{
+ LOGD(("GMPDecryptorParent[%p]::Init()", this));
+
+ if (mIsOpen) {
+ NS_WARNING("Trying to re-use an in-use GMP decrypter!");
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aCallback;
+ if (!SendInit(aDistinctiveIdentifierRequired, aPersistentStateRequired)) {
+ return NS_ERROR_FAILURE;
+ }
+ mIsOpen = true;
+ return NS_OK;
+}
+
+void
+GMPDecryptorParent::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const nsCString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData,
+ GMPSessionType aSessionType)
+{
+ LOGD(("GMPDecryptorParent[%p]::CreateSession(token=%u, promiseId=%u, aInitData='%s')",
+ this, aCreateSessionToken, aPromiseId, ToBase64(aInitData).get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aInitDataType.IsEmpty() && !aInitData.IsEmpty());
+ Unused << SendCreateSession(aCreateSessionToken, aPromiseId, aInitDataType, aInitData, aSessionType);
+}
+
+void
+GMPDecryptorParent::LoadSession(uint32_t aPromiseId,
+ const nsCString& aSessionId)
+{
+ LOGD(("GMPDecryptorParent[%p]::LoadSession(sessionId='%s', promiseId=%u)",
+ this, aSessionId.get(), aPromiseId));
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aSessionId.IsEmpty());
+ Unused << SendLoadSession(aPromiseId, aSessionId);
+}
+
+void
+GMPDecryptorParent::UpdateSession(uint32_t aPromiseId,
+ const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aResponse)
+{
+ LOGD(("GMPDecryptorParent[%p]::UpdateSession(sessionId='%s', promiseId=%u response='%s')",
+ this, aSessionId.get(), aPromiseId, ToBase64(aResponse).get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aSessionId.IsEmpty() && !aResponse.IsEmpty());
+ Unused << SendUpdateSession(aPromiseId, aSessionId, aResponse);
+}
+
+void
+GMPDecryptorParent::CloseSession(uint32_t aPromiseId,
+ const nsCString& aSessionId)
+{
+ LOGD(("GMPDecryptorParent[%p]::CloseSession(sessionId='%s', promiseId=%u)",
+ this, aSessionId.get(), aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aSessionId.IsEmpty());
+ Unused << SendCloseSession(aPromiseId, aSessionId);
+}
+
+void
+GMPDecryptorParent::RemoveSession(uint32_t aPromiseId,
+ const nsCString& aSessionId)
+{
+ LOGD(("GMPDecryptorParent[%p]::RemoveSession(sessionId='%s', promiseId=%u)",
+ this, aSessionId.get(), aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aSessionId.IsEmpty());
+ Unused << SendRemoveSession(aPromiseId, aSessionId);
+}
+
+void
+GMPDecryptorParent::SetServerCertificate(uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aServerCert)
+{
+ LOGD(("GMPDecryptorParent[%p]::SetServerCertificate(promiseId=%u)",
+ this, aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aServerCert.IsEmpty());
+ Unused << SendSetServerCertificate(aPromiseId, aServerCert);
+}
+
+void
+GMPDecryptorParent::Decrypt(uint32_t aId,
+ const CryptoSample& aCrypto,
+ const nsTArray<uint8_t>& aBuffer)
+{
+ LOGV(("GMPDecryptorParent[%p]::Decrypt(id=%d)", this, aId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+
+ // Caller should ensure parameters passed in are valid.
+ MOZ_ASSERT(!aBuffer.IsEmpty());
+
+ if (aCrypto.mValid) {
+ GMPDecryptionData data(aCrypto.mKeyId,
+ aCrypto.mIV,
+ aCrypto.mPlainSizes,
+ aCrypto.mEncryptedSizes,
+ aCrypto.mSessionIds);
+
+ Unused << SendDecrypt(aId, aBuffer, data);
+ } else {
+ GMPDecryptionData data;
+ Unused << SendDecrypt(aId, aBuffer, data);
+ }
+}
+
+bool
+GMPDecryptorParent::RecvSetSessionId(const uint32_t& aCreateSessionId,
+ const nsCString& aSessionId)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvSetSessionId(token=%u, sessionId='%s')",
+ this, aCreateSessionId, aSessionId.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SetSessionId(aCreateSessionId, aSessionId);
+#endif
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
+ const bool& aSuccess)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvResolveLoadSessionPromise(promiseId=%u)",
+ this, aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ResolveLoadSessionPromise(aPromiseId, aSuccess);
+#endif
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvResolvePromise(const uint32_t& aPromiseId)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvResolvePromise(promiseId=%u)",
+ this, aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ResolvePromise(aPromiseId);
+#endif
+ return true;
+}
+
+nsresult
+GMPExToNsresult(GMPDOMException aDomException) {
+ switch (aDomException) {
+ case kGMPNoModificationAllowedError: return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+ case kGMPNotFoundError: return NS_ERROR_DOM_NOT_FOUND_ERR;
+ case kGMPNotSupportedError: return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ case kGMPInvalidStateError: return NS_ERROR_DOM_INVALID_STATE_ERR;
+ case kGMPSyntaxError: return NS_ERROR_DOM_SYNTAX_ERR;
+ case kGMPInvalidModificationError: return NS_ERROR_DOM_INVALID_MODIFICATION_ERR;
+ case kGMPInvalidAccessError: return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ case kGMPSecurityError: return NS_ERROR_DOM_SECURITY_ERR;
+ case kGMPAbortError: return NS_ERROR_DOM_ABORT_ERR;
+ case kGMPQuotaExceededError: return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
+ case kGMPTimeoutError: return NS_ERROR_DOM_TIMEOUT_ERR;
+ case kGMPTypeError: return NS_ERROR_DOM_TYPE_ERR;
+ default: return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+}
+
+bool
+GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId,
+ const GMPDOMException& aException,
+ const nsCString& aMessage)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvRejectPromise(promiseId=%u, exception=%d, msg='%s')",
+ this, aPromiseId, aException, aMessage.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->RejectPromise(aPromiseId, GMPExToNsresult(aException), aMessage);
+#endif
+ return true;
+}
+
+#ifdef MOZ_EME
+static dom::MediaKeyMessageType
+ToMediaKeyMessageType(GMPSessionMessageType aMessageType) {
+ switch (aMessageType) {
+ case kGMPLicenseRequest: return dom::MediaKeyMessageType::License_request;
+ case kGMPLicenseRenewal: return dom::MediaKeyMessageType::License_renewal;
+ case kGMPLicenseRelease: return dom::MediaKeyMessageType::License_release;
+ case kGMPIndividualizationRequest: return dom::MediaKeyMessageType::Individualization_request;
+ default: return dom::MediaKeyMessageType::License_request;
+ };
+};
+#endif
+
+bool
+GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId,
+ const GMPSessionMessageType& aMessageType,
+ nsTArray<uint8_t>&& aMessage)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionMessage(sessionId='%s', type=%d, msg='%s')",
+ this, aSessionId.get(), aMessageType, ToBase64(aMessage).get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionMessage(aSessionId, ToMediaKeyMessageType(aMessageType), aMessage);
+#endif
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId,
+ const double& aExpiryTime)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvExpirationChange(sessionId='%s', expiry=%lf)",
+ this, aSessionId.get(), aExpiryTime));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ExpirationChange(aSessionId, aExpiryTime);
+#endif
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvSessionClosed(const nsCString& aSessionId)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionClosed(sessionId='%s')",
+ this, aSessionId.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionClosed(aSessionId);
+#endif
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId,
+ const GMPDOMException& aException,
+ const uint32_t& aSystemCode,
+ const nsCString& aMessage)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionError(sessionId='%s', exception=%d, sysCode=%d, msg='%s')",
+ this, aSessionId.get(),
+ aException, aSystemCode, aMessage.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionError(aSessionId,
+ GMPExToNsresult(aException),
+ aSystemCode,
+ aMessage);
+#endif
+ return true;
+}
+
+#ifdef MOZ_EME
+static dom::MediaKeyStatus
+ToMediaKeyStatus(GMPMediaKeyStatus aStatus) {
+ switch (aStatus) {
+ case kGMPUsable: return dom::MediaKeyStatus::Usable;
+ case kGMPExpired: return dom::MediaKeyStatus::Expired;
+ case kGMPOutputDownscaled: return dom::MediaKeyStatus::Output_downscaled;
+ case kGMPOutputRestricted: return dom::MediaKeyStatus::Output_restricted;
+ case kGMPInternalError: return dom::MediaKeyStatus::Internal_error;
+ case kGMPReleased: return dom::MediaKeyStatus::Released;
+ case kGMPStatusPending: return dom::MediaKeyStatus::Status_pending;
+ default: return dom::MediaKeyStatus::Internal_error;
+ }
+}
+#endif
+
+bool
+GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+ InfallibleTArray<GMPKeyInformation>&& aKeyInfos)
+{
+#ifdef MOZ_EME
+ LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(sessionId='%s', KeyInfos len='%d')",
+ this, aSessionId.get(), aKeyInfos.Length()));
+
+ if (mIsOpen) {
+ nsTArray<CDMKeyInfo> cdmKeyInfos(aKeyInfos.Length());
+ for (uint32_t i = 0; i < aKeyInfos.Length(); i++) {
+ LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(keyId=%s, gmp-status=%d)",
+ this, ToBase64(aKeyInfos[i].keyId()).get(), aKeyInfos[i].status()));
+ // If the status is kGMPUnknown, we're going to forget(remove) that key info.
+ if (aKeyInfos[i].status() != kGMPUnknown) {
+ auto status = ToMediaKeyStatus(aKeyInfos[i].status());
+ cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId(),
+ dom::Optional<dom::MediaKeyStatus>(status)));
+ } else {
+ cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId()));
+ }
+ }
+ mCallback->BatchedKeyStatusChanged(aSessionId, cdmKeyInfos);
+ }
+#endif
+ return true;
+}
+
+#ifdef MOZ_EME
+DecryptStatus
+ToDecryptStatus(GMPErr aError)
+{
+ switch (aError) {
+ case GMPNoErr: return Ok;
+ case GMPNoKeyErr: return NoKeyErr;
+ case GMPAbortedErr: return AbortedErr;
+ default: return GenericErr;
+ }
+}
+#endif
+
+bool
+GMPDecryptorParent::RecvDecrypted(const uint32_t& aId,
+ const GMPErr& aErr,
+ InfallibleTArray<uint8_t>&& aBuffer)
+{
+#ifdef MOZ_EME
+ LOGV(("GMPDecryptorParent[%p]::RecvDecrypted(id=%d, err=%d)",
+ this, aId, aErr));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->Decrypted(aId, ToDecryptStatus(aErr), aBuffer);
+#endif
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvShutdown()
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvShutdown()", this));
+
+ Shutdown();
+ return true;
+}
+
+// Note: may be called via Terminated()
+void
+GMPDecryptorParent::Close()
+{
+ LOGD(("GMPDecryptorParent[%p]::Close()", this));
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ // Consumer is done with us; we can shut down. No more callbacks should
+ // be made to mCallback. Note: do this before Shutdown()!
+ mCallback = nullptr;
+ // Let Shutdown mark us as dead so it knows if we had been alive
+
+ // In case this is the last reference
+ RefPtr<GMPDecryptorParent> kungfudeathgrip(this);
+ this->Release();
+ Shutdown();
+}
+
+void
+GMPDecryptorParent::Shutdown()
+{
+ LOGD(("GMPDecryptorParent[%p]::Shutdown()", this));
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ if (mShuttingDown) {
+ return;
+ }
+ mShuttingDown = true;
+
+ // Notify client we're gone! Won't occur after Close()
+ if (mCallback) {
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+
+ mIsOpen = false;
+ if (!mActorDestroyed) {
+ Unused << SendDecryptingComplete();
+ }
+}
+
+// Note: Keep this sync'd up with Shutdown
+void
+GMPDecryptorParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("GMPDecryptorParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
+
+ mIsOpen = false;
+ mActorDestroyed = true;
+ if (mCallback) {
+ // May call Close() (and Shutdown()) immediately or with a delay
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+ if (mPlugin) {
+ mPlugin->DecryptorDestroyed(this);
+ mPlugin = nullptr;
+ }
+ MaybeDisconnect(aWhy == AbnormalShutdown);
+}
+
+bool
+GMPDecryptorParent::Recv__delete__()
+{
+ LOGD(("GMPDecryptorParent[%p]::Recv__delete__()", this));
+
+ if (mPlugin) {
+ mPlugin->DecryptorDestroyed(this);
+ mPlugin = nullptr;
+ }
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPDecryptorParent.h b/dom/media/gmp/GMPDecryptorParent.h
new file mode 100644
index 000000000..30ff24690
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPDecryptorParent_h_
+#define GMPDecryptorParent_h_
+
+#include "mozilla/gmp/PGMPDecryptorParent.h"
+#include "mozilla/RefPtr.h"
+#include "gmp-decryption.h"
+#include "GMPDecryptorProxy.h"
+#include "GMPCrashHelperHolder.h"
+
+namespace mozilla {
+
+class CryptoSample;
+
+namespace gmp {
+
+class GMPContentParent;
+
+class GMPDecryptorParent final : public GMPDecryptorProxy
+ , public PGMPDecryptorParent
+ , public GMPCrashHelperHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPDecryptorParent)
+
+ explicit GMPDecryptorParent(GMPContentParent *aPlugin);
+
+ // GMPDecryptorProxy
+
+ uint32_t GetPluginId() const override { return mPluginId; }
+
+ nsresult Init(GMPDecryptorProxyCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const nsCString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData,
+ GMPSessionType aSessionType) override;
+
+ void LoadSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) override;
+
+ void UpdateSession(uint32_t aPromiseId,
+ const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aResponse) override;
+
+ void CloseSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) override;
+
+ void RemoveSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) override;
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aServerCert) override;
+
+ void Decrypt(uint32_t aId,
+ const CryptoSample& aCrypto,
+ const nsTArray<uint8_t>& aBuffer) override;
+
+ void Close() override;
+
+ void Shutdown();
+
+private:
+ ~GMPDecryptorParent();
+
+ // PGMPDecryptorParent
+
+ bool RecvSetDecryptorId(const uint32_t& aId) override;
+
+ bool RecvSetSessionId(const uint32_t& aCreateSessionToken,
+ const nsCString& aSessionId) override;
+
+ bool RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
+ const bool& aSuccess) override;
+
+ bool RecvResolvePromise(const uint32_t& aPromiseId) override;
+
+ bool RecvRejectPromise(const uint32_t& aPromiseId,
+ const GMPDOMException& aException,
+ const nsCString& aMessage) override;
+
+ bool RecvSessionMessage(const nsCString& aSessionId,
+ const GMPSessionMessageType& aMessageType,
+ nsTArray<uint8_t>&& aMessage) override;
+
+ bool RecvExpirationChange(const nsCString& aSessionId,
+ const double& aExpiryTime) override;
+
+ bool RecvSessionClosed(const nsCString& aSessionId) override;
+
+ bool RecvSessionError(const nsCString& aSessionId,
+ const GMPDOMException& aException,
+ const uint32_t& aSystemCode,
+ const nsCString& aMessage) override;
+
+ bool RecvDecrypted(const uint32_t& aId,
+ const GMPErr& aErr,
+ InfallibleTArray<uint8_t>&& aBuffer) override;
+
+ bool RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+ InfallibleTArray<GMPKeyInformation>&& aKeyInfos) override;
+
+ bool RecvShutdown() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool Recv__delete__() override;
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ RefPtr<GMPContentParent> mPlugin;
+ uint32_t mPluginId;
+ GMPDecryptorProxyCallback* mCallback;
+#ifdef DEBUG
+ nsIThread* const mGMPThread;
+#endif
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPDecryptorChild_h_
diff --git a/dom/media/gmp/GMPDecryptorProxy.h b/dom/media/gmp/GMPDecryptorProxy.h
new file mode 100644
index 000000000..ed16755f8
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorProxy.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPDecryptorProxy_h_
+#define GMPDecryptorProxy_h_
+
+#ifdef MOZ_EME
+#include "mozilla/DecryptorProxyCallback.h"
+#endif
+#include "GMPCallbackBase.h"
+#include "gmp-decryption.h"
+#include "nsString.h"
+
+namespace mozilla {
+class CryptoSample;
+} // namespace mozilla
+
+#ifdef MOZ_EME
+class GMPDecryptorProxyCallback : public DecryptorProxyCallback,
+ public GMPCallbackBase {
+#else
+class GMPDecryptorProxyCallback : public GMPCallbackBase {
+#endif
+
+public:
+ virtual ~GMPDecryptorProxyCallback() {}
+};
+
+class GMPDecryptorProxy {
+public:
+ ~GMPDecryptorProxy() {}
+
+ virtual uint32_t GetPluginId() const = 0;
+
+ virtual nsresult Init(GMPDecryptorProxyCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) = 0;
+
+ virtual void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const nsCString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData,
+ GMPSessionType aSessionType) = 0;
+
+ virtual void LoadSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void UpdateSession(uint32_t aPromiseId,
+ const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aResponse) = 0;
+
+ virtual void CloseSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void RemoveSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void SetServerCertificate(uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aServerCert) = 0;
+
+ virtual void Decrypt(uint32_t aId,
+ const mozilla::CryptoSample& aCrypto,
+ const nsTArray<uint8_t>& aBuffer) = 0;
+
+ virtual void Close() = 0;
+};
+
+#endif // GMPDecryptorProxy_h_
diff --git a/dom/media/gmp/GMPDiskStorage.cpp b/dom/media/gmp/GMPDiskStorage.cpp
new file mode 100644
index 000000000..11f49c8fe
--- /dev/null
+++ b/dom/media/gmp/GMPDiskStorage.cpp
@@ -0,0 +1,499 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "plhash.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "GMPParent.h"
+#include "gmp-storage.h"
+#include "mozilla/Unused.h"
+#include "mozilla/EndianUtils.h"
+#include "nsClassHashtable.h"
+#include "prio.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+// We store the records for a given GMP as files in the profile dir.
+// $profileDir/gmp/$platform/$gmpName/storage/$nodeId/
+static nsresult
+GetGMPStorageDir(nsIFile** aTempDir,
+ const nsString& aGMPName,
+ const nsCString& aNodeId)
+{
+ if (NS_WARN_IF(!aTempDir)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<mozIGeckoMediaPluginChromeService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (NS_WARN_IF(!mps)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = mps->GetStorageDir(getter_AddRefs(tmpFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->Append(aGMPName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->AppendNative(NS_LITERAL_CSTRING("storage"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->AppendNative(aNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ tmpFile.forget(aTempDir);
+
+ return NS_OK;
+}
+
+// Disk-backed GMP storage. Records are stored in files on disk in
+// the profile directory. The record name is a hash of the filename,
+// and we resolve hash collisions by just adding 1 to the hash code.
+// The format of records on disk is:
+// 4 byte, uint32_t $recordNameLength, in little-endian byte order,
+// record name (i.e. $recordNameLength bytes, no null terminator)
+// record bytes (entire remainder of file)
+class GMPDiskStorage : public GMPStorage {
+public:
+ explicit GMPDiskStorage(const nsCString& aNodeId,
+ const nsString& aGMPName)
+ : mNodeId(aNodeId)
+ , mGMPName(aGMPName)
+ {
+ }
+
+ ~GMPDiskStorage() {
+ // Close all open file handles.
+ for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
+ Record* record = iter.UserData();
+ if (record->mFileDesc) {
+ PR_Close(record->mFileDesc);
+ record->mFileDesc = nullptr;
+ }
+ }
+ }
+
+ nsresult Init() {
+ // Build our index of records on disk.
+ nsCOMPtr<nsIFile> storageDir;
+ nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DirectoryEnumerator iter(storageDir, DirectoryEnumerator::FilesAndDirs);
+ for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+ PRFileDesc* fd = nullptr;
+ if (NS_FAILED(dirEntry->OpenNSPRFileDesc(PR_RDONLY, 0, &fd))) {
+ continue;
+ }
+ int32_t recordLength = 0;
+ nsCString recordName;
+ nsresult err = ReadRecordMetadata(fd, recordLength, recordName);
+ PR_Close(fd);
+ if (NS_FAILED(err)) {
+ // File is not a valid storage file. Don't index it. Delete the file,
+ // to make our indexing faster in future.
+ dirEntry->Remove(false);
+ continue;
+ }
+
+ nsAutoString filename;
+ rv = dirEntry->GetLeafName(filename);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ mRecords.Put(recordName, new Record(filename, recordName));
+ }
+
+ return NS_OK;
+ }
+
+ GMPErr Open(const nsCString& aRecordName) override
+ {
+ MOZ_ASSERT(!IsOpen(aRecordName));
+ nsresult rv;
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ // New file.
+ nsAutoString filename;
+ rv = GetUnusedFilename(aRecordName, filename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GMPGenericErr;
+ }
+ record = new Record(filename, aRecordName);
+ mRecords.Put(aRecordName, record);
+ }
+
+ MOZ_ASSERT(record);
+ if (record->mFileDesc) {
+ NS_WARNING("Tried to open already open record");
+ return GMPRecordInUse;
+ }
+
+ rv = OpenStorageFile(record->mFilename, ReadWrite, &record->mFileDesc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(IsOpen(aRecordName));
+
+ return GMPNoErr;
+ }
+
+ bool IsOpen(const nsCString& aRecordName) const override {
+ // We are open if we have a record indexed, and it has a valid
+ // file descriptor.
+ const Record* record = mRecords.Get(aRecordName);
+ return record && !!record->mFileDesc;
+ }
+
+ GMPErr Read(const nsCString& aRecordName,
+ nsTArray<uint8_t>& aOutBytes) override
+ {
+ if (!IsOpen(aRecordName)) {
+ return GMPClosedErr;
+ }
+
+ Record* record = nullptr;
+ mRecords.Get(aRecordName, &record);
+ MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
+
+ // Our error strategy is to report records with invalid contents as
+ // containing 0 bytes. Zero length records are considered "deleted" by
+ // the GMPStorage API.
+ aOutBytes.SetLength(0);
+
+ int32_t recordLength = 0;
+ nsCString recordName;
+ nsresult err = ReadRecordMetadata(record->mFileDesc,
+ recordLength,
+ recordName);
+ if (NS_FAILED(err) || recordLength == 0) {
+ // We failed to read the record metadata. Or the record is 0 length.
+ // Treat damaged records as empty.
+ // ReadRecordMetadata() could fail if the GMP opened a new record and
+ // tried to read it before anything was written to it..
+ return GMPNoErr;
+ }
+
+ if (!aRecordName.Equals(recordName)) {
+ NS_WARNING("Record file contains some other record's contents!");
+ return GMPRecordCorrupted;
+ }
+
+ // After calling ReadRecordMetadata, we should be ready to read the
+ // record data.
+ if (PR_Available(record->mFileDesc) != recordLength) {
+ NS_WARNING("Record file length mismatch!");
+ return GMPRecordCorrupted;
+ }
+
+ aOutBytes.SetLength(recordLength);
+ int32_t bytesRead = PR_Read(record->mFileDesc, aOutBytes.Elements(), recordLength);
+ return (bytesRead == recordLength) ? GMPNoErr : GMPRecordCorrupted;
+ }
+
+ GMPErr Write(const nsCString& aRecordName,
+ const nsTArray<uint8_t>& aBytes) override
+ {
+ if (!IsOpen(aRecordName)) {
+ return GMPClosedErr;
+ }
+
+ Record* record = nullptr;
+ mRecords.Get(aRecordName, &record);
+ MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
+
+ // Write operations overwrite the entire record. So close it now.
+ PR_Close(record->mFileDesc);
+ record->mFileDesc = nullptr;
+
+ // Writing 0 bytes means removing (deleting) the file.
+ if (aBytes.Length() == 0) {
+ nsresult rv = RemoveStorageFile(record->mFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Could not delete file -> Continue with trying to erase the contents.
+ } else {
+ return GMPNoErr;
+ }
+ }
+
+ // Write operations overwrite the entire record. So re-open the file
+ // in truncate mode, to clear its contents.
+ if (NS_FAILED(OpenStorageFile(record->mFilename,
+ Truncate,
+ &record->mFileDesc))) {
+ return GMPGenericErr;
+ }
+
+ // Store the length of the record name followed by the record name
+ // at the start of the file.
+ int32_t bytesWritten = 0;
+ char buf[sizeof(uint32_t)] = {0};
+ LittleEndian::writeUint32(buf, aRecordName.Length());
+ bytesWritten = PR_Write(record->mFileDesc, buf, MOZ_ARRAY_LENGTH(buf));
+ if (bytesWritten != MOZ_ARRAY_LENGTH(buf)) {
+ NS_WARNING("Failed to write GMPStorage record name length.");
+ return GMPRecordCorrupted;
+ }
+ bytesWritten = PR_Write(record->mFileDesc,
+ aRecordName.get(),
+ aRecordName.Length());
+ if (bytesWritten != (int32_t)aRecordName.Length()) {
+ NS_WARNING("Failed to write GMPStorage record name.");
+ return GMPRecordCorrupted;
+ }
+
+ bytesWritten = PR_Write(record->mFileDesc, aBytes.Elements(), aBytes.Length());
+ if (bytesWritten != (int32_t)aBytes.Length()) {
+ NS_WARNING("Failed to write GMPStorage record data.");
+ return GMPRecordCorrupted;
+ }
+
+ // Try to sync the file to disk, so that in the event of a crash,
+ // the record is less likely to be corrupted.
+ PR_Sync(record->mFileDesc);
+
+ return GMPNoErr;
+ }
+
+ GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const override
+ {
+ for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
+ aOutRecordNames.AppendElement(iter.UserData()->mRecordName);
+ }
+ return GMPNoErr;
+ }
+
+ void Close(const nsCString& aRecordName) override
+ {
+ Record* record = nullptr;
+ mRecords.Get(aRecordName, &record);
+ if (record && !!record->mFileDesc) {
+ PR_Close(record->mFileDesc);
+ record->mFileDesc = nullptr;
+ }
+ MOZ_ASSERT(!IsOpen(aRecordName));
+ }
+
+private:
+
+ // We store records in a file which is a hash of the record name.
+ // If there is a hash collision, we just keep adding 1 to the hash
+ // code, until we find a free slot.
+ nsresult GetUnusedFilename(const nsACString& aRecordName,
+ nsString& aOutFilename)
+ {
+ nsCOMPtr<nsIFile> storageDir;
+ nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint64_t recordNameHash = HashString(PromiseFlatCString(aRecordName).get());
+ for (int i = 0; i < 1000000; i++) {
+ nsCOMPtr<nsIFile> f;
+ rv = storageDir->Clone(getter_AddRefs(f));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsAutoString hashStr;
+ hashStr.AppendInt(recordNameHash);
+ rv = f->Append(hashStr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ bool exists = false;
+ f->Exists(&exists);
+ if (!exists) {
+ // Filename not in use, we can write into this file.
+ aOutFilename = hashStr;
+ return NS_OK;
+ } else {
+ // Hash collision; just increment the hash name and try that again.
+ ++recordNameHash;
+ continue;
+ }
+ }
+ // Somehow, we've managed to completely fail to find a vacant file name.
+ // Give up.
+ NS_WARNING("GetUnusedFilename had extreme hash collision!");
+ return NS_ERROR_FAILURE;
+ }
+
+ enum OpenFileMode { ReadWrite, Truncate };
+
+ nsresult OpenStorageFile(const nsAString& aFileLeafName,
+ const OpenFileMode aMode,
+ PRFileDesc** aOutFD)
+ {
+ MOZ_ASSERT(aOutFD);
+
+ nsCOMPtr<nsIFile> f;
+ nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ f->Append(aFileLeafName);
+
+ auto mode = PR_RDWR | PR_CREATE_FILE;
+ if (aMode == Truncate) {
+ mode |= PR_TRUNCATE;
+ }
+
+ return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
+ }
+
+ nsresult ReadRecordMetadata(PRFileDesc* aFd,
+ int32_t& aOutRecordLength,
+ nsACString& aOutRecordName)
+ {
+ int32_t offset = PR_Seek(aFd, 0, PR_SEEK_END);
+ PR_Seek(aFd, 0, PR_SEEK_SET);
+
+ if (offset < 0 || offset > GMP_MAX_RECORD_SIZE) {
+ // Refuse to read big records, or records where we can't get a length.
+ return NS_ERROR_FAILURE;
+ }
+ const uint32_t fileLength = static_cast<uint32_t>(offset);
+
+ // At the start of the file the length of the record name is stored in a
+ // uint32_t (little endian byte order) followed by the record name at the
+ // start of the file. The record name is not null terminated. The remainder
+ // of the file is the record's data.
+
+ if (fileLength < sizeof(uint32_t)) {
+ // Record file doesn't have enough contents to store the record name
+ // length. Fail.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Read length, and convert to host byte order.
+ uint32_t recordNameLength = 0;
+ char buf[sizeof(recordNameLength)] = { 0 };
+ int32_t bytesRead = PR_Read(aFd, &buf, sizeof(recordNameLength));
+ recordNameLength = LittleEndian::readUint32(buf);
+ if (sizeof(recordNameLength) != bytesRead ||
+ recordNameLength == 0 ||
+ recordNameLength + sizeof(recordNameLength) > fileLength ||
+ recordNameLength > GMP_MAX_RECORD_NAME_SIZE) {
+ // Record file has invalid contents. Fail.
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString recordName;
+ recordName.SetLength(recordNameLength);
+ bytesRead = PR_Read(aFd, recordName.BeginWriting(), recordNameLength);
+ if ((uint32_t)bytesRead != recordNameLength) {
+ // Read failed.
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(fileLength >= sizeof(recordNameLength) + recordNameLength);
+ int32_t recordLength = fileLength - (sizeof(recordNameLength) + recordNameLength);
+
+ aOutRecordLength = recordLength;
+ aOutRecordName = recordName;
+
+ // Read cursor should be positioned after the record name, before the record contents.
+ if (PR_Seek(aFd, 0, PR_SEEK_CUR) != (int32_t)(sizeof(recordNameLength) + recordNameLength)) {
+ NS_WARNING("Read cursor mismatch after ReadRecordMetadata()");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ nsresult RemoveStorageFile(const nsString& aFilename)
+ {
+ nsCOMPtr<nsIFile> f;
+ nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = f->Append(aFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return f->Remove(/* bool recursive= */ false);
+ }
+
+ struct Record {
+ Record(const nsAString& aFilename,
+ const nsACString& aRecordName)
+ : mFilename(aFilename)
+ , mRecordName(aRecordName)
+ , mFileDesc(0)
+ {}
+ ~Record() {
+ MOZ_ASSERT(!mFileDesc);
+ }
+ nsString mFilename;
+ nsCString mRecordName;
+ PRFileDesc* mFileDesc;
+ };
+
+ // Hash record name to record data.
+ nsClassHashtable<nsCStringHashKey, Record> mRecords;
+ const nsCString mNodeId;
+ const nsString mGMPName;
+};
+
+already_AddRefed<GMPStorage> CreateGMPDiskStorage(const nsCString& aNodeId,
+ const nsString& aGMPName)
+{
+ RefPtr<GMPDiskStorage> storage(new GMPDiskStorage(aNodeId, aGMPName));
+ if (NS_FAILED(storage->Init())) {
+ NS_WARNING("Failed to initialize on disk GMP storage");
+ return nullptr;
+ }
+ return storage.forget();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp
new file mode 100644
index 000000000..206d7b42a
--- /dev/null
+++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPEncryptedBufferDataImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "MediaData.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPEncryptedBufferDataImpl::GMPEncryptedBufferDataImpl(const CryptoSample& aCrypto)
+ : mKeyId(aCrypto.mKeyId)
+ , mIV(aCrypto.mIV)
+ , mClearBytes(aCrypto.mPlainSizes)
+ , mCipherBytes(aCrypto.mEncryptedSizes)
+ , mSessionIdList(aCrypto.mSessionIds)
+{
+}
+
+GMPEncryptedBufferDataImpl::GMPEncryptedBufferDataImpl(const GMPDecryptionData& aData)
+ : mKeyId(aData.mKeyId())
+ , mIV(aData.mIV())
+ , mClearBytes(aData.mClearBytes())
+ , mCipherBytes(aData.mCipherBytes())
+ , mSessionIdList(aData.mSessionIds())
+{
+ MOZ_ASSERT(mClearBytes.Length() == mCipherBytes.Length());
+}
+
+GMPEncryptedBufferDataImpl::~GMPEncryptedBufferDataImpl()
+{
+}
+
+void
+GMPEncryptedBufferDataImpl::RelinquishData(GMPDecryptionData& aData)
+{
+ aData.mKeyId() = Move(mKeyId);
+ aData.mIV() = Move(mIV);
+ aData.mClearBytes() = Move(mClearBytes);
+ aData.mCipherBytes() = Move(mCipherBytes);
+ mSessionIdList.RelinquishData(aData.mSessionIds());
+}
+
+const uint8_t*
+GMPEncryptedBufferDataImpl::KeyId() const
+{
+ return mKeyId.Elements();
+}
+
+uint32_t
+GMPEncryptedBufferDataImpl::KeyIdSize() const
+{
+ return mKeyId.Length();
+}
+
+const uint8_t*
+GMPEncryptedBufferDataImpl::IV() const
+{
+ return mIV.Elements();
+}
+
+uint32_t
+GMPEncryptedBufferDataImpl::IVSize() const
+{
+ return mIV.Length();
+}
+
+const uint16_t*
+GMPEncryptedBufferDataImpl::ClearBytes() const
+{
+ return mClearBytes.Elements();
+}
+
+const uint32_t*
+GMPEncryptedBufferDataImpl::CipherBytes() const
+{
+ return mCipherBytes.Elements();
+}
+
+const GMPStringList*
+GMPEncryptedBufferDataImpl::SessionIds() const
+{
+ return &mSessionIdList;
+}
+
+uint32_t
+GMPEncryptedBufferDataImpl::NumSubsamples() const
+{
+ MOZ_ASSERT(mClearBytes.Length() == mCipherBytes.Length());
+ // Return the min of the two, to ensure there's not chance of array index
+ // out-of-bounds shenanigans.
+ return std::min<uint32_t>(mClearBytes.Length(), mCipherBytes.Length());
+}
+
+GMPStringListImpl::GMPStringListImpl(const nsTArray<nsCString>& aStrings)
+ : mStrings(aStrings)
+{
+}
+
+uint32_t
+GMPStringListImpl::Size() const
+{
+ return mStrings.Length();
+}
+
+void
+GMPStringListImpl::StringAt(uint32_t aIndex,
+ const char** aOutString,
+ uint32_t *aOutLength) const
+{
+ if (NS_WARN_IF(aIndex >= Size())) {
+ return;
+ }
+
+ *aOutString = mStrings[aIndex].BeginReading();
+ *aOutLength = mStrings[aIndex].Length();
+}
+
+void
+GMPStringListImpl::RelinquishData(nsTArray<nsCString>& aStrings)
+{
+ aStrings = Move(mStrings);
+}
+
+GMPStringListImpl::~GMPStringListImpl()
+{
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPEncryptedBufferDataImpl.h b/dom/media/gmp/GMPEncryptedBufferDataImpl.h
new file mode 100644
index 000000000..f0d5728df
--- /dev/null
+++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPEncryptedBufferDataImpl_h_
+#define GMPEncryptedBufferDataImpl_h_
+
+#include "gmp-decryption.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+class CryptoSample;
+
+namespace gmp {
+
+class GMPStringListImpl : public GMPStringList
+{
+public:
+ explicit GMPStringListImpl(const nsTArray<nsCString>& aStrings);
+ uint32_t Size() const override;
+ void StringAt(uint32_t aIndex,
+ const char** aOutString, uint32_t *aOutLength) const override;
+ virtual ~GMPStringListImpl() override;
+ void RelinquishData(nsTArray<nsCString>& aStrings);
+
+private:
+ nsTArray<nsCString> mStrings;
+};
+
+class GMPEncryptedBufferDataImpl : public GMPEncryptedBufferMetadata {
+public:
+ explicit GMPEncryptedBufferDataImpl(const CryptoSample& aCrypto);
+ explicit GMPEncryptedBufferDataImpl(const GMPDecryptionData& aData);
+ virtual ~GMPEncryptedBufferDataImpl();
+
+ void RelinquishData(GMPDecryptionData& aData);
+
+ const uint8_t* KeyId() const override;
+ uint32_t KeyIdSize() const override;
+ const uint8_t* IV() const override;
+ uint32_t IVSize() const override;
+ uint32_t NumSubsamples() const override;
+ const uint16_t* ClearBytes() const override;
+ const uint32_t* CipherBytes() const override;
+ const GMPStringList* SessionIds() const override;
+
+private:
+ nsTArray<uint8_t> mKeyId;
+ nsTArray<uint8_t> mIV;
+ nsTArray<uint16_t> mClearBytes;
+ nsTArray<uint32_t> mCipherBytes;
+
+ GMPStringListImpl mSessionIdList;
+};
+
+class GMPBufferImpl : public GMPBuffer {
+public:
+ GMPBufferImpl(uint32_t aId, const nsTArray<uint8_t>& aData)
+ : mId(aId)
+ , mData(aData)
+ {
+ }
+ uint32_t Id() const override {
+ return mId;
+ }
+ uint8_t* Data() override {
+ return mData.Elements();
+ }
+ uint32_t Size() const override {
+ return mData.Length();
+ }
+ void Resize(uint32_t aSize) override {
+ mData.SetLength(aSize);
+ }
+
+ // Set metadata object to be freed when this buffer is destroyed.
+ void SetMetadata(GMPEncryptedBufferDataImpl* aMetadata) {
+ mMetadata = aMetadata;
+ }
+
+ uint32_t mId;
+ nsTArray<uint8_t> mData;
+ nsAutoPtr<GMPEncryptedBufferDataImpl> mMetadata;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPEncryptedBufferDataImpl_h_
diff --git a/dom/media/gmp/GMPLoader.cpp b/dom/media/gmp/GMPLoader.cpp
new file mode 100644
index 000000000..0bccdd0b1
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPLoader.h"
+#include <stdio.h>
+#include "mozilla/Attributes.h"
+#include "gmp-entrypoints.h"
+#include "prlink.h"
+#include "prenv.h"
+
+#include <string>
+
+#ifdef XP_WIN
+#include "windows.h"
+#endif
+
+#include "GMPDeviceBinding.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPLoaderImpl : public GMPLoader {
+public:
+ explicit GMPLoaderImpl(SandboxStarter* aStarter)
+ : mSandboxStarter(aStarter)
+ , mAdapter(nullptr)
+ {}
+ virtual ~GMPLoaderImpl() {}
+
+ bool Load(const char* aUTF8LibPath,
+ uint32_t aUTF8LibPathLen,
+ char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ const GMPPlatformAPI* aPlatformAPI,
+ GMPAdapter* aAdapter) override;
+
+ GMPErr GetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) override;
+
+ void Shutdown() override;
+
+private:
+ SandboxStarter* mSandboxStarter;
+ UniquePtr<GMPAdapter> mAdapter;
+};
+
+UniquePtr<GMPLoader> CreateGMPLoader(SandboxStarter* aStarter) {
+ return MakeUnique<GMPLoaderImpl>(aStarter);
+}
+
+class PassThroughGMPAdapter : public GMPAdapter {
+public:
+ ~PassThroughGMPAdapter() {
+ // Ensure we're always shutdown, even if caller forgets to call GMPShutdown().
+ GMPShutdown();
+ }
+
+ void SetAdaptee(PRLibrary* aLib) override
+ {
+ mLib = aLib;
+ }
+
+ GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override
+ {
+ if (!mLib) {
+ return GMPGenericErr;
+ }
+ GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
+ if (!initFunc) {
+ return GMPNotImplementedErr;
+ }
+ return initFunc(aPlatformAPI);
+ }
+
+ GMPErr GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) override
+ {
+ if (!mLib) {
+ return GMPGenericErr;
+ }
+ GMPGetAPIFunc getapiFunc = reinterpret_cast<GMPGetAPIFunc>(PR_FindFunctionSymbol(mLib, "GMPGetAPI"));
+ if (!getapiFunc) {
+ return GMPNotImplementedErr;
+ }
+ return getapiFunc(aAPIName, aHostAPI, aPluginAPI);
+ }
+
+ void GMPShutdown() override
+ {
+ if (mLib) {
+ GMPShutdownFunc shutdownFunc = reinterpret_cast<GMPShutdownFunc>(PR_FindFunctionSymbol(mLib, "GMPShutdown"));
+ if (shutdownFunc) {
+ shutdownFunc();
+ }
+ PR_UnloadLibrary(mLib);
+ mLib = nullptr;
+ }
+ }
+
+ void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override
+ {
+ if (!mLib) {
+ return;
+ }
+ GMPSetNodeIdFunc setNodeIdFunc = reinterpret_cast<GMPSetNodeIdFunc>(PR_FindFunctionSymbol(mLib, "GMPSetNodeId"));
+ if (setNodeIdFunc) {
+ setNodeIdFunc(aNodeId, aLength);
+ }
+ }
+
+private:
+ PRLibrary* mLib = nullptr;
+};
+
+bool
+GMPLoaderImpl::Load(const char* aUTF8LibPath,
+ uint32_t aUTF8LibPathLen,
+ char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ const GMPPlatformAPI* aPlatformAPI,
+ GMPAdapter* aAdapter)
+{
+ std::string nodeId;
+ if (!CalculateGMPDeviceId(aOriginSalt, aOriginSaltLen, nodeId)) {
+ return false;
+ }
+
+ // Start the sandbox now that we've generated the device bound node id.
+ // This must happen after the node id is bound to the device id, as
+ // generating the device id requires privileges.
+ if (mSandboxStarter && !mSandboxStarter->Start(aUTF8LibPath)) {
+ return false;
+ }
+
+ // Load the GMP.
+ PRLibSpec libSpec;
+#ifdef XP_WIN
+ int pathLen = MultiByteToWideChar(CP_UTF8, 0, aUTF8LibPath, -1, nullptr, 0);
+ if (pathLen == 0) {
+ return false;
+ }
+
+ auto widePath = MakeUnique<wchar_t[]>(pathLen);
+ if (MultiByteToWideChar(CP_UTF8, 0, aUTF8LibPath, -1, widePath.get(), pathLen) == 0) {
+ return false;
+ }
+
+ libSpec.value.pathname_u = widePath.get();
+ libSpec.type = PR_LibSpec_PathnameU;
+#else
+ libSpec.value.pathname = aUTF8LibPath;
+ libSpec.type = PR_LibSpec_Pathname;
+#endif
+ PRLibrary* lib = PR_LoadLibraryWithFlags(libSpec, 0);
+ if (!lib) {
+ return false;
+ }
+
+ GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(lib, "GMPInit"));
+ if ((initFunc && aAdapter) ||
+ (!initFunc && !aAdapter)) {
+ // Ensure that if we're dealing with a GMP we do *not* use an adapter
+ // provided from the outside world. This is important as it means we
+ // don't call code not covered by Adobe's plugin-container voucher
+ // before we pass the node Id to Adobe's GMP.
+ return false;
+ }
+
+ // Note: PassThroughGMPAdapter's code must remain in this file so that it's
+ // covered by Adobe's plugin-container voucher.
+ mAdapter.reset((!aAdapter) ? new PassThroughGMPAdapter() : aAdapter);
+ mAdapter->SetAdaptee(lib);
+
+ if (mAdapter->GMPInit(aPlatformAPI) != GMPNoErr) {
+ return false;
+ }
+
+ mAdapter->GMPSetNodeId(nodeId.c_str(), nodeId.size());
+
+ return true;
+}
+
+GMPErr
+GMPLoaderImpl::GetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId)
+{
+ return mAdapter->GMPGetAPI(aAPIName, aHostAPI, aPluginAPI, aDecryptorId);
+}
+
+void
+GMPLoaderImpl::Shutdown()
+{
+ if (mAdapter) {
+ mAdapter->GMPShutdown();
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
+
diff --git a/dom/media/gmp/GMPLoader.h b/dom/media/gmp/GMPLoader.h
new file mode 100644
index 000000000..8e6b3cfac
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMP_LOADER_H__
+#define GMP_LOADER_H__
+
+#include <stdint.h>
+#include "prlink.h"
+#include "gmp-entrypoints.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace gmp {
+
+class SandboxStarter {
+public:
+ virtual ~SandboxStarter() {}
+ virtual bool Start(const char* aLibPath) = 0;
+};
+
+// Interface that adapts a plugin to the GMP API.
+class GMPAdapter {
+public:
+ virtual ~GMPAdapter() {}
+ // Sets the adapted to plugin library module.
+ // Note: the GMPAdapter is responsible for calling PR_UnloadLibrary on aLib
+ // when it's finished with it.
+ virtual void SetAdaptee(PRLibrary* aLib) = 0;
+
+ // These are called in place of the corresponding GMP API functions.
+ virtual GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) = 0;
+ virtual GMPErr GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) = 0;
+ virtual void GMPShutdown() = 0;
+ virtual void GMPSetNodeId(const char* aNodeId, uint32_t aLength) = 0;
+};
+
+// Encapsulates generating the device-bound node id, activating the sandbox,
+// loading the GMP, and passing the node id to the GMP (in that order).
+//
+// In Desktop Gecko, the implementation of this lives in plugin-container,
+// and is passed into XUL code from on startup. The GMP IPC child protocol actor
+// uses this interface to load and retrieve interfaces from the GMPs.
+//
+// In Desktop Gecko the implementation lives in the plugin-container so that
+// it can be covered by DRM vendor's voucher.
+//
+// On Android the GMPLoader implementation lives in libxul (because for the time
+// being GMPLoader relies upon NSPR, which we can't use in plugin-container
+// on Android).
+//
+// There is exactly one GMPLoader per GMP child process, and only one GMP
+// per child process (so the GMPLoader only loads one GMP).
+//
+// Load() takes an optional GMPAdapter which can be used to adapt non-GMPs
+// to adhere to the GMP API.
+class GMPLoader {
+public:
+ virtual ~GMPLoader() {}
+
+ // Calculates the device-bound node id, then activates the sandbox,
+ // then loads the GMP library and (if applicable) passes the bound node id
+ // to the GMP. If aAdapter is non-null, the lib path is assumed to be
+ // a non-GMP, and the adapter is initialized with the lib and the adapter
+ // is used to interact with the plugin.
+ virtual bool Load(const char* aUTF8LibPath,
+ uint32_t aLibPathLen,
+ char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ const GMPPlatformAPI* aPlatformAPI,
+ GMPAdapter* aAdapter = nullptr) = 0;
+
+ // Retrieves an interface pointer from the GMP.
+ virtual GMPErr GetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) = 0;
+
+ // Calls the GMPShutdown function exported by the GMP lib, and unloads the
+ // plugin library.
+ virtual void Shutdown() = 0;
+};
+
+// On Desktop, this function resides in plugin-container.
+// On Mobile, this function resides in XUL.
+UniquePtr<GMPLoader> CreateGMPLoader(SandboxStarter* aStarter);
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMP_LOADER_H__
diff --git a/dom/media/gmp/GMPMemoryStorage.cpp b/dom/media/gmp/GMPMemoryStorage.cpp
new file mode 100644
index 000000000..dc799caa1
--- /dev/null
+++ b/dom/media/gmp/GMPMemoryStorage.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPStorage.h"
+#include "nsClassHashtable.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPMemoryStorage : public GMPStorage {
+public:
+ GMPErr Open(const nsCString& aRecordName) override
+ {
+ MOZ_ASSERT(!IsOpen(aRecordName));
+
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ record = new Record();
+ mRecords.Put(aRecordName, record);
+ }
+ record->mIsOpen = true;
+ return GMPNoErr;
+ }
+
+ bool IsOpen(const nsCString& aRecordName) const override {
+ const Record* record = mRecords.Get(aRecordName);
+ if (!record) {
+ return false;
+ }
+ return record->mIsOpen;
+ }
+
+ GMPErr Read(const nsCString& aRecordName,
+ nsTArray<uint8_t>& aOutBytes) override
+ {
+ const Record* record = mRecords.Get(aRecordName);
+ if (!record) {
+ return GMPGenericErr;
+ }
+ aOutBytes = record->mData;
+ return GMPNoErr;
+ }
+
+ GMPErr Write(const nsCString& aRecordName,
+ const nsTArray<uint8_t>& aBytes) override
+ {
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ return GMPClosedErr;
+ }
+ record->mData = aBytes;
+ return GMPNoErr;
+ }
+
+ GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const override
+ {
+ for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
+ aOutRecordNames.AppendElement(iter.Key());
+ }
+ return GMPNoErr;
+ }
+
+ void Close(const nsCString& aRecordName) override
+ {
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ return;
+ }
+ if (!record->mData.Length()) {
+ // Record is empty, delete.
+ mRecords.Remove(aRecordName);
+ } else {
+ record->mIsOpen = false;
+ }
+ }
+
+private:
+
+ struct Record {
+ nsTArray<uint8_t> mData;
+ bool mIsOpen = false;
+ };
+
+ nsClassHashtable<nsCStringHashKey, Record> mRecords;
+};
+
+already_AddRefed<GMPStorage> CreateGMPMemoryStorage()
+{
+ return RefPtr<GMPStorage>(new GMPMemoryStorage()).forget();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPMessageUtils.h b/dom/media/gmp/GMPMessageUtils.h
new file mode 100644
index 000000000..13f6127f3
--- /dev/null
+++ b/dom/media/gmp/GMPMessageUtils.h
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPMessageUtils_h_
+#define GMPMessageUtils_h_
+
+#include "gmp-video-codec.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-audio-codec.h"
+#include "gmp-decryption.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<GMPErr>
+: public ContiguousEnumSerializer<GMPErr,
+ GMPNoErr,
+ GMPLastErr>
+{};
+
+struct GMPDomExceptionValidator {
+ static bool IsLegalValue(GMPDOMException aValue) {
+ switch (aValue) {
+ case kGMPNoModificationAllowedError:
+ case kGMPNotFoundError:
+ case kGMPNotSupportedError:
+ case kGMPInvalidStateError:
+ case kGMPSyntaxError:
+ case kGMPInvalidModificationError:
+ case kGMPInvalidAccessError:
+ case kGMPSecurityError:
+ case kGMPAbortError:
+ case kGMPQuotaExceededError:
+ case kGMPTimeoutError:
+ case kGMPTypeError:
+ return true;
+ default:
+ return false;
+ }
+ }
+};
+
+template <>
+struct ParamTraits<GMPVideoFrameType>
+: public ContiguousEnumSerializer<GMPVideoFrameType,
+ kGMPKeyFrame,
+ kGMPVideoFrameInvalid>
+{};
+
+template<>
+struct ParamTraits<GMPDOMException>
+: public EnumSerializer<GMPDOMException, GMPDomExceptionValidator>
+{};
+
+template <>
+struct ParamTraits<GMPSessionMessageType>
+: public ContiguousEnumSerializer<GMPSessionMessageType,
+ kGMPLicenseRequest,
+ kGMPMessageInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPMediaKeyStatus>
+: public ContiguousEnumSerializer<GMPMediaKeyStatus,
+ kGMPUsable,
+ kGMPMediaKeyStatusInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPSessionType>
+: public ContiguousEnumSerializer<GMPSessionType,
+ kGMPTemporySession,
+ kGMPSessionInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPAudioCodecType>
+: public ContiguousEnumSerializer<GMPAudioCodecType,
+ kGMPAudioCodecAAC,
+ kGMPAudioCodecInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecComplexity>
+: public ContiguousEnumSerializer<GMPVideoCodecComplexity,
+ kGMPComplexityNormal,
+ kGMPComplexityInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVP8ResilienceMode>
+: public ContiguousEnumSerializer<GMPVP8ResilienceMode,
+ kResilienceOff,
+ kResilienceInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecType>
+: public ContiguousEnumSerializer<GMPVideoCodecType,
+ kGMPVideoCodecVP8,
+ kGMPVideoCodecInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecMode>
+: public ContiguousEnumSerializer<GMPVideoCodecMode,
+ kGMPRealtimeVideo,
+ kGMPCodecModeInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPBufferType>
+: public ContiguousEnumSerializer<GMPBufferType,
+ GMP_BufferSingle,
+ GMP_BufferInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPSimulcastStream>
+{
+ typedef GMPSimulcastStream paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mWidth);
+ WriteParam(aMsg, aParam.mHeight);
+ WriteParam(aMsg, aParam.mNumberOfTemporalLayers);
+ WriteParam(aMsg, aParam.mMaxBitrate);
+ WriteParam(aMsg, aParam.mTargetBitrate);
+ WriteParam(aMsg, aParam.mMinBitrate);
+ WriteParam(aMsg, aParam.mQPMax);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ if (ReadParam(aMsg, aIter, &(aResult->mWidth)) &&
+ ReadParam(aMsg, aIter, &(aResult->mHeight)) &&
+ ReadParam(aMsg, aIter, &(aResult->mNumberOfTemporalLayers)) &&
+ ReadParam(aMsg, aIter, &(aResult->mMaxBitrate)) &&
+ ReadParam(aMsg, aIter, &(aResult->mTargetBitrate)) &&
+ ReadParam(aMsg, aIter, &(aResult->mMinBitrate)) &&
+ ReadParam(aMsg, aIter, &(aResult->mQPMax))) {
+ return true;
+ }
+ return false;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ aLog->append(StringPrintf(L"[%u, %u, %u, %u, %u, %u, %u]", aParam.mWidth, aParam.mHeight,
+ aParam.mNumberOfTemporalLayers, aParam.mMaxBitrate,
+ aParam.mTargetBitrate, aParam.mMinBitrate, aParam.mQPMax));
+ }
+};
+
+template <>
+struct ParamTraits<GMPVideoCodec>
+{
+ typedef GMPVideoCodec paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mGMPApiVersion);
+ WriteParam(aMsg, aParam.mCodecType);
+ WriteParam(aMsg, static_cast<const nsCString&>(nsDependentCString(aParam.mPLName)));
+ WriteParam(aMsg, aParam.mPLType);
+ WriteParam(aMsg, aParam.mWidth);
+ WriteParam(aMsg, aParam.mHeight);
+ WriteParam(aMsg, aParam.mStartBitrate);
+ WriteParam(aMsg, aParam.mMaxBitrate);
+ WriteParam(aMsg, aParam.mMinBitrate);
+ WriteParam(aMsg, aParam.mMaxFramerate);
+ WriteParam(aMsg, aParam.mFrameDroppingOn);
+ WriteParam(aMsg, aParam.mKeyFrameInterval);
+ WriteParam(aMsg, aParam.mQPMax);
+ WriteParam(aMsg, aParam.mNumberOfSimulcastStreams);
+ for (uint32_t i = 0; i < aParam.mNumberOfSimulcastStreams; i++) {
+ WriteParam(aMsg, aParam.mSimulcastStream[i]);
+ }
+ WriteParam(aMsg, aParam.mMode);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ // NOTE: make sure this matches any versions supported
+ if (!ReadParam(aMsg, aIter, &(aResult->mGMPApiVersion)) ||
+ aResult->mGMPApiVersion != kGMPVersion33) {
+ return false;
+ }
+ if (!ReadParam(aMsg, aIter, &(aResult->mCodecType))) {
+ return false;
+ }
+
+ nsAutoCString plName;
+ if (!ReadParam(aMsg, aIter, &plName) ||
+ plName.Length() > kGMPPayloadNameSize - 1) {
+ return false;
+ }
+ memcpy(aResult->mPLName, plName.get(), plName.Length());
+ memset(aResult->mPLName + plName.Length(), 0, kGMPPayloadNameSize - plName.Length());
+
+ if (!ReadParam(aMsg, aIter, &(aResult->mPLType)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mWidth)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mHeight)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mStartBitrate)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mMaxBitrate)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mMinBitrate)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mMaxFramerate)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mFrameDroppingOn)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mKeyFrameInterval))) {
+ return false;
+ }
+
+ if (!ReadParam(aMsg, aIter, &(aResult->mQPMax)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mNumberOfSimulcastStreams))) {
+ return false;
+ }
+
+ if (aResult->mNumberOfSimulcastStreams > kGMPMaxSimulcastStreams) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < aResult->mNumberOfSimulcastStreams; i++) {
+ if (!ReadParam(aMsg, aIter, &(aResult->mSimulcastStream[i]))) {
+ return false;
+ }
+ }
+
+ if (!ReadParam(aMsg, aIter, &(aResult->mMode))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ const char* codecName = nullptr;
+ if (aParam.mCodecType == kGMPVideoCodecVP8) {
+ codecName = "VP8";
+ }
+ aLog->append(StringPrintf(L"[%s, %u, %u]",
+ codecName,
+ aParam.mWidth,
+ aParam.mHeight));
+ }
+};
+
+} // namespace IPC
+
+#endif // GMPMessageUtils_h_
diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp
new file mode 100644
index 000000000..e6f797264
--- /dev/null
+++ b/dom/media/gmp/GMPParent.cpp
@@ -0,0 +1,982 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPParent.h"
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIRunnable.h"
+#include "nsIWritablePropertyBag2.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/SSE.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Unused.h"
+#include "nsIObserverService.h"
+#include "GMPTimerParent.h"
+#include "runnable_utils.h"
+#include "GMPContentParent.h"
+#include "MediaPrefs.h"
+#include "VideoUtils.h"
+
+using mozilla::ipc::GeckoChildProcessHost;
+
+#ifdef XP_WIN
+#include "WMFDecoderModule.h"
+#endif
+
+#ifdef MOZ_EME
+#include "mozilla/dom/WidevineCDMManifestBinding.h"
+#include "widevine-adapter/WidevineAdapter.h"
+#endif
+
+namespace mozilla {
+
+#undef LOG
+#undef LOGD
+
+extern LogModule* GetGMPLog();
+#define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__))
+#define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPParent"
+
+namespace gmp {
+
+GMPParent::GMPParent()
+ : mState(GMPStateNotLoaded)
+ , mProcess(nullptr)
+ , mDeleteProcessOnlyOnUnload(false)
+ , mAbnormalShutdownInProgress(false)
+ , mIsBlockingDeletion(false)
+ , mCanDecrypt(false)
+ , mGMPContentChildCount(0)
+ , mAsyncShutdownRequired(false)
+ , mAsyncShutdownInProgress(false)
+ , mChildPid(0)
+ , mHoldingSelfRef(false)
+{
+ mPluginId = GeckoChildProcessHost::GetUniqueID();
+ LOGD("GMPParent ctor id=%u", mPluginId);
+}
+
+GMPParent::~GMPParent()
+{
+ LOGD("GMPParent dtor id=%u", mPluginId);
+ MOZ_ASSERT(!mProcess);
+}
+
+nsresult
+GMPParent::CloneFrom(const GMPParent* aOther)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory");
+
+ mService = aOther->mService;
+ mDirectory = aOther->mDirectory;
+ mName = aOther->mName;
+ mVersion = aOther->mVersion;
+ mDescription = aOther->mDescription;
+ mDisplayName = aOther->mDisplayName;
+#ifdef XP_WIN
+ mLibs = aOther->mLibs;
+#endif
+ for (const GMPCapability& cap : aOther->mCapabilities) {
+ mCapabilities.AppendElement(cap);
+ }
+ mAdapter = aOther->mAdapter;
+ return NS_OK;
+}
+
+RefPtr<GenericPromise>
+GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir)
+{
+ MOZ_ASSERT(aPluginDir);
+ MOZ_ASSERT(aService);
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ mService = aService;
+ mDirectory = aPluginDir;
+
+ // aPluginDir is <profile-dir>/<gmp-plugin-id>/<version>
+ // where <gmp-plugin-id> should be gmp-gmpopenh264
+ nsCOMPtr<nsIFile> parent;
+ nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ nsAutoString parentLeafName;
+ rv = parent->GetLeafName(parentLeafName);
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ LOGD("%s: for %s", __FUNCTION__, NS_LossyConvertUTF16toASCII(parentLeafName).get());
+
+ MOZ_ASSERT(parentLeafName.Length() > 4);
+ mName = Substring(parentLeafName, 4);
+
+ return ReadGMPMetaData();
+}
+
+void
+GMPParent::Crash()
+{
+ if (mState != GMPStateNotLoaded) {
+ Unused << SendCrashPluginNow();
+ }
+}
+
+nsresult
+GMPParent::LoadProcess()
+{
+ MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ MOZ_ASSERT(mState == GMPStateNotLoaded);
+
+ nsAutoString path;
+ if (NS_FAILED(mDirectory->GetPath(path))) {
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: for %s", __FUNCTION__, NS_ConvertUTF16toUTF8(path).get());
+
+ if (!mProcess) {
+ mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get());
+ if (!mProcess->Launch(30 * 1000)) {
+ LOGD("%s: Failed to launch new child process", __FUNCTION__);
+ mProcess->Delete();
+ mProcess = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ mChildPid = base::GetProcId(mProcess->GetChildProcessHandle());
+ LOGD("%s: Launched new child process", __FUNCTION__);
+
+ bool opened = Open(mProcess->GetChannel(),
+ base::GetProcId(mProcess->GetChildProcessHandle()));
+ if (!opened) {
+ LOGD("%s: Failed to open channel to new child process", __FUNCTION__);
+ mProcess->Delete();
+ mProcess = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: Opened channel to new child process", __FUNCTION__);
+
+ bool ok = SendSetNodeId(mNodeId);
+ if (!ok) {
+ LOGD("%s: Failed to send node id to child process", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: Sent node id to child process", __FUNCTION__);
+
+#ifdef XP_WIN
+ if (!mLibs.IsEmpty()) {
+ bool ok = SendPreloadLibs(mLibs);
+ if (!ok) {
+ LOGD("%s: Failed to send preload-libs to child process", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: Sent preload-libs ('%s') to child process", __FUNCTION__, mLibs.get());
+ }
+#endif
+
+ // Intr call to block initialization on plugin load.
+ ok = CallStartPlugin(mAdapter);
+ if (!ok) {
+ LOGD("%s: Failed to send start to child process", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: Sent StartPlugin to child process", __FUNCTION__);
+ }
+
+ mState = GMPStateLoaded;
+
+ // Hold a self ref while the child process is alive. This ensures that
+ // during shutdown the GMPParent stays alive long enough to
+ // terminate the child process.
+ MOZ_ASSERT(!mHoldingSelfRef);
+ mHoldingSelfRef = true;
+ AddRef();
+
+ return NS_OK;
+}
+
+// static
+void
+GMPParent::AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure)
+{
+ NS_WARNING("Timed out waiting for GMP async shutdown!");
+ GMPParent* parent = reinterpret_cast<GMPParent*>(aClosure);
+ MOZ_ASSERT(parent->mService);
+ parent->mService->AsyncShutdownComplete(parent);
+}
+
+nsresult
+GMPParent::EnsureAsyncShutdownTimeoutSet()
+{
+ MOZ_ASSERT(mAsyncShutdownRequired);
+ if (mAsyncShutdownTimeout) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ mAsyncShutdownTimeout = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Set timer to abort waiting for plugin to shutdown if it takes
+ // too long.
+ rv = mAsyncShutdownTimeout->SetTarget(mGMPThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t timeout = MediaPrefs::GMPAsyncShutdownTimeout();
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ if (service) {
+ timeout = service->AsyncShutdownTimeoutMs();
+ }
+ rv = mAsyncShutdownTimeout->InitWithFuncCallback(
+ &AbortWaitingForGMPAsyncShutdown, this, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ return rv;
+}
+
+bool
+GMPParent::RecvPGMPContentChildDestroyed()
+{
+ --mGMPContentChildCount;
+ if (!IsUsed()) {
+ CloseIfUnused();
+ }
+ return true;
+}
+
+void
+GMPParent::CloseIfUnused()
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ LOGD("%s: mAsyncShutdownRequired=%d", __FUNCTION__, mAsyncShutdownRequired);
+
+ if ((mDeleteProcessOnlyOnUnload ||
+ mState == GMPStateLoaded ||
+ mState == GMPStateUnloading) &&
+ !IsUsed()) {
+ // Ensure all timers are killed.
+ for (uint32_t i = mTimers.Length(); i > 0; i--) {
+ mTimers[i - 1]->Shutdown();
+ }
+
+ if (mAsyncShutdownRequired) {
+ if (!mAsyncShutdownInProgress) {
+ LOGD("%s: sending async shutdown notification", __FUNCTION__);
+ mAsyncShutdownInProgress = true;
+ if (!SendBeginAsyncShutdown()) {
+ AbortAsyncShutdown();
+ } else if (NS_FAILED(EnsureAsyncShutdownTimeoutSet())) {
+ AbortAsyncShutdown();
+ }
+ }
+ } else {
+ // No async-shutdown, kill async-shutdown timer started in CloseActive().
+ AbortAsyncShutdown();
+ // Any async shutdown must be complete. Shutdown GMPStorage.
+ for (size_t i = mStorage.Length(); i > 0; i--) {
+ mStorage[i - 1]->Shutdown();
+ }
+ Shutdown();
+ }
+ }
+}
+
+void
+GMPParent::AbortAsyncShutdown()
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ LOGD("%s", __FUNCTION__);
+
+ if (mAsyncShutdownTimeout) {
+ mAsyncShutdownTimeout->Cancel();
+ mAsyncShutdownTimeout = nullptr;
+ }
+
+ if (!mAsyncShutdownRequired || !mAsyncShutdownInProgress) {
+ return;
+ }
+
+ RefPtr<GMPParent> kungFuDeathGrip(this);
+ mService->AsyncShutdownComplete(this);
+ mAsyncShutdownRequired = false;
+ mAsyncShutdownInProgress = false;
+ CloseIfUnused();
+}
+
+void
+GMPParent::CloseActive(bool aDieWhenUnloaded)
+{
+ LOGD("%s: state %d", __FUNCTION__, mState);
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ if (aDieWhenUnloaded) {
+ mDeleteProcessOnlyOnUnload = true; // don't allow this to go back...
+ }
+ if (mState == GMPStateLoaded) {
+ mState = GMPStateUnloading;
+ }
+ if (mState != GMPStateNotLoaded && IsUsed()) {
+ if (!SendCloseActive()) {
+ AbortAsyncShutdown();
+ } else if (IsUsed()) {
+ // We're expecting RecvPGMPContentChildDestroyed's -> Start async-shutdown timer now if needed.
+ if (mAsyncShutdownRequired && NS_FAILED(EnsureAsyncShutdownTimeoutSet())) {
+ AbortAsyncShutdown();
+ }
+ } else {
+ // We're not expecting any RecvPGMPContentChildDestroyed
+ // -> Call CloseIfUnused() now, to run async shutdown if necessary.
+ // Note that CloseIfUnused() may have already been called from a prior
+ // RecvPGMPContentChildDestroyed(), however depending on the state at
+ // that time, it might not have proceeded with shutdown; And calling it
+ // again after shutdown is fine because after the first one we'll be in
+ // GMPStateNotLoaded.
+ CloseIfUnused();
+ }
+ }
+}
+
+void
+GMPParent::MarkForDeletion()
+{
+ mDeleteProcessOnlyOnUnload = true;
+ mIsBlockingDeletion = true;
+}
+
+bool
+GMPParent::IsMarkedForDeletion()
+{
+ return mIsBlockingDeletion;
+}
+
+void
+GMPParent::Shutdown()
+{
+ LOGD("%s", __FUNCTION__);
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ MOZ_ASSERT(!mAsyncShutdownTimeout, "Should have canceled shutdown timeout");
+
+ if (mAbnormalShutdownInProgress) {
+ return;
+ }
+
+ MOZ_ASSERT(!IsUsed());
+ if (mState == GMPStateNotLoaded || mState == GMPStateClosing) {
+ return;
+ }
+
+ RefPtr<GMPParent> self(this);
+ DeleteProcess();
+
+ // XXX Get rid of mDeleteProcessOnlyOnUnload and this code when
+ // Bug 1043671 is fixed
+ if (!mDeleteProcessOnlyOnUnload) {
+ // Destroy ourselves and rise from the fire to save memory
+ mService->ReAddOnGMPThread(self);
+ } // else we've been asked to die and stay dead
+ MOZ_ASSERT(mState == GMPStateNotLoaded);
+}
+
+class NotifyGMPShutdownTask : public Runnable {
+public:
+ explicit NotifyGMPShutdownTask(const nsAString& aNodeId)
+ : mNodeId(aNodeId)
+ {
+ }
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "gmp-shutdown", mNodeId.get());
+ }
+ return NS_OK;
+ }
+ nsString mNodeId;
+};
+
+void
+GMPParent::ChildTerminated()
+{
+ RefPtr<GMPParent> self(this);
+ nsIThread* gmpThread = GMPThread();
+
+ if (!gmpThread) {
+ // Bug 1163239 - this can happen on shutdown.
+ // PluginTerminated removes the GMP from the GMPService.
+ // On shutdown we can have this case where it is already been
+ // removed so there is no harm in not trying to remove it again.
+ LOGD("%s::%s: GMPThread() returned nullptr.", __CLASS__, __FUNCTION__);
+ } else {
+ gmpThread->Dispatch(NewRunnableMethod<RefPtr<GMPParent>>(
+ mService,
+ &GeckoMediaPluginServiceParent::PluginTerminated,
+ self),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+void
+GMPParent::DeleteProcess()
+{
+ LOGD("%s", __FUNCTION__);
+
+ if (mState != GMPStateClosing) {
+ // Don't Close() twice!
+ // Probably remove when bug 1043671 is resolved
+ mState = GMPStateClosing;
+ Close();
+ }
+ mProcess->Delete(NewRunnableMethod(this, &GMPParent::ChildTerminated));
+ LOGD("%s: Shut down process", __FUNCTION__);
+ mProcess = nullptr;
+ mState = GMPStateNotLoaded;
+
+ NS_DispatchToMainThread(
+ new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)),
+ NS_DISPATCH_NORMAL);
+
+ if (mHoldingSelfRef) {
+ Release();
+ mHoldingSelfRef = false;
+ }
+}
+
+GMPState
+GMPParent::State() const
+{
+ return mState;
+}
+
+// Not changing to use mService since we'll be removing it
+nsIThread*
+GMPParent::GMPThread()
+{
+ if (!mGMPThread) {
+ nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mps);
+ if (!mps) {
+ return nullptr;
+ }
+ // Not really safe if we just grab to the mGMPThread, as we don't know
+ // what thread we're running on and other threads may be trying to
+ // access this without locks! However, debug only, and primary failure
+ // mode outside of compiler-helped TSAN is a leak. But better would be
+ // to use swap() under a lock.
+ mps->GetThread(getter_AddRefs(mGMPThread));
+ MOZ_ASSERT(mGMPThread);
+ }
+
+ return mGMPThread;
+}
+
+/* static */
+bool
+GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags)
+{
+ for (const nsCString& tag : aTags) {
+ if (!GMPCapability::Supports(aCapabilities, aAPI, tag)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* static */
+bool
+GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsCString& aAPI,
+ const nsCString& aTag)
+{
+ for (const GMPCapability& capabilities : aCapabilities) {
+ if (!capabilities.mAPIName.Equals(aAPI)) {
+ continue;
+ }
+ for (const nsCString& tag : capabilities.mAPITags) {
+ if (tag.Equals(aTag)) {
+#ifdef XP_WIN
+ // Clearkey on Windows advertises that it can decode in its GMP info
+ // file, but uses Windows Media Foundation to decode. That's not present
+ // on Windows N and KN variants without certain services packs.
+ if (tag.Equals(kEMEKeySystemClearkey)) {
+ if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) {
+ if (!WMFDecoderModule::HasH264()) {
+ continue;
+ }
+ } else if (capabilities.mAPIName.EqualsLiteral(GMP_API_AUDIO_DECODER)) {
+ if (!WMFDecoderModule::HasAAC()) {
+ continue;
+ }
+ }
+ }
+#endif
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool
+GMPParent::EnsureProcessLoaded()
+{
+ if (mState == GMPStateLoaded) {
+ return true;
+ }
+ if (mState == GMPStateClosing ||
+ mState == GMPStateUnloading) {
+ return false;
+ }
+
+ nsresult rv = LoadProcess();
+
+ return NS_SUCCEEDED(rv);
+}
+
+void
+GMPParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD("%s: (%d)", __FUNCTION__, (int)aWhy);
+ // warn us off trying to close again
+ mState = GMPStateClosing;
+ mAbnormalShutdownInProgress = true;
+ CloseActive(false);
+
+ // Normal Shutdown() will delete the process on unwind.
+ if (AbnormalShutdown == aWhy) {
+ RefPtr<GMPParent> self(this);
+ if (mAsyncShutdownRequired) {
+ mService->AsyncShutdownComplete(this);
+ mAsyncShutdownRequired = false;
+ }
+ // Must not call Close() again in DeleteProcess(), as we'll recurse
+ // infinitely if we do.
+ MOZ_ASSERT(mState == GMPStateClosing);
+ DeleteProcess();
+ // Note: final destruction will be Dispatched to ourself
+ mService->ReAddOnGMPThread(self);
+ }
+}
+
+PGMPStorageParent*
+GMPParent::AllocPGMPStorageParent()
+{
+ GMPStorageParent* p = new GMPStorageParent(mNodeId, this);
+ mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent.
+ return p;
+}
+
+bool
+GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor)
+{
+ GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor);
+ p->Shutdown();
+ mStorage.RemoveElement(p);
+ return true;
+}
+
+bool
+GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* aActor)
+{
+ GMPStorageParent* p = (GMPStorageParent*)aActor;
+ if (NS_WARN_IF(NS_FAILED(p->Init()))) {
+ return false;
+ }
+ return true;
+}
+
+bool
+GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
+{
+ return true;
+}
+
+PGMPTimerParent*
+GMPParent::AllocPGMPTimerParent()
+{
+ GMPTimerParent* p = new GMPTimerParent(GMPThread());
+ mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown.
+ return p;
+}
+
+bool
+GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor)
+{
+ GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor);
+ p->Shutdown();
+ mTimers.RemoveElement(p);
+ return true;
+}
+
+bool
+ReadInfoField(GMPInfoFileParser& aParser, const nsCString& aKey, nsACString& aOutValue)
+{
+ if (!aParser.Contains(aKey) || aParser.Get(aKey).IsEmpty()) {
+ return false;
+ }
+ aOutValue = aParser.Get(aKey);
+ return true;
+}
+
+RefPtr<GenericPromise>
+GMPParent::ReadGMPMetaData()
+{
+ MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
+ MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!");
+
+ nsCOMPtr<nsIFile> infoFile;
+ nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile));
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ infoFile->AppendRelativePath(mName + NS_LITERAL_STRING(".info"));
+
+ if (FileExists(infoFile)) {
+ return ReadGMPInfoFile(infoFile);
+ }
+
+#ifdef MOZ_EME
+ // Maybe this is the Widevine adapted plugin?
+ nsCOMPtr<nsIFile> manifestFile;
+ rv = mDirectory->Clone(getter_AddRefs(manifestFile));
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ manifestFile->AppendRelativePath(NS_LITERAL_STRING("manifest.json"));
+ return ReadChromiumManifestFile(manifestFile);
+#else
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+#endif
+}
+
+RefPtr<GenericPromise>
+GMPParent::ReadGMPInfoFile(nsIFile* aFile)
+{
+ GMPInfoFileParser parser;
+ if (!parser.Init(aFile)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsAutoCString apis;
+ if (!ReadInfoField(parser, NS_LITERAL_CSTRING("name"), mDisplayName) ||
+ !ReadInfoField(parser, NS_LITERAL_CSTRING("description"), mDescription) ||
+ !ReadInfoField(parser, NS_LITERAL_CSTRING("version"), mVersion) ||
+ !ReadInfoField(parser, NS_LITERAL_CSTRING("apis"), apis)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+#ifdef XP_WIN
+ // "Libraries" field is optional.
+ ReadInfoField(parser, NS_LITERAL_CSTRING("libraries"), mLibs);
+#endif
+
+ nsTArray<nsCString> apiTokens;
+ SplitAt(", ", apis, apiTokens);
+ for (nsCString api : apiTokens) {
+ int32_t tagsStart = api.FindChar('[');
+ if (tagsStart == 0) {
+ // Not allowed to be the first character.
+ // API name must be at least one character.
+ continue;
+ }
+
+ GMPCapability cap;
+ if (tagsStart == -1) {
+ // No tags.
+ cap.mAPIName.Assign(api);
+ } else {
+ auto tagsEnd = api.FindChar(']');
+ if (tagsEnd == -1 || tagsEnd < tagsStart) {
+ // Invalid syntax, skip whole capability.
+ continue;
+ }
+
+ cap.mAPIName.Assign(Substring(api, 0, tagsStart));
+
+ if ((tagsEnd - tagsStart) > 1) {
+ const nsDependentCSubstring ts(Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1));
+ nsTArray<nsCString> tagTokens;
+ SplitAt(":", ts, tagTokens);
+ for (nsCString tag : tagTokens) {
+ cap.mAPITags.AppendElement(tag);
+ }
+ }
+ }
+
+ // We support the current GMPDecryptor version, and the previous.
+ // We Adapt the previous to the current in the GMPContentChild.
+ if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) {
+ cap.mAPIName.AssignLiteral(GMP_API_DECRYPTOR);
+ }
+
+ if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) {
+ mCanDecrypt = true;
+ }
+
+ mCapabilities.AppendElement(Move(cap));
+ }
+
+ if (mCapabilities.IsEmpty()) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ return GenericPromise::CreateAndResolve(true, __func__);
+}
+
+RefPtr<GenericPromise>
+GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
+{
+ nsAutoCString json;
+ if (!ReadIntoString(aFile, json, 5 * 1024)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ // DOM JSON parsing needs to run on the main thread.
+ return InvokeAsync(AbstractThread::MainThread(), this, __func__,
+ &GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
+}
+
+RefPtr<GenericPromise>
+GMPParent::ParseChromiumManifest(nsString aJSON)
+{
+#ifdef MOZ_EME
+ LOGD("%s: for '%s'", __FUNCTION__, NS_LossyConvertUTF16toASCII(aJSON).get());
+
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::WidevineCDMManifest m;
+ if (!m.Init(aJSON)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsresult ignored; // Note: ToInteger returns 0 on failure.
+ if (!WidevineAdapter::Supports(m.mX_cdm_module_versions.ToInteger(&ignored),
+ m.mX_cdm_interface_versions.ToInteger(&ignored),
+ m.mX_cdm_host_versions.ToInteger(&ignored))) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ mDisplayName = NS_ConvertUTF16toUTF8(m.mName);
+ mDescription = NS_ConvertUTF16toUTF8(m.mDescription);
+ mVersion = NS_ConvertUTF16toUTF8(m.mVersion);
+
+ 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);
+ mCapabilities.AppendElement(Move(video));
+
+ GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
+ decrypt.mAPITags.AppendElement(kEMEKeySystemWidevine);
+ 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__);
+#else // !MOZ_EME
+ MOZ_ASSERT_UNREACHABLE("don't call me if EME isn't enabled");
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+#endif // !MOZ_EME
+}
+
+bool
+GMPParent::CanBeSharedCrossNodeIds() const
+{
+ return !mAsyncShutdownInProgress &&
+ mNodeId.IsEmpty() &&
+ // XXX bug 1159300 hack -- maybe remove after openh264 1.4
+ // We don't want to use CDM decoders for non-encrypted playback
+ // just yet; especially not for WebRTC. Don't allow CDMs to be used
+ // without a node ID.
+ !mCanDecrypt;
+}
+
+bool
+GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const
+{
+ return !mAsyncShutdownInProgress && mNodeId == aNodeId;
+}
+
+void
+GMPParent::SetNodeId(const nsACString& aNodeId)
+{
+ MOZ_ASSERT(!aNodeId.IsEmpty());
+ mNodeId = aNodeId;
+}
+
+const nsCString&
+GMPParent::GetDisplayName() const
+{
+ return mDisplayName;
+}
+
+const nsCString&
+GMPParent::GetVersion() const
+{
+ return mVersion;
+}
+
+uint32_t
+GMPParent::GetPluginId() const
+{
+ return mPluginId;
+}
+
+bool
+GMPParent::RecvAsyncShutdownRequired()
+{
+ LOGD("%s", __FUNCTION__);
+ if (mAsyncShutdownRequired) {
+ NS_WARNING("Received AsyncShutdownRequired message more than once!");
+ return true;
+ }
+ mAsyncShutdownRequired = true;
+ mService->AsyncShutdownNeeded(this);
+ return true;
+}
+
+bool
+GMPParent::RecvAsyncShutdownComplete()
+{
+ LOGD("%s", __FUNCTION__);
+
+ MOZ_ASSERT(mAsyncShutdownRequired);
+ AbortAsyncShutdown();
+ return true;
+}
+
+class RunCreateContentParentCallbacks : public Runnable
+{
+public:
+ explicit RunCreateContentParentCallbacks(GMPContentParent* aGMPContentParent)
+ : mGMPContentParent(aGMPContentParent)
+ {
+ }
+
+ void TakeCallbacks(nsTArray<UniquePtr<GetGMPContentParentCallback>>& aCallbacks)
+ {
+ mCallbacks.SwapElements(aCallbacks);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ for (uint32_t i = 0, length = mCallbacks.Length(); i < length; ++i) {
+ mCallbacks[i]->Done(mGMPContentParent);
+ }
+ return NS_OK;
+ }
+
+private:
+ RefPtr<GMPContentParent> mGMPContentParent;
+ nsTArray<UniquePtr<GetGMPContentParentCallback>> mCallbacks;
+};
+
+PGMPContentParent*
+GMPParent::AllocPGMPContentParent(Transport* aTransport, ProcessId aOtherPid)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ MOZ_ASSERT(!mGMPContentParent);
+
+ mGMPContentParent = new GMPContentParent(this);
+ mGMPContentParent->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(),
+ ipc::ParentSide);
+
+ RefPtr<RunCreateContentParentCallbacks> runCallbacks =
+ new RunCreateContentParentCallbacks(mGMPContentParent);
+ runCallbacks->TakeCallbacks(mCallbacks);
+ NS_DispatchToCurrentThread(runCallbacks);
+ MOZ_ASSERT(mCallbacks.IsEmpty());
+
+ return mGMPContentParent;
+}
+
+bool
+GMPParent::GetGMPContentParent(UniquePtr<GetGMPContentParentCallback>&& aCallback)
+{
+ LOGD("%s %p", __FUNCTION__, this);
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ if (mGMPContentParent) {
+ aCallback->Done(mGMPContentParent);
+ } else {
+ mCallbacks.AppendElement(Move(aCallback));
+ // If we don't have a GMPContentParent and we try to get one for the first
+ // time (mCallbacks.Length() == 1) then call PGMPContent::Open. If more
+ // calls to GetGMPContentParent happen before mGMPContentParent has been
+ // set then we should just store them, so that they get called when we set
+ // mGMPContentParent as a result of the PGMPContent::Open call.
+ if (mCallbacks.Length() == 1) {
+ if (!EnsureProcessLoaded() || !PGMPContent::Open(this)) {
+ return false;
+ }
+ // We want to increment this as soon as possible, to avoid that we'd try
+ // to shut down the GMP process while we're still trying to get a
+ // PGMPContentParent actor.
+ ++mGMPContentChildCount;
+ }
+ }
+ return true;
+}
+
+already_AddRefed<GMPContentParent>
+GMPParent::ForgetGMPContentParent()
+{
+ MOZ_ASSERT(mCallbacks.IsEmpty());
+ return Move(mGMPContentParent.forget());
+}
+
+bool
+GMPParent::EnsureProcessLoaded(base::ProcessId* aID)
+{
+ if (!EnsureProcessLoaded()) {
+ return false;
+ }
+ *aID = OtherPid();
+ return true;
+}
+
+bool
+GMPParent::Bridge(GMPServiceParent* aGMPServiceParent)
+{
+ if (NS_FAILED(PGMPContent::Bridge(aGMPServiceParent, this))) {
+ return false;
+ }
+ ++mGMPContentChildCount;
+ return true;
+}
+
+nsString
+GMPParent::GetPluginBaseName() const
+{
+ return NS_LITERAL_STRING("gmp-") + mName;
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+#undef LOG
+#undef LOGD
diff --git a/dom/media/gmp/GMPParent.h b/dom/media/gmp/GMPParent.h
new file mode 100644
index 000000000..dacd6feeb
--- /dev/null
+++ b/dom/media/gmp/GMPParent.h
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPParent_h_
+#define GMPParent_h_
+
+#include "GMPProcessParent.h"
+#include "GMPServiceParent.h"
+#include "GMPAudioDecoderParent.h"
+#include "GMPDecryptorParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "GMPVideoEncoderParent.h"
+#include "GMPTimerParent.h"
+#include "GMPStorageParent.h"
+#include "mozilla/gmp/PGMPParent.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIFile.h"
+#include "mozilla/MozPromise.h"
+
+class nsIThread;
+
+namespace mozilla {
+namespace gmp {
+
+class GMPCapability
+{
+public:
+ explicit GMPCapability() {}
+ GMPCapability(GMPCapability&& aOther)
+ : mAPIName(Move(aOther.mAPIName))
+ , mAPITags(Move(aOther.mAPITags))
+ {
+ }
+ explicit GMPCapability(const nsCString& aAPIName)
+ : mAPIName(aAPIName)
+ {}
+ explicit GMPCapability(const GMPCapability& aOther) = default;
+ nsCString mAPIName;
+ nsTArray<nsCString> mAPITags;
+
+ static bool Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags);
+
+ static bool Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsCString& aAPI,
+ const nsCString& aTag);
+};
+
+enum GMPState {
+ GMPStateNotLoaded,
+ GMPStateLoaded,
+ GMPStateUnloading,
+ GMPStateClosing
+};
+
+class GMPContentParent;
+
+class GetGMPContentParentCallback
+{
+public:
+ GetGMPContentParentCallback()
+ {
+ MOZ_COUNT_CTOR(GetGMPContentParentCallback);
+ };
+ virtual ~GetGMPContentParentCallback()
+ {
+ MOZ_COUNT_DTOR(GetGMPContentParentCallback);
+ };
+ virtual void Done(GMPContentParent* aGMPContentParent) = 0;
+};
+
+class GMPParent final : public PGMPParent
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPParent)
+
+ GMPParent();
+
+ RefPtr<GenericPromise> Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir);
+ nsresult CloneFrom(const GMPParent* aOther);
+
+ void Crash();
+
+ nsresult LoadProcess();
+
+ // Called internally to close this if we don't need it
+ void CloseIfUnused();
+
+ // Notify all active de/encoders that we are closing, either because of
+ // normal shutdown or unexpected shutdown/crash.
+ void CloseActive(bool aDieWhenUnloaded);
+
+ // Tell the plugin to die after shutdown.
+ void MarkForDeletion();
+ bool IsMarkedForDeletion();
+
+ // Called by the GMPService to forcibly close active de/encoders at shutdown
+ void Shutdown();
+
+ // This must not be called while we're in the middle of abnormal ActorDestroy
+ void DeleteProcess();
+
+ GMPState State() const;
+ nsIThread* GMPThread();
+
+ // A GMP can either be a single instance shared across all NodeIds (like
+ // in the OpenH264 case), or we can require a new plugin instance for every
+ // NodeIds running the plugin (as in the EME plugin case).
+ //
+ // A NodeId is a hash of the ($urlBarOrigin, $ownerDocOrigin) pair.
+ //
+ // Plugins are associated with an NodeIds by calling SetNodeId() before
+ // loading.
+ //
+ // If a plugin has no NodeId specified and it is loaded, it is assumed to
+ // be shared across NodeIds.
+
+ // Specifies that a GMP can only work with the specified NodeIds.
+ void SetNodeId(const nsACString& aNodeId);
+ const nsACString& GetNodeId() const { return mNodeId; }
+
+ const nsCString& GetDisplayName() const;
+ const nsCString& GetVersion() const;
+ uint32_t GetPluginId() const;
+ nsString GetPluginBaseName() const;
+
+ // Returns true if a plugin can be or is being used across multiple NodeIds.
+ bool CanBeSharedCrossNodeIds() const;
+
+ // A GMP can be used from a NodeId if it's already been set to work with
+ // that NodeId, or if it's not been set to work with any NodeId and has
+ // not yet been loaded (i.e. it's not shared across NodeIds).
+ bool CanBeUsedFrom(const nsACString& aNodeId) const;
+
+ already_AddRefed<nsIFile> GetDirectory() {
+ return nsCOMPtr<nsIFile>(mDirectory).forget();
+ }
+
+ void AbortAsyncShutdown();
+
+ // Called when the child process has died.
+ void ChildTerminated();
+
+ bool GetGMPContentParent(UniquePtr<GetGMPContentParentCallback>&& aCallback);
+ already_AddRefed<GMPContentParent> ForgetGMPContentParent();
+
+ bool EnsureProcessLoaded(base::ProcessId* aID);
+
+ bool Bridge(GMPServiceParent* aGMPServiceParent);
+
+ const nsTArray<GMPCapability>& GetCapabilities() const { return mCapabilities; }
+
+private:
+ ~GMPParent();
+
+ RefPtr<GeckoMediaPluginServiceParent> mService;
+ bool EnsureProcessLoaded();
+ RefPtr<GenericPromise> ReadGMPMetaData();
+ RefPtr<GenericPromise> ReadGMPInfoFile(nsIFile* aFile);
+ RefPtr<GenericPromise> ParseChromiumManifest(nsString aJSON); // Main thread.
+ RefPtr<GenericPromise> ReadChromiumManifestFile(nsIFile* aFile); // GMP thread.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ bool RecvPGMPStorageConstructor(PGMPStorageParent* actor) override;
+ PGMPStorageParent* AllocPGMPStorageParent() override;
+ bool DeallocPGMPStorageParent(PGMPStorageParent* aActor) override;
+
+ PGMPContentParent* AllocPGMPContentParent(Transport* aTransport,
+ ProcessId aOtherPid) override;
+
+ bool RecvPGMPTimerConstructor(PGMPTimerParent* actor) override;
+ PGMPTimerParent* AllocPGMPTimerParent() override;
+ bool DeallocPGMPTimerParent(PGMPTimerParent* aActor) override;
+
+ bool RecvAsyncShutdownComplete() override;
+ bool RecvAsyncShutdownRequired() override;
+
+ bool RecvPGMPContentChildDestroyed() override;
+ bool IsUsed()
+ {
+ return mGMPContentChildCount > 0;
+ }
+
+
+ static void AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure);
+ nsresult EnsureAsyncShutdownTimeoutSet();
+
+ GMPState mState;
+ nsCOMPtr<nsIFile> mDirectory; // plugin directory on disk
+ nsString mName; // base name of plugin on disk, UTF-16 because used for paths
+ nsCString mDisplayName; // name of plugin displayed to users
+ nsCString mDescription; // description of plugin for display to users
+ nsCString mVersion;
+#ifdef XP_WIN
+ nsCString mLibs;
+#endif
+ nsString mAdapter;
+ uint32_t mPluginId;
+ nsTArray<GMPCapability> mCapabilities;
+ GMPProcessParent* mProcess;
+ bool mDeleteProcessOnlyOnUnload;
+ bool mAbnormalShutdownInProgress;
+ bool mIsBlockingDeletion;
+
+ bool mCanDecrypt;
+
+ nsTArray<RefPtr<GMPTimerParent>> mTimers;
+ nsTArray<RefPtr<GMPStorageParent>> mStorage;
+ nsCOMPtr<nsIThread> mGMPThread;
+ nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only.
+ // NodeId the plugin is assigned to, or empty if the the plugin is not
+ // assigned to a NodeId.
+ nsCString mNodeId;
+ // This is used for GMP content in the parent, there may be more of these in
+ // the content processes.
+ RefPtr<GMPContentParent> mGMPContentParent;
+ nsTArray<UniquePtr<GetGMPContentParentCallback>> mCallbacks;
+ uint32_t mGMPContentChildCount;
+
+ bool mAsyncShutdownRequired;
+ bool mAsyncShutdownInProgress;
+
+ int mChildPid;
+
+ // We hold a self reference to ourself while the child process is alive.
+ // This ensures that if the GMPService tries to shut us down and drops
+ // its reference to us, we stay alive long enough for the child process
+ // to terminate gracefully.
+ bool mHoldingSelfRef;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPParent_h_
diff --git a/dom/media/gmp/GMPPlatform.cpp b/dom/media/gmp/GMPPlatform.cpp
new file mode 100644
index 000000000..71fa03468
--- /dev/null
+++ b/dom/media/gmp/GMPPlatform.cpp
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPPlatform.h"
+#include "GMPStorageChild.h"
+#include "GMPTimerChild.h"
+#include "mozilla/Monitor.h"
+#include "GMPChild.h"
+#include <ctime>
+
+namespace mozilla {
+namespace gmp {
+
+static MessageLoop* sMainLoop = nullptr;
+static GMPChild* sChild = nullptr;
+
+static bool
+IsOnChildMainThread()
+{
+ return sMainLoop && sMainLoop == MessageLoop::current();
+}
+
+// We just need a refcounted wrapper for GMPTask objects.
+class GMPRunnable final
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPRunnable)
+
+ explicit GMPRunnable(GMPTask* aTask)
+ : mTask(aTask)
+ {
+ MOZ_ASSERT(mTask);
+ }
+
+ void Run()
+ {
+ mTask->Run();
+ mTask->Destroy();
+ mTask = nullptr;
+ }
+
+private:
+ ~GMPRunnable()
+ {
+ }
+
+ GMPTask* mTask;
+};
+
+class GMPSyncRunnable final
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPSyncRunnable)
+
+ GMPSyncRunnable(GMPTask* aTask, MessageLoop* aMessageLoop)
+ : mDone(false)
+ , mTask(aTask)
+ , mMessageLoop(aMessageLoop)
+ , mMonitor("GMPSyncRunnable")
+ {
+ MOZ_ASSERT(mTask);
+ MOZ_ASSERT(mMessageLoop);
+ }
+
+ void Post()
+ {
+ // We assert here for two reasons.
+ // 1) Nobody should be blocking the main thread.
+ // 2) This prevents deadlocks when doing sync calls to main which if the
+ // main thread tries to do a sync call back to the calling thread.
+ MOZ_ASSERT(!IsOnChildMainThread());
+
+ mMessageLoop->PostTask(NewRunnableMethod(this, &GMPSyncRunnable::Run));
+ MonitorAutoLock lock(mMonitor);
+ while (!mDone) {
+ lock.Wait();
+ }
+ }
+
+ void Run()
+ {
+ mTask->Run();
+ mTask->Destroy();
+ mTask = nullptr;
+ MonitorAutoLock lock(mMonitor);
+ mDone = true;
+ lock.Notify();
+ }
+
+private:
+ ~GMPSyncRunnable()
+ {
+ }
+
+ bool mDone;
+ GMPTask* mTask;
+ MessageLoop* mMessageLoop;
+ Monitor mMonitor;
+};
+
+GMPErr
+CreateThread(GMPThread** aThread)
+{
+ if (!aThread) {
+ return GMPGenericErr;
+ }
+
+ *aThread = new GMPThreadImpl();
+
+ return GMPNoErr;
+}
+
+GMPErr
+RunOnMainThread(GMPTask* aTask)
+{
+ if (!aTask || !sMainLoop) {
+ return GMPGenericErr;
+ }
+
+ RefPtr<GMPRunnable> r = new GMPRunnable(aTask);
+ sMainLoop->PostTask(NewRunnableMethod(r, &GMPRunnable::Run));
+
+ return GMPNoErr;
+}
+
+GMPErr
+SyncRunOnMainThread(GMPTask* aTask)
+{
+ if (!aTask || !sMainLoop || IsOnChildMainThread()) {
+ return GMPGenericErr;
+ }
+
+ RefPtr<GMPSyncRunnable> r = new GMPSyncRunnable(aTask, sMainLoop);
+
+ r->Post();
+
+ return GMPNoErr;
+}
+
+GMPErr
+CreateMutex(GMPMutex** aMutex)
+{
+ if (!aMutex) {
+ return GMPGenericErr;
+ }
+
+ *aMutex = new GMPMutexImpl();
+
+ return GMPNoErr;
+}
+
+GMPErr
+CreateRecord(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE ||
+ aRecordNameSize == 0) {
+ NS_WARNING("GMP tried to CreateRecord with too long or 0 record name");
+ return GMPGenericErr;
+ }
+ GMPStorageChild* storage = sChild->GetGMPStorage();
+ if (!storage) {
+ return GMPGenericErr;
+ }
+ MOZ_ASSERT(storage);
+ return storage->CreateRecord(nsDependentCString(aRecordName, aRecordNameSize),
+ aOutRecord,
+ aClient);
+}
+
+GMPErr
+SetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS)
+{
+ if (!aTask || !sMainLoop || !IsOnChildMainThread()) {
+ return GMPGenericErr;
+ }
+ GMPTimerChild* timers = sChild->GetGMPTimers();
+ NS_ENSURE_TRUE(timers, GMPGenericErr);
+ return timers->SetTimer(aTask, aTimeoutMS);
+}
+
+GMPErr
+GetClock(GMPTimestamp* aOutTime)
+{
+ *aOutTime = time(0) * 1000;
+ return GMPNoErr;
+}
+
+GMPErr
+CreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg)
+{
+ if (!aRecvIteratorFunc) {
+ return GMPInvalidArgErr;
+ }
+ GMPStorageChild* storage = sChild->GetGMPStorage();
+ if (!storage) {
+ return GMPGenericErr;
+ }
+ MOZ_ASSERT(storage);
+ return storage->EnumerateRecords(aRecvIteratorFunc, aUserArg);
+}
+
+void
+InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild)
+{
+ if (!sMainLoop) {
+ sMainLoop = MessageLoop::current();
+ }
+ if (!sChild) {
+ sChild = aChild;
+ }
+
+ aPlatformAPI.version = 0;
+ aPlatformAPI.createthread = &CreateThread;
+ aPlatformAPI.runonmainthread = &RunOnMainThread;
+ aPlatformAPI.syncrunonmainthread = &SyncRunOnMainThread;
+ aPlatformAPI.createmutex = &CreateMutex;
+ aPlatformAPI.createrecord = &CreateRecord;
+ aPlatformAPI.settimer = &SetTimerOnMainThread;
+ aPlatformAPI.getcurrenttime = &GetClock;
+ aPlatformAPI.getrecordenumerator = &CreateRecordIterator;
+}
+
+GMPThreadImpl::GMPThreadImpl()
+: mMutex("GMPThreadImpl"),
+ mThread("GMPThread")
+{
+ MOZ_COUNT_CTOR(GMPThread);
+}
+
+GMPThreadImpl::~GMPThreadImpl()
+{
+ MOZ_COUNT_DTOR(GMPThread);
+}
+
+void
+GMPThreadImpl::Post(GMPTask* aTask)
+{
+ MutexAutoLock lock(mMutex);
+
+ if (!mThread.IsRunning()) {
+ bool started = mThread.Start();
+ if (!started) {
+ NS_WARNING("Unable to start GMPThread!");
+ return;
+ }
+ }
+
+ RefPtr<GMPRunnable> r = new GMPRunnable(aTask);
+ mThread.message_loop()->PostTask(NewRunnableMethod(r.get(), &GMPRunnable::Run));
+}
+
+void
+GMPThreadImpl::Join()
+{
+ {
+ MutexAutoLock lock(mMutex);
+ if (mThread.IsRunning()) {
+ mThread.Stop();
+ }
+ }
+ delete this;
+}
+
+GMPMutexImpl::GMPMutexImpl()
+: mMonitor("gmp-mutex")
+{
+ MOZ_COUNT_CTOR(GMPMutexImpl);
+}
+
+GMPMutexImpl::~GMPMutexImpl()
+{
+ MOZ_COUNT_DTOR(GMPMutexImpl);
+}
+
+void
+GMPMutexImpl::Destroy()
+{
+ delete this;
+}
+
+void
+GMPMutexImpl::Acquire()
+{
+ mMonitor.Enter();
+}
+
+void
+GMPMutexImpl::Release()
+{
+ mMonitor.Exit();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPPlatform.h b/dom/media/gmp/GMPPlatform.h
new file mode 100644
index 000000000..79079c327
--- /dev/null
+++ b/dom/media/gmp/GMPPlatform.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPPlatform_h_
+#define GMPPlatform_h_
+
+#include "mozilla/Mutex.h"
+#include "gmp-platform.h"
+#include "base/thread.h"
+#include "mozilla/ReentrantMonitor.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+
+void InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild);
+
+GMPErr RunOnMainThread(GMPTask* aTask);
+
+class GMPThreadImpl : public GMPThread
+{
+public:
+ GMPThreadImpl();
+ virtual ~GMPThreadImpl();
+
+ // GMPThread
+ void Post(GMPTask* aTask) override;
+ void Join() override;
+
+private:
+ Mutex mMutex;
+ base::Thread mThread;
+};
+
+class GMPMutexImpl : public GMPMutex
+{
+public:
+ GMPMutexImpl();
+ virtual ~GMPMutexImpl();
+
+ // GMPMutex
+ void Acquire() override;
+ void Release() override;
+ void Destroy() override;
+
+private:
+ ReentrantMonitor mMonitor;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPPlatform_h_
diff --git a/dom/media/gmp/GMPProcessChild.cpp b/dom/media/gmp/GMPProcessChild.cpp
new file mode 100644
index 000000000..294cabb42
--- /dev/null
+++ b/dom/media/gmp/GMPProcessChild.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPProcessChild.h"
+
+#include "base/command_line.h"
+#include "base/string_util.h"
+#include "mozilla/ipc/IOThreadChild.h"
+#include "mozilla/BackgroundHangMonitor.h"
+
+using mozilla::ipc::IOThreadChild;
+
+namespace mozilla {
+namespace gmp {
+
+GMPProcessChild::GMPProcessChild(ProcessId aParentPid)
+: ProcessChild(aParentPid)
+{
+}
+
+GMPProcessChild::~GMPProcessChild()
+{
+}
+
+bool
+GMPProcessChild::Init()
+{
+ nsAutoString pluginFilename;
+ nsAutoString voucherFilename;
+
+#if defined(OS_POSIX)
+ // NB: need to be very careful in ensuring that the first arg
+ // (after the binary name) here is indeed the plugin module path.
+ // Keep in sync with dom/plugins/PluginModuleParent.
+ std::vector<std::string> values = CommandLine::ForCurrentProcess()->argv();
+ MOZ_ASSERT(values.size() >= 3, "not enough args");
+ pluginFilename = NS_ConvertUTF8toUTF16(nsDependentCString(values[1].c_str()));
+ voucherFilename = NS_ConvertUTF8toUTF16(nsDependentCString(values[2].c_str()));
+#elif defined(OS_WIN)
+ std::vector<std::wstring> values = CommandLine::ForCurrentProcess()->GetLooseValues();
+ MOZ_ASSERT(values.size() >= 2, "not enough loose args");
+ pluginFilename = nsDependentString(values[0].c_str());
+ voucherFilename = nsDependentString(values[1].c_str());
+#else
+#error Not implemented
+#endif
+
+ BackgroundHangMonitor::Startup();
+
+ return mPlugin.Init(pluginFilename,
+ voucherFilename,
+ ParentPid(),
+ IOThreadChild::message_loop(),
+ IOThreadChild::channel());
+}
+
+void
+GMPProcessChild::CleanUp()
+{
+ BackgroundHangMonitor::Shutdown();
+}
+
+GMPLoader* GMPProcessChild::mLoader = nullptr;
+
+/* static */
+void
+GMPProcessChild::SetGMPLoader(GMPLoader* aLoader)
+{
+ mLoader = aLoader;
+}
+
+/* static */
+GMPLoader*
+GMPProcessChild::GetGMPLoader()
+{
+ return mLoader;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPProcessChild.h b/dom/media/gmp/GMPProcessChild.h
new file mode 100644
index 000000000..1a8df7653
--- /dev/null
+++ b/dom/media/gmp/GMPProcessChild.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPProcessChild_h_
+#define GMPProcessChild_h_
+
+#include "mozilla/ipc/ProcessChild.h"
+#include "GMPChild.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPLoader;
+
+class GMPProcessChild final : public mozilla::ipc::ProcessChild {
+protected:
+ typedef mozilla::ipc::ProcessChild ProcessChild;
+
+public:
+ explicit GMPProcessChild(ProcessId aParentPid);
+ ~GMPProcessChild();
+
+ bool Init() override;
+ void CleanUp() override;
+
+ // Set/get the GMPLoader singleton for this child process.
+ // Note: The GMPLoader is not deleted by this object, the caller of
+ // SetGMPLoader() must manage the GMPLoader's lifecycle.
+ static void SetGMPLoader(GMPLoader* aHost);
+ static GMPLoader* GetGMPLoader();
+
+private:
+ GMPChild mPlugin;
+ static GMPLoader* mLoader;
+ DISALLOW_COPY_AND_ASSIGN(GMPProcessChild);
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPProcessChild_h_
diff --git a/dom/media/gmp/GMPProcessParent.cpp b/dom/media/gmp/GMPProcessParent.cpp
new file mode 100644
index 000000000..ef58175e8
--- /dev/null
+++ b/dom/media/gmp/GMPProcessParent.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPProcessParent.h"
+#include "GMPUtils.h"
+#include "nsIFile.h"
+#include "nsIRunnable.h"
+
+#include "base/string_util.h"
+#include "base/process_util.h"
+
+#include <string>
+
+using std::vector;
+using std::string;
+
+using mozilla::gmp::GMPProcessParent;
+using mozilla::ipc::GeckoChildProcessHost;
+using base::ProcessArchitecture;
+
+namespace mozilla {
+
+extern LogModule* GetGMPLog();
+#define GMP_LOG(msg, ...) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+namespace gmp {
+
+GMPProcessParent::GMPProcessParent(const std::string& aGMPPath)
+: GeckoChildProcessHost(GeckoProcessType_GMPlugin),
+ mGMPPath(aGMPPath)
+{
+ MOZ_COUNT_CTOR(GMPProcessParent);
+}
+
+GMPProcessParent::~GMPProcessParent()
+{
+ MOZ_COUNT_DTOR(GMPProcessParent);
+}
+
+bool
+GMPProcessParent::Launch(int32_t aTimeoutMs)
+{
+ nsCOMPtr<nsIFile> path;
+ if (!GetEMEVoucherPath(getter_AddRefs(path))) {
+ NS_WARNING("GMPProcessParent can't get EME voucher path!");
+ return false;
+ }
+ nsAutoCString voucherPath;
+ path->GetNativePath(voucherPath);
+
+ vector<string> args;
+
+ args.push_back(mGMPPath);
+
+ args.push_back(string(voucherPath.BeginReading(), voucherPath.EndReading()));
+
+ return SyncLaunch(args, aTimeoutMs, base::GetCurrentProcessArchitecture());
+}
+
+void
+GMPProcessParent::Delete(nsCOMPtr<nsIRunnable> aCallback)
+{
+ mDeletedCallback = aCallback;
+ XRE_GetIOMessageLoop()->PostTask(NewNonOwningRunnableMethod(this, &GMPProcessParent::DoDelete));
+}
+
+void
+GMPProcessParent::DoDelete()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ Join();
+
+ if (mDeletedCallback) {
+ mDeletedCallback->Run();
+ }
+
+ delete this;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPProcessParent.h b/dom/media/gmp/GMPProcessParent.h
new file mode 100644
index 000000000..b94f203cf
--- /dev/null
+++ b/dom/media/gmp/GMPProcessParent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPProcessParent_h
+#define GMPProcessParent_h 1
+
+#include "mozilla/Attributes.h"
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/thread.h"
+#include "chrome/common/child_process_host.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace gmp {
+
+class GMPProcessParent final : public mozilla::ipc::GeckoChildProcessHost
+{
+public:
+ explicit GMPProcessParent(const std::string& aGMPPath);
+ ~GMPProcessParent();
+
+ // Synchronously launch the plugin process. If the process fails to launch
+ // after timeoutMs, this method will return false.
+ bool Launch(int32_t aTimeoutMs);
+
+ void Delete(nsCOMPtr<nsIRunnable> aCallback = nullptr);
+
+ bool CanShutdown() override { return true; }
+ const std::string& GetPluginFilePath() { return mGMPPath; }
+
+ using mozilla::ipc::GeckoChildProcessHost::GetChannel;
+ using mozilla::ipc::GeckoChildProcessHost::GetChildProcessHandle;
+
+private:
+ void DoDelete();
+
+ std::string mGMPPath;
+ nsCOMPtr<nsIRunnable> mDeletedCallback;
+
+ DISALLOW_COPY_AND_ASSIGN(GMPProcessParent);
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // ifndef GMPProcessParent_h
diff --git a/dom/media/gmp/GMPService.cpp b/dom/media/gmp/GMPService.cpp
new file mode 100644
index 000000000..1901210da
--- /dev/null
+++ b/dom/media/gmp/GMPService.cpp
@@ -0,0 +1,557 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPService.h"
+#include "GMPServiceParent.h"
+#include "GMPServiceChild.h"
+#include "GMPContentParent.h"
+#include "prio.h"
+#include "mozilla/Logging.h"
+#include "GMPParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "nsIObserverService.h"
+#include "GeckoChildProcessHost.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsXPCOMPrivate.h"
+#include "mozilla/Services.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIConsoleService.h"
+#include "mozilla/Unused.h"
+#include "GMPDecryptorParent.h"
+#include "GMPAudioDecoderParent.h"
+#include "nsComponentManagerUtils.h"
+#include "runnable_utils.h"
+#include "VideoUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/dom/PluginCrashedEvent.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+LogModule*
+GetGMPLog()
+{
+ static LazyLogModule sLog("GMP");
+ return sLog;
+}
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPService"
+
+namespace gmp {
+
+static StaticRefPtr<GeckoMediaPluginService> sSingletonService;
+
+class GMPServiceCreateHelper final : public mozilla::Runnable
+{
+ RefPtr<GeckoMediaPluginService> mService;
+
+public:
+ static already_AddRefed<GeckoMediaPluginService>
+ GetOrCreate()
+ {
+ RefPtr<GeckoMediaPluginService> service;
+
+ if (NS_IsMainThread()) {
+ service = GetOrCreateOnMainThread();
+ } else {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+
+ RefPtr<GMPServiceCreateHelper> createHelper = new GMPServiceCreateHelper();
+
+ mozilla::SyncRunnable::DispatchToThread(mainThread, createHelper, true);
+
+ service = createHelper->mService.forget();
+ }
+
+ return service.forget();
+ }
+
+private:
+ GMPServiceCreateHelper()
+ {
+ }
+
+ ~GMPServiceCreateHelper()
+ {
+ MOZ_ASSERT(!mService);
+ }
+
+ static already_AddRefed<GeckoMediaPluginService>
+ GetOrCreateOnMainThread()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sSingletonService) {
+ if (XRE_IsParentProcess()) {
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ new GeckoMediaPluginServiceParent();
+ service->Init();
+ sSingletonService = service;
+ } else {
+ RefPtr<GeckoMediaPluginServiceChild> service =
+ new GeckoMediaPluginServiceChild();
+ service->Init();
+ sSingletonService = service;
+ }
+
+ ClearOnShutdown(&sSingletonService);
+ }
+
+ RefPtr<GeckoMediaPluginService> service = sSingletonService.get();
+ return service.forget();
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mService = GetOrCreateOnMainThread();
+ return NS_OK;
+ }
+};
+
+already_AddRefed<GeckoMediaPluginService>
+GeckoMediaPluginService::GetGeckoMediaPluginService()
+{
+ return GMPServiceCreateHelper::GetOrCreate();
+}
+
+NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService, nsIObserver)
+
+GeckoMediaPluginService::GeckoMediaPluginService()
+ : mMutex("GeckoMediaPluginService::mMutex")
+ , mGMPThreadShutdown(false)
+ , mShuttingDownOnGMPThread(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+GeckoMediaPluginService::~GeckoMediaPluginService()
+{
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::RunPluginCrashCallbacks(uint32_t aPluginId,
+ const nsACString& aPluginName)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
+
+ nsAutoPtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers;
+ {
+ MutexAutoLock lock(mMutex);
+ mPluginCrashHelpers.RemoveAndForget(aPluginId, helpers);
+ }
+ if (!helpers) {
+ LOGD(("%s::%s(%i) No crash helpers, not handling crash.", __CLASS__, __FUNCTION__, aPluginId));
+ return NS_OK;
+ }
+
+ for (const auto& helper : *helpers) {
+ nsCOMPtr<nsPIDOMWindowInner> window = helper->GetPluginCrashedEventTarget();
+ if (NS_WARN_IF(!window)) {
+ continue;
+ }
+ nsCOMPtr<nsIDocument> document(window->GetExtantDoc());
+ if (NS_WARN_IF(!document)) {
+ continue;
+ }
+
+ dom::PluginCrashedEventInit init;
+ init.mPluginID = aPluginId;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ init.mGmpPlugin = true;
+ CopyUTF8toUTF16(aPluginName, init.mPluginName);
+ init.mSubmittedCrashReport = false;
+ RefPtr<dom::PluginCrashedEvent> event =
+ dom::PluginCrashedEvent::Constructor(document,
+ NS_LITERAL_STRING("PluginCrashed"),
+ init);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginService::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false));
+
+ // Kick off scanning for plugins
+ nsCOMPtr<nsIThread> thread;
+ return GetThread(getter_AddRefs(thread));
+}
+
+void
+GeckoMediaPluginService::ShutdownGMPThread()
+{
+ LOGD(("%s::%s", __CLASS__, __FUNCTION__));
+ nsCOMPtr<nsIThread> gmpThread;
+ {
+ MutexAutoLock lock(mMutex);
+ mGMPThreadShutdown = true;
+ mGMPThread.swap(gmpThread);
+ mAbstractGMPThread = nullptr;
+ }
+
+ if (gmpThread) {
+ gmpThread->Shutdown();
+ }
+}
+
+nsresult
+GeckoMediaPluginService::GMPDispatch(nsIRunnable* event,
+ uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> r(event);
+ return GMPDispatch(r.forget());
+}
+
+nsresult
+GeckoMediaPluginService::GMPDispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> r(event);
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = GetThread(getter_AddRefs(thread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return thread->Dispatch(r, flags);
+}
+
+// always call with getter_AddRefs, because it does
+NS_IMETHODIMP
+GeckoMediaPluginService::GetThread(nsIThread** aThread)
+{
+ MOZ_ASSERT(aThread);
+
+ // This can be called from any thread.
+ MutexAutoLock lock(mMutex);
+
+ if (!mGMPThread) {
+ // Don't allow the thread to be created after shutdown has started.
+ if (mGMPThreadShutdown) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mAbstractGMPThread = AbstractThread::CreateXPCOMThreadWrapper(mGMPThread, false);
+
+ // Tell the thread to initialize plugins
+ InitializePlugins(mAbstractGMPThread.get());
+ }
+
+ nsCOMPtr<nsIThread> copy = mGMPThread;
+ copy.forget(aThread);
+
+ return NS_OK;
+}
+
+RefPtr<AbstractThread>
+GeckoMediaPluginService::GetAbstractGMPThread()
+{
+ MutexAutoLock lock(mMutex);
+ return mAbstractGMPThread;
+}
+
+class GetGMPContentParentForAudioDecoderDone : public GetGMPContentParentCallback
+{
+public:
+ explicit GetGMPContentParentForAudioDecoderDone(UniquePtr<GetGMPAudioDecoderCallback>&& aCallback,
+ GMPCrashHelper* aHelper)
+ : mCallback(Move(aCallback))
+ , mHelper(aHelper)
+ {
+ }
+
+ void Done(GMPContentParent* aGMPParent) override
+ {
+ GMPAudioDecoderParent* gmpADP = nullptr;
+ if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPAudioDecoder(&gmpADP))) {
+ gmpADP->SetCrashHelper(mHelper);
+ }
+ mCallback->Done(gmpADP);
+ }
+
+private:
+ UniquePtr<GetGMPAudioDecoderCallback> mCallback;
+ RefPtr<GMPCrashHelper> mHelper;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPAudioDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPAudioDecoderCallback>&& aCallback)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<GetGMPContentParentCallback> callback(
+ new GetGMPContentParentForAudioDecoderDone(Move(aCallback), aHelper));
+ if (!GetContentParentFrom(aHelper,
+ aNodeId,
+ NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
+ *aTags,
+ Move(callback))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+class GetGMPContentParentForVideoDecoderDone : public GetGMPContentParentCallback
+{
+public:
+ explicit GetGMPContentParentForVideoDecoderDone(UniquePtr<GetGMPVideoDecoderCallback>&& aCallback,
+ GMPCrashHelper* aHelper,
+ uint32_t aDecryptorId)
+ : mCallback(Move(aCallback))
+ , mHelper(aHelper)
+ , mDecryptorId(aDecryptorId)
+ {
+ }
+
+ void Done(GMPContentParent* aGMPParent) override
+ {
+ GMPVideoDecoderParent* gmpVDP = nullptr;
+ GMPVideoHostImpl* videoHost = nullptr;
+ if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPVideoDecoder(&gmpVDP, mDecryptorId))) {
+ videoHost = &gmpVDP->Host();
+ gmpVDP->SetCrashHelper(mHelper);
+ }
+ mCallback->Done(gmpVDP, videoHost);
+ }
+
+private:
+ UniquePtr<GetGMPVideoDecoderCallback> mCallback;
+ RefPtr<GMPCrashHelper> mHelper;
+ const uint32_t mDecryptorId;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetDecryptingGMPVideoDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoDecoderCallback>&& aCallback,
+ uint32_t aDecryptorId)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<GetGMPContentParentCallback> callback(
+ new GetGMPContentParentForVideoDecoderDone(Move(aCallback), aHelper, aDecryptorId));
+ if (!GetContentParentFrom(aHelper,
+ aNodeId,
+ NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ *aTags,
+ Move(callback))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+class GetGMPContentParentForVideoEncoderDone : public GetGMPContentParentCallback
+{
+public:
+ explicit GetGMPContentParentForVideoEncoderDone(UniquePtr<GetGMPVideoEncoderCallback>&& aCallback,
+ GMPCrashHelper* aHelper)
+ : mCallback(Move(aCallback))
+ , mHelper(aHelper)
+ {
+ }
+
+ void Done(GMPContentParent* aGMPParent) override
+ {
+ GMPVideoEncoderParent* gmpVEP = nullptr;
+ GMPVideoHostImpl* videoHost = nullptr;
+ if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPVideoEncoder(&gmpVEP))) {
+ videoHost = &gmpVEP->Host();
+ gmpVEP->SetCrashHelper(mHelper);
+ }
+ mCallback->Done(gmpVEP, videoHost);
+ }
+
+private:
+ UniquePtr<GetGMPVideoEncoderCallback> mCallback;
+ RefPtr<GMPCrashHelper> mHelper;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPVideoEncoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoEncoderCallback>&& aCallback)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<GetGMPContentParentCallback> callback(
+ new GetGMPContentParentForVideoEncoderDone(Move(aCallback), aHelper));
+ if (!GetContentParentFrom(aHelper,
+ aNodeId,
+ NS_LITERAL_CSTRING(GMP_API_VIDEO_ENCODER),
+ *aTags,
+ Move(callback))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+class GetGMPContentParentForDecryptorDone : public GetGMPContentParentCallback
+{
+public:
+ explicit GetGMPContentParentForDecryptorDone(UniquePtr<GetGMPDecryptorCallback>&& aCallback,
+ GMPCrashHelper* aHelper)
+ : mCallback(Move(aCallback))
+ , mHelper(aHelper)
+ {
+ }
+
+ void Done(GMPContentParent* aGMPParent) override
+ {
+ GMPDecryptorParent* ksp = nullptr;
+ if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPDecryptor(&ksp))) {
+ ksp->SetCrashHelper(mHelper);
+ }
+ mCallback->Done(ksp);
+ }
+
+private:
+ UniquePtr<GetGMPDecryptorCallback> mCallback;
+ RefPtr<GMPCrashHelper> mHelper;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPDecryptor(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPDecryptorCallback>&& aCallback)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<GetGMPContentParentCallback> callback(
+ new GetGMPContentParentForDecryptorDone(Move(aCallback), aHelper));
+ if (!GetContentParentFrom(aHelper,
+ aNodeId,
+ NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
+ *aTags,
+ Move(callback))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId, GMPCrashHelper* aHelper)
+{
+ if (!aHelper) {
+ return;
+ }
+ MutexAutoLock lock(mMutex);
+ nsTArray<RefPtr<GMPCrashHelper>>* helpers;
+ if (!mPluginCrashHelpers.Get(aPluginId, &helpers)) {
+ helpers = new nsTArray<RefPtr<GMPCrashHelper>>();
+ mPluginCrashHelpers.Put(aPluginId, helpers);
+ } else if (helpers->Contains(aHelper)) {
+ return;
+ }
+ helpers->AppendElement(aHelper);
+}
+
+void GeckoMediaPluginService::DisconnectCrashHelper(GMPCrashHelper* aHelper)
+{
+ if (!aHelper) {
+ return;
+ }
+ MutexAutoLock lock(mMutex);
+ for (auto iter = mPluginCrashHelpers.Iter(); !iter.Done(); iter.Next()) {
+ nsTArray<RefPtr<GMPCrashHelper>>* helpers = iter.Data();
+ if (!helpers->Contains(aHelper)) {
+ continue;
+ }
+ helpers->RemoveElement(aHelper);
+ MOZ_ASSERT(!helpers->Contains(aHelper)); // Ensure there aren't duplicates.
+ if (helpers->IsEmpty()) {
+ iter.Remove();
+ }
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+NS_IMPL_ADDREF(GMPCrashHelper)
+NS_IMPL_RELEASE_WITH_DESTROY(GMPCrashHelper, Destroy())
+
+void
+GMPCrashHelper::Destroy()
+{
+ if (NS_IsMainThread()) {
+ delete this;
+ } else {
+ // Don't addref, as then we'd end up releasing after the detele runs!
+ NS_DispatchToMainThread(mozilla::NewNonOwningRunnableMethod(this, &GMPCrashHelper::Destroy));
+ }
+}
diff --git a/dom/media/gmp/GMPService.h b/dom/media/gmp/GMPService.h
new file mode 100644
index 000000000..7ed318a25
--- /dev/null
+++ b/dom/media/gmp/GMPService.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPService_h_
+#define GMPService_h_
+
+#include "nsString.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Monitor.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsIWeakReference.h"
+#include "mozilla/AbstractThread.h"
+#include "nsClassHashtable.h"
+#include "nsISupportsImpl.h"
+
+template <class> struct already_AddRefed;
+
+// For every GMP actor requested, the caller can specify a crash helper,
+// which is an object which supplies the nsPIDOMWindowInner to which we'll
+// dispatch the PluginCrashed event if the GMP crashes.
+// GMPCrashHelper has threadsafe refcounting. Its release method ensures
+// that instances are destroyed on the main thread.
+class GMPCrashHelper
+{
+public:
+ NS_METHOD_(MozExternalRefCountType) AddRef(void);
+ NS_METHOD_(MozExternalRefCountType) Release(void);
+
+ // Called on the main thread.
+ virtual already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() = 0;
+
+protected:
+ virtual ~GMPCrashHelper()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ void Destroy();
+ mozilla::ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+namespace mozilla {
+
+extern LogModule* GetGMPLog();
+
+namespace gmp {
+
+class GetGMPContentParentCallback;
+
+class GeckoMediaPluginService : public mozIGeckoMediaPluginService
+ , public nsIObserver
+{
+public:
+ static already_AddRefed<GeckoMediaPluginService> GetGeckoMediaPluginService();
+
+ virtual nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // mozIGeckoMediaPluginService
+ NS_IMETHOD GetThread(nsIThread** aThread) override;
+ NS_IMETHOD GetDecryptingGMPVideoDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoDecoderCallback>&& aCallback,
+ uint32_t aDecryptorId)
+ override;
+ NS_IMETHOD GetGMPVideoEncoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoEncoderCallback>&& aCallback)
+ override;
+ NS_IMETHOD GetGMPAudioDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPAudioDecoderCallback>&& aCallback)
+ override;
+ NS_IMETHOD GetGMPDecryptor(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPDecryptorCallback>&& aCallback)
+ override;
+
+ // Helper for backwards compatibility with WebRTC/tests.
+ NS_IMETHOD
+ GetGMPVideoDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoDecoderCallback>&& aCallback) override
+ {
+ return GetDecryptingGMPVideoDecoder(aHelper, aTags, aNodeId, Move(aCallback), 0);
+ }
+
+ int32_t AsyncShutdownTimeoutMs();
+
+ NS_IMETHOD RunPluginCrashCallbacks(uint32_t aPluginId,
+ const nsACString& aPluginName) override;
+
+ RefPtr<AbstractThread> GetAbstractGMPThread();
+
+ void ConnectCrashHelper(uint32_t aPluginId, GMPCrashHelper* aHelper);
+ void DisconnectCrashHelper(GMPCrashHelper* aHelper);
+
+protected:
+ GeckoMediaPluginService();
+ virtual ~GeckoMediaPluginService();
+
+ virtual void InitializePlugins(AbstractThread* aAbstractGMPThread) = 0;
+ virtual bool GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback) = 0;
+
+ nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
+ nsresult GMPDispatch(already_AddRefed<nsIRunnable> event, uint32_t flags = NS_DISPATCH_NORMAL);
+ void ShutdownGMPThread();
+
+ Mutex mMutex; // Protects mGMPThread, mAbstractGMPThread, mPluginCrashHelpers,
+ // mGMPThreadShutdown and some members in derived classes.
+ nsCOMPtr<nsIThread> mGMPThread;
+ RefPtr<AbstractThread> mAbstractGMPThread;
+ bool mGMPThreadShutdown;
+ bool mShuttingDownOnGMPThread;
+
+ nsClassHashtable<nsUint32HashKey, nsTArray<RefPtr<GMPCrashHelper>>> mPluginCrashHelpers;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPService_h_
diff --git a/dom/media/gmp/GMPServiceChild.cpp b/dom/media/gmp/GMPServiceChild.cpp
new file mode 100644
index 000000000..08599039f
--- /dev/null
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -0,0 +1,478 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPServiceChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozIGeckoMediaPluginChromeService.h"
+#include "nsCOMPtr.h"
+#include "GMPParent.h"
+#include "GMPContentParent.h"
+#include "nsXPCOMPrivate.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/StaticMutex.h"
+#include "runnable_utils.h"
+#include "base/task.h"
+#include "nsIObserverService.h"
+#include "nsComponentManagerUtils.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPService"
+
+namespace gmp {
+
+already_AddRefed<GeckoMediaPluginServiceChild>
+GeckoMediaPluginServiceChild::GetSingleton()
+{
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ RefPtr<GeckoMediaPluginService> service(
+ GeckoMediaPluginService::GetGeckoMediaPluginService());
+#ifdef DEBUG
+ if (service) {
+ nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService;
+ CallQueryInterface(service.get(), getter_AddRefs(chromeService));
+ MOZ_ASSERT(!chromeService);
+ }
+#endif
+ return service.forget().downcast<GeckoMediaPluginServiceChild>();
+}
+
+class GetContentParentFromDone : public GetServiceChildCallback
+{
+public:
+ GetContentParentFromDone(GMPCrashHelper* aHelper, const nsACString& aNodeId, const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+ : mHelper(aHelper),
+ mNodeId(aNodeId),
+ mAPI(aAPI),
+ mTags(aTags),
+ mCallback(Move(aCallback))
+ {
+ }
+
+ void Done(GMPServiceChild* aGMPServiceChild) override
+ {
+ if (!aGMPServiceChild) {
+ mCallback->Done(nullptr);
+ return;
+ }
+
+ uint32_t pluginId;
+ nsresult rv;
+ bool ok = aGMPServiceChild->SendSelectGMP(mNodeId, mAPI, mTags, &pluginId, &rv);
+ if (!ok || rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) {
+ mCallback->Done(nullptr);
+ return;
+ }
+
+ if (mHelper) {
+ RefPtr<GeckoMediaPluginService> gmps(GeckoMediaPluginService::GetGeckoMediaPluginService());
+ gmps->ConnectCrashHelper(pluginId, mHelper);
+ }
+
+ nsTArray<base::ProcessId> alreadyBridgedTo;
+ aGMPServiceChild->GetAlreadyBridgedTo(alreadyBridgedTo);
+
+ base::ProcessId otherProcess;
+ nsCString displayName;
+ ok = aGMPServiceChild->SendLaunchGMP(pluginId, alreadyBridgedTo, &otherProcess,
+ &displayName, &rv);
+ if (!ok || rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) {
+ mCallback->Done(nullptr);
+ return;
+ }
+
+ RefPtr<GMPContentParent> parent;
+ aGMPServiceChild->GetBridgedGMPContentParent(otherProcess,
+ getter_AddRefs(parent));
+ if (!alreadyBridgedTo.Contains(otherProcess)) {
+ parent->SetDisplayName(displayName);
+ parent->SetPluginId(pluginId);
+ }
+
+ mCallback->Done(parent);
+ }
+
+private:
+ RefPtr<GMPCrashHelper> mHelper;
+ nsCString mNodeId;
+ nsCString mAPI;
+ const nsTArray<nsCString> mTags;
+ UniquePtr<GetGMPContentParentCallback> mCallback;
+};
+
+bool
+GeckoMediaPluginServiceChild::GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ UniquePtr<GetServiceChildCallback> callback(
+ new GetContentParentFromDone(aHelper, aNodeId, aAPI, aTags, Move(aCallback)));
+ GetServiceChild(Move(callback));
+
+ return true;
+}
+
+typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
+typedef mozilla::dom::GMPAPITags GMPAPITags;
+
+struct GMPCapabilityAndVersion
+{
+ explicit GMPCapabilityAndVersion(const GMPCapabilityData& aCapabilities)
+ : mName(aCapabilities.name())
+ , mVersion(aCapabilities.version())
+ {
+ for (const GMPAPITags& tags : aCapabilities.capabilities()) {
+ GMPCapability cap;
+ cap.mAPIName = tags.api();
+ for (const nsCString& tag : tags.tags()) {
+ cap.mAPITags.AppendElement(tag);
+ }
+ mCapabilities.AppendElement(Move(cap));
+ }
+ }
+
+ nsCString ToString() const
+ {
+ nsCString s;
+ s.Append(mName);
+ s.Append(" version=");
+ s.Append(mVersion);
+ s.Append(" tags=[");
+ nsCString tags;
+ for (const GMPCapability& cap : mCapabilities) {
+ if (!tags.IsEmpty()) {
+ tags.Append(" ");
+ }
+ tags.Append(cap.mAPIName);
+ for (const nsCString& tag : cap.mAPITags) {
+ tags.Append(":");
+ tags.Append(tag);
+ }
+ }
+ s.Append(tags);
+ s.Append("]");
+ return s;
+ }
+
+ nsCString mName;
+ nsCString mVersion;
+ nsTArray<GMPCapability> mCapabilities;
+};
+
+StaticMutex sGMPCapabilitiesMutex;
+StaticAutoPtr<nsTArray<GMPCapabilityAndVersion>> sGMPCapabilities;
+
+static nsCString
+GMPCapabilitiesToString()
+{
+ nsCString s;
+ for (const GMPCapabilityAndVersion& gmp : *sGMPCapabilities) {
+ if (!s.IsEmpty()) {
+ s.Append(", ");
+ }
+ s.Append(gmp.ToString());
+ }
+ return s;
+}
+
+/* static */
+void
+GeckoMediaPluginServiceChild::UpdateGMPCapabilities(nsTArray<GMPCapabilityData>&& aCapabilities)
+{
+ {
+ // The mutex should unlock before sending the "gmp-changed" observer service notification.
+ StaticMutexAutoLock lock(sGMPCapabilitiesMutex);
+ if (!sGMPCapabilities) {
+ sGMPCapabilities = new nsTArray<GMPCapabilityAndVersion>();
+ ClearOnShutdown(&sGMPCapabilities);
+ }
+ sGMPCapabilities->Clear();
+ for (const GMPCapabilityData& plugin : aCapabilities) {
+ sGMPCapabilities->AppendElement(GMPCapabilityAndVersion(plugin));
+ }
+
+ LOGD(("UpdateGMPCapabilities {%s}", GMPCapabilitiesToString().get()));
+ }
+
+ // Fire a notification so that any MediaKeySystemAccess
+ // requests waiting on a CDM to download will retry.
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
+ }
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool* aHasPlugin)
+{
+ StaticMutexAutoLock lock(sGMPCapabilitiesMutex);
+ if (!sGMPCapabilities) {
+ *aHasPlugin = false;
+ return NS_OK;
+ }
+
+ nsCString api(aAPI);
+ for (const GMPCapabilityAndVersion& plugin : *sGMPCapabilities) {
+ if (GMPCapability::Supports(plugin.mCapabilities, api, *aTags)) {
+ *aHasPlugin = true;
+ return NS_OK;
+ }
+ }
+
+ *aHasPlugin = false;
+ return NS_OK;
+}
+
+class GetNodeIdDone : public GetServiceChildCallback
+{
+public:
+ GetNodeIdDone(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing, UniquePtr<GetNodeIdCallback>&& aCallback)
+ : mOrigin(aOrigin),
+ mTopLevelOrigin(aTopLevelOrigin),
+ mGMPName(aGMPName),
+ mInPrivateBrowsing(aInPrivateBrowsing),
+ mCallback(Move(aCallback))
+ {
+ }
+
+ void Done(GMPServiceChild* aGMPServiceChild) override
+ {
+ if (!aGMPServiceChild) {
+ mCallback->Done(NS_ERROR_FAILURE, EmptyCString());
+ return;
+ }
+
+ nsCString outId;
+ if (!aGMPServiceChild->SendGetGMPNodeId(mOrigin, mTopLevelOrigin,
+ mGMPName,
+ mInPrivateBrowsing, &outId)) {
+ mCallback->Done(NS_ERROR_FAILURE, EmptyCString());
+ return;
+ }
+
+ mCallback->Done(NS_OK, outId);
+ }
+
+private:
+ nsString mOrigin;
+ nsString mTopLevelOrigin;
+ nsString mGMPName;
+ bool mInPrivateBrowsing;
+ UniquePtr<GetNodeIdCallback> mCallback;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing,
+ UniquePtr<GetNodeIdCallback>&& aCallback)
+{
+ UniquePtr<GetServiceChildCallback> callback(
+ new GetNodeIdDone(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, Move(aCallback)));
+ GetServiceChild(Move(callback));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData)
+{
+ LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic));
+ if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
+ if (mServiceChild) {
+ mozilla::SyncRunnable::DispatchToThread(mGMPThread,
+ WrapRunnable(mServiceChild.get(),
+ &PGMPServiceChild::Close));
+ mServiceChild = nullptr;
+ }
+ ShutdownGMPThread();
+ }
+
+ return NS_OK;
+}
+
+void
+GeckoMediaPluginServiceChild::GetServiceChild(UniquePtr<GetServiceChildCallback>&& aCallback)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (!mServiceChild) {
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ if (!contentChild) {
+ return;
+ }
+ mGetServiceChildCallbacks.AppendElement(Move(aCallback));
+ if (mGetServiceChildCallbacks.Length() == 1) {
+ NS_DispatchToMainThread(WrapRunnable(contentChild,
+ &dom::ContentChild::SendCreateGMPService));
+ }
+ return;
+ }
+
+ aCallback->Done(mServiceChild.get());
+}
+
+void
+GeckoMediaPluginServiceChild::SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild)
+{
+ mServiceChild = Move(aServiceChild);
+ nsTArray<UniquePtr<GetServiceChildCallback>> getServiceChildCallbacks;
+ getServiceChildCallbacks.SwapElements(mGetServiceChildCallbacks);
+ for (uint32_t i = 0, length = getServiceChildCallbacks.Length(); i < length; ++i) {
+ getServiceChildCallbacks[i]->Done(mServiceChild.get());
+ }
+}
+
+void
+GeckoMediaPluginServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent)
+{
+ if (mServiceChild) {
+ mServiceChild->RemoveGMPContentParent(aGMPContentParent);
+ }
+}
+
+GMPServiceChild::GMPServiceChild()
+{
+}
+
+GMPServiceChild::~GMPServiceChild()
+{
+}
+
+PGMPContentParent*
+GMPServiceChild::AllocPGMPContentParent(Transport* aTransport,
+ ProcessId aOtherPid)
+{
+ MOZ_ASSERT(!mContentParents.GetWeak(aOtherPid));
+
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+
+ RefPtr<GMPContentParent> parent = new GMPContentParent();
+
+ DebugOnly<bool> ok = parent->Open(aTransport, aOtherPid,
+ XRE_GetIOMessageLoop(),
+ mozilla::ipc::ParentSide);
+ MOZ_ASSERT(ok);
+
+ mContentParents.Put(aOtherPid, parent);
+ return parent;
+}
+
+void
+GMPServiceChild::GetBridgedGMPContentParent(ProcessId aOtherPid,
+ GMPContentParent** aGMPContentParent)
+{
+ mContentParents.Get(aOtherPid, aGMPContentParent);
+}
+
+void
+GMPServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent)
+{
+ for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<GMPContentParent>& parent = iter.Data();
+ if (parent == aGMPContentParent) {
+ iter.Remove();
+ break;
+ }
+ }
+}
+
+void
+GMPServiceChild::GetAlreadyBridgedTo(nsTArray<base::ProcessId>& aAlreadyBridgedTo)
+{
+ aAlreadyBridgedTo.SetCapacity(mContentParents.Count());
+ for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) {
+ const uint64_t& id = iter.Key();
+ aAlreadyBridgedTo.AppendElement(id);
+ }
+}
+
+class OpenPGMPServiceChild : public mozilla::Runnable
+{
+public:
+ OpenPGMPServiceChild(UniquePtr<GMPServiceChild>&& aGMPServiceChild,
+ mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherPid)
+ : mGMPServiceChild(Move(aGMPServiceChild)),
+ mTransport(aTransport),
+ mOtherPid(aOtherPid)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ RefPtr<GeckoMediaPluginServiceChild> gmp =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_ASSERT(!gmp->mServiceChild);
+ if (mGMPServiceChild->Open(mTransport, mOtherPid, XRE_GetIOMessageLoop(),
+ ipc::ChildSide)) {
+ gmp->SetServiceChild(Move(mGMPServiceChild));
+ } else {
+ gmp->SetServiceChild(nullptr);
+ }
+ return NS_OK;
+ }
+
+private:
+ UniquePtr<GMPServiceChild> mGMPServiceChild;
+ mozilla::ipc::Transport* mTransport;
+ base::ProcessId mOtherPid;
+};
+
+/* static */
+PGMPServiceChild*
+GMPServiceChild::Create(Transport* aTransport, ProcessId aOtherPid)
+{
+ RefPtr<GeckoMediaPluginServiceChild> gmp =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_ASSERT(!gmp->mServiceChild);
+
+ UniquePtr<GMPServiceChild> serviceChild(new GMPServiceChild());
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ GMPServiceChild* result = serviceChild.get();
+ rv = gmpThread->Dispatch(new OpenPGMPServiceChild(Move(serviceChild),
+ aTransport,
+ aOtherPid),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPServiceChild.h b/dom/media/gmp/GMPServiceChild.h
new file mode 100644
index 000000000..63b1325bb
--- /dev/null
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPServiceChild_h_
+#define GMPServiceChild_h_
+
+#include "GMPService.h"
+#include "base/process.h"
+#include "mozilla/ipc/Transport.h"
+#include "mozilla/gmp/PGMPServiceChild.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentParent;
+class GMPServiceChild;
+
+class GetServiceChildCallback
+{
+public:
+ GetServiceChildCallback()
+ {
+ MOZ_COUNT_CTOR(GetServiceChildCallback);
+ }
+ virtual ~GetServiceChildCallback()
+ {
+ MOZ_COUNT_DTOR(GetServiceChildCallback);
+ }
+ virtual void Done(GMPServiceChild* aGMPServiceChild) = 0;
+};
+
+class GeckoMediaPluginServiceChild : public GeckoMediaPluginService
+{
+ friend class GMPServiceChild;
+
+public:
+ static already_AddRefed<GeckoMediaPluginServiceChild> GetSingleton();
+
+ NS_IMETHOD HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool *aRetVal) override;
+ NS_IMETHOD GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsingMode,
+ UniquePtr<GetNodeIdCallback>&& aCallback) override;
+
+ NS_DECL_NSIOBSERVER
+
+ void SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild);
+
+ void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
+
+ static void UpdateGMPCapabilities(nsTArray<mozilla::dom::GMPCapabilityData>&& aCapabilities);
+
+protected:
+ void InitializePlugins(AbstractThread*) override
+ {
+ // Nothing to do here.
+ }
+ bool GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+ override;
+
+private:
+ friend class OpenPGMPServiceChild;
+
+ void GetServiceChild(UniquePtr<GetServiceChildCallback>&& aCallback);
+
+ UniquePtr<GMPServiceChild> mServiceChild;
+ nsTArray<UniquePtr<GetServiceChildCallback>> mGetServiceChildCallbacks;
+};
+
+class GMPServiceChild : public PGMPServiceChild
+{
+public:
+ explicit GMPServiceChild();
+ virtual ~GMPServiceChild();
+
+ PGMPContentParent* AllocPGMPContentParent(Transport* aTransport,
+ ProcessId aOtherPid) override;
+
+ void GetBridgedGMPContentParent(ProcessId aOtherPid,
+ GMPContentParent** aGMPContentParent);
+ void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
+
+ void GetAlreadyBridgedTo(nsTArray<ProcessId>& aAlreadyBridgedTo);
+
+ static PGMPServiceChild* Create(Transport* aTransport, ProcessId aOtherPid);
+
+private:
+ nsRefPtrHashtable<nsUint64HashKey, GMPContentParent> mContentParents;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPServiceChild_h_
diff --git a/dom/media/gmp/GMPServiceParent.cpp b/dom/media/gmp/GMPServiceParent.cpp
new file mode 100644
index 000000000..a4afbdad4
--- /dev/null
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -0,0 +1,1933 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPServiceParent.h"
+#include "GMPService.h"
+#include "prio.h"
+#include "base/task.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/ContentParent.h"
+#include "GMPParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "nsAutoPtr.h"
+#include "nsIObserverService.h"
+#include "GeckoChildProcessHost.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsXPCOMPrivate.h"
+#include "mozilla/Services.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIConsoleService.h"
+#include "mozilla/Unused.h"
+#include "GMPDecryptorParent.h"
+#include "GMPAudioDecoderParent.h"
+#include "nsComponentManagerUtils.h"
+#include "runnable_utils.h"
+#include "VideoUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIXULRuntime.h"
+#include "GMPDecoderModule.h"
+#include <limits>
+#include "MediaPrefs.h"
+
+using mozilla::ipc::Transport;
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPService"
+
+namespace gmp {
+
+static const uint32_t NodeIdSaltLength = 32;
+
+already_AddRefed<GeckoMediaPluginServiceParent>
+GeckoMediaPluginServiceParent::GetSingleton()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ RefPtr<GeckoMediaPluginService> service(
+ GeckoMediaPluginServiceParent::GetGeckoMediaPluginService());
+#ifdef DEBUG
+ if (service) {
+ nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService;
+ CallQueryInterface(service.get(), getter_AddRefs(chromeService));
+ MOZ_ASSERT(chromeService);
+ }
+#endif
+ return service.forget().downcast<GeckoMediaPluginServiceParent>();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPluginServiceParent,
+ GeckoMediaPluginService,
+ mozIGeckoMediaPluginChromeService,
+ nsIAsyncShutdownBlocker)
+
+GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent()
+ : mShuttingDown(false)
+ , mScannedPluginOnDisk(false)
+ , mWaitingForPluginsSyncShutdown(false)
+ , mInitPromiseMonitor("GeckoMediaPluginServiceParent::mInitPromiseMonitor")
+ , mLoadPluginsFromDiskComplete(false)
+ , mServiceUserCount(0)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mInitPromise.SetMonitor(&mInitPromiseMonitor);
+}
+
+GeckoMediaPluginServiceParent::~GeckoMediaPluginServiceParent()
+{
+ MOZ_ASSERT(mPlugins.IsEmpty());
+ MOZ_ASSERT(mAsyncShutdownPlugins.IsEmpty());
+}
+
+int32_t
+GeckoMediaPluginServiceParent::AsyncShutdownTimeoutMs()
+{
+ return MediaPrefs::GMPAsyncShutdownTimeout();
+}
+
+nsresult
+GeckoMediaPluginServiceParent::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "profile-change-teardown", false));
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "last-pb-context-exited", false));
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "browser:purge-session-history", false));
+
+#ifdef DEBUG
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "mediakeys-request", false));
+#endif
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->AddObserver("media.gmp.plugin.crash", this, false);
+ }
+
+ nsresult rv = InitStorage();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Kick off scanning for plugins
+ nsCOMPtr<nsIThread> thread;
+ rv = GetThread(getter_AddRefs(thread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Detect if GMP storage has an incompatible version, and if so nuke it.
+ int32_t version = Preferences::GetInt("media.gmp.storage.version.observed", 0);
+ int32_t expected = Preferences::GetInt("media.gmp.storage.version.expected", 0);
+ if (version != expected) {
+ Preferences::SetInt("media.gmp.storage.version.observed", expected);
+ return GMPDispatch(NewRunnableMethod(
+ this, &GeckoMediaPluginServiceParent::ClearStorage));
+ }
+ return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+CloneAndAppend(nsIFile* aFile, const nsAString& aDir)
+{
+ nsCOMPtr<nsIFile> f;
+ nsresult rv = aFile->Clone(getter_AddRefs(f));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ rv = f->Append(aDir);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return f.forget();
+}
+
+static nsresult
+GMPPlatformString(nsAString& aOutPlatform)
+{
+ // Append the OS and arch so that we don't reuse the storage if the profile is
+ // copied or used under a different bit-ness, or copied to another platform.
+ nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+ if (!runtime) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString OS;
+ nsresult rv = runtime->GetOS(OS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString arch;
+ rv = runtime->GetXPCOMABI(arch);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString platform;
+ platform.Append(OS);
+ platform.AppendLiteral("_");
+ platform.Append(arch);
+
+ aOutPlatform = NS_ConvertUTF8toUTF16(platform);
+
+ return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginServiceParent::InitStorage()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // GMP storage should be used in the chrome process only.
+ if (!XRE_IsParentProcess()) {
+ return NS_OK;
+ }
+
+ // Directory service is main thread only, so cache the profile dir here
+ // so that we can use it off main thread.
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mStorageBaseDir));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->AppendNative(NS_LITERAL_CSTRING("gmp"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> gmpDirWithoutPlatform;
+ rv = mStorageBaseDir->Clone(getter_AddRefs(gmpDirWithoutPlatform));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString platform;
+ rv = GMPPlatformString(platform);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->Append(platform);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) {
+ return rv;
+ }
+
+ return GeckoMediaPluginService::Init();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData)
+{
+ LOGD(("%s::%s topic='%s' data='%s'", __CLASS__, __FUNCTION__,
+ aTopic, NS_ConvertUTF16toUTF8(aSomeData).get()));
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
+ if (branch) {
+ bool crashNow = false;
+ if (NS_LITERAL_STRING("media.gmp.plugin.crash").Equals(aSomeData)) {
+ branch->GetBoolPref("media.gmp.plugin.crash", &crashNow);
+ }
+ if (crashNow) {
+ nsCOMPtr<nsIThread> gmpThread;
+ {
+ MutexAutoLock lock(mMutex);
+ gmpThread = mGMPThread;
+ }
+ if (gmpThread) {
+ gmpThread->Dispatch(WrapRunnable(this,
+ &GeckoMediaPluginServiceParent::CrashPlugins),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ }
+ } else if (!strcmp("profile-change-teardown", aTopic)) {
+
+ // How shutdown works:
+ //
+ // Some GMPs require time to do bookkeeping upon shutdown. These GMPs
+ // need to be given time to access storage during shutdown. To signal
+ // that time to shutdown is required, those GMPs implement the
+ // GMPAsyncShutdown interface.
+ //
+ // When we startup the child process, we query the GMP for the
+ // GMPAsyncShutdown interface, and if it's present, we send a message
+ // back to the GMPParent, which then registers the GMPParent by calling
+ // GMPService::AsyncShutdownNeeded().
+ //
+ // On shutdown, we set mWaitingForPluginsSyncShutdown to true, and then
+ // call UnloadPlugins on the GMPThread, and process events on the main
+ // thread until 1. An event sets mWaitingForPluginsSyncShutdown=false on
+ // the main thread; then 2. All async-shutdown plugins have indicated
+ // they have completed shutdown.
+ //
+ // UnloadPlugins() sends close messages for all plugins' API objects to
+ // the GMP interfaces in the child process, and then sends the async
+ // shutdown notifications to child GMPs. When a GMP has completed its
+ // shutdown, it calls GMPAsyncShutdownHost::ShutdownComplete(), which
+ // sends a message back to the parent, which calls
+ // GMPService::AsyncShutdownComplete(). If all plugins requiring async
+ // shutdown have called AsyncShutdownComplete() we stick a dummy event on
+ // the main thread, where the list of pending plugins is checked. We must
+ // use an event to do this, as we must ensure the main thread processes an
+ // event to run its loop. This will unblock the main thread, and shutdown
+ // of other components will proceed.
+ //
+ // During shutdown, each GMPParent starts a timer, and pretends shutdown
+ // is complete if it is taking too long.
+ //
+ // We shutdown in "profile-change-teardown", as the profile dir is
+ // still writable then, and it's required for GMPStorage. We block the
+ // shutdown process by spinning the main thread event loop until all GMPs
+ // have shutdown, or timeout has occurred.
+ //
+ // GMPStorage needs to work up until the shutdown-complete notification
+ // arrives from the GMP process.
+
+ mWaitingForPluginsSyncShutdown = true;
+
+ nsCOMPtr<nsIThread> gmpThread;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mShuttingDown);
+ mShuttingDown = true;
+ gmpThread = mGMPThread;
+ }
+
+ if (gmpThread) {
+ LOGD(("%s::%s Starting to unload plugins, waiting for first sync shutdown..."
+ , __CLASS__, __FUNCTION__));
+ gmpThread->Dispatch(
+ NewRunnableMethod(this,
+ &GeckoMediaPluginServiceParent::UnloadPlugins),
+ NS_DISPATCH_NORMAL);
+
+ // Wait for UnloadPlugins() to do initial sync shutdown...
+ while (mWaitingForPluginsSyncShutdown) {
+ NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+ }
+
+ // Wait for other plugins (if any) to do async shutdown...
+ auto syncShutdownPluginsRemaining =
+ std::numeric_limits<decltype(mAsyncShutdownPlugins.Length())>::max();
+ for (;;) {
+ {
+ MutexAutoLock lock(mMutex);
+ if (mAsyncShutdownPlugins.IsEmpty()) {
+ LOGD(("%s::%s Finished unloading all plugins"
+ , __CLASS__, __FUNCTION__));
+ break;
+ } else if (mAsyncShutdownPlugins.Length() < syncShutdownPluginsRemaining) {
+ // First time here, or number of pending plugins has decreased.
+ // -> Update list of pending plugins in crash report.
+ syncShutdownPluginsRemaining = mAsyncShutdownPlugins.Length();
+ LOGD(("%s::%s Still waiting for %d plugins to shutdown..."
+ , __CLASS__, __FUNCTION__, (int)syncShutdownPluginsRemaining));
+ }
+ }
+ NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+ }
+ } else {
+ // GMP thread has already shutdown.
+ MOZ_ASSERT(mPlugins.IsEmpty());
+ mWaitingForPluginsSyncShutdown = false;
+ }
+
+ } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
+ MOZ_ASSERT(mShuttingDown);
+ ShutdownGMPThread();
+ } else if (!strcmp("last-pb-context-exited", aTopic)) {
+ // When Private Browsing mode exits, all we need to do is clear
+ // mTempNodeIds. This drops all the node ids we've cached in memory
+ // for PB origin-pairs. If we try to open an origin-pair for non-PB
+ // mode, we'll get the NodeId salt stored on-disk, and if we try to
+ // open a PB mode origin-pair, we'll re-generate new salt.
+ mTempNodeIds.Clear();
+ } else if (!strcmp("browser:purge-session-history", aTopic)) {
+ // Clear everything!
+ if (!aSomeData || nsDependentString(aSomeData).IsEmpty()) {
+ return GMPDispatch(NewRunnableMethod(
+ this, &GeckoMediaPluginServiceParent::ClearStorage));
+ }
+
+ // Clear nodeIds/records modified after |t|.
+ nsresult rv;
+ PRTime t = nsDependentString(aSomeData).ToInteger64(&rv, 10);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return GMPDispatch(NewRunnableMethod<PRTime>(
+ this, &GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread,
+ t));
+ }
+
+ return NS_OK;
+}
+
+RefPtr<GenericPromise>
+GeckoMediaPluginServiceParent::EnsureInitialized() {
+ MonitorAutoLock lock(mInitPromiseMonitor);
+ if (mLoadPluginsFromDiskComplete) {
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }
+ // We should have an init promise in flight.
+ MOZ_ASSERT(!mInitPromise.IsEmpty());
+ return mInitPromise.Ensure(__func__);
+}
+
+bool
+GeckoMediaPluginServiceParent::GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+{
+ RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+ if (!thread) {
+ return false;
+ }
+
+ RefPtr<GeckoMediaPluginServiceParent> self(this);
+ nsCString nodeId(aNodeId);
+ nsTArray<nsCString> tags(aTags);
+ nsCString api(aAPI);
+ GetGMPContentParentCallback* rawCallback = aCallback.release();
+ RefPtr<GMPCrashHelper> helper(aHelper);
+ EnsureInitialized()->Then(thread, __func__,
+ [self, tags, api, nodeId, rawCallback, helper]() -> void {
+ UniquePtr<GetGMPContentParentCallback> callback(rawCallback);
+ RefPtr<GMPParent> gmp = self->SelectPluginForAPI(nodeId, api, tags);
+ LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)self, (void *)gmp, api.get()));
+ if (!gmp) {
+ NS_WARNING("GeckoMediaPluginServiceParent::GetContentParentFrom failed");
+ callback->Done(nullptr);
+ return;
+ }
+ self->ConnectCrashHelper(gmp->GetPluginId(), helper);
+ gmp->GetGMPContentParent(Move(callback));
+ },
+ [rawCallback]() -> void {
+ UniquePtr<GetGMPContentParentCallback> callback(rawCallback);
+ NS_WARNING("GMPService::EnsureInitialized failed.");
+ callback->Done(nullptr);
+ });
+ return true;
+}
+
+void
+GeckoMediaPluginServiceParent::InitializePlugins(
+ AbstractThread* aAbstractGMPThread)
+{
+ MOZ_ASSERT(aAbstractGMPThread);
+ MonitorAutoLock lock(mInitPromiseMonitor);
+ if (mLoadPluginsFromDiskComplete) {
+ return;
+ }
+
+ RefPtr<GeckoMediaPluginServiceParent> self(this);
+ RefPtr<GenericPromise> p = mInitPromise.Ensure(__func__);
+ InvokeAsync(aAbstractGMPThread, this, __func__,
+ &GeckoMediaPluginServiceParent::LoadFromEnvironment)
+ ->Then(aAbstractGMPThread, __func__,
+ [self]() -> void {
+ MonitorAutoLock lock(self->mInitPromiseMonitor);
+ self->mLoadPluginsFromDiskComplete = true;
+ self->mInitPromise.Resolve(true, __func__);
+ },
+ [self]() -> void {
+ MonitorAutoLock lock(self->mInitPromiseMonitor);
+ self->mLoadPluginsFromDiskComplete = true;
+ self->mInitPromise.Reject(NS_ERROR_FAILURE, __func__);
+ });
+}
+
+void
+GeckoMediaPluginServiceParent::AsyncShutdownNeeded(GMPParent* aParent)
+{
+ LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mAsyncShutdownPlugins.Contains(aParent));
+ mAsyncShutdownPlugins.AppendElement(aParent);
+}
+
+void
+GeckoMediaPluginServiceParent::AsyncShutdownComplete(GMPParent* aParent)
+{
+ LOGD(("%s::%s %p '%s'", __CLASS__, __FUNCTION__,
+ aParent, aParent->GetDisplayName().get()));
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mAsyncShutdownPlugins.RemoveElement(aParent);
+ }
+
+ if (mShuttingDownOnGMPThread) {
+ // The main thread may be waiting for async shutdown of plugins,
+ // one of which has completed. Wake up the main thread by sending a task.
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(
+ this, &GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete));
+ NS_DispatchToMainThread(task);
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // Nothing to do, this task is just used to wake up the event loop in Observe().
+}
+
+void
+GeckoMediaPluginServiceParent::NotifySyncShutdownComplete()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mWaitingForPluginsSyncShutdown = false;
+}
+
+bool
+GeckoMediaPluginServiceParent::IsShuttingDown()
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ return mShuttingDownOnGMPThread;
+}
+
+void
+GeckoMediaPluginServiceParent::UnloadPlugins()
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ MOZ_ASSERT(!mShuttingDownOnGMPThread);
+ mShuttingDownOnGMPThread = true;
+
+ nsTArray<RefPtr<GMPParent>> plugins;
+ {
+ MutexAutoLock lock(mMutex);
+ // Move all plugins references to a local array. This way mMutex won't be
+ // locked when calling CloseActive (to avoid inter-locking).
+ Swap(plugins, mPlugins);
+ }
+
+ LOGD(("%s::%s plugins:%u including async:%u", __CLASS__, __FUNCTION__,
+ plugins.Length(), mAsyncShutdownPlugins.Length()));
+#ifdef DEBUG
+ for (const auto& plugin : plugins) {
+ LOGD(("%s::%s plugin: '%s'", __CLASS__, __FUNCTION__,
+ plugin->GetDisplayName().get()));
+ }
+ for (const auto& plugin : mAsyncShutdownPlugins) {
+ LOGD(("%s::%s async plugin: '%s'", __CLASS__, __FUNCTION__,
+ plugin->GetDisplayName().get()));
+ }
+#endif
+ // Note: CloseActive may be async; it could actually finish
+ // shutting down when all the plugins have unloaded.
+ for (const auto& plugin : plugins) {
+ plugin->CloseActive(true);
+ }
+
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(
+ this, &GeckoMediaPluginServiceParent::NotifySyncShutdownComplete));
+ NS_DispatchToMainThread(task);
+}
+
+void
+GeckoMediaPluginServiceParent::CrashPlugins()
+{
+ LOGD(("%s::%s", __CLASS__, __FUNCTION__));
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ MutexAutoLock lock(mMutex);
+ for (size_t i = 0; i < mPlugins.Length(); i++) {
+ mPlugins[i]->Crash();
+ }
+}
+
+RefPtr<GenericPromise::AllPromiseType>
+GeckoMediaPluginServiceParent::LoadFromEnvironment()
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+ if (!thread) {
+ return GenericPromise::AllPromiseType::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ const char* env = PR_GetEnv("MOZ_GMP_PATH");
+ if (!env || !*env) {
+ return GenericPromise::AllPromiseType::CreateAndResolve(true, __func__);
+ }
+
+ nsString allpaths;
+ if (NS_WARN_IF(NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(env), allpaths)))) {
+ return GenericPromise::AllPromiseType::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsTArray<RefPtr<GenericPromise>> promises;
+ uint32_t pos = 0;
+ while (pos < allpaths.Length()) {
+ // Loop over multiple path entries separated by colons (*nix) or
+ // semicolons (Windows)
+ int32_t next = allpaths.FindChar(XPCOM_ENV_PATH_SEPARATOR[0], pos);
+ if (next == -1) {
+ promises.AppendElement(AddOnGMPThread(nsString(Substring(allpaths, pos))));
+ break;
+ } else {
+ promises.AppendElement(AddOnGMPThread(nsString(Substring(allpaths, pos, next - pos))));
+ pos = next + 1;
+ }
+ }
+
+ mScannedPluginOnDisk = true;
+ return GenericPromise::All(thread, promises);
+}
+
+class NotifyObserversTask final : public mozilla::Runnable {
+public:
+ explicit NotifyObserversTask(const char* aTopic, nsString aData = EmptyString())
+ : mTopic(aTopic)
+ , mData(aData)
+ {}
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, mTopic, mData.get());
+ }
+ return NS_OK;
+ }
+private:
+ ~NotifyObserversTask() {}
+ const char* mTopic;
+ const nsString mData;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::PathRunnable::Run()
+{
+ mService->RemoveOnGMPThread(mPath,
+ mOperation == REMOVE_AND_DELETE_FROM_DISK,
+ mDefer);
+
+ mService->UpdateContentProcessGMPCapabilities();
+ return NS_OK;
+}
+
+void
+GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities()
+{
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> task =
+ NewRunnableMethod(this, &GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities);
+ NS_DispatchToMainThread(task);
+ return;
+ }
+
+ typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
+ typedef mozilla::dom::GMPAPITags GMPAPITags;
+ typedef mozilla::dom::ContentParent ContentParent;
+
+ nsTArray<GMPCapabilityData> caps;
+ {
+ MutexAutoLock lock(mMutex);
+ for (const RefPtr<GMPParent>& gmp : mPlugins) {
+ // We have multiple instances of a GMPParent for a given GMP in the
+ // list, one per origin. So filter the list so that we don't include
+ // the same GMP's capabilities twice.
+ NS_ConvertUTF16toUTF8 name(gmp->GetPluginBaseName());
+ bool found = false;
+ for (const GMPCapabilityData& cap : caps) {
+ if (cap.name().Equals(name)) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ continue;
+ }
+ GMPCapabilityData x;
+ x.name() = name;
+ x.version() = gmp->GetVersion();
+ for (const GMPCapability& tag : gmp->GetCapabilities()) {
+ x.capabilities().AppendElement(GMPAPITags(tag.mAPIName, tag.mAPITags));
+ }
+ caps.AppendElement(Move(x));
+ }
+ }
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ Unused << cp->SendGMPsChanged(caps);
+ }
+
+ // For non-e10s, we must fire a notification so that any MediaKeySystemAccess
+ // requests waiting on a CDM to download will retry.
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
+ }
+}
+
+RefPtr<GenericPromise>
+GeckoMediaPluginServiceParent::AsyncAddPluginDirectory(const nsAString& aDirectory)
+{
+ RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+ if (!thread) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsString dir(aDirectory);
+ RefPtr<GeckoMediaPluginServiceParent> self = this;
+ return InvokeAsync(thread, this, __func__, &GeckoMediaPluginServiceParent::AddOnGMPThread, dir)
+ ->Then(AbstractThread::MainThread(), __func__,
+ [dir, self]() -> void {
+ LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s succeeded",
+ NS_ConvertUTF16toUTF8(dir).get()));
+ MOZ_ASSERT(NS_IsMainThread());
+ self->UpdateContentProcessGMPCapabilities();
+ },
+ [dir]() -> void {
+ LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s failed",
+ NS_ConvertUTF16toUTF8(dir).get()));
+ })
+ ->CompletionPromise();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::AddPluginDirectory(const nsAString& aDirectory)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<GenericPromise> p = AsyncAddPluginDirectory(aDirectory);
+ Unused << p;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::RemovePluginDirectory(const nsAString& aDirectory)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return GMPDispatch(new PathRunnable(this, aDirectory,
+ PathRunnable::EOperation::REMOVE));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::RemoveAndDeletePluginDirectory(
+ const nsAString& aDirectory, const bool aDefer)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return GMPDispatch(
+ new PathRunnable(this, aDirectory,
+ PathRunnable::EOperation::REMOVE_AND_DELETE_FROM_DISK,
+ aDefer));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool* aHasPlugin)
+{
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aHasPlugin);
+
+ nsresult rv = EnsurePluginsOnDiskScanned();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to load GMPs from disk.");
+ return rv;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ nsCString api(aAPI);
+ size_t index = 0;
+ RefPtr<GMPParent> gmp = FindPluginForAPIFrom(index, api, *aTags, &index);
+ *aHasPlugin = !!gmp;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginServiceParent::EnsurePluginsOnDiskScanned()
+{
+ const char* env = nullptr;
+ if (!mScannedPluginOnDisk && (env = PR_GetEnv("MOZ_GMP_PATH")) && *env) {
+ // We have a MOZ_GMP_PATH environment variable which may specify the
+ // location of plugins to load, and we haven't yet scanned the disk to
+ // see if there are plugins there. Get the GMP thread, which will
+ // cause an event to be dispatched to which scans for plugins. We
+ // dispatch a sync event to the GMP thread here in order to wait until
+ // after the GMP thread has scanned any paths in MOZ_GMP_PATH.
+ nsresult rv = GMPDispatch(new mozilla::Runnable(), NS_DISPATCH_SYNC);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(mScannedPluginOnDisk, "Should have scanned MOZ_GMP_PATH by now");
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<GMPParent>
+GeckoMediaPluginServiceParent::FindPluginForAPIFrom(size_t aSearchStartIndex,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ size_t* aOutPluginIndex)
+{
+ mMutex.AssertCurrentThreadOwns();
+ for (size_t i = aSearchStartIndex; i < mPlugins.Length(); i++) {
+ RefPtr<GMPParent> gmp = mPlugins[i];
+ if (!GMPCapability::Supports(gmp->GetCapabilities(), aAPI, aTags)) {
+ continue;
+ }
+ if (aOutPluginIndex) {
+ *aOutPluginIndex = i;
+ }
+ return gmp.forget();
+ }
+ return nullptr;
+}
+
+already_AddRefed<GMPParent>
+GeckoMediaPluginServiceParent::SelectPluginForAPI(const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread,
+ "Can't clone GMP plugins on non-GMP threads.");
+
+ GMPParent* gmpToClone = nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+ size_t index = 0;
+ RefPtr<GMPParent> gmp;
+ while ((gmp = FindPluginForAPIFrom(index, aAPI, aTags, &index))) {
+ if (aNodeId.IsEmpty()) {
+ if (gmp->CanBeSharedCrossNodeIds()) {
+ return gmp.forget();
+ }
+ } else if (gmp->CanBeUsedFrom(aNodeId)) {
+ return gmp.forget();
+ }
+
+ if (!gmpToClone ||
+ (gmpToClone->IsMarkedForDeletion() && !gmp->IsMarkedForDeletion())) {
+ // This GMP has the correct type but has the wrong nodeId; hold on to it
+ // in case we need to clone it.
+ // Prefer GMPs in-use for the case where an upgraded plugin version is
+ // waiting for the old one to die. If the old plugin is in use, we
+ // should continue using it so that any persistent state remains
+ // consistent. Otherwise, just check that the plugin isn't scheduled
+ // for deletion.
+ gmpToClone = gmp;
+ }
+ // Loop around and try the next plugin; it may be usable from aNodeId.
+ index++;
+ }
+ }
+
+ // Plugin exists, but we can't use it due to cross-origin separation. Create a
+ // new one.
+ if (gmpToClone) {
+ RefPtr<GMPParent> clone = ClonePlugin(gmpToClone);
+ {
+ MutexAutoLock lock(mMutex);
+ mPlugins.AppendElement(clone);
+ }
+ if (!aNodeId.IsEmpty()) {
+ clone->SetNodeId(aNodeId);
+ }
+ return clone.forget();
+ }
+
+ return nullptr;
+}
+
+RefPtr<GMPParent>
+CreateGMPParent()
+{
+ return new GMPParent();
+}
+
+already_AddRefed<GMPParent>
+GeckoMediaPluginServiceParent::ClonePlugin(const GMPParent* aOriginal)
+{
+ MOZ_ASSERT(aOriginal);
+
+ RefPtr<GMPParent> gmp = CreateGMPParent();
+ nsresult rv = gmp ? gmp->CloneFrom(aOriginal) : NS_ERROR_NOT_AVAILABLE;
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't Create GMPParent");
+ return nullptr;
+ }
+
+ return gmp.forget();
+}
+
+RefPtr<GenericPromise>
+GeckoMediaPluginServiceParent::AddOnGMPThread(nsString aDirectory)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ nsCString dir = NS_ConvertUTF16toUTF8(aDirectory);
+ RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+ if (!thread) {
+ LOGD(("%s::%s: %s No GMP Thread", __CLASS__, __FUNCTION__, dir.get()));
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+ LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, dir.get()));
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ RefPtr<GMPParent> gmp = CreateGMPParent();
+ if (!gmp) {
+ NS_WARNING("Can't Create GMPParent");
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ RefPtr<GeckoMediaPluginServiceParent> self(this);
+ return gmp->Init(this, directory)->Then(thread, __func__,
+ [gmp, self, dir]() -> void {
+ LOGD(("%s::%s: %s Succeeded", __CLASS__, __FUNCTION__, dir.get()));
+ {
+ MutexAutoLock lock(self->mMutex);
+ self->mPlugins.AppendElement(gmp);
+ }
+ },
+ [dir]() -> void {
+ LOGD(("%s::%s: %s Failed", __CLASS__, __FUNCTION__, dir.get()));
+ })
+ ->CompletionPromise();
+}
+
+void
+GeckoMediaPluginServiceParent::RemoveOnGMPThread(const nsAString& aDirectory,
+ const bool aDeleteFromDisk,
+ const bool aCanDefer)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // Plugin destruction can modify |mPlugins|. Put them aside for now and
+ // destroy them once we're done with |mPlugins|.
+ nsTArray<RefPtr<GMPParent>> deadPlugins;
+
+ bool inUse = false;
+ MutexAutoLock lock(mMutex);
+ for (size_t i = mPlugins.Length(); i-- > 0; ) {
+ nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory();
+ bool equals;
+ if (NS_FAILED(directory->Equals(pluginpath, &equals)) || !equals) {
+ continue;
+ }
+
+ RefPtr<GMPParent> gmp = mPlugins[i];
+ if (aDeleteFromDisk && gmp->State() != GMPStateNotLoaded) {
+ // We have to wait for the child process to release its lib handle
+ // before we can delete the GMP.
+ inUse = true;
+ gmp->MarkForDeletion();
+
+ if (!mPluginsWaitingForDeletion.Contains(aDirectory)) {
+ mPluginsWaitingForDeletion.AppendElement(aDirectory);
+ }
+ }
+
+ if (gmp->State() == GMPStateNotLoaded || !aCanDefer) {
+ // GMP not in use or shutdown is being forced; can shut it down now.
+ deadPlugins.AppendElement(gmp);
+ mPlugins.RemoveElementAt(i);
+ }
+ }
+
+ {
+ MutexAutoUnlock unlock(mMutex);
+ for (auto& gmp : deadPlugins) {
+ gmp->AbortAsyncShutdown();
+ gmp->CloseActive(true);
+ }
+ }
+
+ if (aDeleteFromDisk && !inUse) {
+ // Ensure the GMP dir and all files in it are writable, so we have
+ // permission to delete them.
+ directory->SetPermissions(0700);
+ DirectoryEnumerator iter(directory, DirectoryEnumerator::FilesAndDirs);
+ for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+ dirEntry->SetPermissions(0700);
+ }
+ if (NS_SUCCEEDED(directory->Remove(true))) {
+ mPluginsWaitingForDeletion.RemoveElement(aDirectory);
+ NS_DispatchToMainThread(new NotifyObserversTask("gmp-directory-deleted",
+ nsString(aDirectory)),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+}
+
+// May remove when Bug 1043671 is fixed
+static void Dummy(RefPtr<GMPParent>& aOnDeathsDoor)
+{
+ // exists solely to do nothing and let the Runnable kill the GMPParent
+ // when done.
+}
+
+void
+GeckoMediaPluginServiceParent::PluginTerminated(const RefPtr<GMPParent>& aPlugin)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ if (aPlugin->IsMarkedForDeletion()) {
+ nsCString path8;
+ RefPtr<nsIFile> dir = aPlugin->GetDirectory();
+ nsresult rv = dir->GetNativePath(path8);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsString path = NS_ConvertUTF8toUTF16(path8);
+ if (mPluginsWaitingForDeletion.Contains(path)) {
+ RemoveOnGMPThread(path, true /* delete */, true /* can defer */);
+ }
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ReAddOnGMPThread(const RefPtr<GMPParent>& aOld)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, (void*) aOld));
+
+ RefPtr<GMPParent> gmp;
+ if (!mShuttingDownOnGMPThread) {
+ // We're not shutting down, so replace the old plugin in the list with a
+ // clone which is in a pristine state. Note: We place the plugin in
+ // the same slot in the array as a hack to ensure if we re-request with
+ // the same capabilities we get an instance of the same plugin.
+ gmp = ClonePlugin(aOld);
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mPlugins.Contains(aOld));
+ if (mPlugins.Contains(aOld)) {
+ mPlugins[mPlugins.IndexOf(aOld)] = gmp;
+ }
+ } else {
+ // We're shutting down; don't re-add plugin, let the old plugin die.
+ MutexAutoLock lock(mMutex);
+ mPlugins.RemoveElement(aOld);
+ }
+ // Schedule aOld to be destroyed. We can't destroy it from here since we
+ // may be inside ActorDestroyed() for it.
+ NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetStorageDir(nsIFile** aOutFile)
+{
+ if (NS_WARN_IF(!mStorageBaseDir)) {
+ return NS_ERROR_FAILURE;
+ }
+ return mStorageBaseDir->Clone(aOutFile);
+}
+
+static nsresult
+WriteToFile(nsIFile* aPath,
+ const nsCString& aFileName,
+ const nsCString& aData)
+{
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aPath->Clone(getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->AppendNative(aFileName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ PRFileDesc* f = nullptr;
+ rv = path->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, PR_IRWXU, &f);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t len = PR_Write(f, aData.get(), aData.Length());
+ PR_Close(f);
+ if (NS_WARN_IF(len < 0 || (size_t)len != aData.Length())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+ReadFromFile(nsIFile* aPath,
+ const nsACString& aFileName,
+ nsACString& aOutData,
+ int32_t aMaxLength)
+{
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aPath->Clone(getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->AppendNative(aFileName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ PRFileDesc* f = nullptr;
+ rv = path->OpenNSPRFileDesc(PR_RDONLY | PR_CREATE_FILE, PR_IRWXU, &f);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ auto size = PR_Seek(f, 0, PR_SEEK_END);
+ PR_Seek(f, 0, PR_SEEK_SET);
+
+ if (size > aMaxLength) {
+ return NS_ERROR_FAILURE;
+ }
+ aOutData.SetLength(size);
+
+ auto len = PR_Read(f, aOutData.BeginWriting(), size);
+ PR_Close(f);
+ if (NS_WARN_IF(len != size)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ReadSalt(nsIFile* aPath, nsACString& aOutData)
+{
+ return ReadFromFile(aPath, NS_LITERAL_CSTRING("salt"),
+ aOutData, NodeIdSaltLength);
+
+}
+
+already_AddRefed<GMPStorage>
+GeckoMediaPluginServiceParent::GetMemoryStorageFor(const nsACString& aNodeId)
+{
+ RefPtr<GMPStorage> s;
+ if (!mTempGMPStorage.Get(aNodeId, getter_AddRefs(s))) {
+ s = CreateGMPMemoryStorage();
+ mTempGMPStorage.Put(aNodeId, s);
+ }
+ return s.forget();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::IsPersistentStorageAllowed(const nsACString& aNodeId,
+ bool* aOutAllowed)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aOutAllowed);
+ // We disallow persistent storage for the NodeId used for shared GMP
+ // decoding, to prevent GMP decoding being used to track what a user
+ // watches somehow.
+ *aOutAllowed = !aNodeId.Equals(SHARED_GMP_DECODING_NODE_ID) &&
+ mPersistentStorageAllowed.Get(aNodeId);
+ return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing,
+ nsACString& aOutId)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: (%s, %s), %s", __CLASS__, __FUNCTION__,
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing")));
+
+ nsresult rv;
+
+ if (aOrigin.EqualsLiteral("null") ||
+ aOrigin.IsEmpty() ||
+ aTopLevelOrigin.EqualsLiteral("null") ||
+ aTopLevelOrigin.IsEmpty()) {
+ // (origin, topLevelOrigin) is null or empty; this is for an anonymous
+ // origin, probably a local file, for which we don't provide persistent storage.
+ // Generate a random node id, and don't store it so that the GMP's storage
+ // is temporary and the process for this GMP is not shared with GMP
+ // instances that have the same nodeId.
+ nsAutoCString salt;
+ rv = GenerateRandomPathName(salt, NodeIdSaltLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ aOutId = salt;
+ mPersistentStorageAllowed.Put(salt, false);
+ return NS_OK;
+ }
+
+ const uint32_t hash = AddToHash(HashString(aOrigin),
+ HashString(aTopLevelOrigin));
+
+ if (aInPrivateBrowsing) {
+ // For PB mode, we store the node id, indexed by the origin pair and GMP name,
+ // so that if the same origin pair is opened for the same GMP in this session,
+ // it gets the same node id.
+ const uint32_t pbHash = AddToHash(HashString(aGMPName), hash);
+ nsCString* salt = nullptr;
+ if (!(salt = mTempNodeIds.Get(pbHash))) {
+ // No salt stored, generate and temporarily store some for this id.
+ nsAutoCString newSalt;
+ rv = GenerateRandomPathName(newSalt, NodeIdSaltLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ salt = new nsCString(newSalt);
+ mTempNodeIds.Put(pbHash, salt);
+ mPersistentStorageAllowed.Put(*salt, false);
+ }
+ aOutId = *salt;
+ return NS_OK;
+ }
+
+ // Otherwise, try to see if we've previously generated and stored salt
+ // for this origin pair.
+ nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/
+ rv = GetStorageDir(getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->Append(aGMPName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->AppendNative(NS_LITERAL_CSTRING("id"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString hashStr;
+ hashStr.AppendInt((int64_t)hash);
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash
+ rv = path->AppendNative(hashStr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> saltFile;
+ rv = path->Clone(getter_AddRefs(saltFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = saltFile->AppendNative(NS_LITERAL_CSTRING("salt"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString salt;
+ bool exists = false;
+ rv = saltFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (!exists) {
+ // No stored salt for this origin. Generate salt, and store it and
+ // the origin on disk.
+ nsresult rv = GenerateRandomPathName(salt, NodeIdSaltLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MOZ_ASSERT(salt.Length() == NodeIdSaltLength);
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash/salt
+ rv = WriteToFile(path, NS_LITERAL_CSTRING("salt"), salt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash/origin
+ rv = WriteToFile(path,
+ NS_LITERAL_CSTRING("origin"),
+ NS_ConvertUTF16toUTF8(aOrigin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash/topLevelOrigin
+ rv = WriteToFile(path,
+ NS_LITERAL_CSTRING("topLevelOrigin"),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ } else {
+ rv = ReadSalt(path, salt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ aOutId = salt;
+ mPersistentStorageAllowed.Put(salt, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing,
+ UniquePtr<GetNodeIdCallback>&& aCallback)
+{
+ nsCString nodeId;
+ nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, nodeId);
+ aCallback->Done(rv, nodeId);
+ return rv;
+}
+
+static bool
+ExtractHostName(const nsACString& aOrigin, nsACString& aOutData)
+{
+ nsCString str;
+ str.Assign(aOrigin);
+ int begin = str.Find("://");
+ // The scheme is missing!
+ if (begin == -1) {
+ return false;
+ }
+
+ int end = str.RFind(":");
+ // Remove the port number
+ if (end != begin) {
+ str.SetLength(end);
+ }
+
+ nsDependentCSubstring host(str, begin + 3);
+ aOutData.Assign(host);
+ return true;
+}
+
+bool
+MatchOrigin(nsIFile* aPath,
+ const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+{
+ // http://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax
+ static const uint32_t MaxDomainLength = 253;
+
+ nsresult rv;
+ nsCString str;
+ nsCString originNoSuffix;
+ mozilla::PrincipalOriginAttributes originAttributes;
+
+ rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("origin"), str, MaxDomainLength);
+ if (!originAttributes.PopulateFromOrigin(str, originNoSuffix)) {
+ // Fails on parsing the originAttributes, treat this as a non-match.
+ return false;
+ }
+
+ if (NS_SUCCEEDED(rv) && ExtractHostName(originNoSuffix, str) && str.Equals(aSite) &&
+ aPattern.Matches(originAttributes)) {
+ return true;
+ }
+
+ mozilla::PrincipalOriginAttributes topLevelOriginAttributes;
+ rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("topLevelOrigin"), str, MaxDomainLength);
+ if (!topLevelOriginAttributes.PopulateFromOrigin(str, originNoSuffix)) {
+ // Fails on paring the originAttributes, treat this as a non-match.
+ return false;
+ }
+
+ if (NS_SUCCEEDED(rv) && ExtractHostName(originNoSuffix, str) && str.Equals(aSite) &&
+ aPattern.Matches(topLevelOriginAttributes)) {
+ return true;
+ }
+ return false;
+}
+
+template<typename T> static void
+KillPlugins(const nsTArray<RefPtr<GMPParent>>& aPlugins,
+ Mutex& aMutex, T&& aFilter)
+{
+ // Shutdown the plugins when |aFilter| evaluates to true.
+ // After we clear storage data, node IDs will become invalid and shouldn't be
+ // used anymore. We need to kill plugins with such nodeIDs.
+ // Note: we can't shut them down while holding the lock,
+ // as the lock is not re-entrant and shutdown requires taking the lock.
+ // The plugin list is only edited on the GMP thread, so this should be OK.
+ nsTArray<RefPtr<GMPParent>> pluginsToKill;
+ {
+ MutexAutoLock lock(aMutex);
+ for (size_t i = 0; i < aPlugins.Length(); i++) {
+ RefPtr<GMPParent> parent(aPlugins[i]);
+ if (aFilter(parent)) {
+ pluginsToKill.AppendElement(parent);
+ }
+ }
+ }
+
+ for (size_t i = 0; i < pluginsToKill.Length(); i++) {
+ pluginsToKill[i]->CloseActive(false);
+ // Abort async shutdown because we're going to wipe the plugin's storage,
+ // so we don't want it writing more data in its async shutdown path.
+ pluginsToKill[i]->AbortAsyncShutdown();
+ }
+}
+
+static nsresult
+DeleteDir(nsIFile* aPath)
+{
+ bool exists = false;
+ nsresult rv = aPath->Exists(&exists);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (exists) {
+ return aPath->Remove(true);
+ }
+ return NS_OK;
+}
+
+struct NodeFilter {
+ explicit NodeFilter(const nsTArray<nsCString>& nodeIDs) : mNodeIDs(nodeIDs) {}
+ bool operator()(GMPParent* aParent) {
+ return mNodeIDs.Contains(aParent->GetNodeId());
+ }
+private:
+ const nsTArray<nsCString>& mNodeIDs;
+};
+
+void
+GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(DirectoryFilter& aFilter)
+{
+ // $profileDir/gmp/$platform/
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = GetStorageDir(getter_AddRefs(path));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Iterate all sub-folders of $profileDir/gmp/$platform/, i.e. the dirs in which
+ // specific GMPs store their data.
+ DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly);
+ for (nsCOMPtr<nsIFile> pluginDir; (pluginDir = iter.Next()) != nullptr;) {
+ ClearNodeIdAndPlugin(pluginDir, aFilter);
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir,
+ DirectoryFilter& aFilter)
+{
+ // $profileDir/gmp/$platform/$gmpName/id/
+ nsCOMPtr<nsIFile> path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("id"));
+ if (!path) {
+ return;
+ }
+
+ // Iterate all sub-folders of $profileDir/gmp/$platform/$gmpName/id/
+ nsTArray<nsCString> nodeIDsToClear;
+ DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly);
+ for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+ // dirEntry is the hash of origins, i.e.:
+ // $profileDir/gmp/$platform/$gmpName/id/$originHash/
+ if (!aFilter(dirEntry)) {
+ continue;
+ }
+ nsAutoCString salt;
+ if (NS_SUCCEEDED(ReadSalt(dirEntry, salt))) {
+ // Keep node IDs to clear data/plugins associated with them later.
+ nodeIDsToClear.AppendElement(salt);
+ // Also remove node IDs from the table.
+ mPersistentStorageAllowed.Remove(salt);
+ }
+ // Now we can remove the directory for the origin pair.
+ if (NS_FAILED(dirEntry->Remove(true))) {
+ NS_WARNING("Failed to delete the directory for the origin pair");
+ }
+ }
+
+ // Kill plugin instances that have node IDs being cleared.
+ KillPlugins(mPlugins, mMutex, NodeFilter(nodeIDsToClear));
+
+ // Clear all storage in $profileDir/gmp/$platform/$gmpName/storage/$nodeId/
+ path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("storage"));
+ if (!path) {
+ return;
+ }
+
+ for (const nsCString& nodeId : nodeIDsToClear) {
+ nsCOMPtr<nsIFile> dirEntry;
+ nsresult rv = path->Clone(getter_AddRefs(dirEntry));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ rv = dirEntry->AppendNative(nodeId);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (NS_FAILED(DeleteDir(dirEntry))) {
+ NS_WARNING("Failed to delete GMP storage directory for the node");
+ }
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread(const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: origin=%s", __CLASS__, __FUNCTION__, aSite.Data()));
+
+ struct OriginFilter : public DirectoryFilter {
+ explicit OriginFilter(const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+ : mSite(aSite)
+ , mPattern(aPattern)
+ { }
+ bool operator()(nsIFile* aPath) override {
+ return MatchOrigin(aPath, mSite, mPattern);
+ }
+ private:
+ const nsACString& mSite;
+ const mozilla::OriginAttributesPattern& mPattern;
+ } filter(aSite, aPattern);
+
+ ClearNodeIdAndPlugin(filter);
+}
+
+void
+GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread(PRTime aSince)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: since=%lld", __CLASS__, __FUNCTION__, (int64_t)aSince));
+
+ struct MTimeFilter : public DirectoryFilter {
+ explicit MTimeFilter(PRTime aSince)
+ : mSince(aSince) {}
+
+ // Return true if any files under aPath is modified after |mSince|.
+ bool IsModifiedAfter(nsIFile* aPath) {
+ PRTime lastModified;
+ nsresult rv = aPath->GetLastModifiedTime(&lastModified);
+ if (NS_SUCCEEDED(rv) && lastModified >= mSince) {
+ return true;
+ }
+ DirectoryEnumerator iter(aPath, DirectoryEnumerator::FilesAndDirs);
+ for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+ if (IsModifiedAfter(dirEntry)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // |aPath| is $profileDir/gmp/$platform/$gmpName/id/$originHash/
+ bool operator()(nsIFile* aPath) override {
+ if (IsModifiedAfter(aPath)) {
+ return true;
+ }
+
+ nsAutoCString salt;
+ if (NS_FAILED(ReadSalt(aPath, salt))) {
+ return false;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/
+ nsCOMPtr<nsIFile> idDir;
+ if (NS_FAILED(aPath->GetParent(getter_AddRefs(idDir)))) {
+ return false;
+ }
+ // $profileDir/gmp/$platform/$gmpName/
+ nsCOMPtr<nsIFile> temp;
+ if (NS_FAILED(idDir->GetParent(getter_AddRefs(temp)))) {
+ return false;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/storage/
+ if (NS_FAILED(temp->Append(NS_LITERAL_STRING("storage")))) {
+ return false;
+ }
+ // $profileDir/gmp/$platform/$gmpName/storage/$originSalt
+ return NS_SUCCEEDED(temp->AppendNative(salt)) && IsModifiedAfter(temp);
+ }
+ private:
+ const PRTime mSince;
+ } filter(aSince);
+
+ ClearNodeIdAndPlugin(filter);
+
+ NS_DispatchToMainThread(new NotifyObserversTask("gmp-clear-storage-complete"), NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::ForgetThisSite(const nsAString& aSite,
+ const nsAString& aPattern)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mozilla::OriginAttributesPattern pattern;
+
+ if (!pattern.Init(aPattern)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return ForgetThisSiteNative(aSite, pattern);
+}
+
+nsresult
+GeckoMediaPluginServiceParent::ForgetThisSiteNative(const nsAString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return GMPDispatch(NewRunnableMethod<nsCString, mozilla::OriginAttributesPattern>(
+ this, &GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread,
+ NS_ConvertUTF16toUTF8(aSite), aPattern));
+}
+
+static bool IsNodeIdValid(GMPParent* aParent) {
+ return !aParent->GetNodeId().IsEmpty();
+}
+
+static nsCOMPtr<nsIAsyncShutdownClient>
+GetShutdownBarrier()
+{
+ nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
+ MOZ_RELEASE_ASSERT(svc);
+
+ nsCOMPtr<nsIAsyncShutdownClient> barrier;
+ nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier));
+
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_RELEASE_ASSERT(barrier);
+ return barrier.forget();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetName(nsAString& aName)
+{
+ aName = NS_LITERAL_STRING("GeckoMediaPluginServiceParent: shutdown");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetState(nsIPropertyBag**)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::BlockShutdown(nsIAsyncShutdownClient*)
+{
+ return NS_OK;
+}
+
+void
+GeckoMediaPluginServiceParent::ServiceUserCreated()
+{
+ MOZ_ASSERT(mServiceUserCount >= 0);
+ if (++mServiceUserCount == 1) {
+ nsresult rv = GetShutdownBarrier()->AddBlocker(
+ this, NS_LITERAL_STRING(__FILE__), __LINE__,
+ NS_LITERAL_STRING("GeckoMediaPluginServiceParent shutdown"));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ServiceUserDestroyed()
+{
+ MOZ_ASSERT(mServiceUserCount > 0);
+ if (--mServiceUserCount == 0) {
+ nsresult rv = GetShutdownBarrier()->RemoveBlocker(this);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ClearStorage()
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s", __CLASS__, __FUNCTION__));
+
+ // Kill plugins with valid nodeIDs.
+ KillPlugins(mPlugins, mMutex, &IsNodeIdValid);
+
+ nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/
+ nsresult rv = GetStorageDir(getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (NS_FAILED(DeleteDir(path))) {
+ NS_WARNING("Failed to delete GMP storage directory");
+ }
+
+ // Clear private-browsing storage.
+ mTempGMPStorage.Clear();
+
+ NS_DispatchToMainThread(new NotifyObserversTask("gmp-clear-storage-complete"), NS_DISPATCH_NORMAL);
+}
+
+already_AddRefed<GMPParent>
+GeckoMediaPluginServiceParent::GetById(uint32_t aPluginId)
+{
+ MutexAutoLock lock(mMutex);
+ for (const RefPtr<GMPParent>& gmp : mPlugins) {
+ if (gmp->GetPluginId() == aPluginId) {
+ return do_AddRef(gmp);
+ }
+ }
+ return nullptr;
+}
+
+GMPServiceParent::~GMPServiceParent()
+{
+ NS_DispatchToMainThread(
+ NewRunnableMethod(mService.get(),
+ &GeckoMediaPluginServiceParent::ServiceUserDestroyed));
+}
+
+bool
+GMPServiceParent::RecvSelectGMP(const nsCString& aNodeId,
+ const nsCString& aAPI,
+ nsTArray<nsCString>&& aTags,
+ uint32_t* aOutPluginId,
+ nsresult* aOutRv)
+{
+ if (mService->IsShuttingDown()) {
+ *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ return true;
+ }
+
+ RefPtr<GMPParent> gmp = mService->SelectPluginForAPI(aNodeId, aAPI, aTags);
+ if (gmp) {
+ *aOutPluginId = gmp->GetPluginId();
+ *aOutRv = NS_OK;
+ } else {
+ *aOutRv = NS_ERROR_FAILURE;
+ }
+
+ nsCString api = aTags[0];
+ LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)this, (void *)gmp, api.get()));
+
+ return true;
+}
+
+bool
+GMPServiceParent::RecvLaunchGMP(const uint32_t& aPluginId,
+ nsTArray<ProcessId>&& aAlreadyBridgedTo,
+ ProcessId* aOutProcessId,
+ nsCString* aOutDisplayName,
+ nsresult* aOutRv)
+{
+ *aOutRv = NS_OK;
+ if (mService->IsShuttingDown()) {
+ *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ return true;
+ }
+
+ RefPtr<GMPParent> gmp(mService->GetById(aPluginId));
+ if (!gmp) {
+ *aOutRv = NS_ERROR_FAILURE;
+ return true;
+ }
+
+ if (!gmp->EnsureProcessLoaded(aOutProcessId)) {
+ return false;
+ }
+
+ *aOutDisplayName = gmp->GetDisplayName();
+
+ return aAlreadyBridgedTo.Contains(*aOutProcessId) || gmp->Bridge(this);
+}
+
+bool
+GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin,
+ const nsString& aTopLevelOrigin,
+ const nsString& aGMPName,
+ const bool& aInPrivateBrowsing,
+ nsCString* aID)
+{
+ nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aGMPName,
+ aInPrivateBrowsing, *aID);
+ return NS_SUCCEEDED(rv);
+}
+
+class DeleteGMPServiceParent : public mozilla::Runnable
+{
+public:
+ explicit DeleteGMPServiceParent(GMPServiceParent* aToDelete)
+ : mToDelete(aToDelete)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ return NS_OK;
+ }
+
+private:
+ nsAutoPtr<GMPServiceParent> mToDelete;
+};
+
+void GMPServiceParent::CloseTransport(Monitor* aSyncMonitor, bool* aCompleted)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ MonitorAutoLock lock(*aSyncMonitor);
+
+ // This deletes the transport.
+ SetTransport(nullptr);
+
+ *aCompleted = true;
+ lock.NotifyAll();
+}
+
+void
+GMPServiceParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ Monitor monitor("DeleteGMPServiceParent");
+ bool completed = false;
+
+ // Make sure the IPC channel is closed before destroying mToDelete.
+ MonitorAutoLock lock(monitor);
+ RefPtr<Runnable> task =
+ NewNonOwningRunnableMethod<Monitor*, bool*>(this,
+ &GMPServiceParent::CloseTransport,
+ &monitor,
+ &completed);
+ XRE_GetIOMessageLoop()->PostTask(Move(task.forget()));
+
+ while (!completed) {
+ lock.Wait();
+ }
+
+ NS_DispatchToCurrentThread(new DeleteGMPServiceParent(this));
+}
+
+class OpenPGMPServiceParent : public mozilla::Runnable
+{
+public:
+ OpenPGMPServiceParent(GMPServiceParent* aGMPServiceParent,
+ mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherPid,
+ bool* aResult)
+ : mGMPServiceParent(aGMPServiceParent),
+ mTransport(aTransport),
+ mOtherPid(aOtherPid),
+ mResult(aResult)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ *mResult = mGMPServiceParent->Open(mTransport, mOtherPid,
+ XRE_GetIOMessageLoop(), ipc::ParentSide);
+ return NS_OK;
+ }
+
+private:
+ GMPServiceParent* mGMPServiceParent;
+ mozilla::ipc::Transport* mTransport;
+ base::ProcessId mOtherPid;
+ bool* mResult;
+};
+
+/* static */
+PGMPServiceParent*
+GMPServiceParent::Create(Transport* aTransport, ProcessId aOtherPid)
+{
+ RefPtr<GeckoMediaPluginServiceParent> gmp =
+ GeckoMediaPluginServiceParent::GetSingleton();
+
+ if (gmp->mShuttingDown) {
+ // Shutdown is initiated. There is no point creating a new actor.
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsAutoPtr<GMPServiceParent> serviceParent(new GMPServiceParent(gmp));
+
+ bool ok;
+ rv = gmpThread->Dispatch(new OpenPGMPServiceParent(serviceParent,
+ aTransport,
+ aOtherPid, &ok),
+ NS_DISPATCH_SYNC);
+ if (NS_FAILED(rv) || !ok) {
+ return nullptr;
+ }
+
+ return serviceParent.forget();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPServiceParent.h b/dom/media/gmp/GMPServiceParent.h
new file mode 100644
index 000000000..49d81055b
--- /dev/null
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPServiceParent_h_
+#define GMPServiceParent_h_
+
+#include "GMPService.h"
+#include "mozilla/gmp/PGMPServiceParent.h"
+#include "mozIGeckoMediaPluginChromeService.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "mozilla/Atomics.h"
+#include "nsIAsyncShutdown.h"
+#include "nsThreadUtils.h"
+#include "mozilla/MozPromise.h"
+#include "GMPStorage.h"
+
+template <class> struct already_AddRefed;
+
+namespace mozilla {
+namespace gmp {
+
+class GMPParent;
+
+class GeckoMediaPluginServiceParent final : public GeckoMediaPluginService
+ , public mozIGeckoMediaPluginChromeService
+ , public nsIAsyncShutdownBlocker
+{
+public:
+ static already_AddRefed<GeckoMediaPluginServiceParent> GetSingleton();
+
+ GeckoMediaPluginServiceParent();
+ nsresult Init() override;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIASYNCSHUTDOWNBLOCKER
+
+ // mozIGeckoMediaPluginService
+ NS_IMETHOD HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool *aRetVal) override;
+ NS_IMETHOD GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsingMode,
+ UniquePtr<GetNodeIdCallback>&& aCallback) override;
+
+ NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE
+ NS_DECL_NSIOBSERVER
+
+ void AsyncShutdownNeeded(GMPParent* aParent);
+ void AsyncShutdownComplete(GMPParent* aParent);
+
+ int32_t AsyncShutdownTimeoutMs();
+ RefPtr<GenericPromise> EnsureInitialized();
+ RefPtr<GenericPromise> AsyncAddPluginDirectory(const nsAString& aDirectory);
+
+ // GMP thread access only
+ bool IsShuttingDown();
+
+ already_AddRefed<GMPStorage> GetMemoryStorageFor(const nsACString& aNodeId);
+ nsresult ForgetThisSiteNative(const nsAString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern);
+
+ // Notifies that some user of this class is created/destroyed.
+ void ServiceUserCreated();
+ void ServiceUserDestroyed();
+
+ void UpdateContentProcessGMPCapabilities();
+
+private:
+ friend class GMPServiceParent;
+
+ virtual ~GeckoMediaPluginServiceParent();
+
+ void ClearStorage();
+
+ already_AddRefed<GMPParent> SelectPluginForAPI(const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags);
+
+ already_AddRefed<GMPParent> FindPluginForAPIFrom(size_t aSearchStartIndex,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ size_t* aOutPluginIndex);
+
+ nsresult GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing, nsACString& aOutId);
+
+ void UnloadPlugins();
+ void CrashPlugins();
+ void NotifySyncShutdownComplete();
+ void NotifyAsyncShutdownComplete();
+
+ void ProcessPossiblePlugin(nsIFile* aDir);
+
+ void RemoveOnGMPThread(const nsAString& aDirectory,
+ const bool aDeleteFromDisk,
+ const bool aCanDefer);
+
+ nsresult SetAsyncShutdownTimeout();
+
+ struct DirectoryFilter {
+ virtual bool operator()(nsIFile* aPath) = 0;
+ ~DirectoryFilter() {}
+ };
+ void ClearNodeIdAndPlugin(DirectoryFilter& aFilter);
+ void ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir,
+ DirectoryFilter& aFilter);
+ void ForgetThisSiteOnGMPThread(const nsACString& aOrigin,
+ const mozilla::OriginAttributesPattern& aPattern);
+ void ClearRecentHistoryOnGMPThread(PRTime aSince);
+
+ already_AddRefed<GMPParent> GetById(uint32_t aPluginId);
+
+protected:
+ friend class GMPParent;
+ void ReAddOnGMPThread(const RefPtr<GMPParent>& aOld);
+ void PluginTerminated(const RefPtr<GMPParent>& aOld);
+ void InitializePlugins(AbstractThread* aAbstractGMPThread) override;
+ RefPtr<GenericPromise::AllPromiseType> LoadFromEnvironment();
+ RefPtr<GenericPromise> AddOnGMPThread(nsString aDirectory);
+ bool GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+ override;
+private:
+ // Creates a copy of aOriginal. Note that the caller is responsible for
+ // adding this to GeckoMediaPluginServiceParent::mPlugins.
+ already_AddRefed<GMPParent> ClonePlugin(const GMPParent* aOriginal);
+ nsresult EnsurePluginsOnDiskScanned();
+ nsresult InitStorage();
+
+ class PathRunnable : public Runnable
+ {
+ public:
+ enum EOperation {
+ REMOVE,
+ REMOVE_AND_DELETE_FROM_DISK,
+ };
+
+ PathRunnable(GeckoMediaPluginServiceParent* aService, const nsAString& aPath,
+ EOperation aOperation, bool aDefer = false)
+ : mService(aService)
+ , mPath(aPath)
+ , mOperation(aOperation)
+ , mDefer(aDefer)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ RefPtr<GeckoMediaPluginServiceParent> mService;
+ nsString mPath;
+ EOperation mOperation;
+ bool mDefer;
+ };
+
+ // Protected by mMutex from the base class.
+ nsTArray<RefPtr<GMPParent>> mPlugins;
+ bool mShuttingDown;
+ nsTArray<RefPtr<GMPParent>> mAsyncShutdownPlugins;
+
+ // True if we've inspected MOZ_GMP_PATH on the GMP thread and loaded any
+ // plugins found there into mPlugins.
+ Atomic<bool> mScannedPluginOnDisk;
+
+ template<typename T>
+ class MainThreadOnly {
+ public:
+ MOZ_IMPLICIT MainThreadOnly(T aValue)
+ : mValue(aValue)
+ {}
+ operator T&() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mValue;
+ }
+
+ private:
+ T mValue;
+ };
+
+ MainThreadOnly<bool> mWaitingForPluginsSyncShutdown;
+
+ nsTArray<nsString> mPluginsWaitingForDeletion;
+
+ nsCOMPtr<nsIFile> mStorageBaseDir;
+
+ // Hashes of (origin,topLevelOrigin) to the node id for
+ // non-persistent sessions.
+ nsClassHashtable<nsUint32HashKey, nsCString> mTempNodeIds;
+
+ // Hashes node id to whether that node id is allowed to store data
+ // persistently on disk.
+ nsDataHashtable<nsCStringHashKey, bool> mPersistentStorageAllowed;
+
+ // Synchronization for barrier that ensures we've loaded GMPs from
+ // MOZ_GMP_PATH before allowing GetContentParentFrom() to proceed.
+ Monitor mInitPromiseMonitor;
+ MozPromiseHolder<GenericPromise> mInitPromise;
+ bool mLoadPluginsFromDiskComplete;
+
+ // Hashes nodeId to the hashtable of storage for that nodeId.
+ nsRefPtrHashtable<nsCStringHashKey, GMPStorage> mTempGMPStorage;
+
+ // Tracks how many users are running (on the GMP thread). Only when this count
+ // drops to 0 can we safely shut down the thread.
+ MainThreadOnly<int32_t> mServiceUserCount;
+};
+
+nsresult ReadSalt(nsIFile* aPath, nsACString& aOutData);
+bool MatchOrigin(nsIFile* aPath,
+ const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern);
+
+class GMPServiceParent final : public PGMPServiceParent
+{
+public:
+ explicit GMPServiceParent(GeckoMediaPluginServiceParent* aService)
+ : mService(aService)
+ {
+ mService->ServiceUserCreated();
+ }
+ virtual ~GMPServiceParent();
+
+ bool RecvGetGMPNodeId(const nsString& aOrigin,
+ const nsString& aTopLevelOrigin,
+ const nsString& aGMPName,
+ const bool& aInPrivateBrowsing,
+ nsCString* aID) override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ static PGMPServiceParent* Create(Transport* aTransport, ProcessId aOtherPid);
+
+ bool RecvSelectGMP(const nsCString& aNodeId,
+ const nsCString& aAPI,
+ nsTArray<nsCString>&& aTags,
+ uint32_t* aOutPluginId,
+ nsresult* aOutRv) override;
+
+ bool RecvLaunchGMP(const uint32_t& aPluginId,
+ nsTArray<ProcessId>&& aAlreadyBridgedTo,
+ ProcessId* aOutID,
+ nsCString* aOutDisplayName,
+ nsresult* aOutRv) override;
+
+private:
+ void CloseTransport(Monitor* aSyncMonitor, bool* aCompleted);
+
+ RefPtr<GeckoMediaPluginServiceParent> mService;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPServiceParent_h_
diff --git a/dom/media/gmp/GMPSharedMemManager.cpp b/dom/media/gmp/GMPSharedMemManager.cpp
new file mode 100644
index 000000000..86b5f9810
--- /dev/null
+++ b/dom/media/gmp/GMPSharedMemManager.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPSharedMemManager.h"
+#include "GMPMessageUtils.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace gmp {
+
+// Really one set of pools on each side of the plugin API.
+
+// YUV buffers go from Encoder parent to child; pool there, and then return
+// with Decoded() frames to the Decoder parent and goes into the parent pool.
+// Compressed (encoded) data goes from the Decoder parent to the child;
+// pool there, and then return with Encoded() frames and goes into the parent
+// pool.
+bool
+GMPSharedMemManager::MgrAllocShmem(GMPSharedMem::GMPMemoryClasses aClass, size_t aSize,
+ ipc::Shmem::SharedMemory::SharedMemoryType aType,
+ ipc::Shmem* aMem)
+{
+ mData->CheckThread();
+
+ // first look to see if we have a free buffer large enough
+ for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) {
+ MOZ_ASSERT(GetGmpFreelist(aClass)[i].IsWritable());
+ if (aSize <= GetGmpFreelist(aClass)[i].Size<uint8_t>()) {
+ *aMem = GetGmpFreelist(aClass)[i];
+ GetGmpFreelist(aClass).RemoveElementAt(i);
+ return true;
+ }
+ }
+
+ // Didn't find a buffer free with enough space; allocate one
+ size_t pagesize = ipc::SharedMemory::SystemPageSize();
+ aSize = (aSize + (pagesize-1)) & ~(pagesize-1); // round up to page size
+ bool retval = Alloc(aSize, aType, aMem);
+ if (retval) {
+ // The allocator (or NeedsShmem call) should never return less than we ask for...
+ MOZ_ASSERT(aMem->Size<uint8_t>() >= aSize);
+ mData->mGmpAllocated[aClass]++;
+ }
+ return retval;
+}
+
+bool
+GMPSharedMemManager::MgrDeallocShmem(GMPSharedMem::GMPMemoryClasses aClass, ipc::Shmem& aMem)
+{
+ mData->CheckThread();
+
+ size_t size = aMem.Size<uint8_t>();
+ size_t total = 0;
+
+ // XXX Bug NNNNNNN Until we put better guards on ipc::shmem, verify we
+ // weren't fed an shmem we already had.
+ for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) {
+ if (NS_WARN_IF(aMem == GetGmpFreelist(aClass)[i])) {
+ // Safest to crash in this case; should never happen in normal
+ // operation.
+ MOZ_CRASH("Deallocating Shmem we already have in our cache!");
+ //return true;
+ }
+ }
+
+ // XXX This works; there are better pool algorithms. We need to avoid
+ // "falling off a cliff" with too low a number
+ if (GetGmpFreelist(aClass).Length() > 10) {
+ Dealloc(GetGmpFreelist(aClass)[0]);
+ GetGmpFreelist(aClass).RemoveElementAt(0);
+ // The allocation numbers will be fubar on the Child!
+ mData->mGmpAllocated[aClass]--;
+ }
+ for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) {
+ MOZ_ASSERT(GetGmpFreelist(aClass)[i].IsWritable());
+ total += GetGmpFreelist(aClass)[i].Size<uint8_t>();
+ if (size < GetGmpFreelist(aClass)[i].Size<uint8_t>()) {
+ GetGmpFreelist(aClass).InsertElementAt(i, aMem);
+ return true;
+ }
+ }
+ GetGmpFreelist(aClass).AppendElement(aMem);
+
+ return true;
+}
+
+uint32_t
+GMPSharedMemManager::NumInUse(GMPSharedMem::GMPMemoryClasses aClass)
+{
+ return mData->mGmpAllocated[aClass] - GetGmpFreelist(aClass).Length();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPSharedMemManager.h b/dom/media/gmp/GMPSharedMemManager.h
new file mode 100644
index 000000000..cc36f3fc0
--- /dev/null
+++ b/dom/media/gmp/GMPSharedMemManager.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPSharedMemManager_h_
+#define GMPSharedMemManager_h_
+
+#include "mozilla/ipc/Shmem.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPSharedMemManager;
+
+class GMPSharedMem
+{
+public:
+ typedef enum {
+ kGMPFrameData = 0,
+ kGMPEncodedData,
+ kGMPNumTypes
+ } GMPMemoryClasses;
+
+ // This is a heuristic - max of 10 free in the Child pool, plus those
+ // in-use for the encoder and decoder at the given moment and not yet
+ // returned to the parent pool (which is not included). If more than
+ // this are needed, we presume the client has either crashed or hung
+ // (perhaps temporarily).
+ static const uint32_t kGMPBufLimit = 20;
+
+ GMPSharedMem()
+ {
+ for (size_t i = 0; i < sizeof(mGmpAllocated)/sizeof(mGmpAllocated[0]); i++) {
+ mGmpAllocated[i] = 0;
+ }
+ }
+ virtual ~GMPSharedMem() {}
+
+ // Parent and child impls will differ here
+ virtual void CheckThread() = 0;
+
+protected:
+ friend class GMPSharedMemManager;
+
+ nsTArray<ipc::Shmem> mGmpFreelist[GMPSharedMem::kGMPNumTypes];
+ uint32_t mGmpAllocated[GMPSharedMem::kGMPNumTypes];
+};
+
+class GMPSharedMemManager
+{
+public:
+ explicit GMPSharedMemManager(GMPSharedMem *aData) : mData(aData) {}
+ virtual ~GMPSharedMemManager() {}
+
+ virtual bool MgrAllocShmem(GMPSharedMem::GMPMemoryClasses aClass, size_t aSize,
+ ipc::Shmem::SharedMemory::SharedMemoryType aType,
+ ipc::Shmem* aMem);
+ virtual bool MgrDeallocShmem(GMPSharedMem::GMPMemoryClasses aClass, ipc::Shmem& aMem);
+
+ // So we can know if data is "piling up" for the plugin - I.e. it's hung or crashed
+ virtual uint32_t NumInUse(GMPSharedMem::GMPMemoryClasses aClass);
+
+ // These have to be implemented using the AllocShmem/etc provided by the IPDL-generated interfaces,
+ // so have the Parent/Child implement them.
+ virtual bool Alloc(size_t aSize, ipc::Shmem::SharedMemory::SharedMemoryType aType, ipc::Shmem* aMem) = 0;
+ virtual void Dealloc(ipc::Shmem& aMem) = 0;
+
+private:
+ nsTArray<ipc::Shmem>& GetGmpFreelist(GMPSharedMem::GMPMemoryClasses aTypes)
+ {
+ return mData->mGmpFreelist[aTypes];
+ }
+
+ GMPSharedMem *mData;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPSharedMemManager_h_
diff --git a/dom/media/gmp/GMPStorage.h b/dom/media/gmp/GMPStorage.h
new file mode 100644
index 000000000..db3aebc8c
--- /dev/null
+++ b/dom/media/gmp/GMPStorage.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPStorage_h_
+#define GMPStorage_h_
+
+#include "gmp-storage.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPStorage {
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorage)
+
+ virtual GMPErr Open(const nsCString& aRecordName) = 0;
+ virtual bool IsOpen(const nsCString& aRecordName) const = 0;
+ virtual GMPErr Read(const nsCString& aRecordName,
+ nsTArray<uint8_t>& aOutBytes) = 0;
+ virtual GMPErr Write(const nsCString& aRecordName,
+ const nsTArray<uint8_t>& aBytes) = 0;
+ virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const = 0;
+ virtual void Close(const nsCString& aRecordName) = 0;
+protected:
+ virtual ~GMPStorage() {}
+};
+
+already_AddRefed<GMPStorage> CreateGMPMemoryStorage();
+already_AddRefed<GMPStorage> CreateGMPDiskStorage(const nsCString& aNodeId,
+ const nsString& aGMPName);
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif \ No newline at end of file
diff --git a/dom/media/gmp/GMPStorageChild.cpp b/dom/media/gmp/GMPStorageChild.cpp
new file mode 100644
index 000000000..052b56d1b
--- /dev/null
+++ b/dom/media/gmp/GMPStorageChild.cpp
@@ -0,0 +1,380 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPStorageChild.h"
+#include "GMPChild.h"
+#include "gmp-storage.h"
+#include "base/task.h"
+
+#define ON_GMP_THREAD() (mPlugin->GMPMessageLoop() == MessageLoop::current())
+
+#define CALL_ON_GMP_THREAD(_func, ...) \
+ do { \
+ if (ON_GMP_THREAD()) { \
+ _func(__VA_ARGS__); \
+ } else { \
+ mPlugin->GMPMessageLoop()->PostTask( \
+ dont_add_new_uses_of_this::NewRunnableMethod(this, &GMPStorageChild::_func, ##__VA_ARGS__) \
+ ); \
+ } \
+ } while(false)
+
+static nsTArray<uint8_t>
+ToArray(const uint8_t* aData, uint32_t aDataSize)
+{
+ nsTArray<uint8_t> data;
+ data.AppendElements(aData, aDataSize);
+ return mozilla::Move(data);
+}
+
+namespace mozilla {
+namespace gmp {
+
+GMPRecordImpl::GMPRecordImpl(GMPStorageChild* aOwner,
+ const nsCString& aName,
+ GMPRecordClient* aClient)
+ : mName(aName)
+ , mClient(aClient)
+ , mOwner(aOwner)
+{
+}
+
+GMPErr
+GMPRecordImpl::Open()
+{
+ return mOwner->Open(this);
+}
+
+void
+GMPRecordImpl::OpenComplete(GMPErr aStatus)
+{
+ mClient->OpenComplete(aStatus);
+}
+
+GMPErr
+GMPRecordImpl::Read()
+{
+ return mOwner->Read(this);
+}
+
+void
+GMPRecordImpl::ReadComplete(GMPErr aStatus,
+ const uint8_t* aBytes,
+ uint32_t aLength)
+{
+ mClient->ReadComplete(aStatus, aBytes, aLength);
+}
+
+GMPErr
+GMPRecordImpl::Write(const uint8_t* aData, uint32_t aDataSize)
+{
+ return mOwner->Write(this, aData, aDataSize);
+}
+
+void
+GMPRecordImpl::WriteComplete(GMPErr aStatus)
+{
+ mClient->WriteComplete(aStatus);
+}
+
+GMPErr
+GMPRecordImpl::Close()
+{
+ RefPtr<GMPRecordImpl> kungfuDeathGrip(this);
+ // Delete our self reference.
+ Release();
+ mOwner->Close(this->Name());
+ return GMPNoErr;
+}
+
+GMPStorageChild::GMPStorageChild(GMPChild* aPlugin)
+ : mMonitor("GMPStorageChild")
+ , mPlugin(aPlugin)
+ , mShutdown(false)
+{
+ MOZ_ASSERT(ON_GMP_THREAD());
+}
+
+GMPErr
+GMPStorageChild::CreateRecord(const nsCString& aRecordName,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ MOZ_ASSERT(aRecordName.Length() && aOutRecord);
+
+ if (HasRecord(aRecordName)) {
+ return GMPRecordInUse;
+ }
+
+ RefPtr<GMPRecordImpl> record(new GMPRecordImpl(this, aRecordName, aClient));
+ mRecords.Put(aRecordName, record); // Addrefs
+
+ // The GMPRecord holds a self reference until the GMP calls Close() on
+ // it. This means the object is always valid (even if neutered) while
+ // the GMP expects it to be.
+ record.forget(aOutRecord);
+
+ return GMPNoErr;
+}
+
+bool
+GMPStorageChild::HasRecord(const nsCString& aRecordName)
+{
+ mMonitor.AssertCurrentThreadOwns();
+ return mRecords.Contains(aRecordName);
+}
+
+already_AddRefed<GMPRecordImpl>
+GMPStorageChild::GetRecord(const nsCString& aRecordName)
+{
+ MonitorAutoLock lock(mMonitor);
+ RefPtr<GMPRecordImpl> record;
+ mRecords.Get(aRecordName, getter_AddRefs(record));
+ return record.forget();
+}
+
+GMPErr
+GMPStorageChild::Open(GMPRecordImpl* aRecord)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ if (!HasRecord(aRecord->Name())) {
+ // Trying to re-open a record that has already been closed.
+ return GMPClosedErr;
+ }
+
+ CALL_ON_GMP_THREAD(SendOpen, aRecord->Name());
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPStorageChild::Read(GMPRecordImpl* aRecord)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ if (!HasRecord(aRecord->Name())) {
+ // Record not opened.
+ return GMPClosedErr;
+ }
+
+ CALL_ON_GMP_THREAD(SendRead, aRecord->Name());
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPStorageChild::Write(GMPRecordImpl* aRecord,
+ const uint8_t* aData,
+ uint32_t aDataSize)
+{
+ if (aDataSize > GMP_MAX_RECORD_SIZE) {
+ return GMPQuotaExceededErr;
+ }
+
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ if (!HasRecord(aRecord->Name())) {
+ // Record not opened.
+ return GMPClosedErr;
+ }
+
+ CALL_ON_GMP_THREAD(SendWrite, aRecord->Name(), ToArray(aData, aDataSize));
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPStorageChild::Close(const nsCString& aRecordName)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (!HasRecord(aRecordName)) {
+ // Already closed.
+ return GMPClosedErr;
+ }
+
+ mRecords.Remove(aRecordName);
+
+ if (!mShutdown) {
+ CALL_ON_GMP_THREAD(SendClose, aRecordName);
+ }
+
+ return GMPNoErr;
+}
+
+bool
+GMPStorageChild::RecvOpenComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus)
+{
+ // We don't need a lock to read |mShutdown| since it is only changed in
+ // the GMP thread.
+ if (mShutdown) {
+ return true;
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return true;
+ }
+ record->OpenComplete(aStatus);
+ return true;
+}
+
+bool
+GMPStorageChild::RecvReadComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus,
+ InfallibleTArray<uint8_t>&& aBytes)
+{
+ if (mShutdown) {
+ return true;
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return true;
+ }
+ record->ReadComplete(aStatus, aBytes.Elements(), aBytes.Length());
+ return true;
+}
+
+bool
+GMPStorageChild::RecvWriteComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus)
+{
+ if (mShutdown) {
+ return true;
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return true;
+ }
+ record->WriteComplete(aStatus);
+ return true;
+}
+
+GMPErr
+GMPStorageChild::EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ MOZ_ASSERT(aRecvIteratorFunc);
+ mPendingRecordIterators.push(RecordIteratorContext(aRecvIteratorFunc, aUserArg));
+
+ CALL_ON_GMP_THREAD(SendGetRecordNames);
+
+ return GMPNoErr;
+}
+
+class GMPRecordIteratorImpl : public GMPRecordIterator {
+public:
+ explicit GMPRecordIteratorImpl(const InfallibleTArray<nsCString>& aRecordNames)
+ : mRecordNames(aRecordNames)
+ , mIndex(0)
+ {
+ mRecordNames.Sort();
+ }
+
+ GMPErr GetName(const char** aOutName, uint32_t* aOutNameLength) override {
+ if (!aOutName || !aOutNameLength) {
+ return GMPInvalidArgErr;
+ }
+ if (mIndex == mRecordNames.Length()) {
+ return GMPEndOfEnumeration;
+ }
+ *aOutName = mRecordNames[mIndex].get();
+ *aOutNameLength = mRecordNames[mIndex].Length();
+ return GMPNoErr;
+ }
+
+ GMPErr NextRecord() override {
+ if (mIndex < mRecordNames.Length()) {
+ mIndex++;
+ }
+ return (mIndex < mRecordNames.Length()) ? GMPNoErr
+ : GMPEndOfEnumeration;
+ }
+
+ void Close() override {
+ delete this;
+ }
+
+private:
+ nsTArray<nsCString> mRecordNames;
+ size_t mIndex;
+};
+
+bool
+GMPStorageChild::RecvRecordNames(InfallibleTArray<nsCString>&& aRecordNames,
+ const GMPErr& aStatus)
+{
+ RecordIteratorContext ctx;
+ {
+ MonitorAutoLock lock(mMonitor);
+ if (mShutdown || mPendingRecordIterators.empty()) {
+ return true;
+ }
+ ctx = mPendingRecordIterators.front();
+ mPendingRecordIterators.pop();
+ }
+
+ if (GMP_FAILED(aStatus)) {
+ ctx.mFunc(nullptr, ctx.mUserArg, aStatus);
+ } else {
+ ctx.mFunc(new GMPRecordIteratorImpl(aRecordNames), ctx.mUserArg, GMPNoErr);
+ }
+
+ return true;
+}
+
+bool
+GMPStorageChild::RecvShutdown()
+{
+ // Block any new storage requests, and thus any messages back to the
+ // parent. We don't delete any objects here, as that may invalidate
+ // GMPRecord pointers held by the GMP.
+ MonitorAutoLock lock(mMonitor);
+ mShutdown = true;
+ while (!mPendingRecordIterators.empty()) {
+ mPendingRecordIterators.pop();
+ }
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+// avoid redefined macro in unified build
+#undef ON_GMP_THREAD
+#undef CALL_ON_GMP_THREAD
diff --git a/dom/media/gmp/GMPStorageChild.h b/dom/media/gmp/GMPStorageChild.h
new file mode 100644
index 000000000..3c5948a30
--- /dev/null
+++ b/dom/media/gmp/GMPStorageChild.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPStorageChild_h_
+#define GMPStorageChild_h_
+
+#include "mozilla/gmp/PGMPStorageChild.h"
+#include "gmp-storage.h"
+#include "nsTHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "gmp-platform.h"
+
+#include <queue>
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+class GMPStorageChild;
+
+class GMPRecordImpl : public GMPRecord
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPRecordImpl)
+
+ GMPRecordImpl(GMPStorageChild* aOwner,
+ const nsCString& aName,
+ GMPRecordClient* aClient);
+
+ // GMPRecord.
+ GMPErr Open() override;
+ GMPErr Read() override;
+ GMPErr Write(const uint8_t* aData,
+ uint32_t aDataSize) override;
+ GMPErr Close() override;
+
+ const nsCString& Name() const { return mName; }
+
+ void OpenComplete(GMPErr aStatus);
+ void ReadComplete(GMPErr aStatus, const uint8_t* aBytes, uint32_t aLength);
+ void WriteComplete(GMPErr aStatus);
+
+private:
+ ~GMPRecordImpl() {}
+ const nsCString mName;
+ GMPRecordClient* const mClient;
+ GMPStorageChild* const mOwner;
+};
+
+class GMPStorageChild : public PGMPStorageChild
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorageChild)
+
+ explicit GMPStorageChild(GMPChild* aPlugin);
+
+ GMPErr CreateRecord(const nsCString& aRecordName,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+ GMPErr Open(GMPRecordImpl* aRecord);
+
+ GMPErr Read(GMPRecordImpl* aRecord);
+
+ GMPErr Write(GMPRecordImpl* aRecord,
+ const uint8_t* aData,
+ uint32_t aDataSize);
+
+ GMPErr Close(const nsCString& aRecordName);
+
+ GMPErr EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+private:
+ bool HasRecord(const nsCString& aRecordName);
+ already_AddRefed<GMPRecordImpl> GetRecord(const nsCString& aRecordName);
+
+protected:
+ ~GMPStorageChild() {}
+
+ // PGMPStorageChild
+ bool RecvOpenComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus) override;
+ bool RecvReadComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus,
+ InfallibleTArray<uint8_t>&& aBytes) override;
+ bool RecvWriteComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus) override;
+ bool RecvRecordNames(InfallibleTArray<nsCString>&& aRecordNames,
+ const GMPErr& aStatus) override;
+ bool RecvShutdown() override;
+
+private:
+ Monitor mMonitor;
+ nsRefPtrHashtable<nsCStringHashKey, GMPRecordImpl> mRecords;
+ GMPChild* mPlugin;
+
+ struct RecordIteratorContext {
+ explicit RecordIteratorContext(RecvGMPRecordIteratorPtr aFunc,
+ void* aUserArg)
+ : mFunc(aFunc)
+ , mUserArg(aUserArg)
+ {}
+ RecordIteratorContext() {}
+ RecvGMPRecordIteratorPtr mFunc;
+ void* mUserArg;
+ };
+
+ std::queue<RecordIteratorContext> mPendingRecordIterators;
+ bool mShutdown;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPStorageChild_h_
diff --git a/dom/media/gmp/GMPStorageParent.cpp b/dom/media/gmp/GMPStorageParent.cpp
new file mode 100644
index 000000000..e3eadeccf
--- /dev/null
+++ b/dom/media/gmp/GMPStorageParent.cpp
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPStorageParent.h"
+#include "GMPParent.h"
+#include "gmp-storage.h"
+#include "mozilla/Unused.h"
+#include "mozIGeckoMediaPluginService.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+GMPStorageParent::GMPStorageParent(const nsCString& aNodeId,
+ GMPParent* aPlugin)
+ : mNodeId(aNodeId)
+ , mPlugin(aPlugin)
+ , mShutdown(true)
+{
+}
+
+nsresult
+GMPStorageParent::Init()
+{
+ LOGD(("GMPStorageParent[%p]::Init()", this));
+
+ if (NS_WARN_IF(mNodeId.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<GeckoMediaPluginServiceParent> mps(GeckoMediaPluginServiceParent::GetSingleton());
+ if (NS_WARN_IF(!mps)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool persistent = false;
+ if (NS_WARN_IF(NS_FAILED(mps->IsPersistentStorageAllowed(mNodeId, &persistent)))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (persistent) {
+ mStorage = CreateGMPDiskStorage(mNodeId, mPlugin->GetPluginBaseName());
+ } else {
+ mStorage = mps->GetMemoryStorageFor(mNodeId);
+ }
+ if (!mStorage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mShutdown = false;
+ return NS_OK;
+}
+
+bool
+GMPStorageParent::RecvOpen(const nsCString& aRecordName)
+{
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s')",
+ this, aRecordName.get()));
+
+ if (mShutdown) {
+ return false;
+ }
+
+ if (mNodeId.EqualsLiteral("null")) {
+ // Refuse to open storage if the page is opened from local disk,
+ // or shared across origin.
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; null nodeId",
+ this, aRecordName.get()));
+ Unused << SendOpenComplete(aRecordName, GMPGenericErr);
+ return true;
+ }
+
+ if (aRecordName.IsEmpty()) {
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record name empty",
+ this, aRecordName.get()));
+ Unused << SendOpenComplete(aRecordName, GMPGenericErr);
+ return true;
+ }
+
+ if (mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record in use",
+ this, aRecordName.get()));
+ Unused << SendOpenComplete(aRecordName, GMPRecordInUse);
+ return true;
+ }
+
+ auto err = mStorage->Open(aRecordName);
+ MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName));
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') complete; rv=%d",
+ this, aRecordName.get(), err));
+ Unused << SendOpenComplete(aRecordName, err);
+
+ return true;
+}
+
+bool
+GMPStorageParent::RecvRead(const nsCString& aRecordName)
+{
+ LOGD(("GMPStorageParent[%p]::RecvRead(record='%s')",
+ this, aRecordName.get()));
+
+ if (mShutdown) {
+ return false;
+ }
+
+ nsTArray<uint8_t> data;
+ if (!mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') failed; record not open",
+ this, aRecordName.get()));
+ Unused << SendReadComplete(aRecordName, GMPClosedErr, data);
+ } else {
+ GMPErr rv = mStorage->Read(aRecordName, data);
+ LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') read %d bytes rv=%d",
+ this, aRecordName.get(), data.Length(), rv));
+ Unused << SendReadComplete(aRecordName, rv, data);
+ }
+
+ return true;
+}
+
+bool
+GMPStorageParent::RecvWrite(const nsCString& aRecordName,
+ InfallibleTArray<uint8_t>&& aBytes)
+{
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') %d bytes",
+ this, aRecordName.get(), aBytes.Length()));
+
+ if (mShutdown) {
+ return false;
+ }
+
+ if (!mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record not open",
+ this, aRecordName.get()));
+ Unused << SendWriteComplete(aRecordName, GMPClosedErr);
+ return true;
+ }
+
+ if (aBytes.Length() > GMP_MAX_RECORD_SIZE) {
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record too big",
+ this, aRecordName.get()));
+ Unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr);
+ return true;
+ }
+
+ GMPErr rv = mStorage->Write(aRecordName, aBytes);
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') write complete rv=%d",
+ this, aRecordName.get(), rv));
+
+ Unused << SendWriteComplete(aRecordName, rv);
+
+ return true;
+}
+
+bool
+GMPStorageParent::RecvGetRecordNames()
+{
+ if (mShutdown) {
+ return true;
+ }
+
+ nsTArray<nsCString> recordNames;
+ GMPErr status = mStorage->GetRecordNames(recordNames);
+
+ LOGD(("GMPStorageParent[%p]::RecvGetRecordNames() status=%d numRecords=%d",
+ this, status, recordNames.Length()));
+
+ Unused << SendRecordNames(recordNames, status);
+
+ return true;
+}
+
+bool
+GMPStorageParent::RecvClose(const nsCString& aRecordName)
+{
+ LOGD(("GMPStorageParent[%p]::RecvClose(record='%s')",
+ this, aRecordName.get()));
+
+ if (mShutdown) {
+ return true;
+ }
+
+ mStorage->Close(aRecordName);
+
+ return true;
+}
+
+void
+GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("GMPStorageParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
+ Shutdown();
+}
+
+void
+GMPStorageParent::Shutdown()
+{
+ LOGD(("GMPStorageParent[%p]::Shutdown()", this));
+
+ if (mShutdown) {
+ return;
+ }
+ mShutdown = true;
+ Unused << SendShutdown();
+
+ mStorage = nullptr;
+
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPStorageParent.h b/dom/media/gmp/GMPStorageParent.h
new file mode 100644
index 000000000..3d2ea69ac
--- /dev/null
+++ b/dom/media/gmp/GMPStorageParent.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPStorageParent_h_
+#define GMPStorageParent_h_
+
+#include "mozilla/gmp/PGMPStorageParent.h"
+#include "GMPStorage.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPParent;
+
+class GMPStorageParent : public PGMPStorageParent {
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPStorageParent)
+ GMPStorageParent(const nsCString& aNodeId,
+ GMPParent* aPlugin);
+
+ nsresult Init();
+ void Shutdown();
+
+protected:
+ bool RecvOpen(const nsCString& aRecordName) override;
+ bool RecvRead(const nsCString& aRecordName) override;
+ bool RecvWrite(const nsCString& aRecordName,
+ InfallibleTArray<uint8_t>&& aBytes) override;
+ bool RecvGetRecordNames() override;
+ bool RecvClose(const nsCString& aRecordName) override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+ ~GMPStorageParent() {}
+
+ RefPtr<GMPStorage> mStorage;
+
+ const nsCString mNodeId;
+ RefPtr<GMPParent> mPlugin;
+ // True after Shutdown(), or if Init() has not completed successfully.
+ bool mShutdown;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPStorageParent_h_
diff --git a/dom/media/gmp/GMPTimerChild.cpp b/dom/media/gmp/GMPTimerChild.cpp
new file mode 100644
index 000000000..0b2b55c79
--- /dev/null
+++ b/dom/media/gmp/GMPTimerChild.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPTimerChild.h"
+#include "GMPPlatform.h"
+#include "GMPChild.h"
+
+#define MAX_NUM_TIMERS 1000
+
+namespace mozilla {
+namespace gmp {
+
+GMPTimerChild::GMPTimerChild(GMPChild* aPlugin)
+ : mTimerCount(1)
+ , mPlugin(aPlugin)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+}
+
+GMPTimerChild::~GMPTimerChild()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+}
+
+GMPErr
+GMPTimerChild::SetTimer(GMPTask* aTask, int64_t aTimeoutMS)
+{
+ if (!aTask) {
+ NS_WARNING("Tried to set timer with null task!");
+ return GMPGenericErr;
+ }
+
+ if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
+ NS_WARNING("Tried to set GMP timer on non-main thread.");
+ return GMPGenericErr;
+ }
+
+ if (mTimers.Count() > MAX_NUM_TIMERS) {
+ return GMPQuotaExceededErr;
+ }
+ uint32_t timerId = mTimerCount;
+ mTimers.Put(timerId, aTask);
+ mTimerCount++;
+
+ if (!SendSetTimer(timerId, aTimeoutMS)) {
+ return GMPGenericErr;
+ }
+ return GMPNoErr;
+}
+
+bool
+GMPTimerChild::RecvTimerExpired(const uint32_t& aTimerId)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ GMPTask* task = mTimers.Get(aTimerId);
+ mTimers.Remove(aTimerId);
+ if (task) {
+ RunOnMainThread(task);
+ }
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPTimerChild.h b/dom/media/gmp/GMPTimerChild.h
new file mode 100644
index 000000000..c63a49480
--- /dev/null
+++ b/dom/media/gmp/GMPTimerChild.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPTimerChild_h_
+#define GMPTimerChild_h_
+
+#include "mozilla/gmp/PGMPTimerChild.h"
+#include "mozilla/Monitor.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "gmp-errors.h"
+#include "gmp-platform.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+
+class GMPTimerChild : public PGMPTimerChild
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPTimerChild)
+
+ explicit GMPTimerChild(GMPChild* aPlugin);
+
+ GMPErr SetTimer(GMPTask* aTask, int64_t aTimeoutMS);
+
+protected:
+ // GMPTimerChild
+ bool RecvTimerExpired(const uint32_t& aTimerId) override;
+
+private:
+ ~GMPTimerChild();
+
+ nsDataHashtable<nsUint32HashKey, GMPTask*> mTimers;
+ uint32_t mTimerCount;
+
+ GMPChild* mPlugin;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPTimerChild_h_
diff --git a/dom/media/gmp/GMPTimerParent.cpp b/dom/media/gmp/GMPTimerParent.cpp
new file mode 100644
index 000000000..50861b97f
--- /dev/null
+++ b/dom/media/gmp/GMPTimerParent.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPTimerParent.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Unused.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPParent"
+
+namespace gmp {
+
+GMPTimerParent::GMPTimerParent(nsIThread* aGMPThread)
+ : mGMPThread(aGMPThread)
+ , mIsOpen(true)
+{
+}
+
+bool
+GMPTimerParent::RecvSetTimer(const uint32_t& aTimerId,
+ const uint32_t& aTimeoutMs)
+{
+ LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen));
+
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ if (!mIsOpen) {
+ return true;
+ }
+
+ nsresult rv;
+ nsAutoPtr<Context> ctx(new Context());
+ ctx->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ ctx->mId = aTimerId;
+ rv = ctx->mTimer->SetTarget(mGMPThread);
+ NS_ENSURE_SUCCESS(rv, true);
+ ctx->mParent = this;
+
+ rv = ctx->mTimer->InitWithFuncCallback(&GMPTimerParent::GMPTimerExpired,
+ ctx,
+ aTimeoutMs,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ mTimers.PutEntry(ctx.forget());
+
+ return true;
+}
+
+void
+GMPTimerParent::Shutdown()
+{
+ LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen));
+
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ for (auto iter = mTimers.Iter(); !iter.Done(); iter.Next()) {
+ Context* context = iter.Get()->GetKey();
+ context->mTimer->Cancel();
+ delete context;
+ }
+
+ mTimers.Clear();
+ mIsOpen = false;
+}
+
+void
+GMPTimerParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen));
+
+ Shutdown();
+}
+
+/* static */
+void
+GMPTimerParent::GMPTimerExpired(nsITimer *aTimer, void *aClosure)
+{
+ MOZ_ASSERT(aClosure);
+ nsAutoPtr<Context> ctx(static_cast<Context*>(aClosure));
+ MOZ_ASSERT(ctx->mParent);
+ if (ctx->mParent) {
+ ctx->mParent->TimerExpired(ctx);
+ }
+}
+
+void
+GMPTimerParent::TimerExpired(Context* aContext)
+{
+ LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen));
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ if (!mIsOpen) {
+ return;
+ }
+
+ uint32_t id = aContext->mId;
+ mTimers.RemoveEntry(aContext);
+ if (id) {
+ Unused << SendTimerExpired(id);
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPTimerParent.h b/dom/media/gmp/GMPTimerParent.h
new file mode 100644
index 000000000..0aad36121
--- /dev/null
+++ b/dom/media/gmp/GMPTimerParent.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPTimerParent_h_
+#define GMPTimerParent_h_
+
+#include "mozilla/gmp/PGMPTimerParent.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/Monitor.h"
+#include "nsIThread.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPTimerParent : public PGMPTimerParent {
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPTimerParent)
+ explicit GMPTimerParent(nsIThread* aGMPThread);
+
+ void Shutdown();
+
+protected:
+ bool RecvSetTimer(const uint32_t& aTimerId,
+ const uint32_t& aTimeoutMs) override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+ ~GMPTimerParent() {}
+
+ static void GMPTimerExpired(nsITimer *aTimer, void *aClosure);
+
+ struct Context {
+ Context() {
+ MOZ_COUNT_CTOR(Context);
+ }
+ ~Context() {
+ MOZ_COUNT_DTOR(Context);
+ }
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<GMPTimerParent> mParent; // Note: live timers keep the GMPTimerParent alive.
+ uint32_t mId;
+ };
+
+ void TimerExpired(Context* aContext);
+
+ nsTHashtable<nsPtrHashKey<Context>> mTimers;
+
+ nsCOMPtr<nsIThread> mGMPThread;
+
+ bool mIsOpen;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPTimerParent_h_
diff --git a/dom/media/gmp/GMPTypes.ipdlh b/dom/media/gmp/GMPTypes.ipdlh
new file mode 100644
index 000000000..44df5fc3d
--- /dev/null
+++ b/dom/media/gmp/GMPTypes.ipdlh
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+using GMPBufferType from "gmp-video-codec.h";
+using GMPAudioCodecType from "gmp-audio-codec.h";
+using GMPMediaKeyStatus from "gmp-decryption.h";
+
+namespace mozilla {
+namespace gmp {
+
+struct GMPDecryptionData {
+ uint8_t[] mKeyId;
+ uint8_t[] mIV;
+ uint16_t[] mClearBytes;
+ uint32_t[] mCipherBytes;
+ nsCString[] mSessionIds;
+};
+
+struct GMPVideoEncodedFrameData
+{
+ uint32_t mEncodedWidth;
+ uint32_t mEncodedHeight;
+ uint64_t mTimestamp; // microseconds
+ uint64_t mDuration; // microseconds
+ uint32_t mFrameType;
+ uint32_t mSize;
+ GMPBufferType mBufferType;
+ Shmem mBuffer;
+ bool mCompleteFrame;
+ GMPDecryptionData mDecryptionData;
+};
+
+struct GMPPlaneData
+{
+ int32_t mSize;
+ int32_t mStride;
+ Shmem mBuffer;
+};
+
+struct GMPVideoi420FrameData
+{
+ GMPPlaneData mYPlane;
+ GMPPlaneData mUPlane;
+ GMPPlaneData mVPlane;
+ int32_t mWidth;
+ int32_t mHeight;
+ uint64_t mTimestamp; // microseconds
+ uint64_t mDuration; // microseconds
+};
+
+struct GMPAudioCodecData
+{
+ GMPAudioCodecType mCodecType;
+ uint32_t mChannelCount;
+ uint32_t mBitsPerChannel;
+ uint32_t mSamplesPerSecond;
+
+ uint8_t[] mExtraData;
+};
+
+struct GMPAudioEncodedSampleData
+{
+ uint8_t[] mData;
+ uint64_t mTimeStamp; // microseconds.
+ GMPDecryptionData mDecryptionData;
+ uint32_t mChannelCount;
+ uint32_t mSamplesPerSecond;
+};
+
+struct GMPAudioDecodedSampleData
+{
+ int16_t[] mData;
+ uint64_t mTimeStamp; // microseconds.
+ uint32_t mChannelCount;
+ uint32_t mSamplesPerSecond;
+};
+
+struct GMPKeyInformation {
+ uint8_t[] keyId;
+ GMPMediaKeyStatus status;
+};
+
+}
+}
diff --git a/dom/media/gmp/GMPUtils.cpp b/dom/media/gmp/GMPUtils.cpp
new file mode 100644
index 000000000..d5863516a
--- /dev/null
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsLiteralString.h"
+#include "nsCRTGlue.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/Base64.h"
+#include "nsISimpleEnumerator.h"
+#include "prio.h"
+
+namespace mozilla {
+
+bool
+GetEMEVoucherPath(nsIFile** aPath)
+{
+ nsCOMPtr<nsIFile> path;
+ NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(path));
+ if (!path) {
+ NS_WARNING("GetEMEVoucherPath can't get NS_GRE_DIR!");
+ return false;
+ }
+ path->AppendNative(NS_LITERAL_CSTRING("voucher.bin"));
+ path.forget(aPath);
+ return true;
+}
+
+bool
+EMEVoucherFileExists()
+{
+ nsCOMPtr<nsIFile> path;
+ bool exists;
+ return GetEMEVoucherPath(getter_AddRefs(path)) &&
+ NS_SUCCEEDED(path->Exists(&exists)) &&
+ exists;
+}
+
+void
+SplitAt(const char* aDelims,
+ const nsACString& aInput,
+ nsTArray<nsCString>& aOutTokens)
+{
+ nsAutoCString str(aInput);
+ char* end = str.BeginWriting();
+ const char* start = nullptr;
+ while (!!(start = NS_strtok(aDelims, &end))) {
+ aOutTokens.AppendElement(nsCString(start));
+ }
+}
+
+nsCString
+ToBase64(const nsTArray<uint8_t>& aBytes)
+{
+ nsAutoCString base64;
+ nsDependentCSubstring raw(reinterpret_cast<const char*>(aBytes.Elements()),
+ aBytes.Length());
+ nsresult rv = Base64Encode(raw, base64);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_LITERAL_CSTRING("[Base64EncodeFailed]");
+ }
+ return base64;
+}
+
+bool
+FileExists(nsIFile* aFile)
+{
+ bool exists = false;
+ return aFile && NS_SUCCEEDED(aFile->Exists(&exists)) && exists;
+}
+
+DirectoryEnumerator::DirectoryEnumerator(nsIFile* aPath, Mode aMode)
+ : mMode(aMode)
+{
+ aPath->GetDirectoryEntries(getter_AddRefs(mIter));
+}
+
+already_AddRefed<nsIFile>
+DirectoryEnumerator::Next()
+{
+ if (!mIter) {
+ return nullptr;
+ }
+ bool hasMore = false;
+ while (NS_SUCCEEDED(mIter->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ nsresult rv = mIter->GetNext(getter_AddRefs(supports));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> path(do_QueryInterface(supports, &rv));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (mMode == DirsOnly) {
+ bool isDirectory = false;
+ rv = path->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory) {
+ continue;
+ }
+ }
+ return path.forget();
+ }
+ return nullptr;
+}
+
+bool
+ReadIntoArray(nsIFile* aFile,
+ nsTArray<uint8_t>& aOutDst,
+ size_t aMaxLength)
+{
+ if (!FileExists(aFile)) {
+ return false;
+ }
+
+ PRFileDesc* fd = nullptr;
+ nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ int32_t length = PR_Seek(fd, 0, PR_SEEK_END);
+ PR_Seek(fd, 0, PR_SEEK_SET);
+
+ if (length < 0 || (size_t)length > aMaxLength) {
+ NS_WARNING("EME file is longer than maximum allowed length");
+ PR_Close(fd);
+ return false;
+ }
+ aOutDst.SetLength(length);
+ int32_t bytesRead = PR_Read(fd, aOutDst.Elements(), length);
+ PR_Close(fd);
+ return (bytesRead == length);
+}
+
+bool
+ReadIntoString(nsIFile* aFile,
+ nsCString& aOutDst,
+ size_t aMaxLength)
+{
+ nsTArray<uint8_t> buf;
+ bool rv = ReadIntoArray(aFile, buf, aMaxLength);
+ if (rv) {
+ buf.AppendElement(0); // Append null terminator, required by nsC*String.
+ aOutDst = nsDependentCString((const char*)buf.Elements(), buf.Length() - 1);
+ }
+ return rv;
+}
+
+bool
+GMPInfoFileParser::Init(nsIFile* aInfoFile)
+{
+ nsTArray<nsCString> lines;
+ static const size_t MAX_GMP_INFO_FILE_LENGTH = 5 * 1024;
+
+ nsAutoCString info;
+ if (!ReadIntoString(aInfoFile, info, MAX_GMP_INFO_FILE_LENGTH)) {
+ NS_WARNING("Failed to read info file in GMP process.");
+ return false;
+ }
+
+ // Note: we pass "\r\n" to SplitAt so that we'll split lines delimited
+ // by \n (Unix), \r\n (Windows) and \r (old MacOSX).
+ SplitAt("\r\n", info, lines);
+
+ for (nsCString line : lines) {
+ // Field name is the string up to but not including the first ':'
+ // character on the line.
+ int32_t colon = line.FindChar(':');
+ if (colon <= 0) {
+ // Not allowed to be the first character.
+ // Info field name must be at least one character.
+ continue;
+ }
+ nsAutoCString key(Substring(line, 0, colon));
+ ToLowerCase(key);
+ key.Trim(" ");
+
+ nsCString* value = new nsCString(Substring(line, colon + 1));
+ value->Trim(" ");
+ mValues.Put(key, value); // Hashtable assumes ownership of value.
+ }
+
+ return true;
+}
+
+bool
+GMPInfoFileParser::Contains(const nsCString& aKey) const {
+ nsCString key(aKey);
+ ToLowerCase(key);
+ return mValues.Contains(key);
+}
+
+nsCString
+GMPInfoFileParser::Get(const nsCString& aKey) const {
+ MOZ_ASSERT(Contains(aKey));
+ nsCString key(aKey);
+ ToLowerCase(key);
+ nsCString* p = nullptr;
+ if (mValues.Get(key, &p)) {
+ return nsCString(*p);
+ }
+ return EmptyCString();
+}
+
+bool
+HaveGMPFor(const nsCString& aAPI,
+ nsTArray<nsCString>&& aTags)
+{
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (NS_WARN_IF(!mps)) {
+ return false;
+ }
+
+ bool hasPlugin = false;
+ if (NS_FAILED(mps->HasPluginForAPI(aAPI, &aTags, &hasPlugin))) {
+ return false;
+ }
+ return hasPlugin;
+}
+
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPUtils.h b/dom/media/gmp/GMPUtils.h
new file mode 100644
index 000000000..7e7b9f26f
--- /dev/null
+++ b/dom/media/gmp/GMPUtils.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPUtils_h_
+#define GMPUtils_h_
+
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+
+class nsIFile;
+class nsCString;
+class nsISimpleEnumerator;
+
+namespace mozilla {
+
+template<typename T>
+struct DestroyPolicy
+{
+ void operator()(T* aGMPObject) const {
+ aGMPObject->Destroy();
+ }
+};
+
+template<typename T>
+using GMPUniquePtr = mozilla::UniquePtr<T, DestroyPolicy<T>>;
+
+bool GetEMEVoucherPath(nsIFile** aPath);
+
+bool EMEVoucherFileExists();
+
+void
+SplitAt(const char* aDelims,
+ const nsACString& aInput,
+ nsTArray<nsCString>& aOutTokens);
+
+nsCString
+ToBase64(const nsTArray<uint8_t>& aBytes);
+
+bool
+FileExists(nsIFile* aFile);
+
+// Enumerate directory entries for a specified path.
+class DirectoryEnumerator {
+public:
+
+ enum Mode {
+ DirsOnly, // Enumeration only includes directories.
+ FilesAndDirs // Enumeration includes directories and non-directory files.
+ };
+
+ DirectoryEnumerator(nsIFile* aPath, Mode aMode);
+
+ already_AddRefed<nsIFile> Next();
+
+private:
+ Mode mMode;
+ nsCOMPtr<nsISimpleEnumerator> mIter;
+};
+
+class GMPInfoFileParser {
+public:
+ bool Init(nsIFile* aFile);
+ bool Contains(const nsCString& aKey) const;
+ nsCString Get(const nsCString& aKey) const;
+private:
+ nsClassHashtable<nsCStringHashKey, nsCString> mValues;
+};
+
+bool
+ReadIntoArray(nsIFile* aFile,
+ nsTArray<uint8_t>& aOutDst,
+ size_t aMaxLength);
+
+bool
+ReadIntoString(nsIFile* aFile,
+ nsCString& aOutDst,
+ size_t aMaxLength);
+
+bool
+HaveGMPFor(const nsCString& aAPI,
+ nsTArray<nsCString>&& aTags);
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/gmp/GMPVideoDecoderChild.cpp b/dom/media/gmp/GMPVideoDecoderChild.cpp
new file mode 100644
index 000000000..f9c1956e7
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderChild.cpp
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoDecoderChild.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPContentChild.h"
+#include <stdio.h>
+#include "mozilla/Unused.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "runnable_utils.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoDecoderChild::GMPVideoDecoderChild(GMPContentChild* aPlugin)
+ : GMPSharedMemManager(aPlugin)
+ , mPlugin(aPlugin)
+ , mVideoDecoder(nullptr)
+ , mVideoHost(this)
+ , mNeedShmemIntrCount(0)
+ , mPendingDecodeComplete(false)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoDecoderChild::~GMPVideoDecoderChild()
+{
+ MOZ_ASSERT(!mNeedShmemIntrCount);
+}
+
+void
+GMPVideoDecoderChild::Init(GMPVideoDecoder* aDecoder)
+{
+ MOZ_ASSERT(aDecoder, "Cannot initialize video decoder child without a video decoder!");
+ mVideoDecoder = aDecoder;
+}
+
+GMPVideoHostImpl&
+GMPVideoDecoderChild::Host()
+{
+ return mVideoHost;
+}
+
+void
+GMPVideoDecoderChild::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ if (!aDecodedFrame) {
+ MOZ_CRASH("Not given a decoded frame!");
+ }
+
+ auto df = static_cast<GMPVideoi420FrameImpl*>(aDecodedFrame);
+
+ GMPVideoi420FrameData frameData;
+ df->InitFrameData(frameData);
+ SendDecoded(frameData);
+
+ aDecodedFrame->Destroy();
+}
+
+void
+GMPVideoDecoderChild::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendReceivedDecodedReferenceFrame(aPictureId);
+}
+
+void
+GMPVideoDecoderChild::ReceivedDecodedFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendReceivedDecodedFrame(aPictureId);
+}
+
+void
+GMPVideoDecoderChild::InputDataExhausted()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendInputDataExhausted();
+}
+
+void
+GMPVideoDecoderChild::DrainComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendDrainComplete();
+}
+
+void
+GMPVideoDecoderChild::ResetComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendResetComplete();
+}
+
+void
+GMPVideoDecoderChild::Error(GMPErr aError)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendError(aError);
+}
+
+bool
+GMPVideoDecoderChild::RecvInitDecode(const GMPVideoCodec& aCodecSettings,
+ InfallibleTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aCoreCount)
+{
+ if (!mVideoDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->InitDecode(aCodecSettings,
+ aCodecSpecific.Elements(),
+ aCodecSpecific.Length(),
+ this,
+ aCoreCount);
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDecode(const GMPVideoEncodedFrameData& aInputFrame,
+ const bool& aMissingFrames,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo,
+ const int64_t& aRenderTimeMs)
+{
+ if (!mVideoDecoder) {
+ return false;
+ }
+
+ auto f = new GMPVideoEncodedFrameImpl(aInputFrame, &mVideoHost);
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->Decode(f,
+ aMissingFrames,
+ aCodecSpecificInfo.Elements(),
+ aCodecSpecificInfo.Length(),
+ aRenderTimeMs);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvChildShmemForPool(Shmem&& aFrameBuffer)
+{
+ if (aFrameBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData,
+ aFrameBuffer);
+ }
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvReset()
+{
+ if (!mVideoDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->Reset();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDrain()
+{
+ if (!mVideoDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->Drain();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDecodingComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ if (mNeedShmemIntrCount) {
+ // There's a GMP blocked in Alloc() waiting for the CallNeedShem() to
+ // return a frame they can use. Don't call the GMP's DecodingComplete()
+ // now and don't delete the GMPVideoDecoderChild, defer processing the
+ // DecodingComplete() until once the Alloc() finishes.
+ mPendingDecodeComplete = true;
+ return true;
+ }
+ if (mVideoDecoder) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->DecodingComplete();
+ mVideoDecoder = nullptr;
+ }
+
+ mVideoHost.DoneWithAPI();
+
+ mPlugin = nullptr;
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::Alloc(size_t aSize,
+ Shmem::SharedMemory::SharedMemoryType aType,
+ Shmem* aMem)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ bool rv;
+#ifndef SHMEM_ALLOC_IN_CHILD
+ ++mNeedShmemIntrCount;
+ rv = CallNeedShmem(aSize, aMem);
+ --mNeedShmemIntrCount;
+ if (mPendingDecodeComplete && mNeedShmemIntrCount == 0) {
+ mPendingDecodeComplete = false;
+ mPlugin->GMPMessageLoop()->PostTask(
+ NewRunnableMethod(this, &GMPVideoDecoderChild::RecvDecodingComplete));
+ }
+#else
+#ifdef GMP_SAFE_SHMEM
+ rv = AllocShmem(aSize, aType, aMem);
+#else
+ rv = AllocUnsafeShmem(aSize, aType, aMem);
+#endif
+#endif
+ return rv;
+}
+
+void
+GMPVideoDecoderChild::Dealloc(Shmem& aMem)
+{
+#ifndef SHMEM_ALLOC_IN_CHILD
+ SendParentShmemForPool(aMem);
+#else
+ DeallocShmem(aMem);
+#endif
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoDecoderChild.h b/dom/media/gmp/GMPVideoDecoderChild.h
new file mode 100644
index 000000000..2271a70a1
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderChild.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoDecoderChild_h_
+#define GMPVideoDecoderChild_h_
+
+#include "nsString.h"
+#include "mozilla/gmp/PGMPVideoDecoderChild.h"
+#include "gmp-video-decode.h"
+#include "GMPSharedMemManager.h"
+#include "GMPVideoHost.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPVideoDecoderChild : public PGMPVideoDecoderChild,
+ public GMPVideoDecoderCallback,
+ public GMPSharedMemManager
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoDecoderChild);
+
+ explicit GMPVideoDecoderChild(GMPContentChild* aPlugin);
+
+ void Init(GMPVideoDecoder* aDecoder);
+ GMPVideoHostImpl& Host();
+
+ // GMPVideoDecoderCallback
+ void Decoded(GMPVideoi420Frame* decodedFrame) override;
+ void ReceivedDecodedReferenceFrame(const uint64_t pictureId) override;
+ void ReceivedDecodedFrame(const uint64_t pictureId) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aError) override;
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override;
+ void Dealloc(Shmem& aMem) override;
+
+private:
+ virtual ~GMPVideoDecoderChild();
+
+ // PGMPVideoDecoderChild
+ bool RecvInitDecode(const GMPVideoCodec& aCodecSettings,
+ InfallibleTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aCoreCount) override;
+ bool RecvDecode(const GMPVideoEncodedFrameData& aInputFrame,
+ const bool& aMissingFrames,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo,
+ const int64_t& aRenderTimeMs) override;
+ bool RecvChildShmemForPool(Shmem&& aFrameBuffer) override;
+ bool RecvReset() override;
+ bool RecvDrain() override;
+ bool RecvDecodingComplete() override;
+
+ GMPContentChild* mPlugin;
+ GMPVideoDecoder* mVideoDecoder;
+ GMPVideoHostImpl mVideoHost;
+
+ // Non-zero when a GMP is blocked spinning the IPC message loop while
+ // waiting on an NeedShmem to complete.
+ int mNeedShmemIntrCount;
+ bool mPendingDecodeComplete;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoDecoderChild_h_
diff --git a/dom/media/gmp/GMPVideoDecoderParent.cpp b/dom/media/gmp/GMPVideoDecoderParent.cpp
new file mode 100644
index 000000000..bd5408adb
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderParent.cpp
@@ -0,0 +1,513 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoDecoderParent.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Unused.h"
+#include "nsAutoRef.h"
+#include "nsThreadUtils.h"
+#include "GMPUtils.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPContentParent.h"
+#include "GMPMessageUtils.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOGE(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Error, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+// States:
+// Initial: mIsOpen == false
+// on InitDecode success -> Open
+// on Shutdown -> Dead
+// Open: mIsOpen == true
+// on Close -> Dead
+// on ActorDestroy -> Dead
+// on Shutdown -> Dead
+// Dead: mIsOpen == false
+
+GMPVideoDecoderParent::GMPVideoDecoderParent(GMPContentParent* aPlugin)
+ : GMPSharedMemManager(aPlugin)
+ , mIsOpen(false)
+ , mShuttingDown(false)
+ , mActorDestroyed(false)
+ , mIsAwaitingResetComplete(false)
+ , mIsAwaitingDrainComplete(false)
+ , mPlugin(aPlugin)
+ , mCallback(nullptr)
+ , mVideoHost(this)
+ , mPluginId(aPlugin->GetPluginId())
+ , mFrameCount(0)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoDecoderParent::~GMPVideoDecoderParent()
+{
+}
+
+GMPVideoHostImpl&
+GMPVideoDecoderParent::Host()
+{
+ return mVideoHost;
+}
+
+// Note: may be called via Terminated()
+void
+GMPVideoDecoderParent::Close()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Close()", this));
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ // Ensure if we've received a Close while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the close. This seems unlikely to happen, but better to be careful.
+ UnblockResetAndDrain();
+
+ // Consumer is done with us; we can shut down. No more callbacks should
+ // be made to mCallback. Note: do this before Shutdown()!
+ mCallback = nullptr;
+ // Let Shutdown mark us as dead so it knows if we had been alive
+
+ // In case this is the last reference
+ RefPtr<GMPVideoDecoderParent> kungfudeathgrip(this);
+ Release();
+ Shutdown();
+}
+
+nsresult
+GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoDecoderCallbackProxy* aCallback,
+ int32_t aCoreCount)
+{
+ LOGD(("GMPVideoDecoderParent[%p]::InitDecode()", this));
+
+ if (mActorDestroyed) {
+ NS_WARNING("Trying to use a destroyed GMP video decoder!");
+ return NS_ERROR_FAILURE;
+ }
+ if (mIsOpen) {
+ NS_WARNING("Trying to re-init an in-use GMP video decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!aCallback) {
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aCallback;
+
+ if (!SendInitDecode(aCodecSettings, aCodecSpecific, aCoreCount)) {
+ return NS_ERROR_FAILURE;
+ }
+ mIsOpen = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPVideoDecoderParent::Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
+ bool aMissingFrames,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ int64_t aRenderTimeMs)
+{
+ LOGV(("GMPVideoDecoderParent[%p]::Decode() timestamp=%lld keyframe=%d",
+ this, aInputFrame->TimeStamp(),
+ aInputFrame->FrameType() == kGMPKeyFrame));
+
+ if (!mIsOpen) {
+ LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; dead GMPVideoDecoder", this));
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ GMPUniquePtr<GMPVideoEncodedFrameImpl> inputFrameImpl(
+ static_cast<GMPVideoEncodedFrameImpl*>(aInputFrame.release()));
+
+ // Very rough kill-switch if the plugin stops processing. If it's merely
+ // hung and continues, we'll come back to life eventually.
+ // 3* is because we're using 3 buffers per frame for i420 data for now.
+ if ((NumInUse(GMPSharedMem::kGMPFrameData) > 3*GMPSharedMem::kGMPBufLimit) ||
+ (NumInUse(GMPSharedMem::kGMPEncodedData) > GMPSharedMem::kGMPBufLimit)) {
+ LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; shmem buffer limit hit frame=%d encoded=%d",
+ this, NumInUse(GMPSharedMem::kGMPFrameData), NumInUse(GMPSharedMem::kGMPEncodedData)));
+ return NS_ERROR_FAILURE;
+ }
+
+ GMPVideoEncodedFrameData frameData;
+ inputFrameImpl->RelinquishFrameData(frameData);
+
+ if (!SendDecode(frameData,
+ aMissingFrames,
+ aCodecSpecificInfo,
+ aRenderTimeMs)) {
+ LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; SendDecode() failure.", this));
+ return NS_ERROR_FAILURE;
+ }
+ mFrameCount++;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPVideoDecoderParent::Reset()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Reset()", this));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendReset()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingResetComplete = true;
+
+ RefPtr<GMPVideoDecoderParent> self(this);
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([self]() -> void
+ {
+ LOGD(("GMPVideoDecoderParent[%p]::ResetCompleteTimeout() timed out waiting for ResetComplete", self.get()));
+ self->mResetCompleteTimeout = nullptr;
+ LogToBrowserConsole(NS_LITERAL_STRING("GMPVideoDecoderParent timed out waiting for ResetComplete()"));
+ });
+ CancelResetCompleteTimeout();
+ mResetCompleteTimeout = SimpleTimer::Create(task, 5000, mPlugin->GMPThread());
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+void
+GMPVideoDecoderParent::CancelResetCompleteTimeout()
+{
+ if (mResetCompleteTimeout) {
+ mResetCompleteTimeout->Cancel();
+ mResetCompleteTimeout = nullptr;
+ }
+}
+
+nsresult
+GMPVideoDecoderParent::Drain()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Drain() frameCount=%d", this, mFrameCount));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendDrain()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingDrainComplete = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+const nsCString&
+GMPVideoDecoderParent::GetDisplayName() const
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ return mPlugin->GetDisplayName();
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+nsresult
+GMPVideoDecoderParent::Shutdown()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Shutdown()", this));
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+ mShuttingDown = true;
+
+ // Ensure if we've received a shutdown while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the shutdown.
+ UnblockResetAndDrain();
+
+ // Notify client we're gone! Won't occur after Close()
+ if (mCallback) {
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+
+ mIsOpen = false;
+ if (!mActorDestroyed) {
+ Unused << SendDecodingComplete();
+ }
+
+ return NS_OK;
+}
+
+// Note: Keep this sync'd up with Shutdown
+void
+GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("GMPVideoDecoderParent[%p]::ActorDestroy reason=%d", this, aWhy));
+
+ mIsOpen = false;
+ mActorDestroyed = true;
+
+ // Ensure if we've received a destroy while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the error.
+ UnblockResetAndDrain();
+
+ if (mCallback) {
+ // May call Close() (and Shutdown()) immediately or with a delay
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->VideoDecoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+ mVideoHost.ActorDestroyed();
+ MaybeDisconnect(aWhy == AbnormalShutdown);
+}
+
+bool
+GMPVideoDecoderParent::RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame)
+{
+ --mFrameCount;
+ LOGV(("GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=%lld frameCount=%d",
+ this, aDecodedFrame.mTimestamp(), mFrameCount));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!GMPVideoi420FrameImpl::CheckFrameData(aDecodedFrame)) {
+ LOGE(("GMPVideoDecoderParent[%p]::RecvDecoded() "
+ "timestamp=%lld decoded frame corrupt, ignoring"));
+ return false;
+ }
+ auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost);
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->Decoded(f);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId)
+{
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->ReceivedDecodedReferenceFrame(aPictureId);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvReceivedDecodedFrame(const uint64_t& aPictureId)
+{
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->ReceivedDecodedFrame(aPictureId);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvInputDataExhausted()
+{
+ LOGV(("GMPVideoDecoderParent[%p]::RecvInputDataExhausted()", this));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->InputDataExhausted();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvDrainComplete()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::RecvDrainComplete() frameCount=%d", this, mFrameCount));
+ nsAutoString msg;
+ msg.AppendLiteral("GMPVideoDecoderParent::RecvDrainComplete() outstanding frames=");
+ msg.AppendInt(mFrameCount);
+ LogToBrowserConsole(msg);
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!mIsAwaitingDrainComplete) {
+ return true;
+ }
+ mIsAwaitingDrainComplete = false;
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->DrainComplete();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvResetComplete()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::RecvResetComplete()", this));
+
+ CancelResetCompleteTimeout();
+
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!mIsAwaitingResetComplete) {
+ return true;
+ }
+ mIsAwaitingResetComplete = false;
+ mFrameCount = 0;
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->ResetComplete();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvError(const GMPErr& aError)
+{
+ LOGD(("GMPVideoDecoderParent[%p]::RecvError(error=%d)", this, aError));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ensure if we've received an error while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the error.
+ UnblockResetAndDrain();
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->Error(aError);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvShutdown()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::RecvShutdown()", this));
+
+ Shutdown();
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvParentShmemForPool(Shmem&& aEncodedBuffer)
+{
+ if (aEncodedBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
+ aEncodedBuffer);
+ }
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::AnswerNeedShmem(const uint32_t& aFrameBufferSize,
+ Shmem* aMem)
+{
+ ipc::Shmem mem;
+
+ if (!mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData,
+ aFrameBufferSize,
+ ipc::SharedMemory::TYPE_BASIC, &mem))
+ {
+ LOGE(("%s: Failed to get a shared mem buffer for Child! size %u",
+ __FUNCTION__, aFrameBufferSize));
+ return false;
+ }
+ *aMem = mem;
+ mem = ipc::Shmem();
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::Recv__delete__()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Recv__delete__()", this));
+
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->VideoDecoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+
+ return true;
+}
+
+void
+GMPVideoDecoderParent::UnblockResetAndDrain()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::UnblockResetAndDrain() "
+ "awaitingResetComplete=%d awaitingDrainComplete=%d",
+ this, mIsAwaitingResetComplete, mIsAwaitingDrainComplete));
+
+ if (!mCallback) {
+ MOZ_ASSERT(!mIsAwaitingResetComplete);
+ MOZ_ASSERT(!mIsAwaitingDrainComplete);
+ return;
+ }
+ if (mIsAwaitingResetComplete) {
+ mIsAwaitingResetComplete = false;
+ mCallback->ResetComplete();
+ }
+ if (mIsAwaitingDrainComplete) {
+ mIsAwaitingDrainComplete = false;
+ mCallback->DrainComplete();
+ }
+ CancelResetCompleteTimeout();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoDecoderParent.h b/dom/media/gmp/GMPVideoDecoderParent.h
new file mode 100644
index 000000000..215c784c1
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderParent.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoDecoderParent_h_
+#define GMPVideoDecoderParent_h_
+
+#include "mozilla/RefPtr.h"
+#include "gmp-video-decode.h"
+#include "mozilla/gmp/PGMPVideoDecoderParent.h"
+#include "GMPMessageUtils.h"
+#include "GMPSharedMemManager.h"
+#include "GMPUtils.h"
+#include "GMPVideoHost.h"
+#include "GMPVideoDecoderProxy.h"
+#include "VideoUtils.h"
+#include "GMPCrashHelperHolder.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentParent;
+
+class GMPVideoDecoderParent final : public PGMPVideoDecoderParent
+ , public GMPVideoDecoderProxy
+ , public GMPSharedMemManager
+ , public GMPCrashHelperHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPVideoDecoderParent)
+
+ explicit GMPVideoDecoderParent(GMPContentParent *aPlugin);
+
+ GMPVideoHostImpl& Host();
+ nsresult Shutdown();
+
+ // GMPVideoDecoder
+ void Close() override;
+ nsresult InitDecode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoDecoderCallbackProxy* aCallback,
+ int32_t aCoreCount) override;
+ nsresult Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
+ bool aMissingFrames,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ int64_t aRenderTimeMs = -1) override;
+ nsresult Reset() override;
+ nsresult Drain() override;
+ uint32_t GetPluginId() const override { return mPluginId; }
+ const nsCString& GetDisplayName() const override;
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
+ {
+#ifdef GMP_SAFE_SHMEM
+ return AllocShmem(aSize, aType, aMem);
+#else
+ return AllocUnsafeShmem(aSize, aType, aMem);
+#endif
+ }
+ void Dealloc(Shmem& aMem) override
+ {
+ DeallocShmem(aMem);
+ }
+
+private:
+ ~GMPVideoDecoderParent();
+
+ // PGMPVideoDecoderParent
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame) override;
+ bool RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId) override;
+ bool RecvReceivedDecodedFrame(const uint64_t& aPictureId) override;
+ bool RecvInputDataExhausted() override;
+ bool RecvDrainComplete() override;
+ bool RecvResetComplete() override;
+ bool RecvError(const GMPErr& aError) override;
+ bool RecvShutdown() override;
+ bool RecvParentShmemForPool(Shmem&& aEncodedBuffer) override;
+ bool AnswerNeedShmem(const uint32_t& aFrameBufferSize,
+ Shmem* aMem) override;
+ bool Recv__delete__() override;
+
+ void UnblockResetAndDrain();
+ void CancelResetCompleteTimeout();
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ bool mIsAwaitingResetComplete;
+ bool mIsAwaitingDrainComplete;
+ RefPtr<GMPContentParent> mPlugin;
+ GMPVideoDecoderCallbackProxy* mCallback;
+ GMPVideoHostImpl mVideoHost;
+ const uint32_t mPluginId;
+ int32_t mFrameCount;
+ RefPtr<SimpleTimer> mResetCompleteTimeout;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoDecoderParent_h_
diff --git a/dom/media/gmp/GMPVideoDecoderProxy.h b/dom/media/gmp/GMPVideoDecoderProxy.h
new file mode 100644
index 000000000..b9596fd21
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderProxy.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoDecoderProxy_h_
+#define GMPVideoDecoderProxy_h_
+
+#include "nsTArray.h"
+#include "gmp-video-decode.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+
+#include "GMPCallbackBase.h"
+#include "GMPUtils.h"
+
+class GMPVideoDecoderCallbackProxy : public GMPCallbackBase,
+ public GMPVideoDecoderCallback
+{
+public:
+ virtual ~GMPVideoDecoderCallbackProxy() {}
+};
+
+// A proxy to GMPVideoDecoder in the child process.
+// GMPVideoDecoderParent exposes this to users the GMP.
+// This enables Gecko to pass nsTArrays to the child GMP and avoid
+// an extra copy when doing so.
+
+// The consumer must call Close() when done with the codec, or when
+// Terminated() is called by the GMP plugin indicating an abnormal shutdown
+// of the underlying plugin. After calling Close(), the consumer must
+// not access this again.
+
+// This interface is not thread-safe and must only be used from GMPThread.
+class GMPVideoDecoderProxy {
+public:
+ virtual nsresult InitDecode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoDecoderCallbackProxy* aCallback,
+ int32_t aCoreCount) = 0;
+ virtual nsresult Decode(mozilla::GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
+ bool aMissingFrames,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ int64_t aRenderTimeMs = -1) = 0;
+ virtual nsresult Reset() = 0;
+ virtual nsresult Drain() = 0;
+ virtual uint32_t GetPluginId() const = 0;
+
+ // Call to tell GMP/plugin the consumer will no longer use this
+ // interface/codec.
+ virtual void Close() = 0;
+
+ virtual const nsCString& GetDisplayName() const = 0;
+};
+
+#endif
diff --git a/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp b/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp
new file mode 100644
index 000000000..7725c5521
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp
@@ -0,0 +1,324 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoHost.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPSharedMemManager.h"
+#include "GMPEncryptedBufferDataImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoEncodedFrameImpl::GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost)
+: mEncodedWidth(0),
+ mEncodedHeight(0),
+ mTimeStamp(0ll),
+ mDuration(0ll),
+ mFrameType(kGMPDeltaFrame),
+ mSize(0),
+ mCompleteFrame(false),
+ mHost(aHost),
+ mBufferType(GMP_BufferSingle)
+{
+ MOZ_ASSERT(aHost);
+ aHost->EncodedFrameCreated(this);
+}
+
+GMPVideoEncodedFrameImpl::GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData,
+ GMPVideoHostImpl* aHost)
+: mEncodedWidth(aFrameData.mEncodedWidth()),
+ mEncodedHeight(aFrameData.mEncodedHeight()),
+ mTimeStamp(aFrameData.mTimestamp()),
+ mDuration(aFrameData.mDuration()),
+ mFrameType(static_cast<GMPVideoFrameType>(aFrameData.mFrameType())),
+ mSize(aFrameData.mSize()),
+ mCompleteFrame(aFrameData.mCompleteFrame()),
+ mHost(aHost),
+ mBuffer(aFrameData.mBuffer()),
+ mBufferType(aFrameData.mBufferType())
+{
+ MOZ_ASSERT(aHost);
+ if (aFrameData.mDecryptionData().mKeyId().Length() > 0) {
+ mCrypto = new GMPEncryptedBufferDataImpl(aFrameData.mDecryptionData());
+ }
+ aHost->EncodedFrameCreated(this);
+}
+
+GMPVideoEncodedFrameImpl::~GMPVideoEncodedFrameImpl()
+{
+ DestroyBuffer();
+ if (mHost) {
+ mHost->EncodedFrameDestroyed(this);
+ }
+}
+
+void
+GMPVideoEncodedFrameImpl::InitCrypto(const CryptoSample& aCrypto)
+{
+ mCrypto = new GMPEncryptedBufferDataImpl(aCrypto);
+}
+
+const GMPEncryptedBufferMetadata*
+GMPVideoEncodedFrameImpl::GetDecryptionData() const
+{
+ return mCrypto;
+}
+
+GMPVideoFrameFormat
+GMPVideoEncodedFrameImpl::GetFrameFormat()
+{
+ return kGMPEncodedVideoFrame;
+}
+
+void
+GMPVideoEncodedFrameImpl::DoneWithAPI()
+{
+ DestroyBuffer();
+
+ // Do this after destroying the buffer because destruction
+ // involves deallocation, which requires a host.
+ mHost = nullptr;
+}
+
+void
+GMPVideoEncodedFrameImpl::ActorDestroyed()
+{
+ // Simply clear out Shmem reference, do not attempt to
+ // properly free it. It has already been freed.
+ mBuffer = ipc::Shmem();
+ // No more host.
+ mHost = nullptr;
+}
+
+bool
+GMPVideoEncodedFrameImpl::RelinquishFrameData(GMPVideoEncodedFrameData& aFrameData)
+{
+ aFrameData.mEncodedWidth() = mEncodedWidth;
+ aFrameData.mEncodedHeight() = mEncodedHeight;
+ aFrameData.mTimestamp() = mTimeStamp;
+ aFrameData.mDuration() = mDuration;
+ aFrameData.mFrameType() = mFrameType;
+ aFrameData.mSize() = mSize;
+ aFrameData.mCompleteFrame() = mCompleteFrame;
+ aFrameData.mBuffer() = mBuffer;
+ aFrameData.mBufferType() = mBufferType;
+ if (mCrypto) {
+ mCrypto->RelinquishData(aFrameData.mDecryptionData());
+ }
+
+ // This method is called right before Shmem is sent to another process.
+ // We need to effectively zero out our member copy so that we don't
+ // try to delete Shmem we don't own later.
+ mBuffer = ipc::Shmem();
+
+ return true;
+}
+
+void
+GMPVideoEncodedFrameImpl::DestroyBuffer()
+{
+ if (mHost && mBuffer.IsWritable()) {
+ mHost->SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData, mBuffer);
+ }
+ mBuffer = ipc::Shmem();
+}
+
+GMPErr
+GMPVideoEncodedFrameImpl::CreateEmptyFrame(uint32_t aSize)
+{
+ if (aSize == 0) {
+ DestroyBuffer();
+ } else if (aSize > AllocatedSize()) {
+ DestroyBuffer();
+ if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData, aSize,
+ ipc::SharedMemory::TYPE_BASIC, &mBuffer) ||
+ !Buffer()) {
+ return GMPAllocErr;
+ }
+ }
+ mSize = aSize;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncodedFrameImpl::CopyFrame(const GMPVideoEncodedFrame& aFrame)
+{
+ auto& f = static_cast<const GMPVideoEncodedFrameImpl&>(aFrame);
+
+ if (f.mSize != 0) {
+ GMPErr err = CreateEmptyFrame(f.mSize);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ memcpy(Buffer(), f.Buffer(), f.mSize);
+ }
+ mEncodedWidth = f.mEncodedWidth;
+ mEncodedHeight = f.mEncodedHeight;
+ mTimeStamp = f.mTimeStamp;
+ mDuration = f.mDuration;
+ mFrameType = f.mFrameType;
+ mSize = f.mSize; // already set...
+ mCompleteFrame = f.mCompleteFrame;
+ mBufferType = f.mBufferType;
+ mCrypto = new GMPEncryptedBufferDataImpl(*(f.mCrypto));
+ // Don't copy host, that should have been set properly on object creation via host.
+
+ return GMPNoErr;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetEncodedWidth(uint32_t aEncodedWidth)
+{
+ mEncodedWidth = aEncodedWidth;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::EncodedWidth()
+{
+ return mEncodedWidth;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetEncodedHeight(uint32_t aEncodedHeight)
+{
+ mEncodedHeight = aEncodedHeight;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::EncodedHeight()
+{
+ return mEncodedHeight;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetTimeStamp(uint64_t aTimeStamp)
+{
+ mTimeStamp = aTimeStamp;
+}
+
+uint64_t
+GMPVideoEncodedFrameImpl::TimeStamp()
+{
+ return mTimeStamp;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetDuration(uint64_t aDuration)
+{
+ mDuration = aDuration;
+}
+
+uint64_t
+GMPVideoEncodedFrameImpl::Duration() const
+{
+ return mDuration;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetFrameType(GMPVideoFrameType aFrameType)
+{
+ mFrameType = aFrameType;
+}
+
+GMPVideoFrameType
+GMPVideoEncodedFrameImpl::FrameType()
+{
+ return mFrameType;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetAllocatedSize(uint32_t aNewSize)
+{
+ if (aNewSize <= AllocatedSize()) {
+ return;
+ }
+
+ if (!mHost) {
+ return;
+ }
+
+ ipc::Shmem new_mem;
+ if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData, aNewSize,
+ ipc::SharedMemory::TYPE_BASIC, &new_mem) ||
+ !new_mem.get<uint8_t>()) {
+ return;
+ }
+
+ if (mBuffer.IsReadable()) {
+ memcpy(new_mem.get<uint8_t>(), Buffer(), mSize);
+ }
+
+ DestroyBuffer();
+
+ mBuffer = new_mem;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::AllocatedSize()
+{
+ if (mBuffer.IsWritable()) {
+ return mBuffer.Size<uint8_t>();
+ }
+ return 0;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetSize(uint32_t aSize)
+{
+ mSize = aSize;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::Size()
+{
+ return mSize;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetCompleteFrame(bool aCompleteFrame)
+{
+ mCompleteFrame = aCompleteFrame;
+}
+
+bool
+GMPVideoEncodedFrameImpl::CompleteFrame()
+{
+ return mCompleteFrame;
+}
+
+const uint8_t*
+GMPVideoEncodedFrameImpl::Buffer() const
+{
+ return mBuffer.get<uint8_t>();
+}
+
+uint8_t*
+GMPVideoEncodedFrameImpl::Buffer()
+{
+ return mBuffer.get<uint8_t>();
+}
+
+void
+GMPVideoEncodedFrameImpl::Destroy()
+{
+ delete this;
+}
+
+GMPBufferType
+GMPVideoEncodedFrameImpl::BufferType() const
+{
+ return mBufferType;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetBufferType(GMPBufferType aBufferType)
+{
+ mBufferType = aBufferType;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoEncodedFrameImpl.h b/dom/media/gmp/GMPVideoEncodedFrameImpl.h
new file mode 100644
index 000000000..69858f1c6
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncodedFrameImpl.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMPVideoEncodedFrameImpl_h_
+#define GMPVideoEncodedFrameImpl_h_
+
+#include "gmp-errors.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-decryption.h"
+#include "mozilla/ipc/Shmem.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+class CryptoSample;
+
+namespace gmp {
+
+class GMPVideoHostImpl;
+class GMPVideoEncodedFrameData;
+class GMPEncryptedBufferDataImpl;
+
+class GMPVideoEncodedFrameImpl: public GMPVideoEncodedFrame
+{
+ friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoEncodedFrameImpl>;
+public:
+ explicit GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost);
+ GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData, GMPVideoHostImpl* aHost);
+ virtual ~GMPVideoEncodedFrameImpl();
+
+ void InitCrypto(const CryptoSample& aCrypto);
+
+ // This is called during a normal destroy sequence, which is
+ // when a consumer is finished or during XPCOM shutdown.
+ void DoneWithAPI();
+ // Does not attempt to release Shmem, as the Shmem has already been released.
+ void ActorDestroyed();
+
+ bool RelinquishFrameData(GMPVideoEncodedFrameData& aFrameData);
+
+ // GMPVideoFrame
+ GMPVideoFrameFormat GetFrameFormat() override;
+ void Destroy() override;
+
+ // GMPVideoEncodedFrame
+ GMPErr CreateEmptyFrame(uint32_t aSize) override;
+ GMPErr CopyFrame(const GMPVideoEncodedFrame& aFrame) override;
+ void SetEncodedWidth(uint32_t aEncodedWidth) override;
+ uint32_t EncodedWidth() override;
+ void SetEncodedHeight(uint32_t aEncodedHeight) override;
+ uint32_t EncodedHeight() override;
+ // Microseconds
+ void SetTimeStamp(uint64_t aTimeStamp) override;
+ uint64_t TimeStamp() override;
+ // Set frame duration (microseconds)
+ // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration()
+ // depending on rounding to avoid having to track roundoff errors
+ // and dropped/missing frames(!) (which may leave a large gap)
+ void SetDuration(uint64_t aDuration) override;
+ uint64_t Duration() const override;
+ void SetFrameType(GMPVideoFrameType aFrameType) override;
+ GMPVideoFrameType FrameType() override;
+ void SetAllocatedSize(uint32_t aNewSize) override;
+ uint32_t AllocatedSize() override;
+ void SetSize(uint32_t aSize) override;
+ uint32_t Size() override;
+ void SetCompleteFrame(bool aCompleteFrame) override;
+ bool CompleteFrame() override;
+ const uint8_t* Buffer() const override;
+ uint8_t* Buffer() override;
+ GMPBufferType BufferType() const override;
+ void SetBufferType(GMPBufferType aBufferType) override;
+ const GMPEncryptedBufferMetadata* GetDecryptionData() const override;
+
+private:
+ void DestroyBuffer();
+
+ uint32_t mEncodedWidth;
+ uint32_t mEncodedHeight;
+ uint64_t mTimeStamp;
+ uint64_t mDuration;
+ GMPVideoFrameType mFrameType;
+ uint32_t mSize;
+ bool mCompleteFrame;
+ GMPVideoHostImpl* mHost;
+ ipc::Shmem mBuffer;
+ GMPBufferType mBufferType;
+ nsAutoPtr<GMPEncryptedBufferDataImpl> mCrypto;
+};
+
+} // namespace gmp
+
+} // namespace mozilla
+
+#endif // GMPVideoEncodedFrameImpl_h_
diff --git a/dom/media/gmp/GMPVideoEncoderChild.cpp b/dom/media/gmp/GMPVideoEncoderChild.cpp
new file mode 100644
index 000000000..f5c3dda95
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderChild.cpp
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoEncoderChild.h"
+#include "GMPContentChild.h"
+#include <stdio.h>
+#include "mozilla/Unused.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "runnable_utils.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoEncoderChild::GMPVideoEncoderChild(GMPContentChild* aPlugin)
+ : GMPSharedMemManager(aPlugin)
+ , mPlugin(aPlugin)
+ , mVideoEncoder(nullptr)
+ , mVideoHost(this)
+ , mNeedShmemIntrCount(0)
+ , mPendingEncodeComplete(false)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoEncoderChild::~GMPVideoEncoderChild()
+{
+ MOZ_ASSERT(!mNeedShmemIntrCount);
+}
+
+void
+GMPVideoEncoderChild::Init(GMPVideoEncoder* aEncoder)
+{
+ MOZ_ASSERT(aEncoder, "Cannot initialize video encoder child without a video encoder!");
+ mVideoEncoder = aEncoder;
+}
+
+GMPVideoHostImpl&
+GMPVideoEncoderChild::Host()
+{
+ return mVideoHost;
+}
+
+void
+GMPVideoEncoderChild::Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ auto ef = static_cast<GMPVideoEncodedFrameImpl*>(aEncodedFrame);
+
+ GMPVideoEncodedFrameData frameData;
+ ef->RelinquishFrameData(frameData);
+
+ nsTArray<uint8_t> codecSpecific;
+ codecSpecific.AppendElements(aCodecSpecificInfo, aCodecSpecificInfoLength);
+ SendEncoded(frameData, codecSpecific);
+
+ aEncodedFrame->Destroy();
+}
+
+void
+GMPVideoEncoderChild::Error(GMPErr aError)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendError(aError);
+}
+
+bool
+GMPVideoEncoderChild::RecvInitEncode(const GMPVideoCodec& aCodecSettings,
+ InfallibleTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aNumberOfCores,
+ const uint32_t& aMaxPayloadSize)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->InitEncode(aCodecSettings,
+ aCodecSpecific.Elements(),
+ aCodecSpecific.Length(),
+ this,
+ aNumberOfCores,
+ aMaxPayloadSize);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvEncode(const GMPVideoi420FrameData& aInputFrame,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo,
+ InfallibleTArray<GMPVideoFrameType>&& aFrameTypes)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ auto f = new GMPVideoi420FrameImpl(aInputFrame, &mVideoHost);
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->Encode(f,
+ aCodecSpecificInfo.Elements(),
+ aCodecSpecificInfo.Length(),
+ aFrameTypes.Elements(),
+ aFrameTypes.Length());
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvChildShmemForPool(Shmem&& aEncodedBuffer)
+{
+ if (aEncodedBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
+ aEncodedBuffer);
+ }
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetChannelParameters(const uint32_t& aPacketLoss,
+ const uint32_t& aRTT)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->SetChannelParameters(aPacketLoss, aRTT);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetRates(const uint32_t& aNewBitRate,
+ const uint32_t& aFrameRate)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->SetRates(aNewBitRate, aFrameRate);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetPeriodicKeyFrames(const bool& aEnable)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->SetPeriodicKeyFrames(aEnable);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvEncodingComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ if (mNeedShmemIntrCount) {
+ // There's a GMP blocked in Alloc() waiting for the CallNeedShem() to
+ // return a frame they can use. Don't call the GMP's EncodingComplete()
+ // now and don't delete the GMPVideoEncoderChild, defer processing the
+ // EncodingComplete() until once the Alloc() finishes.
+ mPendingEncodeComplete = true;
+ return true;
+ }
+
+ if (!mVideoEncoder) {
+ Unused << Send__delete__(this);
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->EncodingComplete();
+
+ mVideoHost.DoneWithAPI();
+
+ mPlugin = nullptr;
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::Alloc(size_t aSize,
+ Shmem::SharedMemory::SharedMemoryType aType,
+ Shmem* aMem)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ bool rv;
+#ifndef SHMEM_ALLOC_IN_CHILD
+ ++mNeedShmemIntrCount;
+ rv = CallNeedShmem(aSize, aMem);
+ --mNeedShmemIntrCount;
+ if (mPendingEncodeComplete && mNeedShmemIntrCount == 0) {
+ mPendingEncodeComplete = false;
+ mPlugin->GMPMessageLoop()->PostTask(
+ NewRunnableMethod(this, &GMPVideoEncoderChild::RecvEncodingComplete));
+ }
+#else
+#ifdef GMP_SAFE_SHMEM
+ rv = AllocShmem(aSize, aType, aMem);
+#else
+ rv = AllocUnsafeShmem(aSize, aType, aMem);
+#endif
+#endif
+ return rv;
+}
+
+void
+GMPVideoEncoderChild::Dealloc(Shmem& aMem)
+{
+#ifndef SHMEM_ALLOC_IN_CHILD
+ SendParentShmemForPool(aMem);
+#else
+ DeallocShmem(aMem);
+#endif
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoEncoderChild.h b/dom/media/gmp/GMPVideoEncoderChild.h
new file mode 100644
index 000000000..44e2fe605
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderChild.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoEncoderChild_h_
+#define GMPVideoEncoderChild_h_
+
+#include "nsString.h"
+#include "mozilla/gmp/PGMPVideoEncoderChild.h"
+#include "gmp-video-encode.h"
+#include "GMPSharedMemManager.h"
+#include "GMPVideoHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPVideoEncoderChild : public PGMPVideoEncoderChild,
+ public GMPVideoEncoderCallback,
+ public GMPSharedMemManager
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoEncoderChild);
+
+ explicit GMPVideoEncoderChild(GMPContentChild* aPlugin);
+
+ void Init(GMPVideoEncoder* aEncoder);
+ GMPVideoHostImpl& Host();
+
+ // GMPVideoEncoderCallback
+ void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength) override;
+ void Error(GMPErr aError) override;
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType,
+ Shmem* aMem) override;
+ void Dealloc(Shmem& aMem) override;
+
+private:
+ virtual ~GMPVideoEncoderChild();
+
+ // PGMPVideoEncoderChild
+ bool RecvInitEncode(const GMPVideoCodec& aCodecSettings,
+ InfallibleTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aNumberOfCores,
+ const uint32_t& aMaxPayloadSize) override;
+ bool RecvEncode(const GMPVideoi420FrameData& aInputFrame,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo,
+ InfallibleTArray<GMPVideoFrameType>&& aFrameTypes) override;
+ bool RecvChildShmemForPool(Shmem&& aEncodedBuffer) override;
+ bool RecvSetChannelParameters(const uint32_t& aPacketLoss,
+ const uint32_t& aRTT) override;
+ bool RecvSetRates(const uint32_t& aNewBitRate,
+ const uint32_t& aFrameRate) override;
+ bool RecvSetPeriodicKeyFrames(const bool& aEnable) override;
+ bool RecvEncodingComplete() override;
+
+ GMPContentChild* mPlugin;
+ GMPVideoEncoder* mVideoEncoder;
+ GMPVideoHostImpl mVideoHost;
+
+ // Non-zero when a GMP is blocked spinning the IPC message loop while
+ // waiting on an NeedShmem to complete.
+ int mNeedShmemIntrCount;
+ bool mPendingEncodeComplete;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoEncoderChild_h_
diff --git a/dom/media/gmp/GMPVideoEncoderParent.cpp b/dom/media/gmp/GMPVideoEncoderParent.cpp
new file mode 100644
index 000000000..95583cd6e
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderParent.cpp
@@ -0,0 +1,382 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoEncoderParent.h"
+#include "mozilla/Logging.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "mozilla/Unused.h"
+#include "GMPMessageUtils.h"
+#include "nsAutoRef.h"
+#include "GMPContentParent.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "nsThread.h"
+#include "nsThreadUtils.h"
+#include "runnable_utils.h"
+#include "GMPUtils.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPVideoEncoderParent"
+
+namespace gmp {
+
+// States:
+// Initial: mIsOpen == false
+// on InitDecode success -> Open
+// on Shutdown -> Dead
+// Open: mIsOpen == true
+// on Close -> Dead
+// on ActorDestroy -> Dead
+// on Shutdown -> Dead
+// Dead: mIsOpen == false
+
+GMPVideoEncoderParent::GMPVideoEncoderParent(GMPContentParent *aPlugin)
+: GMPSharedMemManager(aPlugin),
+ mIsOpen(false),
+ mShuttingDown(false),
+ mActorDestroyed(false),
+ mPlugin(aPlugin),
+ mCallback(nullptr),
+ mVideoHost(this),
+ mPluginId(aPlugin->GetPluginId())
+{
+ MOZ_ASSERT(mPlugin);
+
+ nsresult rv = NS_NewNamedThread("GMPEncoded", getter_AddRefs(mEncodedThread));
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH();
+ }
+}
+
+GMPVideoEncoderParent::~GMPVideoEncoderParent()
+{
+ if (mEncodedThread) {
+ mEncodedThread->Shutdown();
+ }
+}
+
+GMPVideoHostImpl&
+GMPVideoEncoderParent::Host()
+{
+ return mVideoHost;
+}
+
+// Note: may be called via Terminated()
+void
+GMPVideoEncoderParent::Close()
+{
+ LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+ // Consumer is done with us; we can shut down. No more callbacks should
+ // be made to mCallback. Note: do this before Shutdown()!
+ mCallback = nullptr;
+ // Let Shutdown mark us as dead so it knows if we had been alive
+
+ // In case this is the last reference
+ RefPtr<GMPVideoEncoderParent> kungfudeathgrip(this);
+ Release();
+ Shutdown();
+}
+
+GMPErr
+GMPVideoEncoderParent::InitEncode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoEncoderCallbackProxy* aCallback,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize)
+{
+ LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+ if (mIsOpen) {
+ NS_WARNING("Trying to re-init an in-use GMP video encoder!");
+ return GMPGenericErr;;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!aCallback) {
+ return GMPGenericErr;
+ }
+ mCallback = aCallback;
+
+ if (!SendInitEncode(aCodecSettings, aCodecSpecific, aNumberOfCores, aMaxPayloadSize)) {
+ return GMPGenericErr;
+ }
+ mIsOpen = true;
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncoderParent::Encode(GMPUniquePtr<GMPVideoi420Frame> aInputFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ const nsTArray<GMPVideoFrameType>& aFrameTypes)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video encoder");
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ GMPUniquePtr<GMPVideoi420FrameImpl> inputFrameImpl(
+ static_cast<GMPVideoi420FrameImpl*>(aInputFrame.release()));
+
+ // Very rough kill-switch if the plugin stops processing. If it's merely
+ // hung and continues, we'll come back to life eventually.
+ // 3* is because we're using 3 buffers per frame for i420 data for now.
+ if ((NumInUse(GMPSharedMem::kGMPFrameData) > 3*GMPSharedMem::kGMPBufLimit) ||
+ (NumInUse(GMPSharedMem::kGMPEncodedData) > GMPSharedMem::kGMPBufLimit)) {
+ return GMPGenericErr;
+ }
+
+ GMPVideoi420FrameData frameData;
+ inputFrameImpl->InitFrameData(frameData);
+
+ if (!SendEncode(frameData,
+ aCodecSpecificInfo,
+ aFrameTypes)) {
+ return GMPGenericErr;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncoderParent::SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an invalid GMP video encoder!");
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendSetChannelParameters(aPacketLoss, aRTT)) {
+ return GMPGenericErr;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncoderParent::SetRates(uint32_t aNewBitRate, uint32_t aFrameRate)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendSetRates(aNewBitRate, aFrameRate)) {
+ return GMPGenericErr;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncoderParent::SetPeriodicKeyFrames(bool aEnable)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an invalid GMP video encoder!");
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendSetPeriodicKeyFrames(aEnable)) {
+ return GMPGenericErr;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+void
+GMPVideoEncoderParent::Shutdown()
+{
+ LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (mShuttingDown) {
+ return;
+ }
+ mShuttingDown = true;
+
+ // Notify client we're gone! Won't occur after Close()
+ if (mCallback) {
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+
+ mIsOpen = false;
+ if (!mActorDestroyed) {
+ Unused << SendEncodingComplete();
+ }
+}
+
+static void
+ShutdownEncodedThread(nsCOMPtr<nsIThread>& aThread)
+{
+ aThread->Shutdown();
+}
+
+// Note: Keep this sync'd up with Shutdown
+void
+GMPVideoEncoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("%s::%s: %p (%d)", __CLASS__, __FUNCTION__, this, (int) aWhy));
+ mIsOpen = false;
+ mActorDestroyed = true;
+ if (mCallback) {
+ // May call Close() (and Shutdown()) immediately or with a delay
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+ // Must be shut down before VideoEncoderDestroyed(), since this can recurse
+ // the GMPThread event loop. See bug 1049501
+ if (mEncodedThread) {
+ // Can't get it to allow me to use WrapRunnable with a nsCOMPtr<nsIThread>()
+ NS_DispatchToMainThread(
+ WrapRunnableNM<decltype(&ShutdownEncodedThread),
+ nsCOMPtr<nsIThread> >(&ShutdownEncodedThread, mEncodedThread));
+ mEncodedThread = nullptr;
+ }
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->VideoEncoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+ mVideoHost.ActorDestroyed(); // same as DoneWithAPI
+ MaybeDisconnect(aWhy == AbnormalShutdown);
+}
+
+static void
+EncodedCallback(GMPVideoEncoderCallbackProxy* aCallback,
+ GMPVideoEncodedFrame* aEncodedFrame,
+ nsTArray<uint8_t>* aCodecSpecificInfo,
+ nsCOMPtr<nsIThread> aThread)
+{
+ aCallback->Encoded(aEncodedFrame, *aCodecSpecificInfo);
+ delete aCodecSpecificInfo;
+ // Ugh. Must destroy the frame on GMPThread.
+ // XXX add locks to the ShmemManager instead?
+ aThread->Dispatch(WrapRunnable(aEncodedFrame,
+ &GMPVideoEncodedFrame::Destroy),
+ NS_DISPATCH_NORMAL);
+}
+
+bool
+GMPVideoEncoderParent::RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo)
+{
+ if (!mCallback) {
+ return false;
+ }
+
+ auto f = new GMPVideoEncodedFrameImpl(aEncodedFrame, &mVideoHost);
+ nsTArray<uint8_t> *codecSpecificInfo = new nsTArray<uint8_t>;
+ codecSpecificInfo->AppendElements((uint8_t*)aCodecSpecificInfo.Elements(), aCodecSpecificInfo.Length());
+ nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
+
+ mEncodedThread->Dispatch(WrapRunnableNM(&EncodedCallback,
+ mCallback, f, codecSpecificInfo, thread),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::RecvError(const GMPErr& aError)
+{
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->Error(aError);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::RecvShutdown()
+{
+ Shutdown();
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::RecvParentShmemForPool(Shmem&& aFrameBuffer)
+{
+ if (aFrameBuffer.IsWritable()) {
+ // This test may be paranoia now that we don't shut down the VideoHost
+ // in ::Shutdown, but doesn't hurt
+ if (mVideoHost.SharedMemMgr()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData,
+ aFrameBuffer);
+ } else {
+ LOGD(("%s::%s: %p Called in shutdown, ignoring and freeing directly", __CLASS__, __FUNCTION__, this));
+ DeallocShmem(aFrameBuffer);
+ }
+ }
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::AnswerNeedShmem(const uint32_t& aEncodedBufferSize,
+ Shmem* aMem)
+{
+ ipc::Shmem mem;
+
+ // This test may be paranoia now that we don't shut down the VideoHost
+ // in ::Shutdown, but doesn't hurt
+ if (!mVideoHost.SharedMemMgr() ||
+ !mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData,
+ aEncodedBufferSize,
+ ipc::SharedMemory::TYPE_BASIC, &mem))
+ {
+ LOG(LogLevel::Error, ("%s::%s: Failed to get a shared mem buffer for Child! size %u",
+ __CLASS__, __FUNCTION__, aEncodedBufferSize));
+ return false;
+ }
+ *aMem = mem;
+ mem = ipc::Shmem();
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::Recv__delete__()
+{
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->VideoEncoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoEncoderParent.h b/dom/media/gmp/GMPVideoEncoderParent.h
new file mode 100644
index 000000000..e7dade692
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderParent.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoEncoderParent_h_
+#define GMPVideoEncoderParent_h_
+
+#include "mozilla/RefPtr.h"
+#include "gmp-video-encode.h"
+#include "mozilla/gmp/PGMPVideoEncoderParent.h"
+#include "GMPMessageUtils.h"
+#include "GMPSharedMemManager.h"
+#include "GMPUtils.h"
+#include "GMPVideoHost.h"
+#include "GMPVideoEncoderProxy.h"
+#include "GMPCrashHelperHolder.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentParent;
+
+class GMPVideoEncoderParent : public GMPVideoEncoderProxy,
+ public PGMPVideoEncoderParent,
+ public GMPSharedMemManager,
+ public GMPCrashHelperHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPVideoEncoderParent)
+
+ explicit GMPVideoEncoderParent(GMPContentParent *aPlugin);
+
+ GMPVideoHostImpl& Host();
+ void Shutdown();
+
+ // GMPVideoEncoderProxy
+ void Close() override;
+ GMPErr InitEncode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoEncoderCallbackProxy* aCallback,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize) override;
+ GMPErr Encode(GMPUniquePtr<GMPVideoi420Frame> aInputFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ const nsTArray<GMPVideoFrameType>& aFrameTypes) override;
+ GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override;
+ GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override;
+ GMPErr SetPeriodicKeyFrames(bool aEnable) override;
+ uint32_t GetPluginId() const override { return mPluginId; }
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
+ {
+#ifdef GMP_SAFE_SHMEM
+ return AllocShmem(aSize, aType, aMem);
+#else
+ return AllocUnsafeShmem(aSize, aType, aMem);
+#endif
+ }
+ void Dealloc(Shmem& aMem) override
+ {
+ DeallocShmem(aMem);
+ }
+
+private:
+ virtual ~GMPVideoEncoderParent();
+
+ // PGMPVideoEncoderParent
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo) override;
+ bool RecvError(const GMPErr& aError) override;
+ bool RecvShutdown() override;
+ bool RecvParentShmemForPool(Shmem&& aFrameBuffer) override;
+ bool AnswerNeedShmem(const uint32_t& aEncodedBufferSize,
+ Shmem* aMem) override;
+ bool Recv__delete__() override;
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ RefPtr<GMPContentParent> mPlugin;
+ GMPVideoEncoderCallbackProxy* mCallback;
+ GMPVideoHostImpl mVideoHost;
+ nsCOMPtr<nsIThread> mEncodedThread;
+ const uint32_t mPluginId;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoEncoderParent_h_
diff --git a/dom/media/gmp/GMPVideoEncoderProxy.h b/dom/media/gmp/GMPVideoEncoderProxy.h
new file mode 100644
index 000000000..655b1e9ae
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderProxy.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoEncoderProxy_h_
+#define GMPVideoEncoderProxy_h_
+
+#include "nsTArray.h"
+#include "gmp-video-encode.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+
+#include "GMPCallbackBase.h"
+#include "GMPUtils.h"
+
+class GMPVideoEncoderCallbackProxy : public GMPCallbackBase {
+public:
+ virtual ~GMPVideoEncoderCallbackProxy() {}
+ virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo) = 0;
+ virtual void Error(GMPErr aError) = 0;
+};
+
+// A proxy to GMPVideoEncoder in the child process.
+// GMPVideoEncoderParent exposes this to users the GMP.
+// This enables Gecko to pass nsTArrays to the child GMP and avoid
+// an extra copy when doing so.
+
+// The consumer must call Close() when done with the codec, or when
+// Terminated() is called by the GMP plugin indicating an abnormal shutdown
+// of the underlying plugin. After calling Close(), the consumer must
+// not access this again.
+
+// This interface is not thread-safe and must only be used from GMPThread.
+class GMPVideoEncoderProxy {
+public:
+ virtual GMPErr InitEncode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoEncoderCallbackProxy* aCallback,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize) = 0;
+ virtual GMPErr Encode(mozilla::GMPUniquePtr<GMPVideoi420Frame> aInputFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ const nsTArray<GMPVideoFrameType>& aFrameTypes) = 0;
+ virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
+ virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
+ virtual GMPErr SetPeriodicKeyFrames(bool aEnable) = 0;
+ virtual uint32_t GetPluginId() const = 0;
+
+ // Call to tell GMP/plugin the consumer will no longer use this
+ // interface/codec.
+ virtual void Close() = 0;
+};
+
+#endif // GMPVideoEncoderProxy_h_
diff --git a/dom/media/gmp/GMPVideoHost.cpp b/dom/media/gmp/GMPVideoHost.cpp
new file mode 100644
index 000000000..db40ebdae
--- /dev/null
+++ b/dom/media/gmp/GMPVideoHost.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoHost.h"
+#include "mozilla/Assertions.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPVideoEncodedFrameImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoHostImpl::GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr)
+: mSharedMemMgr(aSharedMemMgr)
+{
+}
+
+GMPVideoHostImpl::~GMPVideoHostImpl()
+{
+}
+
+GMPErr
+GMPVideoHostImpl::CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame)
+{
+ if (!mSharedMemMgr) {
+ return GMPGenericErr;
+ }
+
+ if (!aFrame) {
+ return GMPGenericErr;
+ }
+ *aFrame = nullptr;
+
+ switch (aFormat) {
+ case kGMPI420VideoFrame:
+ *aFrame = new GMPVideoi420FrameImpl(this);
+ return GMPNoErr;
+ case kGMPEncodedVideoFrame:
+ *aFrame = new GMPVideoEncodedFrameImpl(this);
+ return GMPNoErr;
+ default:
+ NS_NOTREACHED("Unknown frame format!");
+ }
+
+ return GMPGenericErr;
+}
+
+GMPErr
+GMPVideoHostImpl::CreatePlane(GMPPlane** aPlane)
+{
+ if (!mSharedMemMgr) {
+ return GMPGenericErr;
+ }
+
+ if (!aPlane) {
+ return GMPGenericErr;
+ }
+ *aPlane = nullptr;
+
+ auto p = new GMPPlaneImpl(this);
+
+ *aPlane = p;
+
+ return GMPNoErr;
+}
+
+GMPSharedMemManager*
+GMPVideoHostImpl::SharedMemMgr()
+{
+ return mSharedMemMgr;
+}
+
+// XXX This should merge with ActorDestroyed
+void
+GMPVideoHostImpl::DoneWithAPI()
+{
+ ActorDestroyed();
+}
+
+void
+GMPVideoHostImpl::ActorDestroyed()
+{
+ for (uint32_t i = mPlanes.Length(); i > 0; i--) {
+ mPlanes[i - 1]->DoneWithAPI();
+ mPlanes.RemoveElementAt(i - 1);
+ }
+ for (uint32_t i = mEncodedFrames.Length(); i > 0; i--) {
+ mEncodedFrames[i - 1]->DoneWithAPI();
+ mEncodedFrames.RemoveElementAt(i - 1);
+ }
+ mSharedMemMgr = nullptr;
+}
+
+void
+GMPVideoHostImpl::PlaneCreated(GMPPlaneImpl* aPlane)
+{
+ mPlanes.AppendElement(aPlane);
+}
+
+void
+GMPVideoHostImpl::PlaneDestroyed(GMPPlaneImpl* aPlane)
+{
+ MOZ_ALWAYS_TRUE(mPlanes.RemoveElement(aPlane));
+}
+
+void
+GMPVideoHostImpl::EncodedFrameCreated(GMPVideoEncodedFrameImpl* aEncodedFrame)
+{
+ mEncodedFrames.AppendElement(aEncodedFrame);
+}
+
+void
+GMPVideoHostImpl::EncodedFrameDestroyed(GMPVideoEncodedFrameImpl* aFrame)
+{
+ MOZ_ALWAYS_TRUE(mEncodedFrames.RemoveElement(aFrame));
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoHost.h b/dom/media/gmp/GMPVideoHost.h
new file mode 100644
index 000000000..b3e42f08e
--- /dev/null
+++ b/dom/media/gmp/GMPVideoHost.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoHost_h_
+#define GMPVideoHost_h_
+
+#include "gmp-video-host.h"
+#include "gmp-video-plane.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-host.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPSharedMemManager;
+class GMPPlaneImpl;
+class GMPVideoEncodedFrameImpl;
+
+class GMPVideoHostImpl : public GMPVideoHost
+{
+public:
+ explicit GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr);
+ virtual ~GMPVideoHostImpl();
+
+ // Used for shared memory allocation and deallocation.
+ GMPSharedMemManager* SharedMemMgr();
+ void DoneWithAPI();
+ void ActorDestroyed();
+ void PlaneCreated(GMPPlaneImpl* aPlane);
+ void PlaneDestroyed(GMPPlaneImpl* aPlane);
+ void EncodedFrameCreated(GMPVideoEncodedFrameImpl* aEncodedFrame);
+ void EncodedFrameDestroyed(GMPVideoEncodedFrameImpl* aFrame);
+
+ // GMPVideoHost
+ GMPErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) override;
+ GMPErr CreatePlane(GMPPlane** aPlane) override;
+
+private:
+ // All shared memory allocations have to be made by an IPDL actor.
+ // This is a reference to the owning actor. If this reference is
+ // null then the actor has died and all allocations must fail.
+ GMPSharedMemManager* mSharedMemMgr;
+
+ // We track all of these things because they need to handle further
+ // allocations through us and we need to notify them when they
+ // can't use us any more.
+ nsTArray<GMPPlaneImpl*> mPlanes;
+ nsTArray<GMPVideoEncodedFrameImpl*> mEncodedFrames;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoHost_h_
diff --git a/dom/media/gmp/GMPVideoPlaneImpl.cpp b/dom/media/gmp/GMPVideoPlaneImpl.cpp
new file mode 100644
index 000000000..074a965e8
--- /dev/null
+++ b/dom/media/gmp/GMPVideoPlaneImpl.cpp
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoPlaneImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPVideoHost.h"
+#include "GMPSharedMemManager.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPPlaneImpl::GMPPlaneImpl(GMPVideoHostImpl* aHost)
+: mSize(0),
+ mStride(0),
+ mHost(aHost)
+{
+ MOZ_ASSERT(mHost);
+ mHost->PlaneCreated(this);
+}
+
+GMPPlaneImpl::GMPPlaneImpl(const GMPPlaneData& aPlaneData, GMPVideoHostImpl* aHost)
+: mBuffer(aPlaneData.mBuffer()),
+ mSize(aPlaneData.mSize()),
+ mStride(aPlaneData.mStride()),
+ mHost(aHost)
+{
+ MOZ_ASSERT(mHost);
+ mHost->PlaneCreated(this);
+}
+
+GMPPlaneImpl::~GMPPlaneImpl()
+{
+ DestroyBuffer();
+ if (mHost) {
+ mHost->PlaneDestroyed(this);
+ }
+}
+
+void
+GMPPlaneImpl::DoneWithAPI()
+{
+ DestroyBuffer();
+
+ // Do this after destroying the buffer because destruction
+ // involves deallocation, which requires a host.
+ mHost = nullptr;
+}
+
+void
+GMPPlaneImpl::ActorDestroyed()
+{
+ // Simply clear out Shmem reference, do not attempt to
+ // properly free it. It has already been freed.
+ mBuffer = ipc::Shmem();
+ // No more host.
+ mHost = nullptr;
+}
+
+bool
+GMPPlaneImpl::InitPlaneData(GMPPlaneData& aPlaneData)
+{
+ aPlaneData.mBuffer() = mBuffer;
+ aPlaneData.mSize() = mSize;
+ aPlaneData.mStride() = mStride;
+
+ // This method is called right before Shmem is sent to another process.
+ // We need to effectively zero out our member copy so that we don't
+ // try to delete memory we don't own later.
+ mBuffer = ipc::Shmem();
+
+ return true;
+}
+
+GMPErr
+GMPPlaneImpl::MaybeResize(int32_t aNewSize) {
+ if (aNewSize <= AllocatedSize()) {
+ return GMPNoErr;
+ }
+
+ if (!mHost) {
+ return GMPGenericErr;
+ }
+
+ ipc::Shmem new_mem;
+ if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData, aNewSize,
+ ipc::SharedMemory::TYPE_BASIC, &new_mem) ||
+ !new_mem.get<uint8_t>()) {
+ return GMPAllocErr;
+ }
+
+ if (mBuffer.IsReadable()) {
+ memcpy(new_mem.get<uint8_t>(), Buffer(), mSize);
+ }
+
+ DestroyBuffer();
+
+ mBuffer = new_mem;
+
+ return GMPNoErr;
+}
+
+void
+GMPPlaneImpl::DestroyBuffer()
+{
+ if (mHost && mBuffer.IsWritable()) {
+ mHost->SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData, mBuffer);
+ }
+ mBuffer = ipc::Shmem();
+}
+
+GMPErr
+GMPPlaneImpl::CreateEmptyPlane(int32_t aAllocatedSize, int32_t aStride, int32_t aPlaneSize)
+{
+ if (aAllocatedSize < 1 || aStride < 1 || aPlaneSize < 1) {
+ return GMPGenericErr;
+ }
+
+ GMPErr err = MaybeResize(aAllocatedSize);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ mSize = aPlaneSize;
+ mStride = aStride;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPPlaneImpl::Copy(const GMPPlane& aPlane)
+{
+ auto& planeimpl = static_cast<const GMPPlaneImpl&>(aPlane);
+
+ GMPErr err = MaybeResize(planeimpl.mSize);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ if (planeimpl.Buffer() && planeimpl.mSize > 0) {
+ memcpy(Buffer(), planeimpl.Buffer(), mSize);
+ }
+
+ mSize = planeimpl.mSize;
+ mStride = planeimpl.mStride;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPPlaneImpl::Copy(int32_t aSize, int32_t aStride, const uint8_t* aBuffer)
+{
+ GMPErr err = MaybeResize(aSize);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ if (aBuffer && aSize > 0) {
+ memcpy(Buffer(), aBuffer, aSize);
+ }
+
+ mSize = aSize;
+ mStride = aStride;
+
+ return GMPNoErr;
+}
+
+void
+GMPPlaneImpl::Swap(GMPPlane& aPlane)
+{
+ auto& planeimpl = static_cast<GMPPlaneImpl&>(aPlane);
+
+ std::swap(mStride, planeimpl.mStride);
+ std::swap(mSize, planeimpl.mSize);
+ std::swap(mBuffer, planeimpl.mBuffer);
+}
+
+int32_t
+GMPPlaneImpl::AllocatedSize() const
+{
+ if (mBuffer.IsWritable()) {
+ return mBuffer.Size<uint8_t>();
+ }
+ return 0;
+}
+
+void
+GMPPlaneImpl::ResetSize()
+{
+ mSize = 0;
+}
+
+bool
+GMPPlaneImpl::IsZeroSize() const
+{
+ return (mSize == 0);
+}
+
+int32_t
+GMPPlaneImpl::Stride() const
+{
+ return mStride;
+}
+
+const uint8_t*
+GMPPlaneImpl::Buffer() const
+{
+ return mBuffer.get<uint8_t>();
+}
+
+uint8_t*
+GMPPlaneImpl::Buffer()
+{
+ return mBuffer.get<uint8_t>();
+}
+
+void
+GMPPlaneImpl::Destroy()
+{
+ delete this;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoPlaneImpl.h b/dom/media/gmp/GMPVideoPlaneImpl.h
new file mode 100644
index 000000000..d3a0d753a
--- /dev/null
+++ b/dom/media/gmp/GMPVideoPlaneImpl.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoPlaneImpl_h_
+#define GMPVideoPlaneImpl_h_
+
+#include "gmp-video-plane.h"
+#include "mozilla/ipc/Shmem.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPVideoHostImpl;
+class GMPPlaneData;
+
+class GMPPlaneImpl : public GMPPlane
+{
+ friend struct IPC::ParamTraits<mozilla::gmp::GMPPlaneImpl>;
+public:
+ explicit GMPPlaneImpl(GMPVideoHostImpl* aHost);
+ GMPPlaneImpl(const GMPPlaneData& aPlaneData, GMPVideoHostImpl* aHost);
+ virtual ~GMPPlaneImpl();
+
+ // This is called during a normal destroy sequence, which is
+ // when a consumer is finished or during XPCOM shutdown.
+ void DoneWithAPI();
+ // This is called when something has gone wrong - specicifically,
+ // a child process has crashed. Does not attempt to release Shmem,
+ // as the Shmem has already been released.
+ void ActorDestroyed();
+
+ bool InitPlaneData(GMPPlaneData& aPlaneData);
+
+ // GMPPlane
+ GMPErr CreateEmptyPlane(int32_t aAllocatedSize,
+ int32_t aStride,
+ int32_t aPlaneSize) override;
+ GMPErr Copy(const GMPPlane& aPlane) override;
+ GMPErr Copy(int32_t aSize,
+ int32_t aStride,
+ const uint8_t* aBuffer) override;
+ void Swap(GMPPlane& aPlane) override;
+ int32_t AllocatedSize() const override;
+ void ResetSize() override;
+ bool IsZeroSize() const override;
+ int32_t Stride() const override;
+ const uint8_t* Buffer() const override;
+ uint8_t* Buffer() override;
+ void Destroy() override;
+
+private:
+ GMPErr MaybeResize(int32_t aNewSize);
+ void DestroyBuffer();
+
+ ipc::Shmem mBuffer;
+ int32_t mSize;
+ int32_t mStride;
+ GMPVideoHostImpl* mHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoPlaneImpl_h_
diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.cpp b/dom/media/gmp/GMPVideoi420FrameImpl.cpp
new file mode 100644
index 000000000..fdbb9a962
--- /dev/null
+++ b/dom/media/gmp/GMPVideoi420FrameImpl.cpp
@@ -0,0 +1,365 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoi420FrameImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "mozilla/CheckedInt.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost)
+: mYPlane(aHost),
+ mUPlane(aHost),
+ mVPlane(aHost),
+ mWidth(0),
+ mHeight(0),
+ mTimestamp(0ll),
+ mDuration(0ll)
+{
+ MOZ_ASSERT(aHost);
+}
+
+GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData,
+ GMPVideoHostImpl* aHost)
+: mYPlane(aFrameData.mYPlane(), aHost),
+ mUPlane(aFrameData.mUPlane(), aHost),
+ mVPlane(aFrameData.mVPlane(), aHost),
+ mWidth(aFrameData.mWidth()),
+ mHeight(aFrameData.mHeight()),
+ mTimestamp(aFrameData.mTimestamp()),
+ mDuration(aFrameData.mDuration())
+{
+ MOZ_ASSERT(aHost);
+}
+
+GMPVideoi420FrameImpl::~GMPVideoi420FrameImpl()
+{
+}
+
+bool
+GMPVideoi420FrameImpl::InitFrameData(GMPVideoi420FrameData& aFrameData)
+{
+ mYPlane.InitPlaneData(aFrameData.mYPlane());
+ mUPlane.InitPlaneData(aFrameData.mUPlane());
+ mVPlane.InitPlaneData(aFrameData.mVPlane());
+ aFrameData.mWidth() = mWidth;
+ aFrameData.mHeight() = mHeight;
+ aFrameData.mTimestamp() = mTimestamp;
+ aFrameData.mDuration() = mDuration;
+ return true;
+}
+
+GMPVideoFrameFormat
+GMPVideoi420FrameImpl::GetFrameFormat()
+{
+ return kGMPI420VideoFrame;
+}
+
+void
+GMPVideoi420FrameImpl::Destroy()
+{
+ delete this;
+}
+
+/* static */ bool
+GMPVideoi420FrameImpl::CheckFrameData(const GMPVideoi420FrameData& aFrameData)
+{
+ // We may be passed the "wrong" shmem (one smaller than the actual size).
+ // This implies a bug or serious error on the child size. Ignore this frame if so.
+ // Note: Size() greater than expected is also an error, but with no negative consequences
+ int32_t half_width = (aFrameData.mWidth() + 1) / 2;
+ if ((aFrameData.mYPlane().mStride() <= 0) || (aFrameData.mYPlane().mSize() <= 0) ||
+ (aFrameData.mUPlane().mStride() <= 0) || (aFrameData.mUPlane().mSize() <= 0) ||
+ (aFrameData.mVPlane().mStride() <= 0) || (aFrameData.mVPlane().mSize() <= 0) ||
+ (aFrameData.mYPlane().mSize() > (int32_t) aFrameData.mYPlane().mBuffer().Size<uint8_t>()) ||
+ (aFrameData.mUPlane().mSize() > (int32_t) aFrameData.mUPlane().mBuffer().Size<uint8_t>()) ||
+ (aFrameData.mVPlane().mSize() > (int32_t) aFrameData.mVPlane().mBuffer().Size<uint8_t>()) ||
+ (aFrameData.mYPlane().mStride() < aFrameData.mWidth()) ||
+ (aFrameData.mUPlane().mStride() < half_width) ||
+ (aFrameData.mVPlane().mStride() < half_width) ||
+ (aFrameData.mYPlane().mSize() < aFrameData.mYPlane().mStride() * aFrameData.mHeight()) ||
+ (aFrameData.mUPlane().mSize() < aFrameData.mUPlane().mStride() * ((aFrameData.mHeight()+1)/2)) ||
+ (aFrameData.mVPlane().mSize() < aFrameData.mVPlane().mStride() * ((aFrameData.mHeight()+1)/2)))
+ {
+ return false;
+ }
+ return true;
+}
+
+bool
+GMPVideoi420FrameImpl::CheckDimensions(int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+ int32_t half_width = (aWidth + 1) / 2;
+ if (aWidth < 1 || aHeight < 1 ||
+ aStride_y < aWidth || aStride_u < half_width || aStride_v < half_width ||
+ !(CheckedInt<int32_t>(aHeight) * aStride_y
+ + ((CheckedInt<int32_t>(aHeight) + 1) / 2)
+ * (CheckedInt<int32_t>(aStride_u) + aStride_v)).isValid()) {
+ return false;
+ }
+ return true;
+}
+
+const GMPPlaneImpl*
+GMPVideoi420FrameImpl::GetPlane(GMPPlaneType aType) const {
+ switch (aType) {
+ case kGMPYPlane:
+ return &mYPlane;
+ case kGMPUPlane:
+ return &mUPlane;
+ case kGMPVPlane:
+ return &mVPlane;
+ default:
+ MOZ_CRASH("Unknown plane type!");
+ }
+ return nullptr;
+}
+
+GMPPlaneImpl*
+GMPVideoi420FrameImpl::GetPlane(GMPPlaneType aType) {
+ switch (aType) {
+ case kGMPYPlane :
+ return &mYPlane;
+ case kGMPUPlane :
+ return &mUPlane;
+ case kGMPVPlane :
+ return &mVPlane;
+ default:
+ MOZ_CRASH("Unknown plane type!");
+ }
+ return nullptr;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::CreateEmptyFrame(int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+ if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) {
+ return GMPGenericErr;
+ }
+
+ int32_t size_y = aStride_y * aHeight;
+ int32_t half_height = (aHeight + 1) / 2;
+ int32_t size_u = aStride_u * half_height;
+ int32_t size_v = aStride_v * half_height;
+
+ GMPErr err = mYPlane.CreateEmptyPlane(size_y, aStride_y, size_y);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ err = mUPlane.CreateEmptyPlane(size_u, aStride_u, size_u);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ err = mVPlane.CreateEmptyPlane(size_v, aStride_v, size_v);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mTimestamp = 0ll;
+ mDuration = 0ll;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+ int32_t aSize_u, const uint8_t* aBuffer_u,
+ int32_t aSize_v, const uint8_t* aBuffer_v,
+ int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+ MOZ_ASSERT(aBuffer_y);
+ MOZ_ASSERT(aBuffer_u);
+ MOZ_ASSERT(aBuffer_v);
+
+ if (aSize_y < 1 || aSize_u < 1 || aSize_v < 1) {
+ return GMPGenericErr;
+ }
+
+ if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) {
+ return GMPGenericErr;
+ }
+
+ GMPErr err = mYPlane.Copy(aSize_y, aStride_y, aBuffer_y);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ err = mUPlane.Copy(aSize_u, aStride_u, aBuffer_u);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ err = mVPlane.Copy(aSize_v, aStride_v, aBuffer_v);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::CopyFrame(const GMPVideoi420Frame& aFrame)
+{
+ auto& f = static_cast<const GMPVideoi420FrameImpl&>(aFrame);
+
+ GMPErr err = mYPlane.Copy(f.mYPlane);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ err = mUPlane.Copy(f.mUPlane);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ err = mVPlane.Copy(f.mVPlane);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ mWidth = f.mWidth;
+ mHeight = f.mHeight;
+ mTimestamp = f.mTimestamp;
+ mDuration = f.mDuration;
+
+ return GMPNoErr;
+}
+
+void
+GMPVideoi420FrameImpl::SwapFrame(GMPVideoi420Frame* aFrame)
+{
+ auto f = static_cast<GMPVideoi420FrameImpl*>(aFrame);
+ mYPlane.Swap(f->mYPlane);
+ mUPlane.Swap(f->mUPlane);
+ mVPlane.Swap(f->mVPlane);
+ std::swap(mWidth, f->mWidth);
+ std::swap(mHeight, f->mHeight);
+ std::swap(mTimestamp, f->mTimestamp);
+ std::swap(mDuration, f->mDuration);
+}
+
+uint8_t*
+GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType)
+{
+ GMPPlane* p = GetPlane(aType);
+ if (p) {
+ return p->Buffer();
+ }
+ return nullptr;
+}
+
+const uint8_t*
+GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType) const
+{
+ const GMPPlane* p = GetPlane(aType);
+ if (p) {
+ return p->Buffer();
+ }
+ return nullptr;
+}
+
+int32_t
+GMPVideoi420FrameImpl::AllocatedSize(GMPPlaneType aType) const
+{
+ const GMPPlane* p = GetPlane(aType);
+ if (p) {
+ return p->AllocatedSize();
+ }
+ return -1;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Stride(GMPPlaneType aType) const
+{
+ const GMPPlane* p = GetPlane(aType);
+ if (p) {
+ return p->Stride();
+ }
+ return -1;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::SetWidth(int32_t aWidth)
+{
+ if (!CheckDimensions(aWidth, mHeight,
+ mYPlane.Stride(), mUPlane.Stride(),
+ mVPlane.Stride())) {
+ return GMPGenericErr;
+ }
+ mWidth = aWidth;
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::SetHeight(int32_t aHeight)
+{
+ if (!CheckDimensions(mWidth, aHeight,
+ mYPlane.Stride(), mUPlane.Stride(),
+ mVPlane.Stride())) {
+ return GMPGenericErr;
+ }
+ mHeight = aHeight;
+ return GMPNoErr;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Width() const
+{
+ return mWidth;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Height() const
+{
+ return mHeight;
+}
+
+void
+GMPVideoi420FrameImpl::SetTimestamp(uint64_t aTimestamp)
+{
+ mTimestamp = aTimestamp;
+}
+
+uint64_t
+GMPVideoi420FrameImpl::Timestamp() const
+{
+ return mTimestamp;
+}
+
+void
+GMPVideoi420FrameImpl::SetDuration(uint64_t aDuration)
+{
+ mDuration = aDuration;
+}
+
+uint64_t
+GMPVideoi420FrameImpl::Duration() const
+{
+ return mDuration;
+}
+
+bool
+GMPVideoi420FrameImpl::IsZeroSize() const
+{
+ return (mYPlane.IsZeroSize() && mUPlane.IsZeroSize() && mVPlane.IsZeroSize());
+}
+
+void
+GMPVideoi420FrameImpl::ResetSize()
+{
+ mYPlane.ResetSize();
+ mUPlane.ResetSize();
+ mVPlane.ResetSize();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.h b/dom/media/gmp/GMPVideoi420FrameImpl.h
new file mode 100644
index 000000000..f5cb0254b
--- /dev/null
+++ b/dom/media/gmp/GMPVideoi420FrameImpl.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoi420FrameImpl_h_
+#define GMPVideoi420FrameImpl_h_
+
+#include "gmp-video-frame-i420.h"
+#include "mozilla/ipc/Shmem.h"
+#include "GMPVideoPlaneImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPVideoi420FrameData;
+
+class GMPVideoi420FrameImpl : public GMPVideoi420Frame
+{
+ friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoi420FrameImpl>;
+public:
+ explicit GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost);
+ GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData, GMPVideoHostImpl* aHost);
+ virtual ~GMPVideoi420FrameImpl();
+
+ static bool CheckFrameData(const GMPVideoi420FrameData& aFrameData);
+
+ bool InitFrameData(GMPVideoi420FrameData& aFrameData);
+ const GMPPlaneImpl* GetPlane(GMPPlaneType aType) const;
+ GMPPlaneImpl* GetPlane(GMPPlaneType aType);
+
+ // GMPVideoFrame
+ GMPVideoFrameFormat GetFrameFormat() override;
+ void Destroy() override;
+
+ // GMPVideoi420Frame
+ GMPErr CreateEmptyFrame(int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride_y,
+ int32_t aStride_u,
+ int32_t aStride_v) override;
+ GMPErr CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+ int32_t aSize_u, const uint8_t* aBuffer_u,
+ int32_t aSize_v, const uint8_t* aBuffer_v,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride_y,
+ int32_t aStride_u,
+ int32_t aStride_v) override;
+ GMPErr CopyFrame(const GMPVideoi420Frame& aFrame) override;
+ void SwapFrame(GMPVideoi420Frame* aFrame) override;
+ uint8_t* Buffer(GMPPlaneType aType) override;
+ const uint8_t* Buffer(GMPPlaneType aType) const override;
+ int32_t AllocatedSize(GMPPlaneType aType) const override;
+ int32_t Stride(GMPPlaneType aType) const override;
+ GMPErr SetWidth(int32_t aWidth) override;
+ GMPErr SetHeight(int32_t aHeight) override;
+ int32_t Width() const override;
+ int32_t Height() const override;
+ void SetTimestamp(uint64_t aTimestamp) override;
+ uint64_t Timestamp() const override;
+ void SetDuration(uint64_t aDuration) override;
+ uint64_t Duration() const override;
+ bool IsZeroSize() const override;
+ void ResetSize() override;
+
+private:
+ bool CheckDimensions(int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v);
+
+ GMPPlaneImpl mYPlane;
+ GMPPlaneImpl mUPlane;
+ GMPPlaneImpl mVPlane;
+ int32_t mWidth;
+ int32_t mHeight;
+ uint64_t mTimestamp;
+ uint64_t mDuration;
+};
+
+} // namespace gmp
+
+} // namespace mozilla
+
+#endif // GMPVideoi420FrameImpl_h_
diff --git a/dom/media/gmp/PGMP.ipdl b/dom/media/gmp/PGMP.ipdl
new file mode 100644
index 000000000..e1738d010
--- /dev/null
+++ b/dom/media/gmp/PGMP.ipdl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMPContent;
+include protocol PGMPTimer;
+include protocol PGMPStorage;
+
+using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+intr protocol PGMP
+{
+ parent opens PGMPContent;
+
+ manages PGMPTimer;
+ manages PGMPStorage;
+
+parent:
+ async PGMPTimer();
+ async PGMPStorage();
+
+ async PGMPContentChildDestroyed();
+
+ async AsyncShutdownComplete();
+ async AsyncShutdownRequired();
+
+child:
+ async BeginAsyncShutdown();
+ async CrashPluginNow();
+ intr StartPlugin(nsString adapter);
+ async SetNodeId(nsCString nodeId);
+ async PreloadLibs(nsCString libs);
+ async CloseActive();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPAudioDecoder.ipdl b/dom/media/gmp/PGMPAudioDecoder.ipdl
new file mode 100644
index 000000000..9af9415c8
--- /dev/null
+++ b/dom/media/gmp/PGMPAudioDecoder.ipdl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMPContent;
+include GMPTypes;
+
+using GMPCodecSpecificInfo from "gmp-audio-codec.h";
+using GMPErr from "gmp-errors.h";
+
+include "GMPMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPAudioDecoder
+{
+ manager PGMPContent;
+child:
+ async InitDecode(GMPAudioCodecData aCodecSettings);
+ async Decode(GMPAudioEncodedSampleData aInput);
+ async Reset();
+ async Drain();
+ async DecodingComplete();
+parent:
+ async __delete__();
+ async Decoded(GMPAudioDecodedSampleData aDecoded);
+ async InputDataExhausted();
+ async DrainComplete();
+ async ResetComplete();
+ async Error(GMPErr aErr);
+ async Shutdown();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPContent.ipdl b/dom/media/gmp/PGMPContent.ipdl
new file mode 100644
index 000000000..00e16c02f
--- /dev/null
+++ b/dom/media/gmp/PGMPContent.ipdl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMP;
+include protocol PGMPService;
+include protocol PGMPVideoDecoder;
+include protocol PGMPVideoEncoder;
+include protocol PGMPDecryptor;
+include protocol PGMPAudioDecoder;
+
+namespace mozilla {
+namespace gmp {
+
+intr protocol PGMPContent
+{
+ bridges PGMPService, PGMP;
+
+ manages PGMPAudioDecoder;
+ manages PGMPDecryptor;
+ manages PGMPVideoDecoder;
+ manages PGMPVideoEncoder;
+
+child:
+ async PGMPAudioDecoder();
+ async PGMPDecryptor();
+ async PGMPVideoDecoder(uint32_t aDecryptorId);
+ async PGMPVideoEncoder();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPDecryptor.ipdl b/dom/media/gmp/PGMPDecryptor.ipdl
new file mode 100644
index 000000000..06b9b9cb6
--- /dev/null
+++ b/dom/media/gmp/PGMPDecryptor.ipdl
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMPContent;
+include GMPTypes;
+
+using GMPSessionMessageType from "gmp-decryption.h";
+using GMPSessionType from "gmp-decryption.h";
+using GMPDOMException from "gmp-decryption.h";
+using GMPErr from "gmp-errors.h";
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPDecryptor
+{
+ manager PGMPContent;
+child:
+
+ async Init(bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired);
+
+ async CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ nsCString aInitDataType,
+ uint8_t[] aInitData,
+ GMPSessionType aSessionType);
+
+ async LoadSession(uint32_t aPromiseId,
+ nsCString aSessionId);
+
+ async UpdateSession(uint32_t aPromiseId,
+ nsCString aSessionId,
+ uint8_t[] aResponse);
+
+ async CloseSession(uint32_t aPromiseId,
+ nsCString aSessionId);
+
+ async RemoveSession(uint32_t aPromiseId,
+ nsCString aSessionId);
+
+ async SetServerCertificate(uint32_t aPromiseId,
+ uint8_t[] aServerCert);
+
+ async Decrypt(uint32_t aId,
+ uint8_t[] aBuffer,
+ GMPDecryptionData aMetadata);
+
+ async DecryptingComplete();
+
+parent:
+ async __delete__();
+
+ async SetDecryptorId(uint32_t aId);
+
+ async SetSessionId(uint32_t aCreateSessionToken,
+ nsCString aSessionId);
+
+ async ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess);
+
+ async ResolvePromise(uint32_t aPromiseId);
+
+ async RejectPromise(uint32_t aPromiseId,
+ GMPDOMException aDOMExceptionCode,
+ nsCString aMessage);
+
+ async SessionMessage(nsCString aSessionId,
+ GMPSessionMessageType aMessageType,
+ uint8_t[] aMessage);
+
+ async ExpirationChange(nsCString aSessionId, double aExpiryTime);
+
+ async SessionClosed(nsCString aSessionId);
+
+ async SessionError(nsCString aSessionId,
+ GMPDOMException aDOMExceptionCode,
+ uint32_t aSystemCode,
+ nsCString aMessage);
+
+ async Decrypted(uint32_t aId, GMPErr aResult, uint8_t[] aBuffer);
+
+ async Shutdown();
+
+ async BatchedKeyStatusChanged(nsCString aSessionId,
+ GMPKeyInformation[] aKeyInfos);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPService.ipdl b/dom/media/gmp/PGMPService.ipdl
new file mode 100644
index 000000000..db3fb6388
--- /dev/null
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMP;
+
+using base::ProcessId from "base/process.h";
+
+namespace mozilla {
+namespace gmp {
+
+sync protocol PGMPService
+{
+ parent spawns PGMP as child;
+
+parent:
+
+ sync SelectGMP(nsCString nodeId, nsCString api, nsCString[] tags)
+ returns (uint32_t pluginId, nsresult aResult);
+
+ sync LaunchGMP(uint32_t pluginId, ProcessId[] alreadyBridgedTo)
+ returns (ProcessId id, nsCString displayName, nsresult aResult);
+
+ sync GetGMPNodeId(nsString origin, nsString topLevelOrigin,
+ nsString gmpName,
+ bool inPrivateBrowsing)
+ returns (nsCString id);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPStorage.ipdl b/dom/media/gmp/PGMPStorage.ipdl
new file mode 100644
index 000000000..4d858312a
--- /dev/null
+++ b/dom/media/gmp/PGMPStorage.ipdl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMP;
+include GMPTypes;
+
+using GMPErr from "gmp-errors.h";
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPStorage
+{
+ manager PGMP;
+
+child:
+ async OpenComplete(nsCString aRecordName, GMPErr aStatus);
+ async ReadComplete(nsCString aRecordName, GMPErr aStatus, uint8_t[] aBytes);
+ async WriteComplete(nsCString aRecordName, GMPErr aStatus);
+ async RecordNames(nsCString[] aRecordNames, GMPErr aStatus);
+ async Shutdown();
+
+parent:
+ async Open(nsCString aRecordName);
+ async Read(nsCString aRecordName);
+ async Write(nsCString aRecordName, uint8_t[] aBytes);
+ async Close(nsCString aRecordName);
+ async GetRecordNames();
+ async __delete__();
+
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPTimer.ipdl b/dom/media/gmp/PGMPTimer.ipdl
new file mode 100644
index 000000000..7a486bc3a
--- /dev/null
+++ b/dom/media/gmp/PGMPTimer.ipdl
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMP;
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPTimer
+{
+ manager PGMP;
+child:
+ async TimerExpired(uint32_t aTimerId);
+parent:
+ async SetTimer(uint32_t aTimerId, uint32_t aTimeoutMs);
+ async __delete__();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPVideoDecoder.ipdl b/dom/media/gmp/PGMPVideoDecoder.ipdl
new file mode 100644
index 000000000..83ad8f700
--- /dev/null
+++ b/dom/media/gmp/PGMPVideoDecoder.ipdl
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMPContent;
+include GMPTypes;
+
+using GMPVideoCodec from "gmp-video-codec.h";
+using GMPErr from "gmp-errors.h";
+
+include "GMPMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+intr protocol PGMPVideoDecoder
+{
+ manager PGMPContent;
+child:
+ async InitDecode(GMPVideoCodec aCodecSettings,
+ uint8_t[] aCodecSpecific,
+ int32_t aCoreCount);
+ async Decode(GMPVideoEncodedFrameData aInputFrame,
+ bool aMissingFrames,
+ uint8_t[] aCodecSpecificInfo,
+ int64_t aRenderTimeMs);
+ async Reset();
+ async Drain();
+ async DecodingComplete();
+ async ChildShmemForPool(Shmem aFrameBuffer);
+
+parent:
+ async __delete__();
+ async Decoded(GMPVideoi420FrameData aDecodedFrame);
+ async ReceivedDecodedReferenceFrame(uint64_t aPictureId);
+ async ReceivedDecodedFrame(uint64_t aPictureId);
+ async InputDataExhausted();
+ async DrainComplete();
+ async ResetComplete();
+ async Error(GMPErr aErr);
+ async Shutdown();
+ async ParentShmemForPool(Shmem aEncodedBuffer);
+ // MUST be intr - if sync and we create a new Shmem, when the returned
+ // Shmem is received in the Child it will fail to Deserialize
+ intr NeedShmem(uint32_t aFrameBufferSize) returns (Shmem aMem);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPVideoEncoder.ipdl b/dom/media/gmp/PGMPVideoEncoder.ipdl
new file mode 100644
index 000000000..ef013352a
--- /dev/null
+++ b/dom/media/gmp/PGMPVideoEncoder.ipdl
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMPContent;
+include GMPTypes;
+
+using GMPVideoCodec from "gmp-video-codec.h";
+using GMPVideoFrameType from "gmp-video-frame-encoded.h";
+using GMPErr from "gmp-errors.h";
+
+include "GMPMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+intr protocol PGMPVideoEncoder
+{
+ manager PGMPContent;
+child:
+ async InitEncode(GMPVideoCodec aCodecSettings,
+ uint8_t[] aCodecSpecific,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize);
+ async Encode(GMPVideoi420FrameData aInputFrame,
+ uint8_t[] aCodecSpecificInfo,
+ GMPVideoFrameType[] aFrameTypes);
+ async SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT);
+ async SetRates(uint32_t aNewBitRate, uint32_t aFrameRate);
+ async SetPeriodicKeyFrames(bool aEnable);
+ async EncodingComplete();
+ async ChildShmemForPool(Shmem aEncodedBuffer);
+
+parent:
+ async __delete__();
+ async Encoded(GMPVideoEncodedFrameData aEncodedFrame,
+ uint8_t[] aCodecSpecificInfo);
+ async Error(GMPErr aErr);
+ async Shutdown();
+ async ParentShmemForPool(Shmem aFrameBuffer);
+ // MUST be intr - if sync and we create a new Shmem, when the returned
+ // Shmem is received in the Child it will fail to Deserialize
+ intr NeedShmem(uint32_t aEncodedBufferSize) returns (Shmem aMem);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/README.txt b/dom/media/gmp/README.txt
new file mode 100644
index 000000000..189cf3b30
--- /dev/null
+++ b/dom/media/gmp/README.txt
@@ -0,0 +1 @@
+This directory contains code supporting Gecko Media Plugins (GMPs). The GMP API is not the same thing as the Media Plugin API (MPAPI).
diff --git a/dom/media/gmp/gmp-api/gmp-async-shutdown.h b/dom/media/gmp/gmp-api/gmp-async-shutdown.h
new file mode 100644
index 000000000..42268668f
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-async-shutdown.h
@@ -0,0 +1,54 @@
+/*
+* Copyright 2013, 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 GMP_ASYNC_SHUTDOWN_H_
+#define GMP_ASYNC_SHUTDOWN_H_
+
+#define GMP_API_ASYNC_SHUTDOWN "async-shutdown"
+
+// API exposed by the plugin library to manage asynchronous shutdown.
+// Some plugins require special cleanup which may need to make calls
+// to host services and wait for async responses.
+//
+// To enable a plugins to block shutdown until its async shutdown is
+// complete, implement the GMPAsyncShutdown interface and return it when
+// your plugin's GMPGetAPI function is called with "async-shutdown".
+// When your GMPAsyncShutdown's BeginShutdown() implementation is called
+// by the GMP host, you should initate your async shutdown process.
+// Once you have completed shutdown, call the ShutdownComplete() function
+// of the GMPAsyncShutdownHost that is passed as the host argument to the
+// GMPGetAPI() call.
+//
+// Note: Your GMP's GMPShutdown function will still be called after your
+// call to ShutdownComplete().
+//
+// API name macro: GMP_API_ASYNC_SHUTDOWN
+// Host API: GMPAsyncShutdownHost
+class GMPAsyncShutdown {
+public:
+ virtual ~GMPAsyncShutdown() {}
+
+ virtual void BeginShutdown() = 0;
+};
+
+class GMPAsyncShutdownHost {
+public:
+ virtual ~GMPAsyncShutdownHost() {}
+
+ virtual void ShutdownComplete() = 0;
+};
+
+#endif // GMP_ASYNC_SHUTDOWN_H_
diff --git a/dom/media/gmp/gmp-api/gmp-audio-codec.h b/dom/media/gmp/gmp-api/gmp-audio-codec.h
new file mode 100644
index 000000000..5a5c17bb9
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-audio-codec.h
@@ -0,0 +1,43 @@
+/*
+* Copyright 2013, 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 GMP_AUDIO_CODEC_h_
+#define GMP_AUDIO_CODEC_h_
+
+#include <stdint.h>
+
+enum GMPAudioCodecType
+{
+ kGMPAudioCodecAAC,
+ kGMPAudioCodecVorbis,
+ kGMPAudioCodecInvalid // Should always be last.
+};
+
+struct GMPAudioCodec
+{
+ GMPAudioCodecType mCodecType;
+ uint32_t mChannelCount;
+ uint32_t mBitsPerChannel;
+ uint32_t mSamplesPerSecond;
+
+ // Codec extra data, such as vorbis setup header, or
+ // AAC AudioSpecificConfig.
+ // These are null/0 if not externally negotiated
+ const uint8_t* mExtraData;
+ uint32_t mExtraDataLen;
+};
+
+#endif // GMP_AUDIO_CODEC_h_
diff --git a/dom/media/gmp/gmp-api/gmp-audio-decode.h b/dom/media/gmp/gmp-api/gmp-audio-decode.h
new file mode 100644
index 000000000..8b017c0af
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-audio-decode.h
@@ -0,0 +1,84 @@
+/*
+* Copyright 2013, 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 GMP_AUDIO_DECODE_h_
+#define GMP_AUDIO_DECODE_h_
+
+#include "gmp-errors.h"
+#include "gmp-audio-samples.h"
+#include "gmp-audio-codec.h"
+#include <stdint.h>
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPAudioDecoderCallback
+{
+public:
+ virtual ~GMPAudioDecoderCallback() {}
+
+ virtual void Decoded(GMPAudioSamples* aDecodedSamples) = 0;
+
+ virtual void InputDataExhausted() = 0;
+
+ virtual void DrainComplete() = 0;
+
+ virtual void ResetComplete() = 0;
+
+ // Called when the decoder encounters a catestrophic error and cannot
+ // continue. Gecko will not send any more input for decoding.
+ virtual void Error(GMPErr aError) = 0;
+};
+
+#define GMP_API_AUDIO_DECODER "decode-audio"
+
+// Audio decoding for a single stream. A GMP may be asked to create multiple
+// decoders concurrently.
+//
+// API name macro: GMP_API_AUDIO_DECODER
+// Host API: GMPAudioHost
+//
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPAudioDecoder
+{
+public:
+ virtual ~GMPAudioDecoder() {}
+
+ // aCallback: Subclass should retain reference to it until DecodingComplete
+ // is called. Do not attempt to delete it, host retains ownership.
+ // TODO: Pass AudioHost so decoder can create GMPAudioEncodedFrame objects?
+ virtual void InitDecode(const GMPAudioCodec& aCodecSettings,
+ GMPAudioDecoderCallback* aCallback) = 0;
+
+ // Decode encoded audio frames (as a part of an audio stream). The decoded
+ // frames must be returned to the user through the decode complete callback.
+ virtual void Decode(GMPAudioSamples* aEncodedSamples) = 0;
+
+ // Reset decoder state and prepare for a new call to Decode(...).
+ // Flushes the decoder pipeline.
+ // The decoder should enqueue a task to run ResetComplete() on the main
+ // thread once the reset has finished.
+ virtual void Reset() = 0;
+
+ // Output decoded frames for any data in the pipeline, regardless of ordering.
+ // All remaining decoded frames should be immediately returned via callback.
+ // The decoder should enqueue a task to run DrainComplete() on the main
+ // thread once the reset has finished.
+ virtual void Drain() = 0;
+
+ // May free decoder memory.
+ virtual void DecodingComplete() = 0;
+};
+
+#endif // GMP_VIDEO_DECODE_h_
diff --git a/dom/media/gmp/gmp-api/gmp-audio-host.h b/dom/media/gmp/gmp-api/gmp-audio-host.h
new file mode 100644
index 000000000..fe3641938
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-audio-host.h
@@ -0,0 +1,32 @@
+/*
+* Copyright 2013, 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 GMP_AUDIO_HOST_h_
+#define GMP_AUDIO_HOST_h_
+
+#include "gmp-errors.h"
+#include "gmp-audio-samples.h"
+
+class GMPAudioHost
+{
+public:
+ // Construct various Audio API objects. Host does not retain reference,
+ // caller is owner and responsible for deleting.
+ virtual GMPErr CreateSamples(GMPAudioFormat aFormat,
+ GMPAudioSamples** aSamples) = 0;
+};
+
+#endif // GMP_AUDIO_HOST_h_
diff --git a/dom/media/gmp/gmp-api/gmp-audio-samples.h b/dom/media/gmp/gmp-api/gmp-audio-samples.h
new file mode 100644
index 000000000..a47fc74b9
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-audio-samples.h
@@ -0,0 +1,74 @@
+/*
+* Copyright 2013, 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 GMP_AUDIO_FRAME_h_
+#define GMP_AUDIO_FRAME_h_
+
+#include <stdint.h>
+#include "gmp-errors.h"
+#include "gmp-decryption.h"
+
+enum GMPAudioFormat
+{
+ kGMPAudioEncodedSamples, // Raw compressed data, i.e. an AAC/Vorbis packet.
+ kGMPAudioIS16Samples, // Interleaved int16_t PCM samples.
+ kGMPAudioSamplesFormatInvalid // Should always be last.
+};
+
+class GMPAudioSamples {
+public:
+ // The format of the buffer.
+ virtual GMPAudioFormat GetFormat() = 0;
+ virtual void Destroy() = 0;
+
+ // MAIN THREAD ONLY
+ // Buffer size must be exactly what's required to contain all samples in
+ // the buffer; every byte is assumed to be part of a sample.
+ virtual GMPErr SetBufferSize(uint32_t aSize) = 0;
+
+ // Size of the buffer in bytes.
+ virtual uint32_t Size() = 0;
+
+ // Timestamps are in microseconds, and are the playback start time of the
+ // first sample in the buffer.
+ virtual void SetTimeStamp(uint64_t aTimeStamp) = 0;
+ virtual uint64_t TimeStamp() = 0;
+ virtual const uint8_t* Buffer() const = 0;
+ virtual uint8_t* Buffer() = 0;
+
+ // Get metadata describing how this frame is encrypted, or nullptr if the
+ // buffer is not encrypted.
+ virtual const GMPEncryptedBufferMetadata* GetDecryptionData() const = 0;
+
+ virtual uint32_t Channels() const = 0;
+ virtual void SetChannels(uint32_t aChannels) = 0;
+
+ // Rate; the number of frames per second, where a "frame" is one sample for
+ // each channel.
+ //
+ // For IS16 samples, the number of samples should be:
+ // Size() / (Channels() * sizeof(int16_t)).
+ //
+ // Note: Channels() and Rate() may not be constant across a decoding
+ // session. For example the rate for decoded samples may be different
+ // than the rate advertised by the MP4 container for encoded samples
+ // for HE-AAC streams with SBR/PS, and an EME-GMP may need to downsample
+ // to satisfy DRM requirements.
+ virtual uint32_t Rate() const = 0;
+ virtual void SetRate(uint32_t aRate) = 0;
+};
+
+#endif // GMP_AUDIO_FRAME_h_
diff --git a/dom/media/gmp/gmp-api/gmp-decryption.h b/dom/media/gmp/gmp-api/gmp-decryption.h
new file mode 100644
index 000000000..046a05759
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-decryption.h
@@ -0,0 +1,459 @@
+/*
+* Copyright 2013, 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 GMP_DECRYPTION_h_
+#define GMP_DECRYPTION_h_
+
+#include "gmp-platform.h"
+
+class GMPStringList {
+public:
+ virtual uint32_t Size() const = 0;
+
+ virtual void StringAt(uint32_t aIndex,
+ const char** aOutString, uint32_t* aOutLength) const = 0;
+
+ virtual ~GMPStringList() { }
+};
+
+class GMPEncryptedBufferMetadata {
+public:
+ // Key ID to identify the decryption key.
+ virtual const uint8_t* KeyId() const = 0;
+
+ // Size (in bytes) of |KeyId()|.
+ virtual uint32_t KeyIdSize() const = 0;
+
+ // Initialization vector.
+ virtual const uint8_t* IV() const = 0;
+
+ // Size (in bytes) of |IV|.
+ virtual uint32_t IVSize() const = 0;
+
+ // Number of entries returned by ClearBytes() and CipherBytes().
+ virtual uint32_t NumSubsamples() const = 0;
+
+ virtual const uint16_t* ClearBytes() const = 0;
+
+ virtual const uint32_t* CipherBytes() const = 0;
+
+ virtual ~GMPEncryptedBufferMetadata() {}
+
+ // The set of MediaKeySession IDs associated with this decryption key in
+ // the current stream.
+ virtual const GMPStringList* SessionIds() const = 0;
+};
+
+class GMPBuffer {
+public:
+ virtual uint32_t Id() const = 0;
+ virtual uint8_t* Data() = 0;
+ virtual uint32_t Size() const = 0;
+ virtual void Resize(uint32_t aSize) = 0;
+ virtual ~GMPBuffer() {}
+};
+
+// These match to the DOMException codes as per:
+// http://www.w3.org/TR/dom/#domexception
+enum GMPDOMException {
+ kGMPNoModificationAllowedError = 7,
+ kGMPNotFoundError = 8,
+ kGMPNotSupportedError = 9,
+ kGMPInvalidStateError = 11,
+ kGMPSyntaxError = 12,
+ kGMPInvalidModificationError = 13,
+ kGMPInvalidAccessError = 15,
+ kGMPSecurityError = 18,
+ kGMPAbortError = 20,
+ kGMPQuotaExceededError = 22,
+ kGMPTimeoutError = 23,
+ kGMPTypeError = 52
+};
+
+enum GMPSessionMessageType {
+ kGMPLicenseRequest = 0,
+ kGMPLicenseRenewal = 1,
+ kGMPLicenseRelease = 2,
+ kGMPIndividualizationRequest = 3,
+ kGMPMessageInvalid = 4 // Must always be last.
+};
+
+enum GMPMediaKeyStatus {
+ kGMPUsable = 0,
+ kGMPExpired = 1,
+ kGMPOutputDownscaled = 2,
+ kGMPOutputRestricted = 3,
+ kGMPInternalError = 4,
+ kGMPUnknown = 5, // Removes key from MediaKeyStatusMap
+ kGMPReleased = 6,
+ kGMPStatusPending = 7,
+ kGMPMediaKeyStatusInvalid = 8 // Must always be last.
+};
+
+struct GMPMediaKeyInfo {
+ GMPMediaKeyInfo() {}
+ GMPMediaKeyInfo(const uint8_t* aKeyId,
+ uint32_t aKeyIdSize,
+ GMPMediaKeyStatus aStatus)
+ : keyid(aKeyId)
+ , keyid_size(aKeyIdSize)
+ , status(aStatus)
+ {}
+ const uint8_t* keyid;
+ uint32_t keyid_size;
+ GMPMediaKeyStatus status;
+};
+
+// Time in milliseconds, as offset from epoch, 1 Jan 1970.
+typedef int64_t GMPTimestamp;
+
+// Callbacks to be called from the CDM. Threadsafe.
+class GMPDecryptorCallback {
+public:
+
+ // The GMPDecryptor should call this in response to a call to
+ // GMPDecryptor::CreateSession(). The GMP host calls CreateSession() when
+ // MediaKeySession.generateRequest() is called by JavaScript.
+ // After CreateSession() is called, the GMPDecryptor should call
+ // GMPDecryptorCallback::SetSessionId() to set the sessionId exposed to
+ // JavaScript on the MediaKeySession on which the generateRequest() was
+ // called. SetSessionId() must be called before
+ // GMPDecryptorCallback::SessionMessage() will work.
+ // aSessionId must be null terminated.
+ // Note: pass the aCreateSessionToken from the CreateSession() call,
+ // and then once the session has sent any messages required for the
+ // license request to be sent, then resolve the aPromiseId that was passed
+ // to GMPDecryptor::CreateSession().
+ // Note: GMPDecryptor::LoadSession() does *not* need to call SetSessionId()
+ // for GMPDecryptorCallback::SessionMessage() to work.
+ virtual void SetSessionId(uint32_t aCreateSessionToken,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Resolves a promise for a session loaded.
+ // Resolves to false if we don't have any session data stored for the given
+ // session ID.
+ // Must be called before SessionMessage().
+ virtual void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) = 0;
+
+ // Called to resolve a specified promise with "undefined".
+ virtual void ResolvePromise(uint32_t aPromiseId) = 0;
+
+ // Called to reject a promise with a DOMException.
+ // aMessage is logged to the WebConsole.
+ // aMessage is optional, but if present must be null terminated.
+ virtual void RejectPromise(uint32_t aPromiseId,
+ GMPDOMException aException,
+ const char* aMessage,
+ uint32_t aMessageLength) = 0;
+
+ // Called by the CDM when it has a message for a session.
+ // Length parameters should not include null termination.
+ // aSessionId must be null terminated.
+ virtual void SessionMessage(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPSessionMessageType aMessageType,
+ const uint8_t* aMessage,
+ uint32_t aMessageLength) = 0;
+
+ // aSessionId must be null terminated.
+ virtual void ExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPTimestamp aExpiryTime) = 0;
+
+ // Called by the GMP when a session is closed. All file IO
+ // that a session requires should be complete before calling this.
+ // aSessionId must be null terminated.
+ virtual void SessionClosed(const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Called by the GMP when an error occurs in a session.
+ // aSessionId must be null terminated.
+ // aMessage is logged to the WebConsole.
+ // aMessage is optional, but if present must be null terminated.
+ virtual void SessionError(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPDOMException aException,
+ uint32_t aSystemCode,
+ const char* aMessage,
+ uint32_t aMessageLength) = 0;
+
+ // Notifies the status of a key. Gecko will not call into the CDM to decrypt
+ // or decode content encrypted with a key unless the CDM has marked it
+ // usable first. So a CDM *MUST* mark its usable keys as usable!
+ virtual void KeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aKeyId,
+ uint32_t aKeyIdLength,
+ GMPMediaKeyStatus aStatus) = 0;
+
+ // DEPRECATED; this function has no affect.
+ virtual void SetCapabilities(uint64_t aCaps) = 0;
+
+ // Returns decrypted buffer to Gecko, or reports failure.
+ virtual void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) = 0;
+
+ // To aggregate KeyStatusChanged into single callback per session id.
+ virtual void BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength) = 0;
+
+ virtual ~GMPDecryptorCallback() {}
+};
+
+// Host interface, passed to GetAPIFunc(), with "decrypt".
+class GMPDecryptorHost {
+public:
+ virtual void GetSandboxVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength) = 0;
+
+ virtual void GetPluginVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength) = 0;
+
+ virtual ~GMPDecryptorHost() {}
+};
+
+enum GMPSessionType {
+ kGMPTemporySession = 0,
+ kGMPPersistentSession = 1,
+ kGMPSessionInvalid = 2 // Must always be last.
+};
+
+// Gecko supports the current GMPDecryptor version, and the obsolete
+// version that the Adobe GMP still uses.
+#define GMP_API_DECRYPTOR "eme-decrypt-v9"
+#define GMP_API_DECRYPTOR_BACKWARDS_COMPAT "eme-decrypt-v7"
+
+// API exposed by plugin library to manage decryption sessions.
+// When the Host requests this by calling GMPGetAPIFunc().
+//
+// API name macro: GMP_API_DECRYPTOR
+// Host API: GMPDecryptorHost
+class GMPDecryptor {
+public:
+
+ // Sets the callback to use with the decryptor to return results
+ // to Gecko.
+ virtual void Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) = 0;
+
+ // Initiates the creation of a session given |aType| and |aInitData|, and
+ // the generation of a license request message.
+ //
+ // This corresponds to a MediaKeySession.generateRequest() call in JS.
+ //
+ // The GMPDecryptor must do the following, in order, upon this method
+ // being called:
+ //
+ // 1. Generate a sessionId to expose to JS, and call
+ // GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId...)
+ // with the sessionId to be exposed to JS/EME on the MediaKeySession
+ // object on which generateRequest() was called, and then
+ // 2. send any messages to JS/EME required to generate a license request
+ // given the supplied initData, and then
+ // 3. generate a license request message, and send it to JS/EME, and then
+ // 4. call GMPDecryptorCallback::ResolvePromise().
+ //
+ // Note: GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId, ...)
+ // *must* be called before GMPDecryptorCallback::SendMessage(sessionId, ...)
+ // will work.
+ //
+ // If generating the request fails, reject aPromiseId by calling
+ // GMPDecryptorCallback::RejectPromise().
+ virtual void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) = 0;
+
+ // Loads a previously loaded persistent session.
+ //
+ // This corresponds to a MediaKeySession.load() call in JS.
+ //
+ // The GMPDecryptor must do the following, in order, upon this method
+ // being called:
+ //
+ // 1. Send any messages to JS/EME, or read from storage, whatever is
+ // required to load the session, and then
+ // 2. if there is no session with the given sessionId loadable, call
+ // ResolveLoadSessionPromise(aPromiseId, false), otherwise
+ // 2. mark the session's keys as usable, and then
+ // 3. update the session's expiration, and then
+ // 4. call GMPDecryptorCallback::ResolveLoadSessionPromise(aPromiseId, true).
+ //
+ // If loading the session fails due to error, reject aPromiseId by calling
+ // GMPDecryptorCallback::RejectPromise().
+ virtual void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Updates the session with |aResponse|.
+ // This corresponds to a MediaKeySession.update() call in JS.
+ virtual void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) = 0;
+
+ // Releases the resources (keys) for the specified session.
+ // This corresponds to a MediaKeySession.close() call in JS.
+ virtual void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Removes the resources (keys) for the specified session.
+ // This corresponds to a MediaKeySession.remove() call in JS.
+ virtual void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Resolve/reject promise on completion.
+ // This corresponds to a MediaKeySession.setServerCertificate() call in JS.
+ virtual void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) = 0;
+
+ // Asynchronously decrypts aBuffer in place. When the decryption is
+ // complete, GMPDecryptor should write the decrypted data back into the
+ // same GMPBuffer object and return it to Gecko by calling Decrypted(),
+ // with the GMPNoErr successcode. If decryption fails, call Decrypted()
+ // with a failure code, and an error event will fire on the media element.
+ // Note: When Decrypted() is called and aBuffer is passed back, aBuffer
+ // is deleted. Don't forget to call Decrypted(), as otherwise aBuffer's
+ // memory will leak!
+ virtual void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) = 0;
+
+ // Called when the decryption operations are complete.
+ // Do not call the GMPDecryptorCallback's functions after this is called.
+ virtual void DecryptingComplete() = 0;
+
+ virtual ~GMPDecryptor() {}
+};
+
+// v7 is the latest decryptor version supported by the Adobe GMP.
+//
+// API name macro: GMP_API_DECRYPTOR_BACKWARDS_COMPAT
+// Host API: GMPDecryptorHost
+class GMPDecryptor7 {
+public:
+
+ // Sets the callback to use with the decryptor to return results
+ // to Gecko.
+ virtual void Init(GMPDecryptorCallback* aCallback) = 0;
+
+ // Initiates the creation of a session given |aType| and |aInitData|, and
+ // the generation of a license request message.
+ //
+ // This corresponds to a MediaKeySession.generateRequest() call in JS.
+ //
+ // The GMPDecryptor must do the following, in order, upon this method
+ // being called:
+ //
+ // 1. Generate a sessionId to expose to JS, and call
+ // GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId...)
+ // with the sessionId to be exposed to JS/EME on the MediaKeySession
+ // object on which generateRequest() was called, and then
+ // 2. send any messages to JS/EME required to generate a license request
+ // given the supplied initData, and then
+ // 3. generate a license request message, and send it to JS/EME, and then
+ // 4. call GMPDecryptorCallback::ResolvePromise().
+ //
+ // Note: GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId, ...)
+ // *must* be called before GMPDecryptorCallback::SendMessage(sessionId, ...)
+ // will work.
+ //
+ // If generating the request fails, reject aPromiseId by calling
+ // GMPDecryptorCallback::RejectPromise().
+ virtual void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) = 0;
+
+ // Loads a previously loaded persistent session.
+ //
+ // This corresponds to a MediaKeySession.load() call in JS.
+ //
+ // The GMPDecryptor must do the following, in order, upon this method
+ // being called:
+ //
+ // 1. Send any messages to JS/EME, or read from storage, whatever is
+ // required to load the session, and then
+ // 2. if there is no session with the given sessionId loadable, call
+ // ResolveLoadSessionPromise(aPromiseId, false), otherwise
+ // 2. mark the session's keys as usable, and then
+ // 3. update the session's expiration, and then
+ // 4. call GMPDecryptorCallback::ResolveLoadSessionPromise(aPromiseId, true).
+ //
+ // If loading the session fails due to error, reject aPromiseId by calling
+ // GMPDecryptorCallback::RejectPromise().
+ virtual void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Updates the session with |aResponse|.
+ // This corresponds to a MediaKeySession.update() call in JS.
+ virtual void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) = 0;
+
+ // Releases the resources (keys) for the specified session.
+ // This corresponds to a MediaKeySession.close() call in JS.
+ virtual void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Removes the resources (keys) for the specified session.
+ // This corresponds to a MediaKeySession.remove() call in JS.
+ virtual void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Resolve/reject promise on completion.
+ // This corresponds to a MediaKeySession.setServerCertificate() call in JS.
+ virtual void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) = 0;
+
+ // Asynchronously decrypts aBuffer in place. When the decryption is
+ // complete, GMPDecryptor should write the decrypted data back into the
+ // same GMPBuffer object and return it to Gecko by calling Decrypted(),
+ // with the GMPNoErr successcode. If decryption fails, call Decrypted()
+ // with a failure code, and an error event will fire on the media element.
+ // Note: When Decrypted() is called and aBuffer is passed back, aBuffer
+ // is deleted. Don't forget to call Decrypted(), as otherwise aBuffer's
+ // memory will leak!
+ virtual void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) = 0;
+
+ // Called when the decryption operations are complete.
+ // Do not call the GMPDecryptorCallback's functions after this is called.
+ virtual void DecryptingComplete() = 0;
+
+ virtual ~GMPDecryptor7() {}
+};
+
+#endif // GMP_DECRYPTION_h_
diff --git a/dom/media/gmp/gmp-api/gmp-entrypoints.h b/dom/media/gmp/gmp-api/gmp-entrypoints.h
new file mode 100644
index 000000000..214c9dbfc
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-entrypoints.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_ENTRYPOINTS_h_
+#define GMP_ENTRYPOINTS_h_
+
+#include "gmp-errors.h"
+#include "gmp-platform.h"
+
+/* C functions exposed by Gecko Media Plugin shared library. */
+
+// GMPInit
+// - Called once after plugin library is loaded, before GMPGetAPI or GMPShutdown are called.
+// - Called on main thread.
+// - 'aPlatformAPI' is a structure containing platform-provided APIs. It is valid until
+// 'GMPShutdown' is called. Owned and must be deleted by plugin.
+typedef GMPErr (*GMPInitFunc)(const GMPPlatformAPI* aPlatformAPI);
+
+// GMPGetAPI
+// - Called when host wants to use an API.
+// - Called on main thread.
+// - 'aAPIName' is a string indicating the API being requested. This should
+// match one of the GMP_API_* macros. Subsequent iterations of the GMP_APIs
+// may change the value of the GMP_API_* macros when ABI changes occur. So
+// make sure you compare aAPIName against the corresponding GMP_API_* macro!
+// - 'aHostAPI' is the host API which is specific to the API being requested
+// from the plugin. It is valid so long as the API object requested from the
+// plugin is valid. It is owned by the host, plugin should not attempt to delete.
+// May be null.
+// - 'aPluginAPI' is for returning the requested API. Destruction of the requsted
+// API object is defined by the API.
+typedef GMPErr (*GMPGetAPIFunc)(const char* aAPIName, void* aHostAPI, void** aPluginAPI);
+
+// GMPShutdown
+// - Called once before exiting process (unloading library).
+// - Called on main thread.
+typedef void (*GMPShutdownFunc)(void);
+
+// GMPSetNodeId
+// - Optional, not required to be implemented. Only useful for EME plugins.
+// - Called after GMPInit to set the device-bound origin-specific node id
+// that this GMP instance is running under.
+typedef void (*GMPSetNodeIdFunc)(const char* aNodeId, uint32_t aLength);
+
+#endif // GMP_ENTRYPOINTS_h_
diff --git a/dom/media/gmp/gmp-api/gmp-errors.h b/dom/media/gmp/gmp-api/gmp-errors.h
new file mode 100644
index 000000000..7f20e2a79
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-errors.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_ERRORS_h_
+#define GMP_ERRORS_h_
+
+typedef enum {
+ GMPNoErr = 0,
+ GMPGenericErr = 1,
+ GMPClosedErr = 2,
+ GMPAllocErr = 3,
+ GMPNotImplementedErr = 4,
+ GMPRecordInUse = 5,
+ GMPQuotaExceededErr = 6,
+ GMPDecodeErr = 7,
+ GMPEncodeErr = 8,
+ GMPNoKeyErr = 9,
+ GMPCryptoErr = 10,
+ GMPEndOfEnumeration = 11,
+ GMPInvalidArgErr = 12,
+ GMPAbortedErr = 13,
+ GMPRecordCorrupted = 14,
+ GMPLastErr // Placeholder, must be last. This enum's values must remain consecutive!
+} GMPErr;
+
+#define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
+#define GMP_FAILED(x) ((x) != GMPNoErr)
+
+#endif // GMP_ERRORS_h_
diff --git a/dom/media/gmp/gmp-api/gmp-platform.h b/dom/media/gmp/gmp-api/gmp-platform.h
new file mode 100644
index 000000000..f915050b3
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-platform.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_PLATFORM_h_
+#define GMP_PLATFORM_h_
+
+#include "gmp-errors.h"
+#include "gmp-storage.h"
+#include <stdint.h>
+
+/* Platform helper API. */
+
+class GMPTask {
+public:
+ virtual void Destroy() = 0; // Deletes object.
+ virtual ~GMPTask() {}
+ virtual void Run() = 0;
+};
+
+class GMPThread {
+public:
+ virtual ~GMPThread() {}
+ virtual void Post(GMPTask* aTask) = 0;
+ virtual void Join() = 0; // Deletes object after join completes.
+};
+
+// A re-entrant monitor; can be locked from the same thread multiple times.
+// Must be unlocked the same number of times it's locked.
+class GMPMutex {
+public:
+ virtual ~GMPMutex() {}
+ virtual void Acquire() = 0;
+ virtual void Release() = 0;
+ virtual void Destroy() = 0; // Deletes object.
+};
+
+// Time is defined as the number of milliseconds since the
+// Epoch (00:00:00 UTC, January 1, 1970).
+typedef int64_t GMPTimestamp;
+
+typedef GMPErr (*GMPCreateThreadPtr)(GMPThread** aThread);
+typedef GMPErr (*GMPRunOnMainThreadPtr)(GMPTask* aTask);
+typedef GMPErr (*GMPSyncRunOnMainThreadPtr)(GMPTask* aTask);
+typedef GMPErr (*GMPCreateMutexPtr)(GMPMutex** aMutex);
+
+// Call on main thread only.
+typedef GMPErr (*GMPCreateRecordPtr)(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+// Call on main thread only.
+typedef GMPErr (*GMPSetTimerOnMainThreadPtr)(GMPTask* aTask, int64_t aTimeoutMS);
+typedef GMPErr (*GMPGetCurrentTimePtr)(GMPTimestamp* aOutTime);
+
+typedef void (*RecvGMPRecordIteratorPtr)(GMPRecordIterator* aRecordIterator,
+ void* aUserArg,
+ GMPErr aStatus);
+
+// Creates a GMPCreateRecordIterator to enumerate the records in storage.
+// When the iterator is ready, the function at aRecvIteratorFunc
+// is called with the GMPRecordIterator as an argument. If the operation
+// fails, RecvGMPRecordIteratorPtr is called with a failure aStatus code.
+// The list that the iterator is covering is fixed when
+// GMPCreateRecordIterator is called, it is *not* updated when changes are
+// made to storage.
+// Iterator begins pointing at first record.
+// aUserArg is passed to the aRecvIteratorFunc upon completion.
+typedef GMPErr (*GMPCreateRecordIteratorPtr)(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+struct GMPPlatformAPI {
+ // Increment the version when things change. Can only add to the struct,
+ // do not change what already exists. Pointers to functions may be NULL
+ // when passed to plugins, but beware backwards compat implications of
+ // doing that.
+ uint16_t version; // Currently version 0
+
+ GMPCreateThreadPtr createthread;
+ GMPRunOnMainThreadPtr runonmainthread;
+ GMPSyncRunOnMainThreadPtr syncrunonmainthread;
+ GMPCreateMutexPtr createmutex;
+ GMPCreateRecordPtr createrecord;
+ GMPSetTimerOnMainThreadPtr settimer;
+ GMPGetCurrentTimePtr getcurrenttime;
+ GMPCreateRecordIteratorPtr getrecordenumerator;
+};
+
+#endif // GMP_PLATFORM_h_
diff --git a/dom/media/gmp/gmp-api/gmp-storage.h b/dom/media/gmp/gmp-api/gmp-storage.h
new file mode 100644
index 000000000..43ad12b01
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-storage.h
@@ -0,0 +1,141 @@
+/*
+* Copyright 2013, 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 GMP_STORAGE_h_
+#define GMP_STORAGE_h_
+
+#include "gmp-errors.h"
+#include <stdint.h>
+
+// Maximum size of a record, in bytes; 10 megabytes.
+#define GMP_MAX_RECORD_SIZE (10 * 1024 * 1024)
+
+// Maximum length of a record name in bytes.
+#define GMP_MAX_RECORD_NAME_SIZE 2000
+
+// Provides basic per-origin storage for CDMs. GMPRecord instances can be
+// retrieved by calling GMPPlatformAPI->openstorage. Multiple GMPRecords
+// with different names can be open at once, but a single record can only
+// be opened by one client at a time. This interface is asynchronous, with
+// results being returned via callbacks to the GMPRecordClient pointer
+// provided to the GMPPlatformAPI->openstorage call, on the main thread.
+//
+// Lifecycle: Once opened, the GMPRecord object remains allocated until
+// GMPRecord::Close() is called. If any GMPRecord function, either
+// synchronously or asynchronously through a GMPRecordClient callback,
+// returns an error, the GMP is responsible for calling Close() on the
+// GMPRecord to delete the GMPRecord object's memory. If your GMP does not
+// call Close(), the GMPRecord's memory will leak.
+class GMPRecord {
+public:
+
+ // Opens the record. Calls OpenComplete() once the record is open.
+ // Note: Only work when GMP is loading content from a webserver.
+ // Does not work for web pages on loaded from disk.
+ // Note: OpenComplete() is only called if this returns GMPNoErr.
+ virtual GMPErr Open() = 0;
+
+ // Reads the entire contents of the record, and calls
+ // GMPRecordClient::ReadComplete() once the operation is complete.
+ // Note: ReadComplete() is only called if this returns GMPNoErr.
+ virtual GMPErr Read() = 0;
+
+ // Writes aDataSize bytes of aData into the record, overwriting the
+ // contents of the record, truncating it to aDataSize length.
+ // Overwriting with 0 bytes "deletes" the record.
+ // Note: WriteComplete is only called if this returns GMPNoErr.
+ virtual GMPErr Write(const uint8_t* aData, uint32_t aDataSize) = 0;
+
+ // Closes a record, deletes the GMPRecord object. The GMPRecord object
+ // must not be used after this is called, request a new one with
+ // GMPPlatformAPI->openstorage to re-open this record. Cancels all
+ // callbacks.
+ virtual GMPErr Close() = 0;
+
+ virtual ~GMPRecord() {}
+};
+
+// Callback object that receives the results of GMPRecord calls. Callbacks
+// run asynchronously to the GMPRecord call, on the main thread.
+class GMPRecordClient {
+ public:
+
+ // Response to a GMPRecord::Open() call with the open |status|.
+ // aStatus values:
+ // - GMPNoErr - Record opened successfully. Record may be empty.
+ // - GMPRecordInUse - This record is in use by another client.
+ // - GMPGenericErr - Unspecified error.
+ // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
+ // call Close() on the GMPRecord to dispose of it.
+ virtual void OpenComplete(GMPErr aStatus) = 0;
+
+ // Response to a GMPRecord::Read() call, where aData is the record contents,
+ // of length aDataSize.
+ // aData is only valid for the duration of the call to ReadComplete.
+ // Copy it if you want to hang onto it!
+ // aStatus values:
+ // - GMPNoErr - Record contents read successfully, aDataSize 0 means record
+ // is empty.
+ // - GMPRecordInUse - There are other operations or clients in use on
+ // this record.
+ // - GMPGenericErr - Unspecified error.
+ // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
+ // call Close() on the GMPRecord to dispose of it.
+ virtual void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) = 0;
+
+ // Response to a GMPRecord::Write() call.
+ // - GMPNoErr - File contents written successfully.
+ // - GMPRecordInUse - There are other operations or clients in use on
+ // this record.
+ // - GMPGenericErr - Unspecified error.
+ // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
+ // call Close() on the GMPRecord to dispose of it.
+ virtual void WriteComplete(GMPErr aStatus) = 0;
+
+ virtual ~GMPRecordClient() {}
+};
+
+// Iterates over the records that are available. Note: this list maintains
+// a snapshot of the records that were present when the iterator was created.
+// Create by calling the GMPCreateRecordIteratorPtr function on the
+// GMPPlatformAPI struct.
+// Iteration is in alphabetical order.
+class GMPRecordIterator {
+public:
+ // Retrieve the name for the current record.
+ // aOutName is null terminated at character at index (*aOutNameLength).
+ // Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
+ // reached the end.
+ virtual GMPErr GetName(const char ** aOutName, uint32_t * aOutNameLength) = 0;
+
+ // Advance iteration to the next record.
+ // Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
+ // reached the end.
+ virtual GMPErr NextRecord() = 0;
+
+ // Signals to the GMP host that the GMP is finished with the
+ // GMPRecordIterator. GMPs must call this to release memory held by
+ // the GMPRecordIterator. Do not access the GMPRecordIterator pointer
+ // after calling this!
+ // Memory retrieved by GetName is *not* valid after calling Close()!
+ virtual void Close() = 0;
+
+ virtual ~GMPRecordIterator() {}
+};
+
+#endif // GMP_STORAGE_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-codec.h b/dom/media/gmp/gmp-api/gmp-video-codec.h
new file mode 100644
index 000000000..e068ab43d
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-codec.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_CODEC_h_
+#define GMP_VIDEO_CODEC_h_
+
+#include <stdint.h>
+#include <stddef.h>
+
+enum { kGMPPayloadNameSize = 32};
+enum { kGMPMaxSimulcastStreams = 4};
+
+enum GMPVideoCodecComplexity
+{
+ kGMPComplexityNormal = 0,
+ kGMPComplexityHigh = 1,
+ kGMPComplexityHigher = 2,
+ kGMPComplexityMax = 3,
+ kGMPComplexityInvalid // Should always be last
+};
+
+enum GMPVP8ResilienceMode {
+ kResilienceOff, // The stream produced by the encoder requires a
+ // recovery frame (typically a key frame) to be
+ // decodable after a packet loss.
+ kResilientStream, // A stream produced by the encoder is resilient to
+ // packet losses, but packets within a frame subsequent
+ // to a loss can't be decoded.
+ kResilientFrames, // Same as kResilientStream but with added resilience
+ // within a frame.
+ kResilienceInvalid // Should always be last.
+};
+
+// VP8 specific
+struct GMPVideoCodecVP8
+{
+ bool mPictureLossIndicationOn;
+ bool mFeedbackModeOn;
+ GMPVideoCodecComplexity mComplexity;
+ GMPVP8ResilienceMode mResilience;
+ uint32_t mNumberOfTemporalLayers;
+ bool mDenoisingOn;
+ bool mErrorConcealmentOn;
+ bool mAutomaticResizeOn;
+};
+
+// H264 specific
+
+// Needs to match a binary spec for this structure.
+// Note: the mSPS at the end of this structure is variable length.
+struct GMPVideoCodecH264AVCC
+{
+ uint8_t mVersion; // == 0x01
+ uint8_t mProfile; // these 3 are profile_level_id
+ uint8_t mConstraints;
+ uint8_t mLevel;
+ uint8_t mLengthSizeMinusOne; // lower 2 bits (== GMPBufferType-1). Top 6 reserved (1's)
+
+ // SPS/PPS will not generally be present for interactive use unless SDP
+ // parameter-sets are used.
+ uint8_t mNumSPS; // lower 5 bits; top 5 reserved (1's)
+
+ /*** uint8_t mSPS[]; (Not defined due to compiler warnings and warnings-as-errors ...) **/
+ // Following mNumSPS is a variable number of bytes, which is the SPS and PPS.
+ // Each SPS == 16 bit size, ("N"), then "N" bytes,
+ // then uint8_t mNumPPS, then each PPS == 16 bit size ("N"), then "N" bytes.
+};
+
+// Codec specific data for H.264 decoding/encoding.
+// Cast the "aCodecSpecific" parameter of GMPVideoDecoder::InitDecode() and
+// GMPVideoEncoder::InitEncode() to this structure.
+struct GMPVideoCodecH264
+{
+ uint8_t mPacketizationMode; // 0 or 1
+ struct GMPVideoCodecH264AVCC mAVCC; // holds a variable-sized struct GMPVideoCodecH264AVCC mAVCC;
+};
+
+enum GMPVideoCodecType
+{
+ kGMPVideoCodecVP8,
+
+ // Encoded frames are in AVCC format; NAL length field of 4 bytes, followed
+ // by frame data. May be multiple NALUs per sample. Codec specific extra data
+ // is the AVCC extra data (in AVCC format).
+ kGMPVideoCodecH264,
+ kGMPVideoCodecVP9,
+ kGMPVideoCodecInvalid // Should always be last.
+};
+
+// Simulcast is when the same stream is encoded multiple times with different
+// settings such as resolution.
+struct GMPSimulcastStream
+{
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mNumberOfTemporalLayers;
+ uint32_t mMaxBitrate; // kilobits/sec.
+ uint32_t mTargetBitrate; // kilobits/sec.
+ uint32_t mMinBitrate; // kilobits/sec.
+ uint32_t mQPMax; // minimum quality
+};
+
+enum GMPVideoCodecMode {
+ kGMPRealtimeVideo,
+ kGMPScreensharing,
+ kGMPStreamingVideo,
+ kGMPCodecModeInvalid // Should always be last.
+};
+
+enum GMPApiVersion {
+ kGMPVersion32 = 1, // leveraging that V32 had mCodecType first, and only supported H264
+ kGMPVersion33 = 33,
+};
+
+struct GMPVideoCodec
+{
+ uint32_t mGMPApiVersion;
+
+ GMPVideoCodecType mCodecType;
+ char mPLName[kGMPPayloadNameSize]; // Must be NULL-terminated!
+ uint32_t mPLType;
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+
+ uint32_t mStartBitrate; // kilobits/sec.
+ uint32_t mMaxBitrate; // kilobits/sec.
+ uint32_t mMinBitrate; // kilobits/sec.
+ uint32_t mMaxFramerate;
+
+ bool mFrameDroppingOn;
+ int32_t mKeyFrameInterval;
+
+ uint32_t mQPMax;
+ uint32_t mNumberOfSimulcastStreams;
+ GMPSimulcastStream mSimulcastStream[kGMPMaxSimulcastStreams];
+
+ GMPVideoCodecMode mMode;
+};
+
+// Either single encoded unit, or multiple units separated by 8/16/24/32
+// bit lengths, all with the same timestamp. Note there is no final 0-length
+// entry; one should check the overall end-of-buffer against where the next
+// length would be.
+enum GMPBufferType {
+ GMP_BufferSingle = 0,
+ GMP_BufferLength8,
+ GMP_BufferLength16,
+ GMP_BufferLength24,
+ GMP_BufferLength32,
+ GMP_BufferInvalid,
+};
+
+struct GMPCodecSpecificInfoGeneric {
+ uint8_t mSimulcastIdx;
+};
+
+struct GMPCodecSpecificInfoH264 {
+ uint8_t mSimulcastIdx;
+};
+
+// Note: if any pointers are added to this struct, it must be fitted
+// with a copy-constructor. See below.
+struct GMPCodecSpecificInfoVP8
+{
+ bool mHasReceivedSLI;
+ uint8_t mPictureIdSLI;
+ bool mHasReceivedRPSI;
+ uint64_t mPictureIdRPSI;
+ int16_t mPictureId; // negative value to skip pictureId
+ bool mNonReference;
+ uint8_t mSimulcastIdx;
+ uint8_t mTemporalIdx;
+ bool mLayerSync;
+ int32_t mTL0PicIdx; // negative value to skip tl0PicIdx
+ int8_t mKeyIdx; // negative value to skip keyIdx
+};
+
+union GMPCodecSpecificInfoUnion
+{
+ GMPCodecSpecificInfoGeneric mGeneric;
+ GMPCodecSpecificInfoVP8 mVP8;
+ GMPCodecSpecificInfoH264 mH264;
+};
+
+// Note: if any pointers are added to this struct or its sub-structs, it
+// must be fitted with a copy-constructor. This is because it is copied
+// in the copy-constructor of VCMEncodedFrame.
+struct GMPCodecSpecificInfo
+{
+ GMPVideoCodecType mCodecType;
+ GMPBufferType mBufferType;
+ GMPCodecSpecificInfoUnion mCodecSpecific;
+};
+
+#endif // GMP_VIDEO_CODEC_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-decode.h b/dom/media/gmp/gmp-api/gmp-video-decode.h
new file mode 100644
index 000000000..e07a7525e
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-decode.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_DECODE_h_
+#define GMP_VIDEO_DECODE_h_
+
+#include "gmp-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+#include <stdint.h>
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoDecoderCallback
+{
+public:
+ virtual ~GMPVideoDecoderCallback() {}
+
+ virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) = 0;
+
+ virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) = 0;
+
+ virtual void ReceivedDecodedFrame(const uint64_t aPictureId) = 0;
+
+ virtual void InputDataExhausted() = 0;
+
+ virtual void DrainComplete() = 0;
+
+ virtual void ResetComplete() = 0;
+
+ // Called when the decoder encounters a catestrophic error and cannot
+ // continue. Gecko will not send any more input for decoding.
+ virtual void Error(GMPErr aError) = 0;
+};
+
+#define GMP_API_VIDEO_DECODER "decode-video"
+
+// Video decoding for a single stream. A GMP may be asked to create multiple
+// decoders concurrently.
+//
+// API name macro: GMP_API_VIDEO_DECODER
+// Host API: GMPVideoHost
+//
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoDecoder
+{
+public:
+ virtual ~GMPVideoDecoder() {}
+
+ // - aCodecSettings: Details of decoder to create.
+ // - aCodecSpecific: codec specific data, cast to a GMPVideoCodecXXX struct
+ // to get codec specific config data.
+ // - aCodecSpecificLength: number of bytes in aCodecSpecific.
+ // - aCallback: Subclass should retain reference to it until DecodingComplete
+ // is called. Do not attempt to delete it, host retains ownership.
+ // aCoreCount: number of CPU cores.
+ virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount) = 0;
+
+ // Decode encoded frame (as a part of a video stream). The decoded frame
+ // will be returned to the user through the decode complete callback.
+ //
+ // - aInputFrame: Frame to decode. Call Destroy() on frame when it's decoded.
+ // - aMissingFrames: True if one or more frames have been lost since the
+ // previous decode call.
+ // - aCodecSpecificInfo : codec specific data, pointer to a
+ // GMPCodecSpecificInfo structure appropriate for
+ // this codec type.
+ // - aCodecSpecificInfoLength : number of bytes in aCodecSpecificInfo
+ // - renderTimeMs : System time to render in milliseconds. Only used by
+ // decoders with internal rendering.
+ virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs = -1) = 0;
+
+ // Reset decoder state and prepare for a new call to Decode(...).
+ // Flushes the decoder pipeline.
+ // The decoder should enqueue a task to run ResetComplete() on the main
+ // thread once the reset has finished.
+ virtual void Reset() = 0;
+
+ // Output decoded frames for any data in the pipeline, regardless of ordering.
+ // All remaining decoded frames should be immediately returned via callback.
+ // The decoder should enqueue a task to run DrainComplete() on the main
+ // thread once the reset has finished.
+ virtual void Drain() = 0;
+
+ // May free decoder memory.
+ virtual void DecodingComplete() = 0;
+};
+
+#endif // GMP_VIDEO_DECODE_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-encode.h b/dom/media/gmp/gmp-api/gmp-video-encode.h
new file mode 100644
index 000000000..5c9cde39c
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-encode.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_ENCODE_h_
+#define GMP_VIDEO_ENCODE_h_
+
+#include <vector>
+#include <stdint.h>
+
+#include "gmp-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoEncoderCallback
+{
+public:
+ virtual ~GMPVideoEncoderCallback() {}
+
+ virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength) = 0;
+
+ // Called when the encoder encounters a catestrophic error and cannot
+ // continue. Gecko will not send any more input for encoding.
+ virtual void Error(GMPErr aError) = 0;
+};
+
+#define GMP_API_VIDEO_ENCODER "encode-video"
+
+// Video encoding for a single stream. A GMP may be asked to create multiple
+// encoders concurrently.
+//
+// API name macro: GMP_API_VIDEO_ENCODER
+// Host API: GMPVideoHost
+//
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoEncoder
+{
+public:
+ virtual ~GMPVideoEncoder() {}
+
+ // Initialize the encoder with the information from the VideoCodec.
+ //
+ // Input:
+ // - codecSettings : Codec settings
+ // - aCodecSpecific : codec specific data, pointer to a
+ // GMPCodecSpecific structure appropriate for
+ // this codec type.
+ // - aCodecSpecificLength : number of bytes in aCodecSpecific
+ // - aCallback: Subclass should retain reference to it until EncodingComplete
+ // is called. Do not attempt to delete it, host retains ownership.
+ // - aNnumberOfCores : Number of cores available for the encoder
+ // - aMaxPayloadSize : The maximum size each payload is allowed
+ // to have. Usually MTU - overhead.
+ virtual void InitEncode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoEncoderCallback* aCallback,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize) = 0;
+
+ // Encode an I420 frame (as a part of a video stream). The encoded frame
+ // will be returned to the user through the encode complete callback.
+ //
+ // Input:
+ // - aInputFrame : Frame to be encoded
+ // - aCodecSpecificInfo : codec specific data, pointer to a
+ // GMPCodecSpecificInfo structure appropriate for
+ // this codec type.
+ // - aCodecSpecificInfoLength : number of bytes in aCodecSpecific
+ // - aFrameTypes : The frame type to encode
+ // - aFrameTypesLength : The number of elements in aFrameTypes array.
+ virtual void Encode(GMPVideoi420Frame* aInputFrame,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ const GMPVideoFrameType* aFrameTypes,
+ uint32_t aFrameTypesLength) = 0;
+
+ // Inform the encoder about the packet loss and round trip time on the
+ // network used to decide the best pattern and signaling.
+ //
+ // - packetLoss : Fraction lost (loss rate in percent =
+ // 100 * packetLoss / 255)
+ // - rtt : Round-trip time in milliseconds
+ virtual void SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
+
+ // Inform the encoder about the new target bit rate.
+ //
+ // - newBitRate : New target bit rate
+ // - frameRate : The target frame rate
+ virtual void SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
+
+ // Use this function to enable or disable periodic key frames. Can be useful for codecs
+ // which have other ways of stopping error propagation.
+ //
+ // - enable : Enable or disable periodic key frames
+ virtual void SetPeriodicKeyFrames(bool aEnable) = 0;
+
+ // May free Encoder memory.
+ virtual void EncodingComplete() = 0;
+};
+
+#endif // GMP_VIDEO_ENCODE_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h b/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h
new file mode 100644
index 000000000..76af7349f
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_ENCODED_h_
+#define GMP_VIDEO_FRAME_ENCODED_h_
+
+#include <stdint.h>
+#include "gmp-decryption.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-codec.h"
+
+enum GMPVideoFrameType
+{
+ kGMPKeyFrame = 0,
+ kGMPDeltaFrame = 1,
+ kGMPGoldenFrame = 2,
+ kGMPAltRefFrame = 3,
+ kGMPSkipFrame = 4,
+ kGMPVideoFrameInvalid = 5 // Must always be last.
+};
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPVideoEncodedFrame : public GMPVideoFrame
+{
+public:
+ // MAIN THREAD ONLY
+ virtual GMPErr CreateEmptyFrame(uint32_t aSize) = 0;
+ // MAIN THREAD ONLY
+ virtual GMPErr CopyFrame(const GMPVideoEncodedFrame& aVideoFrame) = 0;
+ virtual void SetEncodedWidth(uint32_t aEncodedWidth) = 0;
+ virtual uint32_t EncodedWidth() = 0;
+ virtual void SetEncodedHeight(uint32_t aEncodedHeight) = 0;
+ virtual uint32_t EncodedHeight() = 0;
+ // Microseconds
+ virtual void SetTimeStamp(uint64_t aTimeStamp) = 0;
+ virtual uint64_t TimeStamp() = 0;
+ // Set frame duration (microseconds)
+ // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration()
+ // depending on rounding to avoid having to track roundoff errors
+ // and dropped/missing frames(!) (which may leave a large gap)
+ virtual void SetDuration(uint64_t aDuration) = 0;
+ virtual uint64_t Duration() const = 0;
+ virtual void SetFrameType(GMPVideoFrameType aFrameType) = 0;
+ virtual GMPVideoFrameType FrameType() = 0;
+ virtual void SetAllocatedSize(uint32_t aNewSize) = 0;
+ virtual uint32_t AllocatedSize() = 0;
+ virtual void SetSize(uint32_t aSize) = 0;
+ virtual uint32_t Size() = 0;
+ virtual void SetCompleteFrame(bool aCompleteFrame) = 0;
+ virtual bool CompleteFrame() = 0;
+ virtual const uint8_t* Buffer() const = 0;
+ virtual uint8_t* Buffer() = 0;
+ virtual GMPBufferType BufferType() const = 0;
+ virtual void SetBufferType(GMPBufferType aBufferType) = 0;
+
+ // Get metadata describing how this frame is encrypted, or nullptr if the
+ // frame is not encrypted.
+ virtual const GMPEncryptedBufferMetadata* GetDecryptionData() const = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_ENCODED_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-frame-i420.h b/dom/media/gmp/gmp-api/gmp-video-frame-i420.h
new file mode 100644
index 000000000..14c2c33cd
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame-i420.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_I420_h_
+#define GMP_VIDEO_FRAME_I420_h_
+
+#include "gmp-errors.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-plane.h"
+
+#include <stdint.h>
+
+enum GMPPlaneType {
+ kGMPYPlane = 0,
+ kGMPUPlane = 1,
+ kGMPVPlane = 2,
+ kGMPNumOfPlanes = 3
+};
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPVideoi420Frame : public GMPVideoFrame {
+public:
+ // MAIN THREAD ONLY
+ // CreateEmptyFrame: Sets frame dimensions and allocates buffers based
+ // on set dimensions - height and plane stride.
+ // If required size is bigger than the allocated one, new buffers of adequate
+ // size will be allocated.
+ virtual GMPErr CreateEmptyFrame(int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) = 0;
+
+ // MAIN THREAD ONLY
+ // CreateFrame: Sets the frame's members and buffers. If required size is
+ // bigger than allocated one, new buffers of adequate size will be allocated.
+ virtual GMPErr CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+ int32_t aSize_u, const uint8_t* aBuffer_u,
+ int32_t aSize_v, const uint8_t* aBuffer_v,
+ int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) = 0;
+
+ // MAIN THREAD ONLY
+ // Copy frame: If required size is bigger than allocated one, new buffers of
+ // adequate size will be allocated.
+ virtual GMPErr CopyFrame(const GMPVideoi420Frame& aVideoFrame) = 0;
+
+ // Swap Frame.
+ virtual void SwapFrame(GMPVideoi420Frame* aVideoFrame) = 0;
+
+ // Get pointer to buffer per plane.
+ virtual uint8_t* Buffer(GMPPlaneType aType) = 0;
+
+ // Overloading with const.
+ virtual const uint8_t* Buffer(GMPPlaneType aType) const = 0;
+
+ // Get allocated size per plane.
+ virtual int32_t AllocatedSize(GMPPlaneType aType) const = 0;
+
+ // Get allocated stride per plane.
+ virtual int32_t Stride(GMPPlaneType aType) const = 0;
+
+ // Set frame width.
+ virtual GMPErr SetWidth(int32_t aWidth) = 0;
+
+ // Set frame height.
+ virtual GMPErr SetHeight(int32_t aHeight) = 0;
+
+ // Get frame width.
+ virtual int32_t Width() const = 0;
+
+ // Get frame height.
+ virtual int32_t Height() const = 0;
+
+ // Set frame timestamp (microseconds)
+ virtual void SetTimestamp(uint64_t aTimestamp) = 0;
+
+ // Get frame timestamp (microseconds)
+ virtual uint64_t Timestamp() const = 0;
+
+ // Set frame duration (microseconds)
+ // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration()
+ // depending on rounding to avoid having to track roundoff errors
+ // and dropped/missing frames(!) (which may leave a large gap)
+ virtual void SetDuration(uint64_t aDuration) = 0;
+
+ // Get frame duration (microseconds)
+ virtual uint64_t Duration() const = 0;
+
+ // Return true if underlying plane buffers are of zero size, false if not.
+ virtual bool IsZeroSize() const = 0;
+
+ // Reset underlying plane buffers sizes to 0. This function doesn't clear memory.
+ virtual void ResetSize() = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_I420_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-frame.h b/dom/media/gmp/gmp-api/gmp-video-frame.h
new file mode 100644
index 000000000..b3c9f53ab
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_h_
+#define GMP_VIDEO_FRAME_h_
+
+#include "gmp-video-plane.h"
+
+enum GMPVideoFrameFormat {
+ kGMPEncodedVideoFrame = 0,
+ kGMPI420VideoFrame = 1
+};
+
+class GMPVideoFrame {
+public:
+ virtual GMPVideoFrameFormat GetFrameFormat() = 0;
+ // MAIN THREAD ONLY IF OWNING PROCESS
+ virtual void Destroy() = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-host.h b/dom/media/gmp/gmp-api/gmp-video-host.h
new file mode 100644
index 000000000..cf20e3f46
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-host.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_HOST_h_
+#define GMP_VIDEO_HOST_h_
+
+#include "gmp-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+
+// This interface must be called on the main thread only.
+class GMPVideoHost
+{
+public:
+ // Construct various video API objects. Host does not retain reference,
+ // caller is owner and responsible for deleting.
+ // MAIN THREAD ONLY
+ virtual GMPErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) = 0;
+ virtual GMPErr CreatePlane(GMPPlane** aPlane) = 0;
+};
+
+#endif // GMP_VIDEO_HOST_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-plane.h b/dom/media/gmp/gmp-api/gmp-video-plane.h
new file mode 100644
index 000000000..777cc2495
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-plane.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_PLANE_h_
+#define GMP_VIDEO_PLANE_h_
+
+#include "gmp-errors.h"
+#include <stdint.h>
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPPlane {
+public:
+ // MAIN THREAD ONLY
+ // CreateEmptyPlane - set allocated size, actual plane size and stride:
+ // If current size is smaller than current size, then a buffer of sufficient
+ // size will be allocated.
+ virtual GMPErr CreateEmptyPlane(int32_t aAllocatedSize,
+ int32_t aStride,
+ int32_t aPlaneSize) = 0;
+
+ // MAIN THREAD ONLY
+ // Copy the entire plane data.
+ virtual GMPErr Copy(const GMPPlane& aPlane) = 0;
+
+ // MAIN THREAD ONLY
+ // Copy buffer: If current size is smaller
+ // than current size, then a buffer of sufficient size will be allocated.
+ virtual GMPErr Copy(int32_t aSize, int32_t aStride, const uint8_t* aBuffer) = 0;
+
+ // Swap plane data.
+ virtual void Swap(GMPPlane& aPlane) = 0;
+
+ // Get allocated size.
+ virtual int32_t AllocatedSize() const = 0;
+
+ // Set actual size.
+ virtual void ResetSize() = 0;
+
+ // Return true is plane size is zero, false if not.
+ virtual bool IsZeroSize() const = 0;
+
+ // Get stride value.
+ virtual int32_t Stride() const = 0;
+
+ // Return data pointer.
+ virtual const uint8_t* Buffer() const = 0;
+
+ // Overloading with non-const.
+ virtual uint8_t* Buffer() = 0;
+
+ // MAIN THREAD ONLY IF OWNING PROCESS
+ // Call this when done with the object. This may delete it.
+ virtual void Destroy() = 0;
+};
+
+#endif // GMP_VIDEO_PLANE_h_
diff --git a/dom/media/gmp/moz.build b/dom/media/gmp/moz.build
new file mode 100644
index 000000000..52dc673cd
--- /dev/null
+++ b/dom/media/gmp/moz.build
@@ -0,0 +1,155 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_MODULE = 'content_geckomediaplugins'
+
+XPIDL_SOURCES += [
+ 'mozIGeckoMediaPluginChromeService.idl',
+ 'mozIGeckoMediaPluginService.idl',
+]
+
+EXPORTS += [
+ 'gmp-api/gmp-async-shutdown.h',
+ 'gmp-api/gmp-audio-codec.h',
+ 'gmp-api/gmp-audio-decode.h',
+ 'gmp-api/gmp-audio-host.h',
+ 'gmp-api/gmp-audio-samples.h',
+ 'gmp-api/gmp-decryption.h',
+ 'gmp-api/gmp-entrypoints.h',
+ 'gmp-api/gmp-errors.h',
+ 'gmp-api/gmp-platform.h',
+ 'gmp-api/gmp-storage.h',
+ 'gmp-api/gmp-video-codec.h',
+ 'gmp-api/gmp-video-decode.h',
+ 'gmp-api/gmp-video-encode.h',
+ 'gmp-api/gmp-video-frame-encoded.h',
+ 'gmp-api/gmp-video-frame-i420.h',
+ 'gmp-api/gmp-video-frame.h',
+ 'gmp-api/gmp-video-host.h',
+ 'gmp-api/gmp-video-plane.h',
+ 'GMPAudioDecoderChild.h',
+ 'GMPAudioDecoderParent.h',
+ 'GMPAudioDecoderProxy.h',
+ 'GMPAudioHost.h',
+ 'GMPCallbackBase.h',
+ 'GMPChild.h',
+ 'GMPContentChild.h',
+ 'GMPContentParent.h',
+ 'GMPCrashHelperHolder.h',
+ 'GMPDecryptorChild.h',
+ 'GMPDecryptorParent.h',
+ 'GMPDecryptorProxy.h',
+ 'GMPEncryptedBufferDataImpl.h',
+ 'GMPLoader.h',
+ 'GMPMessageUtils.h',
+ 'GMPParent.h',
+ 'GMPPlatform.h',
+ 'GMPProcessChild.h',
+ 'GMPProcessParent.h',
+ 'GMPService.h',
+ 'GMPServiceChild.h',
+ 'GMPServiceParent.h',
+ 'GMPSharedMemManager.h',
+ 'GMPStorage.h',
+ 'GMPStorageChild.h',
+ 'GMPStorageParent.h',
+ 'GMPTimerChild.h',
+ 'GMPTimerParent.h',
+ 'GMPUtils.h',
+ 'GMPVideoDecoderChild.h',
+ 'GMPVideoDecoderParent.h',
+ 'GMPVideoDecoderProxy.h',
+ 'GMPVideoEncodedFrameImpl.h',
+ 'GMPVideoEncoderChild.h',
+ 'GMPVideoEncoderParent.h',
+ 'GMPVideoEncoderProxy.h',
+ 'GMPVideoHost.h',
+ 'GMPVideoi420FrameImpl.h',
+ 'GMPVideoPlaneImpl.h',
+]
+
+if CONFIG['MOZ_EME']:
+ EXPORTS += [
+ 'GMPCDMCallbackProxy.h',
+ 'GMPCDMProxy.h',
+ ]
+
+SOURCES += [
+ 'GMPAudioDecoderChild.cpp',
+ 'GMPAudioDecoderParent.cpp',
+ 'GMPAudioHost.cpp',
+ 'GMPChild.cpp',
+ 'GMPContentChild.cpp',
+ 'GMPContentParent.cpp',
+ 'GMPDecryptorChild.cpp',
+ 'GMPDecryptorParent.cpp',
+ 'GMPDiskStorage.cpp',
+ 'GMPEncryptedBufferDataImpl.cpp',
+ 'GMPMemoryStorage.cpp',
+ 'GMPParent.cpp',
+ 'GMPPlatform.cpp',
+ 'GMPProcessChild.cpp',
+ 'GMPProcessParent.cpp',
+ 'GMPService.cpp',
+ 'GMPServiceChild.cpp',
+ 'GMPServiceParent.cpp',
+ 'GMPSharedMemManager.cpp',
+ 'GMPStorageChild.cpp',
+ 'GMPStorageParent.cpp',
+ 'GMPTimerChild.cpp',
+ 'GMPTimerParent.cpp',
+ 'GMPUtils.cpp',
+ 'GMPVideoDecoderChild.cpp',
+ 'GMPVideoDecoderParent.cpp',
+ 'GMPVideoEncodedFrameImpl.cpp',
+ 'GMPVideoEncoderChild.cpp',
+ 'GMPVideoEncoderParent.cpp',
+ 'GMPVideoHost.cpp',
+ 'GMPVideoi420FrameImpl.cpp',
+ 'GMPVideoPlaneImpl.cpp',
+]
+
+if CONFIG['MOZ_EME']:
+ SOURCES += [
+ 'GMPCDMCallbackProxy.cpp',
+ 'GMPCDMProxy.cpp',
+ ]
+
+DIRS += ['rlz']
+
+if CONFIG['MOZ_EME']:
+ DIRS += ['widevine-adapter']
+
+IPDL_SOURCES += [
+ 'GMPTypes.ipdlh',
+ 'PGMP.ipdl',
+ 'PGMPAudioDecoder.ipdl',
+ 'PGMPContent.ipdl',
+ 'PGMPDecryptor.ipdl',
+ 'PGMPService.ipdl',
+ 'PGMPStorage.ipdl',
+ 'PGMPTimer.ipdl',
+ 'PGMPVideoDecoder.ipdl',
+ 'PGMPVideoEncoder.ipdl',
+]
+
+if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
+ NO_VISIBILITY_FLAGS = True
+
+# comment this out to use Unsafe Shmem for more performance
+DEFINES['GMP_SAFE_SHMEM'] = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/xpcom/base',
+ '/xpcom/build',
+ '/xpcom/threads',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl b/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl
new file mode 100644
index 000000000..9e3286485
--- /dev/null
+++ b/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIFile.idl"
+
+[scriptable, uuid(32d35d21-181f-4630-8caa-a431e2ebad72)]
+interface mozIGeckoMediaPluginChromeService : nsISupports
+{
+ /**
+ * Add a directory to scan for gecko media plugins.
+ * @note Main-thread API.
+ */
+ void addPluginDirectory(in AString directory);
+
+ /**
+ * Remove a directory for gecko media plugins.
+ * @note Main-thread API.
+ */
+ void removePluginDirectory(in AString directory);
+
+ /**
+ * Remove a directory for gecko media plugins and delete it from disk.
+ * If |defer| is true, wait until the plugin is unused before removing.
+ * @note Main-thread API.
+ */
+ void removeAndDeletePluginDirectory(in AString directory,
+ [optional] in bool defer);
+
+ /**
+ * Clears storage data associated with the site and the originAttributes
+ * pattern in JSON format.
+ */
+ void forgetThisSite(in AString site,
+ in DOMString aPattern);
+
+ /**
+ * Returns true if the given node id is allowed to store things
+ * persistently on disk. Private Browsing and local content are not
+ * allowed to store persistent data.
+ */
+ bool isPersistentStorageAllowed(in ACString nodeId);
+
+ /**
+ * Returns the directory to use as the base for storing data about GMPs.
+ */
+ nsIFile getStorageDir();
+
+};
diff --git a/dom/media/gmp/mozIGeckoMediaPluginService.idl b/dom/media/gmp/mozIGeckoMediaPluginService.idl
new file mode 100644
index 000000000..388c58142
--- /dev/null
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIThread.idl"
+
+%{C++
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "nsStringGlue.h"
+class GMPAudioDecoderProxy;
+class GMPDecryptorProxy;
+class GMPVideoDecoderProxy;
+class GMPVideoEncoderProxy;
+class GMPVideoHost;
+class GMPCrashHelper;
+
+template<class T>
+class GMPGetterCallback
+{
+public:
+ GMPGetterCallback() { MOZ_COUNT_CTOR(GMPGetterCallback<T>); }
+ virtual ~GMPGetterCallback() { MOZ_COUNT_DTOR(GMPGetterCallback<T>); }
+ virtual void Done(T*) = 0;
+};
+template<class T>
+class GMPVideoGetterCallback
+{
+public:
+ GMPVideoGetterCallback() { MOZ_COUNT_CTOR(GMPVideoGetterCallback<T>); }
+ virtual ~GMPVideoGetterCallback() { MOZ_COUNT_DTOR(GMPVideoGetterCallback<T>); }
+ virtual void Done(T*, GMPVideoHost*) = 0;
+};
+typedef GMPGetterCallback<GMPDecryptorProxy> GetGMPDecryptorCallback;
+typedef GMPGetterCallback<GMPAudioDecoderProxy> GetGMPAudioDecoderCallback;
+typedef GMPVideoGetterCallback<GMPVideoDecoderProxy> GetGMPVideoDecoderCallback;
+typedef GMPVideoGetterCallback<GMPVideoEncoderProxy> GetGMPVideoEncoderCallback;
+class GetNodeIdCallback
+{
+public:
+ GetNodeIdCallback() { MOZ_COUNT_CTOR(GetNodeIdCallback); }
+ virtual ~GetNodeIdCallback() { MOZ_COUNT_DTOR(GetNodeIdCallback); }
+ virtual void Done(nsresult aResult, const nsACString& aNodeId) = 0;
+};
+%}
+
+[ptr] native TagArray(nsTArray<nsCString>);
+native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&);
+native GetGMPAudioDecoderCallback(mozilla::UniquePtr<GetGMPAudioDecoderCallback>&&);
+native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&);
+native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&);
+native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&);
+native GMPCrashHelperPtr(GMPCrashHelper*);
+
+[scriptable, uuid(44d362ae-937a-4803-bee6-f2512a0149d1)]
+interface mozIGeckoMediaPluginService : nsISupports
+{
+
+ /**
+ * The GMP thread. Callable from any thread.
+ */
+ readonly attribute nsIThread thread;
+
+ /**
+ * Run through windows registered registered for pluginId, sending
+ * 'PluginCrashed' chrome-only event
+ */
+ void RunPluginCrashCallbacks(in unsigned long pluginId, in ACString pluginName);
+
+ /**
+ * Get a plugin that supports the specified tags.
+ * Callable on any thread
+ */
+ [noscript]
+ boolean hasPluginForAPI(in ACString api, in TagArray tags);
+
+ /**
+ * Get a video decoder that supports the specified tags.
+ * The array of tags should at least contain a codec tag, and optionally
+ * other tags such as for EME keysystem.
+ * Callable only on GMP thread.
+ * This is an asynchronous operation, the Done method of the callback object
+ * will be called on the GMP thread with the result (which might be null in
+ * the case of failure). This method always takes ownership of the callback
+ * object, but if this method returns an error then the Done method of the
+ * callback object will not be called at all.
+ */
+ [noscript]
+ void getGMPVideoDecoder(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ [optional] in ACString nodeId,
+ in GetGMPVideoDecoderCallback callback);
+
+ /**
+ * Gets a video decoder as per getGMPVideoDecoder, except it is linked to
+ * with a corresponding GMPDecryptor via the decryptor's ID.
+ * This is a temporary measure, until we can implement a Chromium CDM
+ * GMP protocol which does both decryption and decoding.
+ */
+ [noscript]
+ void getDecryptingGMPVideoDecoder(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ in ACString nodeId,
+ in GetGMPVideoDecoderCallback callback,
+ in uint32_t decryptorId);
+
+ /**
+ * Get a video encoder that supports the specified tags.
+ * The array of tags should at least contain a codec tag, and optionally
+ * other tags.
+ * Callable only on GMP thread.
+ * This is an asynchronous operation, the Done method of the callback object
+ * will be called on the GMP thread with the result (which might be null in
+ * the case of failure). This method always takes ownership of the callback
+ * object, but if this method returns an error then the Done method of the
+ * callback object will not be called at all.
+ */
+ [noscript]
+ void getGMPVideoEncoder(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ [optional] in ACString nodeId,
+ in GetGMPVideoEncoderCallback callback);
+
+ /**
+ * Returns an audio decoder that supports the specified tags.
+ * The array of tags should at least contain a codec tag, and optionally
+ * other tags such as for EME keysystem.
+ * Callable only on GMP thread.
+ * This is an asynchronous operation, the Done method of the callback object
+ * will be called on the GMP thread with the result (which might be null in
+ * the case of failure). This method always takes ownership of the callback
+ * object, but if this method returns an error then the Done method of the
+ * callback object will not be called at all.
+ */
+ [noscript]
+ void getGMPAudioDecoder(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ [optional] in ACString nodeId,
+ in GetGMPAudioDecoderCallback callback);
+
+ /**
+ * Returns a decryption session manager that supports the specified tags.
+ * The array of tags should at least contain a key system tag, and optionally
+ * other tags.
+ * Callable only on GMP thread.
+ * This is an asynchronous operation, the Done method of the callback object
+ * will be called on the GMP thread with the result (which might be null in
+ * the case of failure). This method always takes ownership of the callback
+ * object, but if this method returns an error then the Done method of the
+ * callback object will not be called at all.
+ */
+ [noscript]
+ void getGMPDecryptor(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ in ACString nodeId,
+ in GetGMPDecryptorCallback callback);
+
+ /**
+ * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
+ */
+ [noscript]
+ void getNodeId(in AString origin,
+ in AString topLevelOrigin,
+ in AString gmpName,
+ in bool inPrivateBrowsingMode,
+ in GetNodeIdCallback callback);
+};
diff --git a/dom/media/gmp/rlz/COPYING b/dom/media/gmp/rlz/COPYING
new file mode 100644
index 000000000..b89042ace
--- /dev/null
+++ b/dom/media/gmp/rlz/COPYING
@@ -0,0 +1,14 @@
+Copyright 2010 Google Inc.
+
+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.
+
diff --git a/dom/media/gmp/rlz/GMPDeviceBinding.cpp b/dom/media/gmp/rlz/GMPDeviceBinding.cpp
new file mode 100644
index 000000000..0871d2e4e
--- /dev/null
+++ b/dom/media/gmp/rlz/GMPDeviceBinding.cpp
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPDeviceBinding.h"
+#include "mozilla/Attributes.h"
+#include "prenv.h"
+
+#include <string>
+
+#ifdef XP_WIN
+#include "windows.h"
+#endif
+
+#if defined(HASH_NODE_ID_WITH_DEVICE_ID)
+
+// In order to provide EME plugins with a "device binding" capability,
+// in the parent we generate and store some random bytes as salt for every
+// (origin, urlBarOrigin) pair that uses EME. We store these bytes so
+// that every time we revisit the same origin we get the same salt.
+// We send this salt to the child on startup. The child collects some
+// device specific data and munges that with the salt to create the
+// "node id" that we expose to EME plugins. It then overwrites the device
+// specific data, and activates the sandbox.
+
+#include "rlz/lib/machine_id.h"
+#include "rlz/lib/string_utils.h"
+#include "sha256.h"
+
+#ifdef XP_WIN
+#include "windows.h"
+#endif
+
+
+#endif // HASH_NODE_ID_WITH_DEVICE_ID
+
+namespace mozilla {
+namespace gmp {
+
+#if defined(XP_WIN) && defined(HASH_NODE_ID_WITH_DEVICE_ID)
+MOZ_NEVER_INLINE
+static bool
+GetStackAfterCurrentFrame(uint8_t** aOutTop, uint8_t** aOutBottom)
+{
+ // "Top" of the free space on the stack is directly after the memory
+ // holding our return address.
+ uint8_t* top = (uint8_t*)_AddressOfReturnAddress();
+
+ // Look down the stack until we find the guard page...
+ MEMORY_BASIC_INFORMATION memInfo = {0};
+ uint8_t* bottom = top;
+ while (1) {
+ if (!VirtualQuery(bottom, &memInfo, sizeof(memInfo))) {
+ return false;
+ }
+ if ((memInfo.Protect & PAGE_GUARD) == PAGE_GUARD) {
+ bottom = (uint8_t*)memInfo.BaseAddress + memInfo.RegionSize;
+#ifdef DEBUG
+ if (!VirtualQuery(bottom, &memInfo, sizeof(memInfo))) {
+ return false;
+ }
+ assert(!(memInfo.Protect & PAGE_GUARD)); // Should have found boundary.
+#endif
+ break;
+ } else if (memInfo.State != MEM_COMMIT ||
+ (memInfo.AllocationProtect & PAGE_READWRITE) != PAGE_READWRITE) {
+ return false;
+ }
+ bottom = (uint8_t*)memInfo.BaseAddress - 1;
+ }
+ *aOutTop = top;
+ *aOutBottom = bottom;
+ return true;
+}
+#endif
+
+#ifdef HASH_NODE_ID_WITH_DEVICE_ID
+static void SecureMemset(void* start, uint8_t value, size_t size)
+{
+ // Inline instructions equivalent to RtlSecureZeroMemory().
+ for (size_t i = 0; i < size; ++i) {
+ volatile uint8_t* p = static_cast<volatile uint8_t*>(start) + i;
+ *p = value;
+ }
+}
+#endif
+
+bool
+CalculateGMPDeviceId(char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ std::string& aOutNodeId)
+{
+#ifdef HASH_NODE_ID_WITH_DEVICE_ID
+ if (aOriginSaltLen > 0) {
+ std::vector<uint8_t> deviceId;
+ int volumeId;
+ if (!rlz_lib::GetRawMachineId(&deviceId, &volumeId)) {
+ return false;
+ }
+
+ SHA256Context ctx;
+ SHA256_Begin(&ctx);
+ SHA256_Update(&ctx, (const uint8_t*)aOriginSalt, aOriginSaltLen);
+ SHA256_Update(&ctx, deviceId.data(), deviceId.size());
+ SHA256_Update(&ctx, (const uint8_t*)&volumeId, sizeof(int));
+ uint8_t digest[SHA256_LENGTH] = {0};
+ unsigned int digestLen = 0;
+ SHA256_End(&ctx, digest, &digestLen, SHA256_LENGTH);
+
+ // Overwrite all data involved in calculation as it could potentially
+ // identify the user, so there's no chance a GMP can read it and use
+ // it for identity tracking.
+ SecureMemset(&ctx, 0, sizeof(ctx));
+ SecureMemset(aOriginSalt, 0, aOriginSaltLen);
+ SecureMemset(&volumeId, 0, sizeof(volumeId));
+ SecureMemset(deviceId.data(), '*', deviceId.size());
+ deviceId.clear();
+
+ if (!rlz_lib::BytesToString(digest, SHA256_LENGTH, &aOutNodeId)) {
+ return false;
+ }
+
+ if (!PR_GetEnv("MOZ_GMP_DISABLE_NODE_ID_CLEANUP")) {
+ // We've successfully bound the origin salt to node id.
+ // rlz_lib::GetRawMachineId and/or the system functions it
+ // called could have left user identifiable data on the stack,
+ // so carefully zero the stack down to the guard page.
+ uint8_t* top;
+ uint8_t* bottom;
+ if (!GetStackAfterCurrentFrame(&top, &bottom)) {
+ return false;
+ }
+ assert(top >= bottom);
+ // Inline instructions equivalent to RtlSecureZeroMemory().
+ // We can't just use RtlSecureZeroMemory here directly, as in debug
+ // builds, RtlSecureZeroMemory() can't be inlined, and the stack
+ // memory it uses would get wiped by itself running, causing crashes.
+ for (volatile uint8_t* p = (volatile uint8_t*)bottom; p < top; p++) {
+ *p = 0;
+ }
+ }
+ } else
+#endif
+ {
+ aOutNodeId = std::string(aOriginSalt, aOriginSalt + aOriginSaltLen);
+ }
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/rlz/GMPDeviceBinding.h b/dom/media/gmp/rlz/GMPDeviceBinding.h
new file mode 100644
index 000000000..ee1664466
--- /dev/null
+++ b/dom/media/gmp/rlz/GMPDeviceBinding.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMP_DEVICE_BINDING_h_
+#define GMP_DEVICE_BINDING_h_
+
+#include <string>
+
+namespace mozilla {
+namespace gmp {
+
+bool CalculateGMPDeviceId(char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ std::string& aOutNodeId);
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMP_DEVICE_BINDING_h_
diff --git a/dom/media/gmp/rlz/README.mozilla b/dom/media/gmp/rlz/README.mozilla
new file mode 100644
index 000000000..fffc5deb5
--- /dev/null
+++ b/dom/media/gmp/rlz/README.mozilla
@@ -0,0 +1,6 @@
+Code taken from rlz project: https://code.google.com/p/rlz/
+
+Revision: 134, then with unused code stripped out.
+
+Note: base/ contains wrappers/dummies to provide implementations of the
+Chromium APIs that this code relies upon.
diff --git a/dom/media/gmp/rlz/lib/assert.h b/dom/media/gmp/rlz/lib/assert.h
new file mode 100644
index 000000000..68737b1e2
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/assert.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FAKE_ASSERT_H_
+#define FAKE_ASSERT_H_
+
+#include <assert.h>
+
+#define ASSERT_STRING(x) { assert(false); }
+#define VERIFY(x) { assert(x); };
+
+#endif
diff --git a/dom/media/gmp/rlz/lib/machine_id.h b/dom/media/gmp/rlz/lib/machine_id.h
new file mode 100644
index 000000000..67661cfce
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/machine_id.h
@@ -0,0 +1,19 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+// Use of this source code is governed by an Apache-style license that can be
+// found in the COPYING file.
+
+#ifndef RLZ_LIB_MACHINE_ID_H_
+#define RLZ_LIB_MACHINE_ID_H_
+
+#include <vector>
+
+namespace rlz_lib {
+
+// Retrieves a raw machine identifier string and a machine-specific
+// 4 byte value. GetMachineId() will SHA1 |data|, append |more_data|, compute
+// the Crc8 of that, and return a hex-encoded string of that data.
+bool GetRawMachineId(std::vector<uint8_t>* data, int* more_data);
+
+} // namespace rlz_lib
+
+#endif // RLZ_LIB_MACHINE_ID_H_
diff --git a/dom/media/gmp/rlz/lib/string_utils.cc b/dom/media/gmp/rlz/lib/string_utils.cc
new file mode 100644
index 000000000..b6a71acdb
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/string_utils.cc
@@ -0,0 +1,34 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Use of this source code is governed by an Apache-style license that can be
+// found in the COPYING file.
+//
+// String manipulation functions used in the RLZ library.
+
+#include "rlz/lib/string_utils.h"
+
+namespace rlz_lib {
+
+bool BytesToString(const unsigned char* data,
+ int data_len,
+ std::string* string) {
+ if (!string)
+ return false;
+
+ string->clear();
+ if (data_len < 1 || !data)
+ return false;
+
+ static const char kHex[] = "0123456789ABCDEF";
+
+ // Fix the buffer size to begin with to avoid repeated re-allocation.
+ string->resize(data_len * 2);
+ int index = data_len;
+ while (index--) {
+ string->at(2 * index) = kHex[data[index] >> 4]; // high digit
+ string->at(2 * index + 1) = kHex[data[index] & 0x0F]; // low digit
+ }
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/dom/media/gmp/rlz/lib/string_utils.h b/dom/media/gmp/rlz/lib/string_utils.h
new file mode 100644
index 000000000..294f1b2d9
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/string_utils.h
@@ -0,0 +1,20 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Use of this source code is governed by an Apache-style license that can be
+// found in the COPYING file.
+//
+// String manipulation functions used in the RLZ library.
+
+#ifndef RLZ_LIB_STRING_UTILS_H_
+#define RLZ_LIB_STRING_UTILS_H_
+
+#include <string>
+
+namespace rlz_lib {
+
+bool BytesToString(const unsigned char* data,
+ int data_len,
+ std::string* string);
+
+}; // namespace
+
+#endif // RLZ_LIB_STRING_UTILS_H_
diff --git a/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc b/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc
new file mode 100644
index 000000000..2bea0f55f
--- /dev/null
+++ b/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc
@@ -0,0 +1,320 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/network/IOEthernetController.h>
+#include <IOKit/network/IOEthernetInterface.h>
+#include <IOKit/network/IONetworkInterface.h>
+#include <vector>
+#include <string>
+
+// Note: The original machine_id_mac.cc code is in namespace rlz_lib below.
+// It depends on some external files, which would bring in a log of Chromium
+// code if imported as well.
+// Instead only the necessary code has been extracted from the relevant files,
+// and further combined and reduced to limit the maintenance burden.
+
+// [Extracted from base/logging.h]
+#define DCHECK assert
+
+namespace base {
+
+// [Extracted from base/mac/scoped_typeref.h and base/mac/scoped_cftyperef.h]
+template<typename T>
+class ScopedCFTypeRef {
+ public:
+ typedef T element_type;
+
+ explicit ScopedCFTypeRef(T object)
+ : object_(object) {
+ }
+
+ ScopedCFTypeRef(const ScopedCFTypeRef<T>& that) = delete;
+ ScopedCFTypeRef(ScopedCFTypeRef<T>&& that) = delete;
+
+ ~ScopedCFTypeRef() {
+ if (object_)
+ CFRelease(object_);
+ }
+
+ ScopedCFTypeRef& operator=(const ScopedCFTypeRef<T>& that) = delete;
+ ScopedCFTypeRef& operator=(ScopedCFTypeRef<T>&& that) = delete;
+
+ operator T() const {
+ return object_;
+ }
+
+ // ScopedCFTypeRef<>::release() is like scoped_ptr<>::release. It is NOT
+ // a wrapper for CFRelease().
+ T release() {
+ T temp = object_;
+ object_ = NULL;
+ return temp;
+ }
+
+ private:
+ T object_;
+};
+
+namespace mac {
+
+// [Extracted from base/mac/scoped_ioobject.h]
+// Just like ScopedCFTypeRef but for io_object_t and subclasses.
+template<typename IOT>
+class ScopedIOObject {
+ public:
+ typedef IOT element_type;
+
+ explicit ScopedIOObject(IOT object = IO_OBJECT_NULL)
+ : object_(object) {
+ }
+
+ ~ScopedIOObject() {
+ if (object_)
+ IOObjectRelease(object_);
+ }
+
+ ScopedIOObject(const ScopedIOObject&) = delete;
+ void operator=(const ScopedIOObject&) = delete;
+
+ void reset(IOT object = IO_OBJECT_NULL) {
+ if (object_)
+ IOObjectRelease(object_);
+ object_ = object;
+ }
+
+ operator IOT() const {
+ return object_;
+ }
+
+ private:
+ IOT object_;
+};
+
+// [Extracted from base/mac/foundation_util.h]
+template<typename T>
+T CFCast(const CFTypeRef& cf_val);
+
+template<>
+CFDataRef
+CFCast<CFDataRef>(const CFTypeRef& cf_val) {
+ if (cf_val == NULL) {
+ return NULL;
+ }
+ if (CFGetTypeID(cf_val) == CFDataGetTypeID()) {
+ return (CFDataRef)(cf_val);
+ }
+ return NULL;
+}
+
+template<>
+CFStringRef
+CFCast<CFStringRef>(const CFTypeRef& cf_val) {
+ if (cf_val == NULL) {
+ return NULL;
+ }
+ if (CFGetTypeID(cf_val) == CFStringGetTypeID()) {
+ return (CFStringRef)(cf_val);
+ }
+ return NULL;
+}
+
+} // namespace mac
+
+// [Extracted from base/strings/sys_string_conversions_mac.mm]
+static const CFStringEncoding kNarrowStringEncoding = kCFStringEncodingUTF8;
+
+template<typename StringType>
+static StringType CFStringToSTLStringWithEncodingT(CFStringRef cfstring,
+ CFStringEncoding encoding) {
+ CFIndex length = CFStringGetLength(cfstring);
+ if (length == 0)
+ return StringType();
+
+ CFRange whole_string = CFRangeMake(0, length);
+ CFIndex out_size;
+ CFIndex converted = CFStringGetBytes(cfstring,
+ whole_string,
+ encoding,
+ 0, // lossByte
+ false, // isExternalRepresentation
+ NULL, // buffer
+ 0, // maxBufLen
+ &out_size);
+ if (converted == 0 || out_size == 0)
+ return StringType();
+
+ // out_size is the number of UInt8-sized units needed in the destination.
+ // A buffer allocated as UInt8 units might not be properly aligned to
+ // contain elements of StringType::value_type. Use a container for the
+ // proper value_type, and convert out_size by figuring the number of
+ // value_type elements per UInt8. Leave room for a NUL terminator.
+ typename StringType::size_type elements =
+ out_size * sizeof(UInt8) / sizeof(typename StringType::value_type) + 1;
+
+ std::vector<typename StringType::value_type> out_buffer(elements);
+ converted = CFStringGetBytes(cfstring,
+ whole_string,
+ encoding,
+ 0, // lossByte
+ false, // isExternalRepresentation
+ reinterpret_cast<UInt8*>(&out_buffer[0]),
+ out_size,
+ NULL); // usedBufLen
+ if (converted == 0)
+ return StringType();
+
+ out_buffer[elements - 1] = '\0';
+ return StringType(&out_buffer[0], elements - 1);
+}
+
+std::string SysCFStringRefToUTF8(CFStringRef ref)
+{
+ return CFStringToSTLStringWithEncodingT<std::string>(ref,
+ kNarrowStringEncoding);
+}
+
+} // namespace base
+
+
+namespace rlz_lib {
+
+namespace {
+
+// See http://developer.apple.com/library/mac/#technotes/tn1103/_index.html
+
+// The caller is responsible for freeing |matching_services|.
+bool FindEthernetInterfaces(io_iterator_t* matching_services) {
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> matching_dict(
+ IOServiceMatching(kIOEthernetInterfaceClass));
+ if (!matching_dict)
+ return false;
+
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> primary_interface(
+ CFDictionaryCreateMutable(kCFAllocatorDefault,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+ if (!primary_interface)
+ return false;
+
+ CFDictionarySetValue(
+ primary_interface, CFSTR(kIOPrimaryInterface), kCFBooleanTrue);
+ CFDictionarySetValue(
+ matching_dict, CFSTR(kIOPropertyMatchKey), primary_interface);
+
+ kern_return_t kern_result = IOServiceGetMatchingServices(
+ kIOMasterPortDefault, matching_dict.release(), matching_services);
+
+ return kern_result == KERN_SUCCESS;
+}
+
+bool GetMACAddressFromIterator(io_iterator_t primary_interface_iterator,
+ uint8_t* buffer, size_t buffer_size) {
+ if (buffer_size < kIOEthernetAddressSize)
+ return false;
+
+ bool success = false;
+
+ bzero(buffer, buffer_size);
+ base::mac::ScopedIOObject<io_object_t> primary_interface;
+ while (primary_interface.reset(IOIteratorNext(primary_interface_iterator)),
+ primary_interface) {
+ io_object_t primary_interface_parent;
+ kern_return_t kern_result = IORegistryEntryGetParentEntry(
+ primary_interface, kIOServicePlane, &primary_interface_parent);
+ base::mac::ScopedIOObject<io_object_t> primary_interface_parent_deleter(
+ primary_interface_parent);
+ success = kern_result == KERN_SUCCESS;
+
+ if (!success)
+ continue;
+
+ base::ScopedCFTypeRef<CFTypeRef> mac_data(
+ IORegistryEntryCreateCFProperty(primary_interface_parent,
+ CFSTR(kIOMACAddress),
+ kCFAllocatorDefault,
+ 0));
+ CFDataRef mac_data_data = base::mac::CFCast<CFDataRef>(mac_data);
+ if (mac_data_data) {
+ CFDataGetBytes(
+ mac_data_data, CFRangeMake(0, kIOEthernetAddressSize), buffer);
+ }
+ }
+
+ return success;
+}
+
+bool GetMacAddress(unsigned char* buffer, size_t size) {
+ io_iterator_t primary_interface_iterator;
+ if (!FindEthernetInterfaces(&primary_interface_iterator))
+ return false;
+ bool result = GetMACAddressFromIterator(
+ primary_interface_iterator, buffer, size);
+ IOObjectRelease(primary_interface_iterator);
+ return result;
+}
+
+CFStringRef CopySerialNumber() {
+ base::mac::ScopedIOObject<io_service_t> expert_device(
+ IOServiceGetMatchingService(kIOMasterPortDefault,
+ IOServiceMatching("IOPlatformExpertDevice")));
+ if (!expert_device)
+ return NULL;
+
+ base::ScopedCFTypeRef<CFTypeRef> serial_number(
+ IORegistryEntryCreateCFProperty(expert_device,
+ CFSTR(kIOPlatformSerialNumberKey),
+ kCFAllocatorDefault,
+ 0));
+ CFStringRef serial_number_cfstring =
+ base::mac::CFCast<CFStringRef>(serial_number.release());
+ if (!serial_number_cfstring)
+ return NULL;
+
+ return serial_number_cfstring;
+}
+
+} // namespace
+
+bool GetRawMachineId(std::vector<uint8_t>* data, int* more_data) {
+ uint8_t mac_address[kIOEthernetAddressSize];
+
+ std::string id;
+ if (GetMacAddress(mac_address, sizeof(mac_address))) {
+ id += "mac:";
+ static const char hex[] =
+ { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ for (int i = 0; i < kIOEthernetAddressSize; ++i) {
+ uint8_t byte = mac_address[i];
+ id += hex[byte >> 4];
+ id += hex[byte & 0xF];
+ }
+ }
+
+ // A MAC address is enough to uniquely identify a machine, but it's only 6
+ // bytes, 3 of which are manufacturer-determined. To make brute-forcing the
+ // SHA1 of this harder, also append the system's serial number.
+ CFStringRef serial = CopySerialNumber();
+ if (serial) {
+ if (!id.empty()) {
+ id += ' ';
+ }
+ id += "serial:";
+ id += base::SysCFStringRefToUTF8(serial);
+ CFRelease(serial);
+ }
+
+ // Get the contents of the string 'id' as a bunch of bytes.
+ data->assign(&id[0], &id[id.size()]);
+
+ // On windows, this is set to the volume id. Since it's not scrambled before
+ // being sent, just set it to 1.
+ *more_data = 1;
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/dom/media/gmp/rlz/moz.build b/dom/media/gmp/rlz/moz.build
new file mode 100644
index 000000000..aa6f5fece
--- /dev/null
+++ b/dom/media/gmp/rlz/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Note: build rlz in its own moz.build, so it doesn't pickup any of
+# Chromium IPC's headers used in the moz.build of the parent file.
+
+Library('rlz')
+
+UNIFIED_SOURCES += [
+ 'GMPDeviceBinding.cpp',
+]
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ UNIFIED_SOURCES += [
+ 'win/lib/machine_id_win.cc',
+ ]
+
+if CONFIG['OS_TARGET'] == 'Darwin':
+ UNIFIED_SOURCES += [
+ 'mac/lib/machine_id_mac.cc',
+ ]
+ OS_LIBS += [
+ '-framework IOKit',
+ ]
+
+LOCAL_INCLUDES += [
+ '..',
+]
+
+EXPORTS += [
+ 'GMPDeviceBinding.h',
+]
diff --git a/dom/media/gmp/rlz/sha256.c b/dom/media/gmp/rlz/sha256.c
new file mode 100644
index 000000000..072de5195
--- /dev/null
+++ b/dom/media/gmp/rlz/sha256.c
@@ -0,0 +1,469 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Stripped down version of security/nss/lib/freebl/sha512.c
+// and related headers.
+
+#include "sha256.h"
+#include "string.h"
+#include "prcpucfg.h"
+#if defined(NSS_X86) || defined(SHA_NO_LONG_LONG)
+#define NOUNROLL512 1
+#undef HAVE_LONG_LONG
+#endif
+
+#define SHA256_BLOCK_LENGTH 64 /* bytes */
+
+typedef enum _SECStatus {
+ SECWouldBlock = -2,
+ SECFailure = -1,
+ SECSuccess = 0
+} SECStatus;
+
+/* ============= Common constants and defines ======================= */
+
+#define W ctx->u.w
+#define B ctx->u.b
+#define H ctx->h
+
+#define SHR(x,n) (x >> n)
+#define SHL(x,n) (x << n)
+#define Ch(x,y,z) ((x & y) ^ (~x & z))
+#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z))
+#define SHA_MIN(a,b) (a < b ? a : b)
+
+/* Padding used with all flavors of SHA */
+static const PRUint8 pad[240] = {
+0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+ /* compiler will fill the rest in with zeros */
+};
+
+/* ============= SHA256 implementation ================================== */
+
+/* SHA-256 constants, K256. */
+static const PRUint32 K256[64] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+/* SHA-256 initial hash values */
+static const PRUint32 H256[8] = {
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
+};
+
+#if defined(_MSC_VER)
+#include <stdlib.h>
+#pragma intrinsic(_byteswap_ulong)
+#define SHA_HTONL(x) _byteswap_ulong(x)
+#define BYTESWAP4(x) x = SHA_HTONL(x)
+#elif defined(__GNUC__) && defined(NSS_X86_OR_X64)
+static __inline__ PRUint32 swap4b(PRUint32 value)
+{
+ __asm__("bswap %0" : "+r" (value));
+ return (value);
+}
+#define SHA_HTONL(x) swap4b(x)
+#define BYTESWAP4(x) x = SHA_HTONL(x)
+
+#elif defined(__GNUC__) && (defined(__thumb2__) || \
+ (!defined(__thumb__) && \
+ (defined(__ARM_ARCH_6__) || \
+ defined(__ARM_ARCH_6J__) || \
+ defined(__ARM_ARCH_6K__) || \
+ defined(__ARM_ARCH_6Z__) || \
+ defined(__ARM_ARCH_6ZK__) || \
+ defined(__ARM_ARCH_6T2__) || \
+ defined(__ARM_ARCH_7__) || \
+ defined(__ARM_ARCH_7A__) || \
+ defined(__ARM_ARCH_7R__))))
+static __inline__ PRUint32 swap4b(PRUint32 value)
+{
+ PRUint32 ret;
+ __asm__("rev %0, %1" : "=r" (ret) : "r"(value));
+ return ret;
+}
+#define SHA_HTONL(x) swap4b(x)
+#define BYTESWAP4(x) x = SHA_HTONL(x)
+
+#else
+#define SWAP4MASK 0x00FF00FF
+#define SHA_HTONL(x) (t1 = (x), t1 = (t1 << 16) | (t1 >> 16), \
+ ((t1 & SWAP4MASK) << 8) | ((t1 >> 8) & SWAP4MASK))
+#define BYTESWAP4(x) x = SHA_HTONL(x)
+#endif
+
+#if defined(_MSC_VER)
+#pragma intrinsic (_lrotr, _lrotl)
+#define ROTR32(x,n) _lrotr(x,n)
+#define ROTL32(x,n) _lrotl(x,n)
+#else
+#define ROTR32(x,n) ((x >> n) | (x << ((8 * sizeof x) - n)))
+#define ROTL32(x,n) ((x << n) | (x >> ((8 * sizeof x) - n)))
+#endif
+
+/* Capitol Sigma and lower case sigma functions */
+#define S0(x) (ROTR32(x, 2) ^ ROTR32(x,13) ^ ROTR32(x,22))
+#define S1(x) (ROTR32(x, 6) ^ ROTR32(x,11) ^ ROTR32(x,25))
+#define s0(x) (t1 = x, ROTR32(t1, 7) ^ ROTR32(t1,18) ^ SHR(t1, 3))
+#define s1(x) (t2 = x, ROTR32(t2,17) ^ ROTR32(t2,19) ^ SHR(t2,10))
+
+void
+SHA256_Begin(SHA256Context *ctx)
+{
+ memset(ctx, 0, sizeof *ctx);
+ memcpy(H, H256, sizeof H256);
+}
+
+static void
+SHA256_Compress(SHA256Context *ctx)
+{
+ {
+ register PRUint32 t1, t2;
+
+#if defined(IS_LITTLE_ENDIAN)
+ BYTESWAP4(W[0]);
+ BYTESWAP4(W[1]);
+ BYTESWAP4(W[2]);
+ BYTESWAP4(W[3]);
+ BYTESWAP4(W[4]);
+ BYTESWAP4(W[5]);
+ BYTESWAP4(W[6]);
+ BYTESWAP4(W[7]);
+ BYTESWAP4(W[8]);
+ BYTESWAP4(W[9]);
+ BYTESWAP4(W[10]);
+ BYTESWAP4(W[11]);
+ BYTESWAP4(W[12]);
+ BYTESWAP4(W[13]);
+ BYTESWAP4(W[14]);
+ BYTESWAP4(W[15]);
+#endif
+
+#define INITW(t) W[t] = (s1(W[t-2]) + W[t-7] + s0(W[t-15]) + W[t-16])
+
+ /* prepare the "message schedule" */
+#ifdef NOUNROLL256
+ {
+ int t;
+ for (t = 16; t < 64; ++t) {
+ INITW(t);
+ }
+ }
+#else
+ INITW(16);
+ INITW(17);
+ INITW(18);
+ INITW(19);
+
+ INITW(20);
+ INITW(21);
+ INITW(22);
+ INITW(23);
+ INITW(24);
+ INITW(25);
+ INITW(26);
+ INITW(27);
+ INITW(28);
+ INITW(29);
+
+ INITW(30);
+ INITW(31);
+ INITW(32);
+ INITW(33);
+ INITW(34);
+ INITW(35);
+ INITW(36);
+ INITW(37);
+ INITW(38);
+ INITW(39);
+
+ INITW(40);
+ INITW(41);
+ INITW(42);
+ INITW(43);
+ INITW(44);
+ INITW(45);
+ INITW(46);
+ INITW(47);
+ INITW(48);
+ INITW(49);
+
+ INITW(50);
+ INITW(51);
+ INITW(52);
+ INITW(53);
+ INITW(54);
+ INITW(55);
+ INITW(56);
+ INITW(57);
+ INITW(58);
+ INITW(59);
+
+ INITW(60);
+ INITW(61);
+ INITW(62);
+ INITW(63);
+
+#endif
+#undef INITW
+ }
+ {
+ PRUint32 a, b, c, d, e, f, g, h;
+
+ a = H[0];
+ b = H[1];
+ c = H[2];
+ d = H[3];
+ e = H[4];
+ f = H[5];
+ g = H[6];
+ h = H[7];
+
+#define ROUND(n,a,b,c,d,e,f,g,h) \
+ h += S1(e) + Ch(e,f,g) + K256[n] + W[n]; \
+ d += h; \
+ h += S0(a) + Maj(a,b,c);
+
+#ifdef NOUNROLL256
+ {
+ int t;
+ for (t = 0; t < 64; t+= 8) {
+ ROUND(t+0,a,b,c,d,e,f,g,h)
+ ROUND(t+1,h,a,b,c,d,e,f,g)
+ ROUND(t+2,g,h,a,b,c,d,e,f)
+ ROUND(t+3,f,g,h,a,b,c,d,e)
+ ROUND(t+4,e,f,g,h,a,b,c,d)
+ ROUND(t+5,d,e,f,g,h,a,b,c)
+ ROUND(t+6,c,d,e,f,g,h,a,b)
+ ROUND(t+7,b,c,d,e,f,g,h,a)
+ }
+ }
+#else
+ ROUND( 0,a,b,c,d,e,f,g,h)
+ ROUND( 1,h,a,b,c,d,e,f,g)
+ ROUND( 2,g,h,a,b,c,d,e,f)
+ ROUND( 3,f,g,h,a,b,c,d,e)
+ ROUND( 4,e,f,g,h,a,b,c,d)
+ ROUND( 5,d,e,f,g,h,a,b,c)
+ ROUND( 6,c,d,e,f,g,h,a,b)
+ ROUND( 7,b,c,d,e,f,g,h,a)
+
+ ROUND( 8,a,b,c,d,e,f,g,h)
+ ROUND( 9,h,a,b,c,d,e,f,g)
+ ROUND(10,g,h,a,b,c,d,e,f)
+ ROUND(11,f,g,h,a,b,c,d,e)
+ ROUND(12,e,f,g,h,a,b,c,d)
+ ROUND(13,d,e,f,g,h,a,b,c)
+ ROUND(14,c,d,e,f,g,h,a,b)
+ ROUND(15,b,c,d,e,f,g,h,a)
+
+ ROUND(16,a,b,c,d,e,f,g,h)
+ ROUND(17,h,a,b,c,d,e,f,g)
+ ROUND(18,g,h,a,b,c,d,e,f)
+ ROUND(19,f,g,h,a,b,c,d,e)
+ ROUND(20,e,f,g,h,a,b,c,d)
+ ROUND(21,d,e,f,g,h,a,b,c)
+ ROUND(22,c,d,e,f,g,h,a,b)
+ ROUND(23,b,c,d,e,f,g,h,a)
+
+ ROUND(24,a,b,c,d,e,f,g,h)
+ ROUND(25,h,a,b,c,d,e,f,g)
+ ROUND(26,g,h,a,b,c,d,e,f)
+ ROUND(27,f,g,h,a,b,c,d,e)
+ ROUND(28,e,f,g,h,a,b,c,d)
+ ROUND(29,d,e,f,g,h,a,b,c)
+ ROUND(30,c,d,e,f,g,h,a,b)
+ ROUND(31,b,c,d,e,f,g,h,a)
+
+ ROUND(32,a,b,c,d,e,f,g,h)
+ ROUND(33,h,a,b,c,d,e,f,g)
+ ROUND(34,g,h,a,b,c,d,e,f)
+ ROUND(35,f,g,h,a,b,c,d,e)
+ ROUND(36,e,f,g,h,a,b,c,d)
+ ROUND(37,d,e,f,g,h,a,b,c)
+ ROUND(38,c,d,e,f,g,h,a,b)
+ ROUND(39,b,c,d,e,f,g,h,a)
+
+ ROUND(40,a,b,c,d,e,f,g,h)
+ ROUND(41,h,a,b,c,d,e,f,g)
+ ROUND(42,g,h,a,b,c,d,e,f)
+ ROUND(43,f,g,h,a,b,c,d,e)
+ ROUND(44,e,f,g,h,a,b,c,d)
+ ROUND(45,d,e,f,g,h,a,b,c)
+ ROUND(46,c,d,e,f,g,h,a,b)
+ ROUND(47,b,c,d,e,f,g,h,a)
+
+ ROUND(48,a,b,c,d,e,f,g,h)
+ ROUND(49,h,a,b,c,d,e,f,g)
+ ROUND(50,g,h,a,b,c,d,e,f)
+ ROUND(51,f,g,h,a,b,c,d,e)
+ ROUND(52,e,f,g,h,a,b,c,d)
+ ROUND(53,d,e,f,g,h,a,b,c)
+ ROUND(54,c,d,e,f,g,h,a,b)
+ ROUND(55,b,c,d,e,f,g,h,a)
+
+ ROUND(56,a,b,c,d,e,f,g,h)
+ ROUND(57,h,a,b,c,d,e,f,g)
+ ROUND(58,g,h,a,b,c,d,e,f)
+ ROUND(59,f,g,h,a,b,c,d,e)
+ ROUND(60,e,f,g,h,a,b,c,d)
+ ROUND(61,d,e,f,g,h,a,b,c)
+ ROUND(62,c,d,e,f,g,h,a,b)
+ ROUND(63,b,c,d,e,f,g,h,a)
+#endif
+
+ H[0] += a;
+ H[1] += b;
+ H[2] += c;
+ H[3] += d;
+ H[4] += e;
+ H[5] += f;
+ H[6] += g;
+ H[7] += h;
+ }
+#undef ROUND
+}
+
+#undef s0
+#undef s1
+#undef S0
+#undef S1
+
+void
+SHA256_Update(SHA256Context *ctx, const unsigned char *input,
+ unsigned int inputLen)
+{
+ unsigned int inBuf = ctx->sizeLo & 0x3f;
+ if (!inputLen)
+ return;
+
+ /* Add inputLen into the count of bytes processed, before processing */
+ if ((ctx->sizeLo += inputLen) < inputLen)
+ ctx->sizeHi++;
+
+ /* if data already in buffer, attemp to fill rest of buffer */
+ if (inBuf) {
+ unsigned int todo = SHA256_BLOCK_LENGTH - inBuf;
+ if (inputLen < todo)
+ todo = inputLen;
+ memcpy(B + inBuf, input, todo);
+ input += todo;
+ inputLen -= todo;
+ if (inBuf + todo == SHA256_BLOCK_LENGTH)
+ SHA256_Compress(ctx);
+ }
+
+ /* if enough data to fill one or more whole buffers, process them. */
+ while (inputLen >= SHA256_BLOCK_LENGTH) {
+ memcpy(B, input, SHA256_BLOCK_LENGTH);
+ input += SHA256_BLOCK_LENGTH;
+ inputLen -= SHA256_BLOCK_LENGTH;
+ SHA256_Compress(ctx);
+ }
+ /* if data left over, fill it into buffer */
+ if (inputLen)
+ memcpy(B, input, inputLen);
+}
+
+void
+SHA256_End(SHA256Context *ctx, unsigned char *digest,
+ unsigned int *digestLen, unsigned int maxDigestLen)
+{
+ unsigned int inBuf = ctx->sizeLo & 0x3f;
+ unsigned int padLen = (inBuf < 56) ? (56 - inBuf) : (56 + 64 - inBuf);
+ PRUint32 hi, lo;
+#ifdef SWAP4MASK
+ PRUint32 t1;
+#endif
+
+ hi = (ctx->sizeHi << 3) | (ctx->sizeLo >> 29);
+ lo = (ctx->sizeLo << 3);
+
+ SHA256_Update(ctx, pad, padLen);
+
+#if defined(IS_LITTLE_ENDIAN)
+ W[14] = SHA_HTONL(hi);
+ W[15] = SHA_HTONL(lo);
+#else
+ W[14] = hi;
+ W[15] = lo;
+#endif
+ SHA256_Compress(ctx);
+
+ /* now output the answer */
+#if defined(IS_LITTLE_ENDIAN)
+ BYTESWAP4(H[0]);
+ BYTESWAP4(H[1]);
+ BYTESWAP4(H[2]);
+ BYTESWAP4(H[3]);
+ BYTESWAP4(H[4]);
+ BYTESWAP4(H[5]);
+ BYTESWAP4(H[6]);
+ BYTESWAP4(H[7]);
+#endif
+ padLen = PR_MIN(SHA256_LENGTH, maxDigestLen);
+ memcpy(digest, H, padLen);
+ if (digestLen)
+ *digestLen = padLen;
+}
+
+void
+SHA256_EndRaw(SHA256Context *ctx, unsigned char *digest,
+ unsigned int *digestLen, unsigned int maxDigestLen)
+{
+ PRUint32 h[8];
+ unsigned int len;
+#ifdef SWAP4MASK
+ PRUint32 t1;
+#endif
+
+ memcpy(h, ctx->h, sizeof(h));
+
+#if defined(IS_LITTLE_ENDIAN)
+ BYTESWAP4(h[0]);
+ BYTESWAP4(h[1]);
+ BYTESWAP4(h[2]);
+ BYTESWAP4(h[3]);
+ BYTESWAP4(h[4]);
+ BYTESWAP4(h[5]);
+ BYTESWAP4(h[6]);
+ BYTESWAP4(h[7]);
+#endif
+
+ len = PR_MIN(SHA256_LENGTH, maxDigestLen);
+ memcpy(digest, h, len);
+ if (digestLen)
+ *digestLen = len;
+}
+
+SECStatus
+SHA256_HashBuf(unsigned char *dest, const unsigned char *src,
+ PRUint32 src_length)
+{
+ SHA256Context ctx;
+ unsigned int outLen;
+
+ SHA256_Begin(&ctx);
+ SHA256_Update(&ctx, src, src_length);
+ SHA256_End(&ctx, dest, &outLen, SHA256_LENGTH);
+ memset(&ctx, 0, sizeof ctx);
+
+ return SECSuccess;
+}
diff --git a/dom/media/gmp/rlz/sha256.h b/dom/media/gmp/rlz/sha256.h
new file mode 100644
index 000000000..6958e382c
--- /dev/null
+++ b/dom/media/gmp/rlz/sha256.h
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Stripped down version of security/nss/lib/freebl/blapi.h
+// and related headers.
+
+#ifndef _SHA256_H_
+#define _SHA256_H_
+
+#define SHA256_LENGTH 32
+
+#include "prtypes.h" /* for PRUintXX */
+#include "prlong.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct SHA256Context {
+ union {
+ PRUint32 w[64]; /* message schedule, input buffer, plus 48 words */
+ PRUint8 b[256];
+ } u;
+ PRUint32 h[8]; /* 8 state variables */
+ PRUint32 sizeHi,sizeLo; /* 64-bit count of hashed bytes. */
+};
+
+typedef struct SHA256Context SHA256Context;
+
+extern void
+SHA256_Begin(SHA256Context *ctx);
+
+extern void
+SHA256_Update(SHA256Context *ctx, const unsigned char *input,
+ unsigned int inputLen);
+
+extern void
+SHA256_End(SHA256Context *ctx, unsigned char *digest,
+ unsigned int *digestLen, unsigned int maxDigestLen);
+
+#ifdef __cplusplus
+} /* extern C */
+#endif
+
+#endif /* _SHA256_H_ */
diff --git a/dom/media/gmp/rlz/win/lib/machine_id_win.cc b/dom/media/gmp/rlz/win/lib/machine_id_win.cc
new file mode 100644
index 000000000..0a636ebd3
--- /dev/null
+++ b/dom/media/gmp/rlz/win/lib/machine_id_win.cc
@@ -0,0 +1,134 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Use of this source code is governed by an Apache-style license that can be
+// found in the COPYING file.
+
+#include <windows.h>
+#include <sddl.h> // For ConvertSidToStringSidW.
+#include <string>
+#include <vector>
+#include "mozilla/ArrayUtils.h"
+
+#include "rlz/lib/assert.h"
+
+namespace rlz_lib {
+
+namespace {
+
+bool GetSystemVolumeSerialNumber(int* number) {
+ if (!number)
+ return false;
+
+ *number = 0;
+
+ // Find the system root path (e.g: C:\).
+ wchar_t system_path[MAX_PATH + 1];
+ if (!GetSystemDirectoryW(system_path, MAX_PATH))
+ return false;
+
+ wchar_t* first_slash = wcspbrk(system_path, L"\\/");
+ if (first_slash != NULL)
+ *(first_slash + 1) = 0;
+
+ DWORD number_local = 0;
+ if (!GetVolumeInformationW(system_path, NULL, 0, &number_local, NULL, NULL,
+ NULL, 0))
+ return false;
+
+ *number = (int)number_local;
+ return true;
+}
+
+bool GetComputerSid(const wchar_t* account_name, SID* sid, DWORD sid_size) {
+ static const DWORD kStartDomainLength = 128; // reasonable to start with
+
+ std::vector<wchar_t> domain_buffer(kStartDomainLength, 0);
+ DWORD domain_size = kStartDomainLength;
+ DWORD sid_dword_size = sid_size;
+ SID_NAME_USE sid_name_use;
+
+ BOOL success = ::LookupAccountNameW(NULL, account_name, sid,
+ &sid_dword_size, &domain_buffer.front(),
+ &domain_size, &sid_name_use);
+ if (!success && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ // We could have gotten the insufficient buffer error because
+ // one or both of sid and szDomain was too small. Check for that
+ // here.
+ if (sid_dword_size > sid_size)
+ return false;
+
+ if (domain_size > kStartDomainLength)
+ domain_buffer.resize(domain_size);
+
+ success = ::LookupAccountNameW(NULL, account_name, sid, &sid_dword_size,
+ &domain_buffer.front(), &domain_size,
+ &sid_name_use);
+ }
+
+ return success != FALSE;
+}
+
+std::vector<uint8_t> ConvertSidToBytes(SID* sid) {
+ std::wstring sid_string;
+#if _WIN32_WINNT >= 0x500
+ wchar_t* sid_buffer = NULL;
+ if (ConvertSidToStringSidW(sid, &sid_buffer)) {
+ sid_string = sid_buffer;
+ LocalFree(sid_buffer);
+ }
+#else
+ SID_IDENTIFIER_AUTHORITY* sia = ::GetSidIdentifierAuthority(sid);
+
+ if(sia->Value[0] || sia->Value[1]) {
+ base::SStringPrintf(
+ &sid_string, L"S-%d-0x%02hx%02hx%02hx%02hx%02hx%02hx",
+ SID_REVISION, (USHORT)sia->Value[0], (USHORT)sia->Value[1],
+ (USHORT)sia->Value[2], (USHORT)sia->Value[3], (USHORT)sia->Value[4],
+ (USHORT)sia->Value[5]);
+ } else {
+ ULONG authority = 0;
+ for (int i = 2; i < 6; ++i) {
+ authority <<= 8;
+ authority |= sia->Value[i];
+ }
+ base::SStringPrintf(&sid_string, L"S-%d-%lu", SID_REVISION, authority);
+ }
+
+ int sub_auth_count = *::GetSidSubAuthorityCount(sid);
+ for(int i = 0; i < sub_auth_count; ++i)
+ base::StringAppendF(&sid_string, L"-%lu", *::GetSidSubAuthority(sid, i));
+#endif
+
+ // Get the contents of the string as a bunch of bytes.
+ return std::vector<uint8_t>(
+ reinterpret_cast<uint8_t*>(&sid_string[0]),
+ reinterpret_cast<uint8_t*>(&sid_string[sid_string.size()]));
+}
+
+} // namespace
+
+bool GetRawMachineId(std::vector<uint8_t>* sid_bytes, int* volume_id) {
+ // Calculate the Windows SID.
+
+ wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {0};
+ DWORD size = mozilla::ArrayLength(computer_name);
+
+ if (!GetComputerNameW(computer_name, &size)) {
+ return false;
+ }
+ char sid_buffer[SECURITY_MAX_SID_SIZE];
+ SID* sid = reinterpret_cast<SID*>(sid_buffer);
+ if (GetComputerSid(computer_name, sid, SECURITY_MAX_SID_SIZE)) {
+ *sid_bytes = ConvertSidToBytes(sid);
+ }
+
+ // Get the system drive volume serial number.
+ *volume_id = 0;
+ if (!GetSystemVolumeSerialNumber(volume_id)) {
+ ASSERT_STRING("GetMachineId: Failed to retrieve volume serial number");
+ *volume_id = 0;
+ }
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
new file mode 100644
index 000000000..57d4ecec2
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WidevineAdapter.h"
+#include "content_decryption_module.h"
+#include "VideoUtils.h"
+#include "WidevineDecryptor.h"
+#include "WidevineUtils.h"
+#include "WidevineVideoDecoder.h"
+#include "gmp-api/gmp-entrypoints.h"
+#include "gmp-api/gmp-decryption.h"
+#include "gmp-api/gmp-video-codec.h"
+#include "gmp-api/gmp-platform.h"
+
+static const GMPPlatformAPI* sPlatform = nullptr;
+
+namespace mozilla {
+
+GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime) {
+ return sPlatform->getcurrenttime(aOutTime);
+}
+
+// Call on main thread only.
+GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS) {
+ return sPlatform->settimer(aTask, aTimeoutMS);
+}
+
+GMPErr GMPCreateRecord(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ return sPlatform->createrecord(aRecordName, aRecordNameSize, aOutRecord, aClient);
+}
+
+void
+WidevineAdapter::SetAdaptee(PRLibrary* aLib)
+{
+ mLib = aLib;
+}
+
+void* GetCdmHost(int aHostInterfaceVersion, void* aUserData)
+{
+ Log("GetCdmHostFunc(%d, %p)", aHostInterfaceVersion, aUserData);
+ WidevineDecryptor* decryptor = reinterpret_cast<WidevineDecryptor*>(aUserData);
+ MOZ_ASSERT(decryptor);
+ return static_cast<cdm::Host_9*>(decryptor);
+}
+
+#define STRINGIFY(s) _STRINGIFY(s)
+#define _STRINGIFY(s) #s
+
+GMPErr
+WidevineAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
+{
+#ifdef ENABLE_WIDEVINE_LOG
+ if (getenv("GMP_LOG_FILE")) {
+ // Clear log file.
+ FILE* f = fopen(getenv("GMP_LOG_FILE"), "w");
+ if (f) {
+ fclose(f);
+ }
+ }
+#endif
+
+ sPlatform = aPlatformAPI;
+ if (!mLib) {
+ return GMPGenericErr;
+ }
+
+ auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
+ PR_FindFunctionSymbol(mLib, STRINGIFY(INITIALIZE_CDM_MODULE)));
+ if (!init) {
+ return GMPGenericErr;
+ }
+
+ Log(STRINGIFY(INITIALIZE_CDM_MODULE)"()");
+ init();
+
+ return GMPNoErr;
+}
+
+GMPErr
+WidevineAdapter::GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId)
+{
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p",
+ aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
+ if (!strcmp(aAPIName, GMP_API_DECRYPTOR)) {
+ if (WidevineDecryptor::GetInstance(aDecryptorId)) {
+ // We only support one CDM instance per PGMPDecryptor. Fail!
+ Log("WidevineAdapter::GMPGetAPI() Tried to create more than once CDM per IPDL actor! FAIL!");
+ return GMPQuotaExceededErr;
+ }
+ auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
+ 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);
+ return GMPGenericErr;
+ }
+
+ WidevineDecryptor* decryptor = new WidevineDecryptor();
+
+ auto cdm = reinterpret_cast<cdm::ContentDecryptionModule_9*>(
+ create(cdm::ContentDecryptionModule_9::kVersion,
+ kEMEKeySystemWidevine.get(),
+ kEMEKeySystemWidevine.Length(),
+ &GetCdmHost,
+ decryptor));
+ if (!cdm) {
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to create cdm",
+ aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
+ return GMPGenericErr;
+ }
+ Log("cdm: 0x%x", cdm);
+ RefPtr<CDMWrapper> wrapper(new CDMWrapper(cdm, decryptor));
+ decryptor->SetCDM(wrapper, aDecryptorId);
+ *aPluginAPI = decryptor;
+
+ } else if (!strcmp(aAPIName, GMP_API_VIDEO_DECODER)) {
+ RefPtr<CDMWrapper> wrapper = WidevineDecryptor::GetInstance(aDecryptorId);
+ 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;
+ }
+ *aPluginAPI = new WidevineVideoDecoder(static_cast<GMPVideoHost*>(aHostAPI),
+ wrapper);
+ }
+ return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
+}
+
+void
+WidevineAdapter::GMPShutdown()
+{
+ Log("WidevineAdapter::GMPShutdown()");
+
+ decltype(::DeinitializeCdmModule)* deinit;
+ deinit = (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
+ if (deinit) {
+ Log("DeinitializeCdmModule()");
+ deinit();
+ }
+}
+
+void
+WidevineAdapter::GMPSetNodeId(const char* aNodeId, uint32_t aLength)
+{
+
+}
+
+/* static */
+bool
+WidevineAdapter::Supports(int32_t aModuleVersion,
+ int32_t aInterfaceVersion,
+ int32_t aHostVersion)
+{
+ return aModuleVersion == CDM_MODULE_VERSION &&
+ aInterfaceVersion == cdm::ContentDecryptionModule_9::kVersion &&
+ aHostVersion == cdm::Host_9::kVersion;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineAdapter.h b/dom/media/gmp/widevine-adapter/WidevineAdapter.h
new file mode 100644
index 000000000..714e041be
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineAdapter_h_
+#define WidevineAdapter_h_
+
+#include "GMPLoader.h"
+#include "prlink.h"
+#include "GMPUtils.h"
+
+struct GMPPlatformAPI;
+
+namespace mozilla {
+
+class WidevineAdapter : public gmp::GMPAdapter {
+public:
+
+ void SetAdaptee(PRLibrary* aLib) override;
+
+ // These are called in place of the corresponding GMP API functions.
+ GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override;
+ GMPErr GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) override;
+ void GMPShutdown() override;
+ void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override;
+
+ static bool Supports(int32_t aModuleVersion,
+ int32_t aInterfaceVersion,
+ int32_t aHostVersion);
+
+private:
+ PRLibrary* mLib = nullptr;
+};
+
+GMPErr GMPCreateThread(GMPThread** aThread);
+GMPErr GMPRunOnMainThread(GMPTask* aTask);
+GMPErr GMPCreateMutex(GMPMutex** aMutex);
+
+// Call on main thread only.
+GMPErr GMPCreateRecord(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+// Call on main thread only.
+GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS);
+
+GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime);
+
+GMPErr GMPCreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+} // namespace mozilla
+
+#endif // WidevineAdapter_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
new file mode 100644
index 000000000..4d3408804
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -0,0 +1,554 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WidevineDecryptor.h"
+
+#include "WidevineAdapter.h"
+#include "WidevineUtils.h"
+#include "WidevineFileIO.h"
+#include <mozilla/SizePrintfMacros.h>
+#include <stdarg.h>
+#include "base/time.h"
+
+using namespace cdm;
+using namespace std;
+
+namespace mozilla {
+
+static map<uint32_t, RefPtr<CDMWrapper>> sDecryptors;
+
+/* static */
+RefPtr<CDMWrapper>
+WidevineDecryptor::GetInstance(uint32_t aInstanceId)
+{
+ auto itr = sDecryptors.find(aInstanceId);
+ if (itr != sDecryptors.end()) {
+ return itr->second;
+ }
+ return nullptr;
+}
+
+
+WidevineDecryptor::WidevineDecryptor()
+ : mCallback(nullptr)
+{
+ Log("WidevineDecryptor created this=%p", this);
+ AddRef(); // Released in DecryptingComplete().
+}
+
+WidevineDecryptor::~WidevineDecryptor()
+{
+ Log("WidevineDecryptor destroyed this=%p", this);
+}
+
+void
+WidevineDecryptor::SetCDM(RefPtr<CDMWrapper> aCDM, uint32_t aInstanceId)
+{
+ mCDM = aCDM;
+ mInstanceId = aInstanceId;
+ sDecryptors[mInstanceId] = aCDM;
+}
+
+void
+WidevineDecryptor::Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+{
+ Log("WidevineDecryptor::Init() this=%p distinctiveId=%d persistentState=%d",
+ this, aDistinctiveIdentifierRequired, aPersistentStateRequired);
+ MOZ_ASSERT(aCallback);
+ mCallback = aCallback;
+ MOZ_ASSERT(mCDM);
+ mDistinctiveIdentifierRequired = aDistinctiveIdentifierRequired;
+ mPersistentStateRequired = aPersistentStateRequired;
+ if (CDM()) {
+ CDM()->Initialize(aDistinctiveIdentifierRequired,
+ aPersistentStateRequired);
+ }
+}
+
+static SessionType
+ToCDMSessionType(GMPSessionType aSessionType)
+{
+ switch (aSessionType) {
+ case kGMPTemporySession: return kTemporary;
+ case kGMPPersistentSession: return kPersistentLicense;
+ case kGMPSessionInvalid: return kTemporary;
+ // TODO: kPersistentKeyRelease
+ }
+ MOZ_ASSERT(false); // Not supposed to get here.
+ return kTemporary;
+}
+
+void
+WidevineDecryptor::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType)
+{
+ Log("Decryptor::CreateSession(token=%d, pid=%d)", aCreateSessionToken, aPromiseId);
+ InitDataType initDataType;
+ if (!strcmp(aInitDataType, "cenc")) {
+ initDataType = kCenc;
+ } else if (!strcmp(aInitDataType, "webm")) {
+ initDataType = kWebM;
+ } else if (!strcmp(aInitDataType, "keyids")) {
+ initDataType = kKeyIds;
+ } else {
+ // Invalid init data type
+ const char* errorMsg = "Invalid init data type when creating session.";
+ OnRejectPromise(aPromiseId, kExceptionNotSupportedError, 0, errorMsg, sizeof(errorMsg));
+ return;
+ }
+ mPromiseIdToNewSessionTokens[aPromiseId] = aCreateSessionToken;
+ CDM()->CreateSessionAndGenerateRequest(aPromiseId,
+ ToCDMSessionType(aSessionType),
+ initDataType,
+ aInitData, aInitDataSize);
+}
+
+void
+WidevineDecryptor::LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::LoadSession(pid=%d, %s)", aPromiseId, aSessionId);
+ // TODO: session type??
+ CDM()->LoadSession(aPromiseId, kPersistentLicense, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize)
+{
+ Log("Decryptor::UpdateSession(pid=%d, session=%s)", aPromiseId, aSessionId);
+ CDM()->UpdateSession(aPromiseId, aSessionId, aSessionIdLength, aResponse, aResponseSize);
+}
+
+void
+WidevineDecryptor::CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::CloseSession(pid=%d, session=%s)", aPromiseId, aSessionId);
+ CDM()->CloseSession(aPromiseId, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::RemoveSession(%s)", aSessionId);
+ CDM()->RemoveSession(aPromiseId, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize)
+{
+ Log("Decryptor::SetServerCertificate()");
+ CDM()->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize);
+}
+
+class WidevineDecryptedBlock : public cdm::DecryptedBlock {
+public:
+
+ WidevineDecryptedBlock()
+ : mBuffer(nullptr)
+ , mTimestamp(0)
+ {
+ }
+
+ ~WidevineDecryptedBlock() {
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+ }
+
+ void SetDecryptedBuffer(cdm::Buffer* aBuffer) override {
+ mBuffer = aBuffer;
+ }
+
+ cdm::Buffer* DecryptedBuffer() override {
+ return mBuffer;
+ }
+
+ void SetTimestamp(int64_t aTimestamp) override {
+ mTimestamp = aTimestamp;
+ }
+
+ int64_t Timestamp() const override {
+ return mTimestamp;
+ }
+
+private:
+ cdm::Buffer* mBuffer;
+ int64_t mTimestamp;
+};
+
+void
+WidevineDecryptor::Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata)
+{
+ if (!mCallback) {
+ Log("WidevineDecryptor::Decrypt() this=%p FAIL; !mCallback", this);
+ return;
+ }
+ const GMPEncryptedBufferMetadata* crypto = aMetadata;
+ InputBuffer sample;
+ nsTArray<SubsampleEntry> subsamples;
+ InitInputBuffer(crypto, aBuffer->Id(), aBuffer->Data(), aBuffer->Size(), sample, subsamples);
+ WidevineDecryptedBlock decrypted;
+ Status rv = CDM()->Decrypt(sample, &decrypted);
+ Log("Decryptor::Decrypt(timestamp=%lld) rv=%d sz=%d",
+ sample.timestamp, rv, decrypted.DecryptedBuffer()->Size());
+ if (rv == kSuccess) {
+ aBuffer->Resize(decrypted.DecryptedBuffer()->Size());
+ memcpy(aBuffer->Data(),
+ decrypted.DecryptedBuffer()->Data(),
+ decrypted.DecryptedBuffer()->Size());
+ }
+ mCallback->Decrypted(aBuffer, ToGMPErr(rv));
+}
+
+void
+WidevineDecryptor::DecryptingComplete()
+{
+ Log("WidevineDecryptor::DecryptingComplete() this=%p", this);
+ // 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
+ // the CDM.
+ mCDM = nullptr;
+ sDecryptors.erase(mInstanceId);
+ mCallback = nullptr;
+ Release();
+}
+
+class WidevineBuffer : public cdm::Buffer {
+public:
+ explicit WidevineBuffer(size_t aSize) {
+ Log("WidevineBuffer(size=" PRIuSIZE ") created", aSize);
+ mBuffer.SetLength(aSize);
+ }
+ ~WidevineBuffer() {
+ Log("WidevineBuffer(size=" PRIuSIZE ") destroyed", Size());
+ }
+ void Destroy() override { delete this; }
+ uint32_t Capacity() const override { return mBuffer.Length(); };
+ uint8_t* Data() override { return mBuffer.Elements(); }
+ void SetSize(uint32_t aSize) override { mBuffer.SetLength(aSize); }
+ uint32_t Size() const override { return mBuffer.Length(); }
+
+private:
+ WidevineBuffer(const WidevineBuffer&);
+ void operator=(const WidevineBuffer&);
+
+ nsTArray<uint8_t> mBuffer;
+};
+
+Buffer*
+WidevineDecryptor::Allocate(uint32_t aCapacity)
+{
+ Log("Decryptor::Allocate(capacity=%u)", aCapacity);
+ return new WidevineBuffer(aCapacity);
+}
+
+class TimerTask : public GMPTask {
+public:
+ TimerTask(WidevineDecryptor* aDecryptor,
+ RefPtr<CDMWrapper> aCDM,
+ void* aContext)
+ : mDecryptor(aDecryptor)
+ , mCDM(aCDM)
+ , mContext(aContext)
+ {
+ }
+ ~TimerTask() override {}
+ void Run() override {
+ mCDM->GetCDM()->TimerExpired(mContext);
+ }
+ void Destroy() override { delete this; }
+private:
+ RefPtr<WidevineDecryptor> mDecryptor;
+ RefPtr<CDMWrapper> mCDM;
+ void* mContext;
+};
+
+void
+WidevineDecryptor::SetTimer(int64_t aDelayMs, void* aContext)
+{
+ Log("Decryptor::SetTimer(delay_ms=%lld, context=0x%x)", aDelayMs, aContext);
+ if (mCDM) {
+ GMPSetTimerOnMainThread(new TimerTask(this, mCDM, aContext), aDelayMs);
+ }
+}
+
+Time
+WidevineDecryptor::GetCurrentWallTime()
+{
+ return base::Time::Now().ToDoubleT();
+}
+
+void
+WidevineDecryptor::OnResolveKeyStatusPromise(uint32_t aPromiseId,
+ cdm::KeyStatus aKeyStatus) {
+ //TODO: The callback of GetStatusForPolicy. See Mozilla bug 1404230.
+}
+
+void
+WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
+ return;
+ }
+ Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId);
+ auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId);
+ if (iter == mPromiseIdToNewSessionTokens.end()) {
+ Log("FAIL: Decryptor::OnResolveNewSessionPromise(aPromiseId=%d) unknown aPromiseId", aPromiseId);
+ return;
+ }
+ mCallback->SetSessionId(iter->second, aSessionId, aSessionIdSize);
+ mCallback->ResolvePromise(aPromiseId);
+ mPromiseIdToNewSessionTokens.erase(iter);
+}
+
+void
+WidevineDecryptor::OnResolvePromise(uint32_t aPromiseId)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnResolvePromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
+ return;
+ }
+ Log("Decryptor::OnResolvePromise(aPromiseId=%d)", aPromiseId);
+ mCallback->ResolvePromise(aPromiseId);
+}
+
+static GMPDOMException
+ConvertCDMExceptionToGMPDOMException(cdm::Exception aException)
+{
+ switch (aException) {
+ case kExceptionNotSupportedError: return kGMPNotSupportedError;
+ case kExceptionInvalidStateError: return kGMPInvalidStateError;
+ case kExceptionTypeError: return kGMPTypeError;
+ case kExceptionQuotaExceededError: return kGMPQuotaExceededError;
+ case kUnknownError: return kGMPInvalidModificationError; // Note: Unique placeholder.
+ case kClientError: return kGMPAbortError; // Note: Unique placeholder.
+ case kOutputError: return kGMPSecurityError; // Note: Unique placeholder.
+ };
+ return kGMPInvalidStateError; // Note: Unique placeholder.
+}
+
+// Align with spec, the Exceptions used by CDM to reject promises .
+// https://w3c.github.io/encrypted-media/#exceptions
+cdm::Exception
+ConvertCDMErrorToCDMException(cdm::Error error) {
+ switch (error) {
+ case cdm::kNotSupportedError:
+ return cdm::Exception::kExceptionNotSupportedError;
+ case cdm::kInvalidStateError:
+ return cdm::Exception::kExceptionInvalidStateError;
+ case cdm::kInvalidAccessError:
+ return cdm::Exception::kExceptionTypeError;
+ case cdm::kQuotaExceededError:
+ return cdm::Exception::kExceptionQuotaExceededError;
+
+ case cdm::kUnknownError:
+ case cdm::kClientError:
+ case cdm::kOutputError:
+ break;
+ }
+
+ return cdm::Exception::kExceptionInvalidStateError;
+}
+
+void
+WidevineDecryptor::OnRejectPromise(uint32_t aPromiseId,
+ cdm::Exception aException,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s) FAIL; !mCallback",
+ aPromiseId, (int)aException, aSystemCode, aErrorMessage);
+ return;
+ }
+ Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s)",
+ aPromiseId, (int)aException, aSystemCode, aErrorMessage);
+ mCallback->RejectPromise(aPromiseId,
+ ConvertCDMExceptionToGMPDOMException(aException),
+ !aErrorMessageSize ? "" : aErrorMessage,
+ aErrorMessageSize);
+}
+
+static GMPSessionMessageType
+ToGMPMessageType(MessageType message_type)
+{
+ switch (message_type) {
+ case kLicenseRequest: return kGMPLicenseRequest;
+ case kLicenseRenewal: return kGMPLicenseRenewal;
+ case kLicenseRelease: return kGMPLicenseRelease;
+ }
+ return kGMPMessageInvalid;
+}
+
+void
+WidevineDecryptor::OnSessionMessage(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::MessageType aMessageType,
+ const char* aMessage,
+ uint32_t aMessageSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionMessage() FAIL; !mCallback");
+ return;
+ }
+ Log("Decryptor::OnSessionMessage()");
+ mCallback->SessionMessage(aSessionId,
+ aSessionIdSize,
+ ToGMPMessageType(aMessageType),
+ reinterpret_cast<const uint8_t*>(aMessage),
+ aMessageSize);
+}
+
+static GMPMediaKeyStatus
+ToGMPKeyStatus(KeyStatus aStatus)
+{
+ switch (aStatus) {
+ case kUsable: return kGMPUsable;
+ case kInternalError: return kGMPInternalError;
+ case kExpired: return kGMPExpired;
+ case kOutputRestricted: return kGMPOutputRestricted;
+ case kOutputDownscaled: return kGMPOutputDownscaled;
+ case kStatusPending: return kGMPStatusPending;
+ case kReleased: return kGMPReleased;
+ }
+ return kGMPUnknown;
+}
+
+void
+WidevineDecryptor::OnSessionKeysChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionKeysChange() FAIL; !mCallback");
+ return;
+ }
+ Log("Decryptor::OnSessionKeysChange()");
+
+ nsTArray<GMPMediaKeyInfo> key_infos;
+ for (uint32_t i = 0; i < aKeysInfoCount; i++) {
+ key_infos.AppendElement(GMPMediaKeyInfo(aKeysInfo[i].key_id,
+ aKeysInfo[i].key_id_size,
+ ToGMPKeyStatus(aKeysInfo[i].status)));
+ }
+ mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdSize,
+ key_infos.Elements(), key_infos.Length());
+}
+
+static GMPTimestamp
+ToGMPTime(Time aCDMTime)
+{
+ return static_cast<GMPTimestamp>(aCDMTime * 1000);
+}
+
+void
+WidevineDecryptor::OnExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ Time aNewExpiryTime)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnExpirationChange(sid=%s) t=%lf FAIL; !mCallback",
+ aSessionId, aNewExpiryTime);
+ return;
+ }
+ Log("Decryptor::OnExpirationChange(sid=%s) t=%lf", aSessionId, aNewExpiryTime);
+ GMPTimestamp expiry = ToGMPTime(aNewExpiryTime);
+ if (aNewExpiryTime == 0) {
+ return;
+ }
+ mCallback->ExpirationChange(aSessionId, aSessionIdSize, expiry);
+}
+
+void
+WidevineDecryptor::OnSessionClosed(const char* aSessionId,
+ uint32_t aSessionIdSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionClosed(sid=%s) FAIL; !mCallback", aSessionId);
+ return;
+ }
+ Log("Decryptor::OnSessionClosed(sid=%s)", aSessionId);
+ mCallback->SessionClosed(aSessionId, aSessionIdSize);
+}
+
+void
+WidevineDecryptor::SendPlatformChallenge(const char* aServiceId,
+ uint32_t aServiceIdSize,
+ const char* aChallenge,
+ uint32_t aChallengeSize)
+{
+ Log("Decryptor::SendPlatformChallenge(service_id=%s)", aServiceId);
+}
+
+void
+WidevineDecryptor::EnableOutputProtection(uint32_t aDesiredProtectionMask)
+{
+ Log("Decryptor::EnableOutputProtection(mask=0x%x)", aDesiredProtectionMask);
+}
+
+void
+WidevineDecryptor::QueryOutputProtectionStatus()
+{
+ Log("Decryptor::QueryOutputProtectionStatus()");
+}
+
+void
+WidevineDecryptor::OnDeferredInitializationDone(StreamType aStreamType,
+ Status aDecoderStatus)
+{
+ Log("Decryptor::OnDeferredInitializationDone()");
+}
+
+FileIO*
+WidevineDecryptor::CreateFileIO(FileIOClient* aClient)
+{
+ Log("Decryptor::CreateFileIO()");
+ if (!mPersistentStateRequired) {
+ return nullptr;
+ }
+ return new WidevineFileIO(aClient);
+}
+
+void
+WidevineDecryptor::RequestStorageId(uint32_t aVersion)
+{
+ Log("Decryptor::RequestStorageId() aVersion = %u", aVersion);
+ if (aVersion >= 0x80000000) {
+ mCDM->OnStorageId(aVersion, nullptr, 0);
+ return;
+ }
+
+ //TODO: Need to provide a menaingful buffer instead of a dummy one.
+ mCDM->OnStorageId(aVersion, new uint8_t[1024*1024], 1024 * 1024);
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineDecryptor.h b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h
new file mode 100644
index 000000000..f291c321d
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineDecryptor_h_
+#define WidevineDecryptor_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-decryption.h"
+#include "mozilla/RefPtr.h"
+#include "WidevineUtils.h"
+#include <map>
+
+namespace mozilla {
+
+class WidevineDecryptor : public GMPDecryptor
+ , public cdm::Host_9
+{
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineDecryptor)
+
+ WidevineDecryptor();
+
+ void SetCDM(RefPtr<CDMWrapper> aCDM, uint32_t aDecryptorId);
+
+ static RefPtr<CDMWrapper> GetInstance(uint32_t aDecryptorId);
+
+ // GMPDecryptor
+ void Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) override;
+
+ 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 LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) override;
+
+ void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) override;
+
+ void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) override;
+
+ void DecryptingComplete() override;
+
+
+ // cdm::Host_9 implementation
+ cdm::Buffer* Allocate(uint32_t aCapacity) override;
+ void SetTimer(int64_t aDelayMs, void* aContext) override;
+ cdm::Time GetCurrentWallTime() override;
+ // cdm::Host_9 interface
+ void OnResolveKeyStatusPromise(uint32_t aPromiseId,
+ cdm::KeyStatus aKeyStatus) override;
+ void OnResolveNewSessionPromise(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdSize) override;
+ void OnResolvePromise(uint32_t aPromiseId) override;
+ void OnRejectPromise(uint32_t aPromiseId,
+ cdm::Exception aException,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageSize) override;
+ void OnSessionMessage(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::MessageType aMessageType,
+ const char* aMessage,
+ uint32_t aMessageSize) override;
+ void OnSessionKeysChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const cdm::KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount) override;
+ void OnExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::Time aNewExpiryTime) override;
+ void OnSessionClosed(const char* aSessionId,
+ uint32_t aSessionIdSize) override;
+ void SendPlatformChallenge(const char* aServiceId,
+ uint32_t aServiceIdSize,
+ const char* aChallenge,
+ uint32_t aChallengeSize) override;
+ void EnableOutputProtection(uint32_t aDesiredProtectionMask) override;
+ void QueryOutputProtectionStatus() override;
+ void OnDeferredInitializationDone(cdm::StreamType aStreamType,
+ cdm::Status aDecoderStatus) override;
+ // cdm::Host_9 interface
+ // NOTE: the interface has changed upstream.
+ void RequestStorageId(uint32_t aVersion) override;
+ cdm::FileIO* CreateFileIO(cdm::FileIOClient* aClient) override;
+
+ GMPDecryptorCallback* Callback() const { return mCallback; }
+ RefPtr<CDMWrapper> GetCDMWrapper() const { return mCDM; }
+private:
+ ~WidevineDecryptor();
+ RefPtr<CDMWrapper> mCDM;
+ cdm::ContentDecryptionModule_9* CDM() { return mCDM->GetCDM(); }
+
+ GMPDecryptorCallback* mCallback;
+ std::map<uint32_t, uint32_t> mPromiseIdToNewSessionTokens;
+ bool mDistinctiveIdentifierRequired = false;
+ bool mPersistentStateRequired = false;
+ uint32_t mInstanceId = 0;
+};
+
+} // namespace mozilla
+
+#endif // WidevineDecryptor_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
new file mode 100644
index 000000000..b5fb1d705
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
@@ -0,0 +1,97 @@
+#include "WidevineFileIO.h"
+#include "WidevineUtils.h"
+#include "WidevineAdapter.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+void
+WidevineFileIO::Open(const char* aFilename, uint32_t aFilenameLength)
+{
+ mName = std::string(aFilename, aFilename + aFilenameLength);
+ GMPRecord* record = nullptr;
+ GMPErr err = GMPCreateRecord(aFilename, aFilenameLength, &record, static_cast<GMPRecordClient*>(this));
+ if (GMP_FAILED(err)) {
+ Log("WidevineFileIO::Open() '%s' GMPCreateRecord failed", mName.c_str());
+ mClient->OnOpenComplete(FileIOClient::kError);
+ return;
+ }
+ if (GMP_FAILED(record->Open())) {
+ Log("WidevineFileIO::Open() '%s' record open failed", mName.c_str());
+ mClient->OnOpenComplete(FileIOClient::kError);
+ return;
+ }
+
+ Log("WidevineFileIO::Open() '%s'", mName.c_str());
+ mRecord = record;
+}
+
+void
+WidevineFileIO::Read()
+{
+ if (!mRecord) {
+ Log("WidevineFileIO::Read() '%s' used uninitialized!", mName.c_str());
+ mClient->OnReadComplete(FileIOClient::kError, nullptr, 0);
+ return;
+ }
+ Log("WidevineFileIO::Read() '%s'", mName.c_str());
+ mRecord->Read();
+}
+
+void
+WidevineFileIO::Write(const uint8_t* aData, uint32_t aDataSize)
+{
+ if (!mRecord) {
+ Log("WidevineFileIO::Write() '%s' used uninitialized!", mName.c_str());
+ mClient->OnWriteComplete(FileIOClient::kError);
+ return;
+ }
+ mRecord->Write(aData, aDataSize);
+}
+
+void
+WidevineFileIO::Close()
+{
+ Log("WidevineFileIO::Close() '%s'", mName.c_str());
+ if (mRecord) {
+ mRecord->Close();
+ mRecord = nullptr;
+ }
+ delete this;
+}
+
+static FileIOClient::Status
+GMPToWidevineFileStatus(GMPErr aStatus)
+{
+ switch (aStatus) {
+ case GMPRecordInUse: return FileIOClient::kInUse;
+ case GMPNoErr: return FileIOClient::kSuccess;
+ default: return FileIOClient::kError;
+ }
+}
+
+void
+WidevineFileIO::OpenComplete(GMPErr aStatus)
+{
+ Log("WidevineFileIO::OpenComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnOpenComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+void
+WidevineFileIO::ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize)
+{
+ Log("WidevineFileIO::OnReadComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnReadComplete(GMPToWidevineFileStatus(aStatus), aData, aDataSize);
+}
+
+void
+WidevineFileIO::WriteComplete(GMPErr aStatus)
+{
+ Log("WidevineFileIO::WriteComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnWriteComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.h b/dom/media/gmp/widevine-adapter/WidevineFileIO.h
new file mode 100644
index 000000000..63003d9b6
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineFileIO_h_
+#define WidevineFileIO_h_
+
+#include <stddef.h>
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-storage.h"
+#include <string>
+
+namespace mozilla {
+
+class WidevineFileIO : public cdm::FileIO
+ , public GMPRecordClient
+{
+public:
+ explicit WidevineFileIO(cdm::FileIOClient* aClient)
+ : mClient(aClient)
+ , mRecord(nullptr)
+ {}
+
+ // cdm::FileIO
+ void Open(const char* aFilename, uint32_t aFilenameLength) override;
+ void Read() override;
+ void Write(const uint8_t* aData, uint32_t aDataSize) override;
+ void Close() override;
+
+ // GMPRecordClient
+ void OpenComplete(GMPErr aStatus) override;
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override;
+ void WriteComplete(GMPErr aStatus) override;
+
+private:
+ cdm::FileIOClient* mClient;
+ GMPRecord* mRecord;
+ std::string mName;
+};
+
+} // namespace mozilla
+
+#endif // WidevineFileIO_h_ \ No newline at end of file
diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.cpp b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp
new file mode 100644
index 000000000..10c6c2e18
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WidevineUtils.h"
+#include "WidevineDecryptor.h"
+
+#include "gmp-api/gmp-errors.h"
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace mozilla {
+
+#ifdef ENABLE_WIDEVINE_LOG
+void
+Log(const char* aFormat, ...)
+{
+ va_list ap;
+ va_start(ap, aFormat);
+ const size_t len = 1024;
+ char buf[len];
+ vsnprintf(buf, len, aFormat, ap);
+ va_end(ap);
+ if (getenv("GMP_LOG_FILE")) {
+ FILE* f = fopen(getenv("GMP_LOG_FILE"), "a");
+ if (f) {
+ fprintf(f, "%s\n", buf);
+ fflush(f);
+ fclose(f);
+ f = nullptr;
+ }
+ } else {
+ printf("LOG: %s\n", buf);
+ }
+}
+#endif // ENABLE_WIDEVINE_LOG
+
+GMPErr
+ToGMPErr(cdm::Status aStatus)
+{
+ switch (aStatus) {
+ case cdm::kSuccess: return GMPNoErr;
+ case cdm::kNeedMoreData: return GMPGenericErr;
+ case cdm::kNoKey: return GMPNoKeyErr;
+ case cdm::kInitializationError: return GMPGenericErr;
+ case cdm::kDecryptError: return GMPCryptoErr;
+ case cdm::kDecodeError: return GMPDecodeErr;
+ case cdm::kDeferredInitialization: return GMPGenericErr;
+ default: return GMPGenericErr;
+ }
+}
+
+void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto,
+ int64_t aTimestamp,
+ const uint8_t* aData,
+ size_t aDataSize,
+ cdm::InputBuffer &aInputBuffer,
+ nsTArray<cdm::SubsampleEntry> &aSubsamples)
+{
+ if (aCrypto) {
+ aInputBuffer.key_id = aCrypto->KeyId();
+ aInputBuffer.key_id_size = aCrypto->KeyIdSize();
+ aInputBuffer.iv = aCrypto->IV();
+ aInputBuffer.iv_size = aCrypto->IVSize();
+ aInputBuffer.num_subsamples = aCrypto->NumSubsamples();
+ aSubsamples.SetCapacity(aInputBuffer.num_subsamples);
+ const uint16_t* clear = aCrypto->ClearBytes();
+ const uint32_t* cipher = aCrypto->CipherBytes();
+ for (size_t i = 0; i < aCrypto->NumSubsamples(); i++) {
+ aSubsamples.AppendElement(cdm::SubsampleEntry(clear[i], cipher[i]));
+ }
+ }
+ aInputBuffer.data = aData;
+ aInputBuffer.data_size = aDataSize;
+ aInputBuffer.subsamples = aSubsamples.Elements();
+ aInputBuffer.timestamp = aTimestamp;
+}
+
+CDMWrapper::CDMWrapper(cdm::ContentDecryptionModule_9* aCDM,
+ WidevineDecryptor* aDecryptor)
+ : mCDM(aCDM)
+ , mDecryptor(aDecryptor)
+{
+ MOZ_ASSERT(mCDM);
+}
+
+CDMWrapper::~CDMWrapper()
+{
+ Log("CDMWrapper destroying CDM=%p", mCDM);
+ mCDM->Destroy();
+ mCDM = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.h b/dom/media/gmp/widevine-adapter/WidevineUtils.h
new file mode 100644
index 000000000..2f6137fe3
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineUtils.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineUtils_h_
+#define WidevineUtils_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-decryption.h"
+#include "gmp-api/gmp-platform.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+// Uncomment for logging...
+//#define ENABLE_WIDEVINE_LOG 1
+#ifdef ENABLE_WIDEVINE_LOG
+void
+Log(const char* aFormat, ...);
+#else
+#define Log(...)
+#endif // ENABLE_WIDEVINE_LOG
+
+
+#define ENSURE_TRUE(condition, rv) { \
+ if (!(condition)) {\
+ Log("ENSURE_TRUE FAILED %s:%d", __FILE__, __LINE__); \
+ return rv; \
+ } \
+} \
+
+#define ENSURE_GMP_SUCCESS(err, rv) { \
+ if (GMP_FAILED(err)) {\
+ Log("ENSURE_GMP_SUCCESS FAILED %s:%d", __FILE__, __LINE__); \
+ return rv; \
+ } \
+} \
+
+GMPErr
+ToGMPErr(cdm::Status aStatus);
+
+class WidevineDecryptor;
+
+class CDMWrapper {
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMWrapper)
+
+ explicit CDMWrapper(cdm::ContentDecryptionModule_9* aCDM,
+ WidevineDecryptor* aDecryptor);
+ cdm::ContentDecryptionModule_9* GetCDM() const { return mCDM; }
+ void OnStorageId(uint32_t aVersion, const uint8_t* aStorageId,
+ uint32_t aStorageIdSize) {
+ mCDM->OnStorageId(aVersion, aStorageId, aStorageIdSize);
+ }
+private:
+ ~CDMWrapper();
+ cdm::ContentDecryptionModule_9* mCDM;
+ RefPtr<WidevineDecryptor> mDecryptor;
+};
+
+void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto,
+ int64_t aTimestamp,
+ const uint8_t* aData,
+ size_t aDataSize,
+ cdm::InputBuffer &aInputBuffer,
+ nsTArray<cdm::SubsampleEntry> &aSubsamples);
+
+} // namespace mozilla
+
+#endif // WidevineUtils_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
new file mode 100644
index 000000000..70d2fd8e0
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
@@ -0,0 +1,400 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WidevineVideoDecoder.h"
+
+#include "mp4_demuxer/AnnexB.h"
+#include "WidevineUtils.h"
+#include "WidevineVideoFrame.h"
+#include "mozilla/Move.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
+ RefPtr<CDMWrapper> aCDMWrapper)
+ : mVideoHost(aVideoHost)
+ , mCDMWrapper(Move(aCDMWrapper))
+ , mExtraData(new MediaByteBuffer())
+ , mSentInput(false)
+ , mCodecType(kGMPVideoCodecInvalid)
+ , mReturnOutputCallDepth(0)
+ , mDrainPending(false)
+ , mResetInProgress(false)
+{
+ // Expect to start with a CDM wrapper, will release it in DecodingComplete().
+ MOZ_ASSERT(mCDMWrapper);
+ Log("WidevineVideoDecoder created this=%p", this);
+
+ // Corresponding Release is in DecodingComplete().
+ AddRef();
+}
+
+WidevineVideoDecoder::~WidevineVideoDecoder()
+{
+ Log("WidevineVideoDecoder destroyed this=%p", this);
+}
+
+static
+VideoDecoderConfig::VideoCodecProfile
+ToCDMH264Profile(uint8_t aProfile)
+{
+ switch (aProfile) {
+ case 66: return VideoDecoderConfig::kH264ProfileBaseline;
+ case 77: return VideoDecoderConfig::kH264ProfileMain;
+ case 88: return VideoDecoderConfig::kH264ProfileExtended;
+ case 100: return VideoDecoderConfig::kH264ProfileHigh;
+ case 110: return VideoDecoderConfig::kH264ProfileHigh10;
+ case 122: return VideoDecoderConfig::kH264ProfileHigh422;
+ case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive;
+ }
+ return VideoDecoderConfig::kUnknownVideoCodecProfile;
+}
+
+void
+WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount)
+{
+ mCallback = aCallback;
+ VideoDecoderConfig config;
+ mCodecType = aCodecSettings.mCodecType;
+ if (mCodecType == kGMPVideoCodecH264) {
+ config.codec = VideoDecoderConfig::kCodecH264;
+ const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific);
+ config.profile = ToCDMH264Profile(h264->mAVCC.mProfile);
+ } else if (mCodecType == kGMPVideoCodecVP8) {
+ config.codec = VideoDecoderConfig::kCodecVp8;
+ config.profile = VideoDecoderConfig::kProfileNotNeeded;
+ } else if (mCodecType == kGMPVideoCodecVP9) {
+ config.codec = VideoDecoderConfig::kCodecVp9;
+ config.profile = VideoDecoderConfig::kProfileNotNeeded;
+ } else {
+ mCallback->Error(GMPInvalidArgErr);
+ return;
+ }
+ config.format = kYv12;
+ config.coded_size = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
+ mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength);
+ config.extra_data = mExtraData->Elements();
+ config.extra_data_size = mExtraData->Length();
+ Status rv = CDM()->InitializeVideoDecoder(config);
+ if (rv != kSuccess) {
+ mCallback->Error(ToGMPErr(rv));
+ return;
+ }
+ Log("WidevineVideoDecoder::InitDecode() rv=%d", rv);
+ mAnnexB = mp4_demuxer::AnnexB::ConvertExtraDataToAnnexB(mExtraData);
+}
+
+void
+WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs)
+{
+ // We should not be given new input if a drain has been initiated
+ MOZ_ASSERT(!mDrainPending);
+ // We may not get the same out of the CDM decoder as we put in, and there
+ // may be some latency, i.e. we may need to input (say) 30 frames before
+ // we receive output. So we need to store the durations of the frames input,
+ // and retrieve them on output.
+ mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();
+
+ mSentInput = true;
+ InputBuffer sample;
+
+ RefPtr<MediaRawData> raw(
+ new MediaRawData(aInputFrame->Buffer(), aInputFrame->Size()));
+ if (aInputFrame->Size() && !raw->Data()) {
+ // OOM.
+ mCallback->Error(GMPAllocErr);
+ return;
+ }
+ raw->mExtraData = mExtraData;
+ raw->mKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
+ if (mCodecType == kGMPVideoCodecH264) {
+ // Convert input from AVCC, which GMPAPI passes in, to AnnexB, which
+ // Chromium uses internally.
+ mp4_demuxer::AnnexB::ConvertSampleToAnnexB(raw);
+ }
+
+ const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
+ nsTArray<SubsampleEntry> subsamples;
+ InitInputBuffer(crypto, aInputFrame->TimeStamp(), raw->Data(), raw->Size(), sample, subsamples);
+
+ // For keyframes, ConvertSampleToAnnexB will stick the AnnexB extra data
+ // at the start of the input. So we need to account for that as clear data
+ // in the subsamples.
+ if (raw->mKeyframe && !subsamples.IsEmpty() && mCodecType == kGMPVideoCodecH264) {
+ subsamples[0].clear_bytes += mAnnexB->Length();
+ }
+
+ WidevineVideoFrame frame;
+ Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
+ Log("WidevineVideoDecoder::Decode(timestamp=%lld) rv=%d", sample.timestamp, rv);
+
+ // Destroy frame, so that the shmem is now free to be used to return
+ // output to the Gecko process.
+ aInputFrame->Destroy();
+ aInputFrame = nullptr;
+
+ if (rv == kSuccess) {
+ if (!ReturnOutput(frame)) {
+ Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
+ mCallback->Error(GMPDecodeErr);
+ return;
+ }
+ // A reset should only be started at most at level mReturnOutputCallDepth 1,
+ // and if it's started it should be finished by that call by the time
+ // the it returns, so it should always be false by this point.
+ MOZ_ASSERT(!mResetInProgress);
+ // Only request more data if we don't have pending samples.
+ if (mFrameAllocationQueue.empty()) {
+ MOZ_ASSERT(mCDMWrapper);
+ mCallback->InputDataExhausted();
+ }
+ } else if (rv == kNeedMoreData) {
+ MOZ_ASSERT(mCDMWrapper);
+ mCallback->InputDataExhausted();
+ } else {
+ mCallback->Error(ToGMPErr(rv));
+ }
+ // Finish a drain if pending and we have no pending ReturnOutput calls on the stack.
+ if (mDrainPending && mReturnOutputCallDepth == 0) {
+ Drain();
+ }
+}
+
+// Util class to assist with counting mReturnOutputCallDepth.
+class CounterHelper {
+public:
+ // RAII, increment counter
+ explicit CounterHelper(int32_t& counter)
+ : mCounter(counter)
+ {
+ mCounter++;
+ }
+
+ // RAII, decrement counter
+ ~CounterHelper()
+ {
+ mCounter--;
+ }
+
+private:
+ int32_t& mCounter;
+};
+
+// Util class to make sure GMP frames are freed. Holds a GMPVideoi420Frame*
+// and will destroy it when the helper is destroyed unless the held frame
+// if forgotten with ForgetFrame.
+class FrameDestroyerHelper {
+public:
+ explicit FrameDestroyerHelper(GMPVideoi420Frame*& frame)
+ : frame(frame)
+ {
+ }
+
+ // RAII, destroy frame if held.
+ ~FrameDestroyerHelper()
+ {
+ if (frame) {
+ frame->Destroy();
+ }
+ frame = nullptr;
+ }
+
+ // Forget the frame without destroying it.
+ void ForgetFrame()
+ {
+ frame = nullptr;
+ }
+
+private:
+ GMPVideoi420Frame* frame;
+};
+
+
+// Special handing is needed around ReturnOutput as it spins the IPC message
+// queue when creating an empty frame and can end up with reentrant calls into
+// the class methods.
+bool
+WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame)
+{
+ MOZ_ASSERT(mReturnOutputCallDepth >= 0);
+ CounterHelper counterHelper(mReturnOutputCallDepth);
+ mFrameAllocationQueue.push_back(Move(aCDMFrame));
+ if (mReturnOutputCallDepth > 1) {
+ // In a reentrant call.
+ return true;
+ }
+ while (!mFrameAllocationQueue.empty()) {
+ MOZ_ASSERT(mReturnOutputCallDepth == 1);
+ // If we're at call level 1 a reset should not have been started. A
+ // reset may be received during CreateEmptyFrame below, but we should not
+ // be in a reset at this stage -- this would indicate receiving decode
+ // messages before completing our reset, which we should not.
+ MOZ_ASSERT(!mResetInProgress);
+ WidevineVideoFrame currentCDMFrame = Move(mFrameAllocationQueue.front());
+ mFrameAllocationQueue.pop_front();
+ GMPVideoFrame* f = nullptr;
+ auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f);
+ if (GMP_FAILED(err) || !f) {
+ Log("Failed to create i420 frame!\n");
+ return false;
+ }
+ auto gmpFrame = static_cast<GMPVideoi420Frame*>(f);
+ FrameDestroyerHelper frameDestroyerHelper(gmpFrame);
+ Size size = currentCDMFrame.Size();
+ const int32_t yStride = currentCDMFrame.Stride(VideoFrame::kYPlane);
+ const int32_t uStride = currentCDMFrame.Stride(VideoFrame::kUPlane);
+ const int32_t vStride = currentCDMFrame.Stride(VideoFrame::kVPlane);
+ const int32_t halfHeight = size.height / 2;
+ // This call can cause a shmem alloc, during this alloc other calls
+ // may be made to this class and placed on the stack. ***WARNING***:
+ // other IPC calls can happen during this call, resulting in calls
+ // being made to the CDM. After this call state can have changed,
+ // and should be reevaluated.
+ err = gmpFrame->CreateEmptyFrame(size.width,
+ size.height,
+ yStride,
+ uStride,
+ vStride);
+ // Assert possible reentrant calls or resets haven't altered level unexpectedly.
+ MOZ_ASSERT(mReturnOutputCallDepth == 1);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ // If a reset started we need to dump the current frame and complete the reset.
+ if (mResetInProgress) {
+ MOZ_ASSERT(mCDMWrapper);
+ MOZ_ASSERT(mFrameAllocationQueue.empty());
+ CompleteReset();
+ return true;
+ }
+
+ err = gmpFrame->SetWidth(size.width);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ err = gmpFrame->SetHeight(size.height);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ Buffer* buffer = currentCDMFrame.FrameBuffer();
+ uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kYPlane),
+ yStride * size.height);
+
+ outBuffer = gmpFrame->Buffer(kGMPUPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kUPlane),
+ uStride * halfHeight);
+
+ outBuffer = gmpFrame->Buffer(kGMPVPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kVPlane),
+ vStride * halfHeight);
+
+ gmpFrame->SetTimestamp(currentCDMFrame.Timestamp());
+
+ auto d = mFrameDurations.find(currentCDMFrame.Timestamp());
+ if (d != mFrameDurations.end()) {
+ gmpFrame->SetDuration(d->second);
+ mFrameDurations.erase(d);
+ }
+
+ // Forget frame so it's not deleted, call back taking ownership.
+ frameDestroyerHelper.ForgetFrame();
+ mCallback->Decoded(gmpFrame);
+ }
+
+ return true;
+}
+
+void
+WidevineVideoDecoder::Reset()
+{
+ Log("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput);
+ // We shouldn't reset if a drain is pending.
+ MOZ_ASSERT(!mDrainPending);
+ mResetInProgress = true;
+ if (mSentInput) {
+ CDM()->ResetDecoder(kStreamTypeVideo);
+ }
+ // Remove queued frames, but do not reset mReturnOutputCallDepth, let the
+ // ReturnOutput calls unwind and decrement the counter as needed.
+ mFrameAllocationQueue.clear();
+ mFrameDurations.clear();
+ // Only if no ReturnOutput calls are in progress can we complete, otherwise
+ // ReturnOutput needs to finalize the reset.
+ if (mReturnOutputCallDepth == 0) {
+ CompleteReset();
+ }
+}
+
+void
+WidevineVideoDecoder::CompleteReset()
+{
+ mCallback->ResetComplete();
+ mSentInput = false;
+ mResetInProgress = false;
+}
+
+void
+WidevineVideoDecoder::Drain()
+{
+ Log("WidevineVideoDecoder::Drain()");
+ if (mReturnOutputCallDepth > 0) {
+ Log("Drain call is reentrant, postponing drain");
+ mDrainPending = true;
+ return;
+ }
+
+ Status rv = kSuccess;
+ while (rv == kSuccess) {
+ WidevineVideoFrame frame;
+ InputBuffer sample;
+ Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
+ Log("WidevineVideoDecoder::Drain(); DecryptAndDecodeFrame() rv=%d", rv);
+ if (frame.Format() == kUnknownVideoFormat) {
+ break;
+ }
+ if (rv == kSuccess) {
+ if (!ReturnOutput(frame)) {
+ Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
+ }
+ }
+ }
+ // Shouldn't be reset while draining.
+ MOZ_ASSERT(!mResetInProgress);
+
+ CDM()->ResetDecoder(kStreamTypeVideo);
+ mDrainPending = false;
+ mCallback->DrainComplete();
+}
+
+void
+WidevineVideoDecoder::DecodingComplete()
+{
+ Log("WidevineVideoDecoder::DecodingComplete()");
+ if (mCDMWrapper) {
+ CDM()->DeinitializeDecoder(kStreamTypeVideo);
+ mCDMWrapper = nullptr;
+ }
+ // Release that corresponds to AddRef() in constructor.
+ Release();
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
new file mode 100644
index 000000000..f5e63519b
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineVideoDecoder_h_
+#define WidevineVideoDecoder_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-video-decode.h"
+#include "gmp-api/gmp-video-host.h"
+#include "MediaData.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "WidevineDecryptor.h"
+#include "WidevineVideoFrame.h"
+#include <map>
+#include <deque>
+
+namespace mozilla {
+
+class WidevineVideoDecoder : public GMPVideoDecoder {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineVideoDecoder)
+
+ WidevineVideoDecoder(GMPVideoHost* aVideoHost,
+ RefPtr<CDMWrapper> aCDMWrapper);
+ 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:
+
+ ~WidevineVideoDecoder();
+
+ cdm::ContentDecryptionModule_9* CDM() const {
+ // CDM should only be accessed before 'DecodingComplete'.
+ MOZ_ASSERT(mCDMWrapper);
+ // CDMWrapper ensure the CDM is non-null, no need to check again.
+ return mCDMWrapper->GetCDM();
+ }
+
+ bool ReturnOutput(WidevineVideoFrame& aFrame);
+ void CompleteReset();
+
+ GMPVideoHost* mVideoHost;
+ RefPtr<CDMWrapper> mCDMWrapper;
+ RefPtr<MediaByteBuffer> mExtraData;
+ RefPtr<MediaByteBuffer> mAnnexB;
+ GMPVideoDecoderCallback* mCallback;
+ std::map<uint64_t, uint64_t> mFrameDurations;
+ bool mSentInput;
+ GMPVideoCodecType mCodecType;
+ // Frames waiting on allocation
+ std::deque<WidevineVideoFrame> mFrameAllocationQueue;
+ // Number of calls of ReturnOutput currently in progress.
+ int32_t mReturnOutputCallDepth;
+ // If we're waiting to drain. Used to prevent drain completing while
+ // ReturnOutput calls are still on the stack.
+ bool mDrainPending;
+ // If a reset is being performed. Used to track if ReturnOutput should
+ // dump current frame.
+ bool mResetInProgress;
+};
+
+} // namespace mozilla
+
+#endif // WidevineVideoDecoder_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp
new file mode 100644
index 000000000..4221bf15b
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WidevineVideoFrame.h"
+
+#include "WidevineUtils.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+WidevineVideoFrame::WidevineVideoFrame()
+ : mFormat(kUnknownVideoFormat)
+ , mSize(0,0)
+ , mBuffer(nullptr)
+ , mTimestamp(0)
+{
+ Log("WidevineVideoFrame::WidevineVideoFrame() this=%p", this);
+ memset(mPlaneOffsets, 0, sizeof(mPlaneOffsets));
+ memset(mPlaneStrides, 0, sizeof(mPlaneStrides));
+}
+
+WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&& aOther)
+ : mFormat(aOther.mFormat)
+ , mSize(aOther.mSize)
+ , mBuffer(aOther.mBuffer)
+ , mTimestamp(aOther.mTimestamp)
+{
+ Log("WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&&) this=%p, other=%p",
+ this, &aOther);
+ memcpy(mPlaneOffsets, aOther.mPlaneOffsets, sizeof(mPlaneOffsets));
+ memcpy(mPlaneStrides, aOther.mPlaneStrides, sizeof(mPlaneStrides));
+ aOther.mBuffer = nullptr;
+}
+
+WidevineVideoFrame::~WidevineVideoFrame()
+{
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+}
+
+void
+WidevineVideoFrame::SetFormat(cdm::VideoFormat aFormat)
+{
+ Log("WidevineVideoFrame::SetFormat(%d) this=%p", aFormat, this);
+ mFormat = aFormat;
+}
+
+cdm::VideoFormat
+WidevineVideoFrame::Format() const
+{
+ return mFormat;
+}
+
+void
+WidevineVideoFrame::SetSize(cdm::Size aSize)
+{
+ Log("WidevineVideoFrame::SetSize(%d,%d) this=%p", aSize.width, aSize.height, this);
+ mSize.width = aSize.width;
+ mSize.height = aSize.height;
+}
+
+cdm::Size
+WidevineVideoFrame::Size() const
+{
+ return mSize;
+}
+
+void
+WidevineVideoFrame::SetFrameBuffer(cdm::Buffer* aFrameBuffer)
+{
+ Log("WidevineVideoFrame::SetFrameBuffer(%p) this=%p", aFrameBuffer, this);
+ MOZ_ASSERT(!mBuffer);
+ mBuffer = aFrameBuffer;
+}
+
+cdm::Buffer*
+WidevineVideoFrame::FrameBuffer()
+{
+ return mBuffer;
+}
+
+void
+WidevineVideoFrame::SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset)
+{
+ Log("WidevineVideoFrame::SetPlaneOffset(%d, %d) this=%p", aPlane, aOffset, this);
+ mPlaneOffsets[aPlane] = aOffset;
+}
+
+uint32_t
+WidevineVideoFrame::PlaneOffset(cdm::VideoFrame::VideoPlane aPlane)
+{
+ return mPlaneOffsets[aPlane];
+}
+
+void
+WidevineVideoFrame::SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride)
+{
+ Log("WidevineVideoFrame::SetStride(%d, %d) this=%p", aPlane, aStride, this);
+ mPlaneStrides[aPlane] = aStride;
+}
+
+uint32_t
+WidevineVideoFrame::Stride(cdm::VideoFrame::VideoPlane aPlane)
+{
+ return mPlaneStrides[aPlane];
+}
+
+void
+WidevineVideoFrame::SetTimestamp(int64_t timestamp)
+{
+ Log("WidevineVideoFrame::SetTimestamp(%lld) this=%p", timestamp, this);
+ mTimestamp = timestamp;
+}
+
+int64_t
+WidevineVideoFrame::Timestamp() const
+{
+ return mTimestamp;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
new file mode 100644
index 000000000..96d4f20f8
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineVideoFrame_h_
+#define WidevineVideoFrame_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include <vector>
+
+namespace mozilla {
+
+class WidevineVideoFrame : public cdm::VideoFrame {
+public:
+ WidevineVideoFrame();
+ WidevineVideoFrame(WidevineVideoFrame&& other);
+ ~WidevineVideoFrame();
+
+ void SetFormat(cdm::VideoFormat aFormat) override;
+ cdm::VideoFormat Format() const override;
+
+ void SetSize(cdm::Size aSize) override;
+ cdm::Size Size() const override;
+
+ void SetFrameBuffer(cdm::Buffer* aFrameBuffer) override;
+ cdm::Buffer* FrameBuffer() override;
+
+ void SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset) override;
+ uint32_t PlaneOffset(cdm::VideoFrame::VideoPlane aPlane) override;
+
+ void SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride) override;
+ uint32_t Stride(cdm::VideoFrame::VideoPlane aPlane) override;
+
+ void SetTimestamp(int64_t aTimestamp) override;
+ int64_t Timestamp() const override;
+
+protected:
+ cdm::VideoFormat mFormat;
+ cdm::Size mSize;
+ cdm::Buffer* mBuffer;
+ uint32_t mPlaneOffsets[kMaxPlanes];
+ uint32_t mPlaneStrides[kMaxPlanes];
+ int64_t mTimestamp;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module.h b/dom/media/gmp/widevine-adapter/content_decryption_module.h
new file mode 100644
index 000000000..0539135fb
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module.h
@@ -0,0 +1,1278 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_H_
+
+#include "content_decryption_module_export.h"
+
+#if defined(_MSC_VER)
+typedef unsigned char uint8_t;
+typedef unsigned int uint32_t;
+typedef int int32_t;
+typedef __int64 int64_t;
+#else
+#include <stdint.h>
+#endif
+
+// Define CDM_CLASS_API to export class types. We have to add visibility
+// attributes to make sure virtual tables in CDM consumer and CDM implementation
+// are the same. Generally, it was always a good idea, as there're no guarantees
+// about that for the internal symbols, but it has only become a practical issue
+// after introduction of LTO devirtualization. See more details on
+// https://crbug.com/609564#c35
+#if defined(_WIN32)
+#if defined(__clang__)
+#define CDM_CLASS_API [[clang::lto_visibility_public]]
+#else
+#define CDM_CLASS_API
+#endif
+#else // defined(_WIN32)
+#define CDM_CLASS_API __attribute__((visibility("default")))
+#endif // defined(_WIN32)
+
+// The version number must be rolled when the exported functions are updated!
+// If the CDM and the adapter use different versions of these functions, the
+// adapter will fail to load or crash!
+#define CDM_MODULE_VERSION 4
+
+// Build the versioned entrypoint name.
+// The extra macros are necessary to expand version to an actual value.
+#define INITIALIZE_CDM_MODULE \
+ BUILD_ENTRYPOINT(InitializeCdmModule, CDM_MODULE_VERSION)
+#define BUILD_ENTRYPOINT(name, version) \
+ BUILD_ENTRYPOINT_NO_EXPANSION(name, version)
+#define BUILD_ENTRYPOINT_NO_EXPANSION(name, version) name##_##version
+
+extern "C" {
+CDM_API void INITIALIZE_CDM_MODULE();
+
+CDM_API void DeinitializeCdmModule();
+
+// Returns a pointer to the requested CDM Host interface upon success.
+// Returns NULL if the requested CDM Host interface is not supported.
+// The caller should cast the returned pointer to the type matching
+// |host_interface_version|.
+typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data);
+
+// Returns a pointer to the requested CDM upon success.
+// Returns NULL if an error occurs or the requested |cdm_interface_version| or
+// |key_system| is not supported or another error occurs.
+// The caller should cast the returned pointer to the type matching
+// |cdm_interface_version|.
+// Caller retains ownership of arguments and must call Destroy() on the returned
+// object.
+CDM_API void* CreateCdmInstance(
+ int cdm_interface_version,
+ const char* key_system, uint32_t key_system_size,
+ GetCdmHostFunc get_cdm_host_func, void* user_data);
+
+CDM_API const char* GetCdmVersion();
+}
+
+namespace cdm {
+
+class CDM_CLASS_API AudioFrames;
+class CDM_CLASS_API DecryptedBlock;
+class CDM_CLASS_API VideoFrame;
+
+class CDM_CLASS_API Host_8;
+class CDM_CLASS_API Host_9;
+
+enum Status {
+ kSuccess = 0,
+ kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample.
+ kNoKey, // The required decryption key is not available.
+ kInitializationError, // Initialization error.
+ kDecryptError, // Decryption failed.
+ kDecodeError, // Error decoding audio or video.
+ kDeferredInitialization // Decoder is not ready for initialization.
+};
+
+// This must at least contain the exceptions defined in the spec:
+// https://w3c.github.io/encrypted-media/#exceptions
+// The following starts with the list of DOM4 exceptions from:
+// http://www.w3.org/TR/dom/#domexception
+// Some DOM4 exceptions are not included as they are not expected to be used.
+// Should only be used on Host_8 and before.
+enum Error {
+ kNotSupportedError = 9,
+ kInvalidStateError = 11,
+ kInvalidAccessError = 15,
+ kQuotaExceededError = 22,
+
+ // Additional exceptions that do not have assigned codes.
+ // There are other non-EME-specific values, not included in this list.
+ kUnknownError = 30,
+
+ // Additional values from previous EME versions. They currently have no
+ // matching DOMException.
+ kClientError = 100,
+ kOutputError = 101
+};
+
+// Exceptions used by the CDM to reject promises.
+// https://w3c.github.io/encrypted-media/#exceptions
+enum Exception {
+ kExceptionTypeError,
+ kExceptionNotSupportedError,
+ kExceptionInvalidStateError,
+ kExceptionQuotaExceededError
+};
+
+// Time is defined as the number of seconds since the Epoch
+// (00:00:00 UTC, January 1, 1970), not including any added leap second.
+// Also see Time definition in spec: https://w3c.github.io/encrypted-media/#time
+// Note that Time is defined in millisecond accuracy in the spec but in second
+// accuracy here.
+typedef double Time;
+
+// An input buffer can be split into several continuous subsamples.
+// A SubsampleEntry specifies the number of clear and cipher bytes in each
+// subsample. For example, the following buffer has three subsamples:
+//
+// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
+// | clear1 | cipher1 | clear2 | cipher2 | clear3 | cipher3 |
+//
+// For decryption, all of the cipher bytes in a buffer should be concatenated
+// (in the subsample order) into a single logical stream. The clear bytes should
+// not be considered as part of decryption.
+//
+// Stream to decrypt: | cipher1 | cipher2 | cipher3 |
+// Decrypted stream: | decrypted1| decrypted2 | decrypted3 |
+//
+// After decryption, the decrypted bytes should be copied over the position
+// of the corresponding cipher bytes in the original buffer to form the output
+// buffer. Following the above example, the decrypted buffer should be:
+//
+// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
+// | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 |
+//
+struct SubsampleEntry {
+ SubsampleEntry(uint32_t clear_bytes, uint32_t cipher_bytes)
+ : clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {}
+
+ uint32_t clear_bytes;
+ uint32_t cipher_bytes;
+};
+
+// Represents an input buffer to be decrypted (and possibly decoded). It does
+// not own any pointers in this struct. If |iv_size| = 0, the data is
+// unencrypted.
+struct InputBuffer {
+ InputBuffer()
+ : data(nullptr),
+ data_size(0),
+ key_id(nullptr),
+ key_id_size(0),
+ iv(nullptr),
+ iv_size(0),
+ subsamples(nullptr),
+ num_subsamples(0),
+ timestamp(0) {}
+
+ const uint8_t* data; // Pointer to the beginning of the input data.
+ uint32_t data_size; // Size (in bytes) of |data|.
+
+ const uint8_t* key_id; // Key ID to identify the decryption key.
+ uint32_t key_id_size; // Size (in bytes) of |key_id|.
+
+ const uint8_t* iv; // Initialization vector.
+ uint32_t iv_size; // Size (in bytes) of |iv|.
+
+ const struct SubsampleEntry* subsamples;
+ uint32_t num_subsamples; // Number of subsamples in |subsamples|.
+
+ int64_t timestamp; // Presentation timestamp in microseconds.
+};
+
+struct AudioDecoderConfig {
+ enum AudioCodec {
+ kUnknownAudioCodec = 0,
+ kCodecVorbis,
+ kCodecAac
+ };
+
+ AudioDecoderConfig()
+ : codec(kUnknownAudioCodec),
+ channel_count(0),
+ bits_per_channel(0),
+ samples_per_second(0),
+ extra_data(nullptr),
+ extra_data_size(0) {}
+
+ AudioCodec codec;
+ int32_t channel_count;
+ int32_t bits_per_channel;
+ int32_t samples_per_second;
+
+ // Optional byte data required to initialize audio decoders, such as the
+ // vorbis setup header.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+};
+
+// Supported sample formats for AudioFrames.
+enum AudioFormat {
+ kUnknownAudioFormat = 0, // Unknown format value. Used for error reporting.
+ kAudioFormatU8, // Interleaved unsigned 8-bit w/ bias of 128.
+ kAudioFormatS16, // Interleaved signed 16-bit.
+ kAudioFormatS32, // Interleaved signed 32-bit.
+ kAudioFormatF32, // Interleaved float 32-bit.
+ kAudioFormatPlanarS16, // Signed 16-bit planar.
+ kAudioFormatPlanarF32, // Float 32-bit planar.
+};
+
+// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php
+// Values are chosen to be consistent with Chromium's VideoPixelFormat values.
+enum VideoFormat {
+ kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting.
+ kYv12 = 1, // 12bpp YVU planar 1x1 Y, 2x2 VU samples.
+ kI420 = 2, // 12bpp YUV planar 1x1 Y, 2x2 UV samples.
+
+ // In the following formats, each sample uses 16-bit in storage, while the
+ // sample value is stored in the least significant N bits where N is
+ // specified by the number after "P". For example, for YUV420P9, each Y, U,
+ // and V sample is stored in the least significant 9 bits in a 2-byte block.
+ kYUV420P9 = 16,
+ kYUV420P10 = 17,
+ kYUV422P9 = 18,
+ kYUV422P10 = 19,
+ kYUV444P9 = 20,
+ kYUV444P10 = 21,
+ kYUV420P12 = 22,
+ kYUV422P12 = 23,
+ kYUV444P12 = 24,
+};
+
+struct Size {
+ Size() : width(0), height(0) {}
+ Size(int32_t width, int32_t height) : width(width), height(height) {}
+
+ int32_t width;
+ int32_t height;
+};
+
+struct VideoDecoderConfig {
+ enum VideoCodec {
+ kUnknownVideoCodec = 0,
+ kCodecVp8,
+ kCodecH264,
+ kCodecVp9
+ };
+
+ enum VideoCodecProfile {
+ kUnknownVideoCodecProfile = 0,
+ kProfileNotNeeded,
+ kH264ProfileBaseline,
+ kH264ProfileMain,
+ kH264ProfileExtended,
+ kH264ProfileHigh,
+ kH264ProfileHigh10,
+ kH264ProfileHigh422,
+ kH264ProfileHigh444Predictive,
+ // VP9 Profiles are only passed in starting from CDM_9.
+ kVP9Profile0,
+ kVP9Profile1,
+ kVP9Profile2,
+ kVP9Profile3
+ };
+
+ VideoDecoderConfig()
+ : codec(kUnknownVideoCodec),
+ profile(kUnknownVideoCodecProfile),
+ format(kUnknownVideoFormat),
+ extra_data(nullptr),
+ extra_data_size(0) {}
+
+ VideoCodec codec;
+ VideoCodecProfile profile;
+ VideoFormat format;
+
+ // Width and height of video frame immediately post-decode. Not all pixels
+ // in this region are valid.
+ Size coded_size;
+
+ // Optional byte data required to initialize video decoders, such as H.264
+ // AAVC data.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+};
+
+enum StreamType {
+ kStreamTypeAudio = 0,
+ kStreamTypeVideo = 1
+};
+
+// Structure provided to ContentDecryptionModule::OnPlatformChallengeResponse()
+// after a platform challenge was initiated via Host::SendPlatformChallenge().
+// All values will be NULL / zero in the event of a challenge failure.
+struct PlatformChallengeResponse {
+ // |challenge| provided during Host::SendPlatformChallenge() combined with
+ // nonce data and signed with the platform's private key.
+ const uint8_t* signed_data;
+ uint32_t signed_data_length;
+
+ // RSASSA-PKCS1-v1_5-SHA256 signature of the |signed_data| block.
+ const uint8_t* signed_data_signature;
+ uint32_t signed_data_signature_length;
+
+ // X.509 device specific certificate for the |service_id| requested.
+ const uint8_t* platform_key_certificate;
+ uint32_t platform_key_certificate_length;
+};
+
+// Used when passing arrays of binary data. Does not own the referenced data.
+struct BinaryData {
+ BinaryData() : data(nullptr), length(0) {}
+ const uint8_t* data;
+ uint32_t length;
+};
+
+// The current status of the associated key. The valid types are defined in the
+// spec: https://w3c.github.io/encrypted-media/#idl-def-MediaKeyStatus
+enum KeyStatus {
+ kUsable = 0,
+ kInternalError = 1,
+ kExpired = 2,
+ kOutputRestricted = 3,
+ kOutputDownscaled = 4,
+ kStatusPending = 5,
+ kReleased = 6
+};
+
+// Used when passing arrays of key information. Does not own the referenced
+// data. |system_code| is an additional error code for unusable keys and
+// should be 0 when |status| == kUsable.
+struct KeyInformation {
+ KeyInformation()
+ : key_id(nullptr),
+ key_id_size(0),
+ status(kInternalError),
+ system_code(0) {}
+ const uint8_t* key_id;
+ uint32_t key_id_size;
+ KeyStatus status;
+ uint32_t system_code;
+};
+
+// Supported output protection methods for use with EnableOutputProtection() and
+// returned by OnQueryOutputProtectionStatus().
+enum OutputProtectionMethods {
+ kProtectionNone = 0,
+ kProtectionHDCP = 1 << 0
+};
+
+// Connected output link types returned by OnQueryOutputProtectionStatus().
+enum OutputLinkTypes {
+ kLinkTypeNone = 0,
+ kLinkTypeUnknown = 1 << 0,
+ kLinkTypeInternal = 1 << 1,
+ kLinkTypeVGA = 1 << 2,
+ kLinkTypeHDMI = 1 << 3,
+ kLinkTypeDVI = 1 << 4,
+ kLinkTypeDisplayPort = 1 << 5,
+ kLinkTypeNetwork = 1 << 6
+};
+
+// Result of the QueryOutputProtectionStatus() call.
+enum QueryResult {
+ kQuerySucceeded = 0,
+ kQueryFailed
+};
+
+// The Initialization Data Type. The valid types are defined in the spec:
+// http://w3c.github.io/encrypted-media/initdata-format-registry.html#registry
+enum InitDataType {
+ kCenc = 0,
+ kKeyIds = 1,
+ kWebM = 2
+};
+
+// The type of session to create. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/#idl-def-SessionType
+enum SessionType {
+ kTemporary = 0,
+ kPersistentLicense = 1,
+ kPersistentKeyRelease = 2
+};
+
+// The type of the message event. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/#idl-def-MediaKeyMessageType
+enum MessageType {
+ kLicenseRequest = 0,
+ kLicenseRenewal = 1,
+ kLicenseRelease = 2
+};
+
+enum HdcpVersion {
+ kHdcpVersionNone,
+ kHdcpVersion1_0,
+ kHdcpVersion1_1,
+ kHdcpVersion1_2,
+ kHdcpVersion1_3,
+ kHdcpVersion1_4,
+ kHdcpVersion2_0,
+ kHdcpVersion2_1,
+ kHdcpVersion2_2
+};
+
+struct Policy {
+ Policy() : min_hdcp_version(kHdcpVersionNone) {}
+
+ HdcpVersion min_hdcp_version;
+};
+
+// FileIO interface provides a way for the CDM to store data in a file in
+// persistent storage. This interface aims only at providing basic read/write
+// capabilities and should not be used as a full fledged file IO API.
+// Each CDM and origin (e.g. HTTPS, "foo.example.com", 443) combination has
+// its own persistent storage. All instances of a given CDM associated with a
+// given origin share the same persistent storage.
+// Note to implementors of this interface:
+// Per-origin storage and the ability for users to clear it are important.
+// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo.
+class CDM_CLASS_API FileIO {
+ public:
+ // Opens the file with |file_name| for read and write.
+ // FileIOClient::OnOpenComplete() will be called after the opening
+ // operation finishes.
+ // - When the file is opened by a CDM instance, it will be classified as "in
+ // use". In this case other CDM instances in the same domain may receive
+ // kInUse status when trying to open it.
+ // - |file_name| must only contain letters (A-Za-z), digits(0-9), or "._-".
+ // It must not start with an underscore ('_'), and must be at least 1
+ // character and no more than 256 characters long.
+ virtual void Open(const char* file_name, uint32_t file_name_size) = 0;
+
+ // Reads the contents of the file. FileIOClient::OnReadComplete() will be
+ // called with the read status. Read() should not be called if a previous
+ // Read() or Write() call is still pending; otherwise OnReadComplete() will
+ // be called with kInUse.
+ virtual void Read() = 0;
+
+ // Writes |data_size| bytes of |data| into the file.
+ // FileIOClient::OnWriteComplete() will be called with the write status.
+ // All existing contents in the file will be overwritten. Calling Write() with
+ // NULL |data| will clear all contents in the file. Write() should not be
+ // called if a previous Write() or Read() call is still pending; otherwise
+ // OnWriteComplete() will be called with kInUse.
+ virtual void Write(const uint8_t* data, uint32_t data_size) = 0;
+
+ // Closes the file if opened, destroys this FileIO object and releases any
+ // resources allocated. The CDM must call this method when it finished using
+ // this object. A FileIO object must not be used after Close() is called.
+ virtual void Close() = 0;
+
+ protected:
+ FileIO() {}
+ virtual ~FileIO() {}
+};
+
+// Responses to FileIO calls. All responses will be called asynchronously.
+// When kError is returned, the FileIO object could be in an error state. All
+// following calls (other than Close()) could return kError. The CDM should
+// still call Close() to destroy the FileIO object.
+class CDM_CLASS_API FileIOClient {
+ public:
+ enum Status {
+ kSuccess = 0,
+ kInUse,
+ kError
+ };
+
+ // Response to a FileIO::Open() call with the open |status|.
+ virtual void OnOpenComplete(Status status) = 0;
+
+ // Response to a FileIO::Read() call to provide |data_size| bytes of |data|
+ // read from the file.
+ // - kSuccess indicates that all contents of the file has been successfully
+ // read. In this case, 0 |data_size| means that the file is empty.
+ // - kInUse indicates that there are other read/write operations pending.
+ // - kError indicates read failure, e.g. the storage is not open or cannot be
+ // fully read.
+ virtual void OnReadComplete(Status status,
+ const uint8_t* data, uint32_t data_size) = 0;
+
+ // Response to a FileIO::Write() call.
+ // - kSuccess indicates that all the data has been written into the file
+ // successfully.
+ // - kInUse indicates that there are other read/write operations pending.
+ // - kError indicates write failure, e.g. the storage is not open or cannot be
+ // fully written. Upon write failure, the contents of the file should be
+ // regarded as corrupt and should not used.
+ virtual void OnWriteComplete(Status status) = 0;
+
+ protected:
+ FileIOClient() {}
+ virtual ~FileIOClient() {}
+};
+
+// ContentDecryptionModule interface that all CDMs need to implement.
+// The interface is versioned for backward compatibility.
+// Note: ContentDecryptionModule implementations must use the allocator
+// provided in CreateCdmInstance() to allocate any Buffer that needs to
+// be passed back to the caller. Implementations must call Buffer::Destroy()
+// when a Buffer is created that will never be returned to the caller.
+class CDM_CLASS_API ContentDecryptionModule_8 {
+ public:
+ static const int kVersion = 8;
+ typedef Host_8 Host;
+
+ // Initializes the CDM instance, providing information about permitted
+ // functionalities.
+ // If |allow_distinctive_identifier| is false, messages from the CDM,
+ // such as message events, must not contain a Distinctive Identifier,
+ // even in an encrypted form.
+ // If |allow_persistent_state| is false, the CDM must not attempt to
+ // persist state. Calls to CreateFileIO() will fail.
+ virtual void Initialize(bool allow_distinctive_identifier,
+ bool allow_persistent_state) = 0;
+
+ // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(),
+ // UpdateSession(), CloseSession(), and RemoveSession() all accept a
+ // |promise_id|, which must be passed to the completion Host method
+ // (e.g. Host::OnResolveNewSessionPromise()).
+
+ // Provides a server certificate to be used to encrypt messages to the
+ // license server. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void SetServerCertificate(uint32_t promise_id,
+ const uint8_t* server_certificate_data,
+ uint32_t server_certificate_data_size) = 0;
+
+ // Creates a session given |session_type|, |init_data_type|, and |init_data|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise().
+ virtual void CreateSessionAndGenerateRequest(uint32_t promise_id,
+ SessionType session_type,
+ InitDataType init_data_type,
+ const uint8_t* init_data,
+ uint32_t init_data_size) = 0;
+
+ // Loads the session of type |session_type| specified by |session_id|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise(). If the session is not found, call
+ // Host::OnResolveNewSessionPromise() with session_id = NULL.
+ virtual void LoadSession(uint32_t promise_id,
+ SessionType session_type,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Updates the session with |response|. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void UpdateSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size,
+ const uint8_t* response,
+ uint32_t response_size) = 0;
+
+ // Requests that the CDM close the session. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request
+ // has been processed. This may be before the session is closed. Once the
+ // session is closed, Host::OnSessionClosed() must also be called.
+ virtual void CloseSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Removes any stored session data associated with this session. Will only be
+ // called for persistent sessions. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has
+ // been processed.
+ virtual void RemoveSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Performs scheduled operation with |context| when the timer fires.
+ virtual void TimerExpired(void* context) = 0;
+
+ // Decrypts the |encrypted_buffer|.
+ //
+ // Returns kSuccess if decryption succeeded, in which case the callee
+ // should have filled the |decrypted_buffer| and passed the ownership of
+ // |data| in |decrypted_buffer| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kDecryptError if any other error happened.
+ // If the return value is not kSuccess, |decrypted_buffer| should be ignored
+ // by the caller.
+ virtual Status Decrypt(const InputBuffer& encrypted_buffer,
+ DecryptedBlock* decrypted_buffer) = 0;
+
+ // Initializes the CDM audio decoder with |audio_decoder_config|. This
+ // function must be called before DecryptAndDecodeSamples() is called.
+ //
+ // Returns kSuccess if the |audio_decoder_config| is supported and the CDM
+ // audio decoder is successfully initialized.
+ // Returns kSessionError if |audio_decoder_config| is not supported. The CDM
+ // may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeAudioDecoder(
+ const AudioDecoderConfig& audio_decoder_config) = 0;
+
+ // Initializes the CDM video decoder with |video_decoder_config|. This
+ // function must be called before DecryptAndDecodeFrame() is called.
+ //
+ // Returns kSuccess if the |video_decoder_config| is supported and the CDM
+ // video decoder is successfully initialized.
+ // Returns kSessionError if |video_decoder_config| is not supported. The CDM
+ // may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeVideoDecoder(
+ const VideoDecoderConfig& video_decoder_config) = 0;
+
+ // De-initializes the CDM decoder and sets it to an uninitialized state. The
+ // caller can initialize the decoder again after this call to re-initialize
+ // it. This can be used to reconfigure the decoder if the configuration
+ // changes.
+ virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
+
+ // Resets the CDM decoder to an initialized clean state. All internal buffers
+ // MUST be flushed.
+ virtual void ResetDecoder(StreamType decoder_type) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
+ // |video_frame|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |video_frame| (|format| == kEmptyVideoFrame) is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled the |video_frame| and passed the ownership of
+ // |frame_buffer| in |video_frame| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // a decoded frame (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |video_frame| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer,
+ VideoFrame* video_frame) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
+ // |audio_frames|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |audio_frames| is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled |audio_frames| and passed the ownership of
+ // |data| in |audio_frames| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // audio samples (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |audio_frames| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer,
+ AudioFrames* audio_frames) = 0;
+
+ // Called by the host after a platform challenge was initiated via
+ // Host::SendPlatformChallenge().
+ virtual void OnPlatformChallengeResponse(
+ const PlatformChallengeResponse& response) = 0;
+
+ // Called by the host after a call to Host::QueryOutputProtectionStatus(). The
+ // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask|
+ // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed,
+ // then |link_mask| and |output_protection_mask| are undefined and should
+ // be ignored.
+ virtual void OnQueryOutputProtectionStatus(
+ QueryResult result,
+ uint32_t link_mask,
+ uint32_t output_protection_mask) = 0;
+
+ // Destroys the object in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ protected:
+ ContentDecryptionModule_8() {}
+ virtual ~ContentDecryptionModule_8() {}
+};
+
+// ContentDecryptionModule interface that all CDMs need to implement.
+// The interface is versioned for backward compatibility.
+// Note: ContentDecryptionModule implementations must use the allocator
+// provided in CreateCdmInstance() to allocate any Buffer that needs to
+// be passed back to the caller. Implementations must call Buffer::Destroy()
+// when a Buffer is created that will never be returned to the caller.
+class CDM_CLASS_API ContentDecryptionModule_9 {
+ public:
+ static const int kVersion = 9;
+ typedef Host_9 Host;
+
+ // Initializes the CDM instance, providing information about permitted
+ // functionalities.
+ // If |allow_distinctive_identifier| is false, messages from the CDM,
+ // such as message events, must not contain a Distinctive Identifier,
+ // even in an encrypted form.
+ // If |allow_persistent_state| is false, the CDM must not attempt to
+ // persist state. Calls to CreateFileIO() will fail.
+ virtual void Initialize(bool allow_distinctive_identifier,
+ bool allow_persistent_state) = 0;
+
+ // Gets the key status if the CDM has a hypothetical key with the |policy|.
+ // The CDM must respond by calling either Host::OnResolveKeyStatusPromise()
+ // with the result key status or Host::OnRejectPromise() if an unexpected
+ // error happened or this method is not supported.
+ virtual void GetStatusForPolicy(uint32_t promise_id,
+ const Policy& policy) = 0;
+
+ // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(),
+ // UpdateSession(), CloseSession(), and RemoveSession() all accept a
+ // |promise_id|, which must be passed to the completion Host method
+ // (e.g. Host::OnResolveNewSessionPromise()).
+
+ // Provides a server certificate to be used to encrypt messages to the
+ // license server. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void SetServerCertificate(uint32_t promise_id,
+ const uint8_t* server_certificate_data,
+ uint32_t server_certificate_data_size) = 0;
+
+ // Creates a session given |session_type|, |init_data_type|, and |init_data|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise().
+ virtual void CreateSessionAndGenerateRequest(uint32_t promise_id,
+ SessionType session_type,
+ InitDataType init_data_type,
+ const uint8_t* init_data,
+ uint32_t init_data_size) = 0;
+
+ // Loads the session of type |session_type| specified by |session_id|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise(). If the session is not found, call
+ // Host::OnResolveNewSessionPromise() with session_id = NULL.
+ virtual void LoadSession(uint32_t promise_id,
+ SessionType session_type,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Updates the session with |response|. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void UpdateSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size,
+ const uint8_t* response,
+ uint32_t response_size) = 0;
+
+ // Requests that the CDM close the session. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request
+ // has been processed. This may be before the session is closed. Once the
+ // session is closed, Host::OnSessionClosed() must also be called.
+ virtual void CloseSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Removes any stored session data associated with this session. Will only be
+ // called for persistent sessions. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has
+ // been processed.
+ virtual void RemoveSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Performs scheduled operation with |context| when the timer fires.
+ virtual void TimerExpired(void* context) = 0;
+
+ // Decrypts the |encrypted_buffer|.
+ //
+ // Returns kSuccess if decryption succeeded, in which case the callee
+ // should have filled the |decrypted_buffer| and passed the ownership of
+ // |data| in |decrypted_buffer| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kDecryptError if any other error happened.
+ // If the return value is not kSuccess, |decrypted_buffer| should be ignored
+ // by the caller.
+ virtual Status Decrypt(const InputBuffer& encrypted_buffer,
+ DecryptedBlock* decrypted_buffer) = 0;
+
+ // Initializes the CDM audio decoder with |audio_decoder_config|. This
+ // function must be called before DecryptAndDecodeSamples() is called.
+ //
+ // Returns kSuccess if the |audio_decoder_config| is supported and the CDM
+ // audio decoder is successfully initialized.
+ // Returns kInitializationError if |audio_decoder_config| is not supported.
+ // The CDM may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeAudioDecoder(
+ const AudioDecoderConfig& audio_decoder_config) = 0;
+
+ // Initializes the CDM video decoder with |video_decoder_config|. This
+ // function must be called before DecryptAndDecodeFrame() is called.
+ //
+ // Returns kSuccess if the |video_decoder_config| is supported and the CDM
+ // video decoder is successfully initialized.
+ // Returns kInitializationError if |video_decoder_config| is not supported.
+ // The CDM may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeVideoDecoder(
+ const VideoDecoderConfig& video_decoder_config) = 0;
+
+ // De-initializes the CDM decoder and sets it to an uninitialized state. The
+ // caller can initialize the decoder again after this call to re-initialize
+ // it. This can be used to reconfigure the decoder if the configuration
+ // changes.
+ virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
+
+ // Resets the CDM decoder to an initialized clean state. All internal buffers
+ // MUST be flushed.
+ virtual void ResetDecoder(StreamType decoder_type) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
+ // |video_frame|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |video_frame| (|format| == kEmptyVideoFrame) is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled the |video_frame| and passed the ownership of
+ // |frame_buffer| in |video_frame| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // a decoded frame (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |video_frame| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer,
+ VideoFrame* video_frame) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
+ // |audio_frames|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |audio_frames| is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled |audio_frames| and passed the ownership of
+ // |data| in |audio_frames| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // audio samples (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |audio_frames| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer,
+ AudioFrames* audio_frames) = 0;
+
+ // Called by the host after a platform challenge was initiated via
+ // Host::SendPlatformChallenge().
+ virtual void OnPlatformChallengeResponse(
+ const PlatformChallengeResponse& response) = 0;
+
+ // Called by the host after a call to Host::QueryOutputProtectionStatus(). The
+ // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask|
+ // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed,
+ // then |link_mask| and |output_protection_mask| are undefined and should
+ // be ignored.
+ virtual void OnQueryOutputProtectionStatus(
+ QueryResult result,
+ uint32_t link_mask,
+ uint32_t output_protection_mask) = 0;
+
+ // Called by the host after a call to Host::RequestStorageId(). If the
+ // version of the storage ID requested is available, |storage_id| and
+ // |storage_id_size| are set appropriately. |version| will be the same as
+ // what was requested, unless 0 (latest) was requested, in which case
+ // |version| will be the actual version number for the |storage_id| returned.
+ // If the requested version is not available, null/zero will be provided as
+ // |storage_id| and |storage_id_size|, respectively, and |version| should be
+ // ignored.
+ virtual void OnStorageId(uint32_t version,
+ const uint8_t* storage_id,
+ uint32_t storage_id_size) = 0;
+
+ // Destroys the object in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ protected:
+ ContentDecryptionModule_9() {}
+ virtual ~ContentDecryptionModule_9() {}
+};
+
+typedef ContentDecryptionModule_9 ContentDecryptionModule;
+
+// Represents a buffer created by Allocator implementations.
+class CDM_CLASS_API Buffer {
+ public:
+ // Destroys the buffer in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ virtual uint32_t Capacity() const = 0;
+ virtual uint8_t* Data() = 0;
+ virtual void SetSize(uint32_t size) = 0;
+ virtual uint32_t Size() const = 0;
+
+ protected:
+ Buffer() {}
+ virtual ~Buffer() {}
+
+ private:
+ Buffer(const Buffer&);
+ void operator=(const Buffer&);
+};
+
+class CDM_CLASS_API Host_8 {
+ public:
+ static const int kVersion = 8;
+
+ // Returns a Buffer* containing non-zero members upon success, or NULL on
+ // failure. The caller owns the Buffer* after this call. The buffer is not
+ // guaranteed to be zero initialized. The capacity of the allocated Buffer
+ // is guaranteed to be not less than |capacity|.
+ virtual Buffer* Allocate(uint32_t capacity) = 0;
+
+ // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms|
+ // from now with |context|.
+ virtual void SetTimer(int64_t delay_ms, void* context) = 0;
+
+ // Returns the current wall time.
+ virtual Time GetCurrentWallTime() = 0;
+
+ // Called by the CDM when a session is created or loaded and the value for the
+ // MediaKeySession's sessionId attribute is available (|session_id|).
+ // This must be called before OnSessionMessage() or
+ // OnSessionKeysChange() is called for the same session. |session_id_size|
+ // should not include null termination.
+ // When called in response to LoadSession(), the |session_id| must be the
+ // same as the |session_id| passed in LoadSession(), or NULL if the
+ // session could not be loaded.
+ virtual void OnResolveNewSessionPromise(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when a session is updated or released.
+ virtual void OnResolvePromise(uint32_t promise_id) = 0;
+
+ // Called by the CDM when an error occurs as a result of one of the
+ // ContentDecryptionModule calls that accept a |promise_id|.
+ // |error| must be specified, |error_message| and |system_code|
+ // are optional. |error_message_size| should not include null termination.
+ virtual void OnRejectPromise(uint32_t promise_id,
+ Error error,
+ uint32_t system_code,
+ const char* error_message,
+ uint32_t error_message_size) = 0;
+
+ // Called by the CDM when it has a message for session |session_id|.
+ // Size parameters should not include null termination.
+ // |legacy_destination_url| is only for supporting the prefixed EME API and
+ // is ignored by unprefixed EME. It should only be non-null if |message_type|
+ // is kLicenseRenewal.
+ virtual void OnSessionMessage(const char* session_id,
+ uint32_t session_id_size,
+ MessageType message_type,
+ const char* message,
+ uint32_t message_size,
+ const char* legacy_destination_url,
+ uint32_t legacy_destination_url_length) = 0;
+
+ // Called by the CDM when there has been a change in keys or their status for
+ // session |session_id|. |has_additional_usable_key| should be set if a
+ // key is newly usable (e.g. new key available, previously expired key has
+ // been renewed, etc.) and the browser should attempt to resume playback.
+ // |key_ids| is the list of key ids for this session along with their
+ // current status. |key_ids_count| is the number of entries in |key_ids|.
+ // Size parameter for |session_id| should not include null termination.
+ virtual void OnSessionKeysChange(const char* session_id,
+ uint32_t session_id_size,
+ bool has_additional_usable_key,
+ const KeyInformation* keys_info,
+ uint32_t keys_info_count) = 0;
+
+ // Called by the CDM when there has been a change in the expiration time for
+ // session |session_id|. This can happen as the result of an Update() call
+ // or some other event. If this happens as a result of a call to Update(),
+ // it must be called before resolving the Update() promise. |new_expiry_time|
+ // represents the time after which the key(s) in the session will no longer
+ // be usable for decryption. It can be 0 if no such time exists or if the
+ // license explicitly never expires. Size parameter should not include null
+ // termination.
+ virtual void OnExpirationChange(const char* session_id,
+ uint32_t session_id_size,
+ Time new_expiry_time) = 0;
+
+ // Called by the CDM when session |session_id| is closed. Size
+ // parameter should not include null termination.
+ virtual void OnSessionClosed(const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when an error occurs in session |session_id|
+ // unrelated to one of the ContentDecryptionModule calls that accept a
+ // |promise_id|. |error| must be specified, |error_message| and
+ // |system_code| are optional. Length parameters should not include null
+ // termination.
+ // Note:
+ // - This method is only for supporting prefixed EME API.
+ // - This method will be ignored by unprefixed EME. All errors reported
+ // in this method should probably also be reported by one of other methods.
+ virtual void OnLegacySessionError(
+ const char* session_id, uint32_t session_id_length,
+ Error error,
+ uint32_t system_code,
+ const char* error_message, uint32_t error_message_length) = 0;
+
+ // The following are optional methods that may not be implemented on all
+ // platforms.
+
+ // Sends a platform challenge for the given |service_id|. |challenge| is at
+ // most 256 bits of data to be signed. Once the challenge has been completed,
+ // the host will call ContentDecryptionModule::OnPlatformChallengeResponse()
+ // with the signed challenge response and platform certificate. Size
+ // parameters should not include null termination.
+ virtual void SendPlatformChallenge(const char* service_id,
+ uint32_t service_id_size,
+ const char* challenge,
+ uint32_t challenge_size) = 0;
+
+ // Attempts to enable output protection (e.g. HDCP) on the display link. The
+ // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No
+ // status callback is issued, the CDM must call QueryOutputProtectionStatus()
+ // periodically to ensure the desired protections are applied.
+ virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0;
+
+ // Requests the current output protection status. Once the host has the status
+ // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus().
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // Must be called by the CDM if it returned kDeferredInitialization during
+ // InitializeAudioDecoder() or InitializeVideoDecoder().
+ virtual void OnDeferredInitializationDone(StreamType stream_type,
+ Status decoder_status) = 0;
+
+ // Creates a FileIO object from the host to do file IO operation. Returns NULL
+ // if a FileIO object cannot be obtained. Once a valid FileIO object is
+ // returned, |client| must be valid until FileIO::Close() is called. The
+ // CDM can call this method multiple times to operate on different files.
+ virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
+
+ protected:
+ Host_8() {}
+ virtual ~Host_8() {}
+};
+
+class CDM_CLASS_API Host_9 {
+ public:
+ static const int kVersion = 9;
+
+ // Returns a Buffer* containing non-zero members upon success, or NULL on
+ // failure. The caller owns the Buffer* after this call. The buffer is not
+ // guaranteed to be zero initialized. The capacity of the allocated Buffer
+ // is guaranteed to be not less than |capacity|.
+ virtual Buffer* Allocate(uint32_t capacity) = 0;
+
+ // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms|
+ // from now with |context|.
+ virtual void SetTimer(int64_t delay_ms, void* context) = 0;
+
+ // Returns the current wall time.
+ virtual Time GetCurrentWallTime() = 0;
+
+ // Called by the CDM when a key status is available in response to
+ // GetStatusForPolicy().
+ virtual void OnResolveKeyStatusPromise(uint32_t promise_id,
+ KeyStatus key_status) = 0;
+
+ // Called by the CDM when a session is created or loaded and the value for the
+ // MediaKeySession's sessionId attribute is available (|session_id|).
+ // This must be called before OnSessionMessage() or
+ // OnSessionKeysChange() is called for the same session. |session_id_size|
+ // should not include null termination.
+ // When called in response to LoadSession(), the |session_id| must be the
+ // same as the |session_id| passed in LoadSession(), or NULL if the
+ // session could not be loaded.
+ virtual void OnResolveNewSessionPromise(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when a session is updated or released.
+ virtual void OnResolvePromise(uint32_t promise_id) = 0;
+
+ // Called by the CDM when an error occurs as a result of one of the
+ // ContentDecryptionModule calls that accept a |promise_id|.
+ // |exception| must be specified. |error_message| and |system_code|
+ // are optional. |error_message_size| should not include null termination.
+ virtual void OnRejectPromise(uint32_t promise_id,
+ Exception exception,
+ uint32_t system_code,
+ const char* error_message,
+ uint32_t error_message_size) = 0;
+
+ // Called by the CDM when it has a message for session |session_id|.
+ // Size parameters should not include null termination.
+ virtual void OnSessionMessage(const char* session_id,
+ uint32_t session_id_size,
+ MessageType message_type,
+ const char* message,
+ uint32_t message_size) = 0;
+
+ // Called by the CDM when there has been a change in keys or their status for
+ // session |session_id|. |has_additional_usable_key| should be set if a
+ // key is newly usable (e.g. new key available, previously expired key has
+ // been renewed, etc.) and the browser should attempt to resume playback.
+ // |key_ids| is the list of key ids for this session along with their
+ // current status. |key_ids_count| is the number of entries in |key_ids|.
+ // Size parameter for |session_id| should not include null termination.
+ virtual void OnSessionKeysChange(const char* session_id,
+ uint32_t session_id_size,
+ bool has_additional_usable_key,
+ const KeyInformation* keys_info,
+ uint32_t keys_info_count) = 0;
+
+ // Called by the CDM when there has been a change in the expiration time for
+ // session |session_id|. This can happen as the result of an Update() call
+ // or some other event. If this happens as a result of a call to Update(),
+ // it must be called before resolving the Update() promise. |new_expiry_time|
+ // represents the time after which the key(s) in the session will no longer
+ // be usable for decryption. It can be 0 if no such time exists or if the
+ // license explicitly never expires. Size parameter should not include null
+ // termination.
+ virtual void OnExpirationChange(const char* session_id,
+ uint32_t session_id_size,
+ Time new_expiry_time) = 0;
+
+ // Called by the CDM when session |session_id| is closed. Size
+ // parameter should not include null termination.
+ virtual void OnSessionClosed(const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // The following are optional methods that may not be implemented on all
+ // platforms.
+
+ // Sends a platform challenge for the given |service_id|. |challenge| is at
+ // most 256 bits of data to be signed. Once the challenge has been completed,
+ // the host will call ContentDecryptionModule::OnPlatformChallengeResponse()
+ // with the signed challenge response and platform certificate. Size
+ // parameters should not include null termination.
+ virtual void SendPlatformChallenge(const char* service_id,
+ uint32_t service_id_size,
+ const char* challenge,
+ uint32_t challenge_size) = 0;
+
+ // Attempts to enable output protection (e.g. HDCP) on the display link. The
+ // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No
+ // status callback is issued, the CDM must call QueryOutputProtectionStatus()
+ // periodically to ensure the desired protections are applied.
+ virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0;
+
+ // Requests the current output protection status. Once the host has the status
+ // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus().
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // Must be called by the CDM if it returned kDeferredInitialization during
+ // InitializeAudioDecoder() or InitializeVideoDecoder().
+ virtual void OnDeferredInitializationDone(StreamType stream_type,
+ Status decoder_status) = 0;
+
+ // Creates a FileIO object from the host to do file IO operation. Returns NULL
+ // if a FileIO object cannot be obtained. Once a valid FileIO object is
+ // returned, |client| must be valid until FileIO::Close() is called. The
+ // CDM can call this method multiple times to operate on different files.
+ virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
+
+ // Requests a specific version of the storage ID. A storage ID is a stable,
+ // device specific ID used by the CDM to securely store persistent data. The
+ // ID will be returned by the host via ContentDecryptionModule::OnStorageId().
+ // If |version| is 0, the latest version will be returned. All |version|s
+ // that are greater than or equal to 0x80000000 are reserved for the CDM and
+ // should not be supported or returned by the host. The CDM must not expose
+ // the ID outside the client device, even in encrypted form.
+ virtual void RequestStorageId(uint32_t version) = 0;
+
+ protected:
+ Host_9() {}
+ virtual ~Host_9() {}
+};
+
+// Represents a decrypted block that has not been decoded.
+class CDM_CLASS_API DecryptedBlock {
+ public:
+ virtual void SetDecryptedBuffer(Buffer* buffer) = 0;
+ virtual Buffer* DecryptedBuffer() = 0;
+
+ // TODO(tomfinegan): Figure out if timestamp is really needed. If it is not,
+ // we can just pass Buffer pointers around.
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual int64_t Timestamp() const = 0;
+
+ protected:
+ DecryptedBlock() {}
+ virtual ~DecryptedBlock() {}
+};
+
+class CDM_CLASS_API VideoFrame {
+ public:
+ enum VideoPlane {
+ kYPlane = 0,
+ kUPlane = 1,
+ kVPlane = 2,
+ kMaxPlanes = 3,
+ };
+
+ virtual void SetFormat(VideoFormat format) = 0;
+ virtual VideoFormat Format() const = 0;
+
+ virtual void SetSize(cdm::Size size) = 0;
+ virtual cdm::Size Size() const = 0;
+
+ virtual void SetFrameBuffer(Buffer* frame_buffer) = 0;
+ virtual Buffer* FrameBuffer() = 0;
+
+ virtual void SetPlaneOffset(VideoPlane plane, uint32_t offset) = 0;
+ virtual uint32_t PlaneOffset(VideoPlane plane) = 0;
+
+ virtual void SetStride(VideoPlane plane, uint32_t stride) = 0;
+ virtual uint32_t Stride(VideoPlane plane) = 0;
+
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual int64_t Timestamp() const = 0;
+
+ protected:
+ VideoFrame() {}
+ virtual ~VideoFrame() {}
+};
+
+// Represents decrypted and decoded audio frames. AudioFrames can contain
+// multiple audio output buffers, which are serialized into this format:
+//
+// |<------------------- serialized audio buffer ------------------->|
+// | int64_t timestamp | int64_t length | length bytes of audio data |
+//
+// For example, with three audio output buffers, the AudioFrames will look
+// like this:
+//
+// |<----------------- AudioFrames ------------------>|
+// | audio buffer 0 | audio buffer 1 | audio buffer 2 |
+class CDM_CLASS_API AudioFrames {
+ public:
+ virtual void SetFrameBuffer(Buffer* buffer) = 0;
+ virtual Buffer* FrameBuffer() = 0;
+
+ // The CDM must call this method, providing a valid format, when providing
+ // frame buffers. Planar data should be stored end to end; e.g.,
+ // |ch1 sample1||ch1 sample2|....|ch1 sample_last||ch2 sample1|...
+ virtual void SetFormat(AudioFormat format) = 0;
+ virtual AudioFormat Format() const = 0;
+
+ protected:
+ AudioFrames() {}
+ virtual ~AudioFrames() {}
+};
+
+} // namespace cdm
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_H_
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module_export.h b/dom/media/gmp/widevine-adapter/content_decryption_module_export.h
new file mode 100644
index 000000000..51d485892
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module_export.h
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
+
+// Define CDM_API so that functionality implemented by the CDM module
+// can be exported to consumers.
+#if defined(_WIN32)
+
+#if defined(CDM_IMPLEMENTATION)
+#define CDM_API __declspec(dllexport)
+#else
+#define CDM_API __declspec(dllimport)
+#endif // defined(CDM_IMPLEMENTATION)
+
+#else // defined(_WIN32)
+#define CDM_API __attribute__((visibility("default")))
+#endif // defined(_WIN32)
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h b/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
new file mode 100644
index 000000000..5df8344e6
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
+
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+
+#include "content_decryption_module_export.h"
+
+#if defined(_MSC_VER)
+typedef unsigned int uint32_t;
+#else
+#include <stdint.h>
+#endif
+
+namespace cdm {
+
+#if defined(_WIN32)
+typedef wchar_t FilePathCharType;
+typedef HANDLE PlatformFile;
+const PlatformFile kInvalidPlatformFile = INVALID_HANDLE_VALUE;
+#else
+typedef char FilePathCharType;
+typedef int PlatformFile;
+const PlatformFile kInvalidPlatformFile = -1;
+#endif // defined(_WIN32)
+
+struct HostFile {
+ HostFile(const FilePathCharType* file_path,
+ PlatformFile file,
+ PlatformFile sig_file)
+ : file_path(file_path), file(file), sig_file(sig_file) {}
+
+ // File that is part of the host of the CDM.
+ const FilePathCharType* file_path = nullptr;
+ PlatformFile file = kInvalidPlatformFile;
+
+ // Signature file for |file|.
+ PlatformFile sig_file = kInvalidPlatformFile;
+};
+
+} // namespace cdm
+
+extern "C" {
+
+// Functions in this file are dynamically retrieved by their versioned function
+// names. Increment the version number for any backward incompatible API
+// changes.
+
+// Verifies CDM host. All files in |host_files| are opened in read-only mode.
+//
+// Returns false and closes all files if there is an immediate failure.
+// Otherwise returns true as soon as possible and processes the files
+// asynchronously. All files MUST be closed by the CDM after this one-time
+// processing is finished.
+CDM_API bool VerifyCdmHost_0(const cdm::HostFile* host_files,
+ uint32_t num_files);
+}
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
diff --git a/dom/media/gmp/widevine-adapter/moz.build b/dom/media/gmp/widevine-adapter/moz.build
new file mode 100644
index 000000000..4a3a079ce
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'WidevineAdapter.cpp',
+ 'WidevineDecryptor.cpp',
+ 'WidevineFileIO.cpp',
+ 'WidevineUtils.cpp',
+ 'WidevineVideoDecoder.cpp',
+ 'WidevineVideoFrame.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/media/gmp',
+]
+
+if CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+include('/ipc/chromium/chromium-config.mozbuild')
diff --git a/dom/media/gtest/GMPTestMonitor.h b/dom/media/gtest/GMPTestMonitor.h
new file mode 100644
index 000000000..13d662b17
--- /dev/null
+++ b/dom/media/gtest/GMPTestMonitor.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsThreadUtils.h"
+
+#ifndef __GMPTestMonitor_h__
+#define __GMPTestMonitor_h__
+
+class GMPTestMonitor
+{
+public:
+ GMPTestMonitor()
+ : mFinished(false)
+ {
+ }
+
+ void AwaitFinished()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ while (!mFinished) {
+ NS_ProcessNextEvent(nullptr, true);
+ }
+ mFinished = false;
+ }
+
+private:
+ void MarkFinished()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mFinished = true;
+ }
+
+public:
+ void SetFinished()
+ {
+ NS_DispatchToMainThread(mozilla::NewNonOwningRunnableMethod(this,
+ &GMPTestMonitor::MarkFinished));
+ }
+
+private:
+ bool mFinished;
+};
+
+#endif // __GMPTestMonitor_h__
diff --git a/dom/media/gtest/TestGMPCrossOrigin.cpp b/dom/media/gtest/TestGMPCrossOrigin.cpp
new file mode 100644
index 000000000..eb8b48d67
--- /dev/null
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -0,0 +1,1546 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "nsAutoPtr.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "GMPTestMonitor.h"
+#include "GMPVideoDecoderProxy.h"
+#include "GMPVideoEncoderProxy.h"
+#include "GMPDecryptorProxy.h"
+#include "GMPServiceParent.h"
+#include "MediaPrefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/Atomics.h"
+#include "nsNSSComponent.h"
+#include "mozilla/DebugOnly.h"
+#include "GMPDeviceBinding.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/MediaKeyMessageEventBinding.h" // For MediaKeyMessageType
+
+#if defined(XP_WIN)
+#include "mozilla/WindowsVersion.h"
+#endif
+
+using namespace std;
+
+using namespace mozilla;
+using namespace mozilla::gmp;
+
+struct GMPTestRunner
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPTestRunner)
+
+ GMPTestRunner() { MediaPrefs::GetSingleton(); }
+ void DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&));
+ void RunTestGMPTestCodec1(GMPTestMonitor& aMonitor);
+ void RunTestGMPTestCodec2(GMPTestMonitor& aMonitor);
+ void RunTestGMPTestCodec3(GMPTestMonitor& aMonitor);
+ void RunTestGMPCrossOrigin1(GMPTestMonitor& aMonitor);
+ void RunTestGMPCrossOrigin2(GMPTestMonitor& aMonitor);
+ void RunTestGMPCrossOrigin3(GMPTestMonitor& aMonitor);
+ void RunTestGMPCrossOrigin4(GMPTestMonitor& aMonitor);
+
+private:
+ ~GMPTestRunner() { }
+};
+
+template<class T, class Base,
+ nsresult (NS_STDCALL GeckoMediaPluginService::*Getter)(GMPCrashHelper*,
+ nsTArray<nsCString>*,
+ const nsACString&,
+ UniquePtr<Base>&&)>
+class RunTestGMPVideoCodec : public Base
+{
+public:
+ void Done(T* aGMP, GMPVideoHost* aHost) override
+ {
+ EXPECT_TRUE(aGMP);
+ EXPECT_TRUE(aHost);
+ if (aGMP) {
+ aGMP->Close();
+ }
+ mMonitor.SetFinished();
+ }
+
+ static void Run(GMPTestMonitor& aMonitor, const nsCString& aOrigin)
+ {
+ UniquePtr<GMPCallbackType> callback(new RunTestGMPVideoCodec(aMonitor));
+ Get(aOrigin, Move(callback));
+ }
+
+protected:
+ typedef T GMPCodecType;
+ typedef Base GMPCallbackType;
+
+ explicit RunTestGMPVideoCodec(GMPTestMonitor& aMonitor)
+ : mMonitor(aMonitor)
+ {
+ }
+
+ static nsresult Get(const nsACString& aNodeId, UniquePtr<Base>&& aCallback)
+ {
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ tags.AppendElement(NS_LITERAL_CSTRING("fake"));
+
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ return ((*service).*Getter)(nullptr, &tags, aNodeId, Move(aCallback));
+ }
+
+protected:
+ GMPTestMonitor& mMonitor;
+};
+
+typedef RunTestGMPVideoCodec<GMPVideoDecoderProxy,
+ GetGMPVideoDecoderCallback,
+ &GeckoMediaPluginService::GetGMPVideoDecoder>
+ RunTestGMPVideoDecoder;
+typedef RunTestGMPVideoCodec<GMPVideoEncoderProxy,
+ GetGMPVideoEncoderCallback,
+ &GeckoMediaPluginService::GetGMPVideoEncoder>
+ RunTestGMPVideoEncoder;
+
+void
+GMPTestRunner::RunTestGMPTestCodec1(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoDecoder::Run(aMonitor, NS_LITERAL_CSTRING("o"));
+}
+
+void
+GMPTestRunner::RunTestGMPTestCodec2(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoDecoder::Run(aMonitor, NS_LITERAL_CSTRING(""));
+}
+
+void
+GMPTestRunner::RunTestGMPTestCodec3(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoEncoder::Run(aMonitor, NS_LITERAL_CSTRING(""));
+}
+
+template<class Base>
+class RunTestGMPCrossOrigin : public Base
+{
+public:
+ void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) override
+ {
+ EXPECT_TRUE(aGMP);
+
+ UniquePtr<typename Base::GMPCallbackType> callback(
+ new Step2(Base::mMonitor, aGMP, mShouldBeEqual));
+ nsresult rv = Base::Get(mOrigin2, Move(callback));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ Base::mMonitor.SetFinished();
+ }
+ }
+
+ static void Run(GMPTestMonitor& aMonitor, const nsCString& aOrigin1,
+ const nsCString& aOrigin2)
+ {
+ UniquePtr<typename Base::GMPCallbackType> callback(
+ new RunTestGMPCrossOrigin<Base>(aMonitor, aOrigin1, aOrigin2));
+ nsresult rv = Base::Get(aOrigin1, Move(callback));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ aMonitor.SetFinished();
+ }
+ }
+
+private:
+ RunTestGMPCrossOrigin(GMPTestMonitor& aMonitor, const nsCString& aOrigin1,
+ const nsCString& aOrigin2)
+ : Base(aMonitor),
+ mGMP(nullptr),
+ mOrigin2(aOrigin2),
+ mShouldBeEqual(aOrigin1.Equals(aOrigin2))
+ {
+ }
+
+ class Step2 : public Base
+ {
+ public:
+ Step2(GMPTestMonitor& aMonitor,
+ typename Base::GMPCodecType* aGMP,
+ bool aShouldBeEqual)
+ : Base(aMonitor),
+ mGMP(aGMP),
+ mShouldBeEqual(aShouldBeEqual)
+ {
+ }
+ void Done(typename Base::GMPCodecType* aGMP, GMPVideoHost* aHost) override
+ {
+ EXPECT_TRUE(aGMP);
+ if (aGMP) {
+ EXPECT_TRUE(mGMP &&
+ (mGMP->GetPluginId() == aGMP->GetPluginId()) == mShouldBeEqual);
+ }
+ if (mGMP) {
+ mGMP->Close();
+ }
+ Base::Done(aGMP, aHost);
+ }
+
+ private:
+ typename Base::GMPCodecType* mGMP;
+ bool mShouldBeEqual;
+ };
+
+ typename Base::GMPCodecType* mGMP;
+ nsCString mOrigin2;
+ bool mShouldBeEqual;
+};
+
+typedef RunTestGMPCrossOrigin<RunTestGMPVideoDecoder>
+ RunTestGMPVideoDecoderCrossOrigin;
+typedef RunTestGMPCrossOrigin<RunTestGMPVideoEncoder>
+ RunTestGMPVideoEncoderCrossOrigin;
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin1(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoDecoderCrossOrigin::Run(
+ aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin2"));
+}
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin2(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoEncoderCrossOrigin::Run(
+ aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin2"));
+}
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin3(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoDecoderCrossOrigin::Run(
+ aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin1"));
+}
+
+void
+GMPTestRunner::RunTestGMPCrossOrigin4(GMPTestMonitor& aMonitor)
+{
+ RunTestGMPVideoEncoderCrossOrigin::Run(
+ aMonitor, NS_LITERAL_CSTRING("origin1"), NS_LITERAL_CSTRING("origin1"));
+}
+
+static already_AddRefed<nsIThread>
+GetGMPThread()
+{
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ nsCOMPtr<nsIThread> thread;
+ EXPECT_TRUE(NS_SUCCEEDED(service->GetThread(getter_AddRefs(thread))));
+ return thread.forget();
+}
+
+/**
+ * Enumerate files under |aPath| (non-recursive).
+ */
+template<typename T>
+static nsresult
+EnumerateDir(nsIFile* aPath, T&& aDirIter)
+{
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ nsresult rv = aPath->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ rv = iter->GetNext(getter_AddRefs(supports));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> entry(do_QueryInterface(supports, &rv));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ aDirIter(entry);
+ }
+ return NS_OK;
+}
+
+/**
+ * Enumerate files under $profileDir/gmp/$platform/gmp-fake/$aDir/ (non-recursive).
+ */
+template<typename T>
+static nsresult
+EnumerateGMPStorageDir(const nsACString& aDir, T&& aDirIter)
+{
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ MOZ_ASSERT(service);
+
+ // $profileDir/gmp/$platform/
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = service->GetStorageDir(getter_AddRefs(path));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+
+ // $profileDir/gmp/$platform/gmp-fake/
+ rv = path->Append(NS_LITERAL_STRING("gmp-fake"));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/gmp-fake/$aDir/
+ rv = path->AppendNative(aDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return EnumerateDir(path, aDirIter);
+}
+
+class GMPShutdownObserver : public nsIRunnable
+ , public nsIObserver {
+public:
+ GMPShutdownObserver(already_AddRefed<nsIRunnable> aShutdownTask,
+ already_AddRefed<nsIRunnable> Continuation,
+ const nsACString& aNodeId)
+ : mShutdownTask(aShutdownTask)
+ , mContinuation(Continuation)
+ , mNodeId(NS_ConvertUTF8toUTF16(aNodeId))
+ {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ EXPECT_TRUE(observerService);
+ observerService->AddObserver(this, "gmp-shutdown", false);
+
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ thread->Dispatch(mShutdownTask, NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData) override
+ {
+ if (!strcmp(aTopic, "gmp-shutdown") &&
+ mNodeId.Equals(nsDependentString(aSomeData))) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ EXPECT_TRUE(observerService);
+ observerService->RemoveObserver(this, "gmp-shutdown");
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ thread->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+ }
+
+private:
+ virtual ~GMPShutdownObserver() {}
+ nsCOMPtr<nsIRunnable> mShutdownTask;
+ nsCOMPtr<nsIRunnable> mContinuation;
+ const nsString mNodeId;
+};
+
+NS_IMPL_ISUPPORTS(GMPShutdownObserver, nsIRunnable, nsIObserver)
+
+class NotifyObserversTask : public Runnable {
+public:
+ explicit NotifyObserversTask(const char* aTopic)
+ : mTopic(aTopic)
+ {}
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, mTopic, nullptr);
+ }
+ return NS_OK;
+ }
+ const char* mTopic;
+};
+
+class ClearGMPStorageTask : public nsIRunnable
+ , public nsIObserver {
+public:
+ ClearGMPStorageTask(already_AddRefed<nsIRunnable> Continuation,
+ nsIThread* aTarget, PRTime aSince)
+ : mContinuation(Continuation)
+ , mTarget(aTarget)
+ , mSince(aSince)
+ {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ EXPECT_TRUE(observerService);
+ observerService->AddObserver(this, "gmp-clear-storage-complete", false);
+ if (observerService) {
+ nsAutoString str;
+ if (mSince >= 0) {
+ str.AppendInt(static_cast<int64_t>(mSince));
+ }
+ observerService->NotifyObservers(
+ nullptr, "browser:purge-session-history", str.Data());
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData) override
+ {
+ if (!strcmp(aTopic, "gmp-clear-storage-complete")) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ EXPECT_TRUE(observerService);
+ observerService->RemoveObserver(this, "gmp-clear-storage-complete");
+ mTarget->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+ }
+
+private:
+ virtual ~ClearGMPStorageTask() {}
+ nsCOMPtr<nsIRunnable> mContinuation;
+ nsCOMPtr<nsIThread> mTarget;
+ const PRTime mSince;
+};
+
+NS_IMPL_ISUPPORTS(ClearGMPStorageTask, nsIRunnable, nsIObserver)
+
+static void
+ClearGMPStorage(already_AddRefed<nsIRunnable> aContinuation,
+ nsIThread* aTarget, PRTime aSince = -1)
+{
+ RefPtr<ClearGMPStorageTask> task(
+ new ClearGMPStorageTask(Move(aContinuation), aTarget, aSince));
+ NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+}
+
+static void
+SimulatePBModeExit()
+{
+ NS_DispatchToMainThread(new NotifyObserversTask("last-pb-context-exited"), NS_DISPATCH_SYNC);
+}
+
+class TestGetNodeIdCallback : public GetNodeIdCallback
+{
+public:
+ TestGetNodeIdCallback(nsCString& aNodeId, nsresult& aResult)
+ : mNodeId(aNodeId),
+ mResult(aResult)
+ {
+ }
+
+ void Done(nsresult aResult, const nsACString& aNodeId)
+ {
+ mResult = aResult;
+ mNodeId = aNodeId;
+ }
+
+private:
+ nsCString& mNodeId;
+ nsresult& mResult;
+};
+
+static nsCString
+GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ bool aInPBMode)
+{
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ EXPECT_TRUE(service);
+ nsCString nodeId;
+ nsresult result;
+ UniquePtr<GetNodeIdCallback> callback(new TestGetNodeIdCallback(nodeId,
+ result));
+ // We rely on the fact that the GetNodeId implementation for
+ // GeckoMediaPluginServiceParent is synchronous.
+ nsresult rv = service->GetNodeId(aOrigin,
+ aTopLevelOrigin,
+ NS_LITERAL_STRING("gmp-fake"),
+ aInPBMode,
+ Move(callback));
+ EXPECT_TRUE(NS_SUCCEEDED(rv) && NS_SUCCEEDED(result));
+ return nodeId;
+}
+
+static bool
+IsGMPStorageIsEmpty()
+{
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ MOZ_ASSERT(service);
+ nsCOMPtr<nsIFile> storage;
+ nsresult rv = service->GetStorageDir(getter_AddRefs(storage));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ bool exists = false;
+ if (storage) {
+ storage->Exists(&exists);
+ }
+ return !exists;
+}
+
+static void
+AssertIsOnGMPThread()
+{
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ MOZ_ASSERT(service);
+ nsCOMPtr<nsIThread> thread;
+ service->GetThread(getter_AddRefs(thread));
+ MOZ_ASSERT(thread);
+ nsCOMPtr<nsIThread> currentThread;
+ DebugOnly<nsresult> rv = NS_GetCurrentThread(getter_AddRefs(currentThread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(currentThread == thread);
+}
+
+class GMPStorageTest : public GMPDecryptorProxyCallback
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorageTest)
+
+ void DoTest(void (GMPStorageTest::*aTestMethod)()) {
+ EnsureNSSInitializedChromeOrContent();
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ ClearGMPStorage(NewRunnableMethod(this, aTestMethod), thread);
+ AwaitFinished();
+ }
+
+ GMPStorageTest()
+ : mDecryptor(nullptr)
+ , mMonitor("GMPStorageTest")
+ , mFinished(false)
+ {
+ }
+
+ void
+ Update(const nsCString& aMessage)
+ {
+ nsTArray<uint8_t> msg;
+ msg.AppendElements(aMessage.get(), aMessage.Length());
+ mDecryptor->UpdateSession(1, NS_LITERAL_CSTRING("fake-session-id"), msg);
+ }
+
+ void TestGetNodeId()
+ {
+ AssertIsOnGMPThread();
+
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ const nsString origin1 = NS_LITERAL_STRING("http://example1.com");
+ const nsString origin2 = NS_LITERAL_STRING("http://example2.org");
+
+ nsCString PBnodeId1 = GetNodeId(origin1, origin2, true);
+ nsCString PBnodeId2 = GetNodeId(origin1, origin2, true);
+
+ // Node ids for the same origins should be the same in PB mode.
+ EXPECT_TRUE(PBnodeId1.Equals(PBnodeId2));
+
+ nsCString PBnodeId3 = GetNodeId(origin2, origin1, true);
+
+ // Node ids with origin and top level origin swapped should be different.
+ EXPECT_TRUE(!PBnodeId3.Equals(PBnodeId1));
+
+ // Getting node ids in PB mode should not result in the node id being stored.
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ nsCString nodeId1 = GetNodeId(origin1, origin2, false);
+ nsCString nodeId2 = GetNodeId(origin1, origin2, false);
+
+ // NodeIds for the same origin pair in non-pb mode should be the same.
+ EXPECT_TRUE(nodeId1.Equals(nodeId2));
+
+ // Node ids for a given origin pair should be different for the PB origins should be the same in PB mode.
+ EXPECT_TRUE(!PBnodeId1.Equals(nodeId1));
+ EXPECT_TRUE(!PBnodeId2.Equals(nodeId2));
+
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ ClearGMPStorage(NewRunnableMethod<nsCString>(
+ this, &GMPStorageTest::TestGetNodeId_Continuation, nodeId1), thread);
+ }
+
+ void TestGetNodeId_Continuation(nsCString aNodeId1) {
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Once we clear storage, the node ids generated for the same origin-pair
+ // should be different.
+ const nsString origin1 = NS_LITERAL_STRING("http://example1.com");
+ const nsString origin2 = NS_LITERAL_STRING("http://example2.org");
+ nsCString nodeId3 = GetNodeId(origin1, origin2, false);
+ EXPECT_TRUE(!aNodeId1.Equals(nodeId3));
+
+ SetFinished();
+ }
+
+ class CreateDecryptorDone : public GetGMPDecryptorCallback
+ {
+ public:
+ explicit CreateDecryptorDone(GMPStorageTest* aRunner)
+ : mRunner(aRunner)
+ {
+ }
+
+ void Done(GMPDecryptorProxy* aDecryptor) override
+ {
+ mRunner->mDecryptor = aDecryptor;
+ EXPECT_TRUE(!!mRunner->mDecryptor);
+
+ if (mRunner->mDecryptor) {
+ mRunner->mDecryptor->Init(mRunner, false, true);
+ }
+ }
+
+ private:
+ RefPtr<GMPStorageTest> mRunner;
+ };
+
+ void CreateDecryptor(const nsCString& aNodeId,
+ const nsCString& aUpdate)
+ {
+ nsTArray<nsCString> updates;
+ updates.AppendElement(aUpdate);
+ nsCOMPtr<nsIRunnable> continuation(new Updates(this, Move(updates)));
+ CreateDecryptor(aNodeId, continuation);
+ }
+
+ void CreateDecryptor(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ bool aInPBMode,
+ const nsCString& aUpdate)
+ {
+ nsTArray<nsCString> updates;
+ updates.AppendElement(aUpdate);
+ CreateDecryptor(aOrigin, aTopLevelOrigin, aInPBMode, Move(updates));
+ }
+ class Updates : public Runnable
+ {
+ public:
+ Updates(GMPStorageTest* aRunner, nsTArray<nsCString>&& aUpdates)
+ : mRunner(aRunner),
+ mUpdates(Move(aUpdates))
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ for (auto& update : mUpdates) {
+ mRunner->Update(update);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<GMPStorageTest> mRunner;
+ nsTArray<nsCString> mUpdates;
+ };
+ void CreateDecryptor(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ bool aInPBMode,
+ nsTArray<nsCString>&& aUpdates) {
+ nsCOMPtr<nsIRunnable> updates(new Updates(this, Move(aUpdates)));
+ CreateDecryptor(GetNodeId(aOrigin, aTopLevelOrigin, aInPBMode), updates);
+ }
+
+ void CreateDecryptor(const nsCString& aNodeId,
+ nsIRunnable* aContinuation) {
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ EXPECT_TRUE(service);
+
+ mNodeId = aNodeId;
+ EXPECT_TRUE(!mNodeId.IsEmpty());
+
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_LITERAL_CSTRING("fake"));
+
+ UniquePtr<GetGMPDecryptorCallback> callback(
+ new CreateDecryptorDone(this));
+
+ // Continue after the OnSetDecryptorId message, so that we don't
+ // get warnings in the async shutdown tests due to receiving the
+ // SetDecryptorId message after we've started shutdown.
+ mSetDecryptorIdContinuation = aContinuation;
+
+ nsresult rv =
+ service->GetGMPDecryptor(nullptr, &tags, mNodeId, Move(callback));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ void TestBasicStorage() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+
+ // Send a message to the fake GMP for it to run its own tests internally.
+ // It sends us a "test-storage complete" message when its passed, or
+ // some other message if its tests fail.
+ Expect(NS_LITERAL_CSTRING("test-storage complete"),
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ /**
+ * 1. Generate storage data for some sites.
+ * 2. Forget about one of the sites.
+ * 3. Check if the storage data for the forgotten site are erased correctly.
+ * 4. Check if the storage data for other sites remain unchanged.
+ */
+ void TestForgetThisSite() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Generate storage data for some site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestForgetThisSite_AnotherSite);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ void TestForgetThisSite_AnotherSite() {
+ Shutdown();
+
+ // Generate storage data for another site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestForgetThisSite_CollectSiteInfo);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example3.com"),
+ NS_LITERAL_STRING("http://example4.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ struct NodeInfo {
+ explicit NodeInfo(const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+ : siteToForget(aSite)
+ , mPattern(aPattern)
+ { }
+ nsCString siteToForget;
+ mozilla::OriginAttributesPattern mPattern;
+ nsTArray<nsCString> expectedRemainingNodeIds;
+ };
+
+ class NodeIdCollector {
+ public:
+ explicit NodeIdCollector(NodeInfo* aInfo) : mNodeInfo(aInfo) {}
+ void operator()(nsIFile* aFile) {
+ nsCString salt;
+ nsresult rv = ReadSalt(aFile, salt);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ if (!MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern)) {
+ mNodeInfo->expectedRemainingNodeIds.AppendElement(salt);
+ }
+ }
+ private:
+ NodeInfo* mNodeInfo;
+ };
+
+ void TestForgetThisSite_CollectSiteInfo() {
+ mozilla::OriginAttributesPattern pattern;
+
+ nsAutoPtr<NodeInfo> siteInfo(
+ new NodeInfo(NS_LITERAL_CSTRING("http://example1.com"),
+ pattern));
+ // Collect nodeIds that are expected to remain for later comparison.
+ EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), NodeIdCollector(siteInfo));
+ // Invoke "Forget this site" on the main thread.
+ NS_DispatchToMainThread(NewRunnableMethod<nsAutoPtr<NodeInfo>>(
+ this, &GMPStorageTest::TestForgetThisSite_Forget, siteInfo));
+ }
+
+ void TestForgetThisSite_Forget(nsAutoPtr<NodeInfo> aSiteInfo) {
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ service->ForgetThisSiteNative(NS_ConvertUTF8toUTF16(aSiteInfo->siteToForget),
+ aSiteInfo->mPattern);
+
+ nsCOMPtr<nsIThread> thread;
+ service->GetThread(getter_AddRefs(thread));
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod<nsAutoPtr<NodeInfo>>(
+ this, &GMPStorageTest::TestForgetThisSite_Verify, aSiteInfo);
+ thread->Dispatch(r, NS_DISPATCH_NORMAL);
+
+ nsCOMPtr<nsIRunnable> f = NewRunnableMethod(
+ this, &GMPStorageTest::SetFinished);
+ thread->Dispatch(f, NS_DISPATCH_NORMAL);
+ }
+
+ class NodeIdVerifier {
+ public:
+ explicit NodeIdVerifier(const NodeInfo* aInfo)
+ : mNodeInfo(aInfo)
+ , mExpectedRemainingNodeIds(aInfo->expectedRemainingNodeIds) {}
+ void operator()(nsIFile* aFile) {
+ nsCString salt;
+ nsresult rv = ReadSalt(aFile, salt);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ // Shouldn't match the origin if we clear correctly.
+ EXPECT_FALSE(MatchOrigin(aFile, mNodeInfo->siteToForget, mNodeInfo->mPattern));
+ // Check if remaining nodeIDs are as expected.
+ EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt));
+ }
+ ~NodeIdVerifier() {
+ EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty());
+ }
+ private:
+ const NodeInfo* mNodeInfo;
+ nsTArray<nsCString> mExpectedRemainingNodeIds;
+ };
+
+ class StorageVerifier {
+ public:
+ explicit StorageVerifier(const NodeInfo* aInfo)
+ : mExpectedRemainingNodeIds(aInfo->expectedRemainingNodeIds) {}
+ void operator()(nsIFile* aFile) {
+ nsCString salt;
+ nsresult rv = aFile->GetNativeLeafName(salt);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ EXPECT_TRUE(mExpectedRemainingNodeIds.RemoveElement(salt));
+ }
+ ~StorageVerifier() {
+ EXPECT_TRUE(mExpectedRemainingNodeIds.IsEmpty());
+ }
+ private:
+ nsTArray<nsCString> mExpectedRemainingNodeIds;
+ };
+
+ void TestForgetThisSite_Verify(nsAutoPtr<NodeInfo> aSiteInfo) {
+ nsresult rv = EnumerateGMPStorageDir(
+ NS_LITERAL_CSTRING("id"), NodeIdVerifier(aSiteInfo));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = EnumerateGMPStorageDir(
+ NS_LITERAL_CSTRING("storage"), StorageVerifier(aSiteInfo));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ /**
+ * 1. Generate some storage data.
+ * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/id/.
+ * 3. Pass |t| to clear recent history.
+ * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+ * $profileDir/gmp/$platform/gmp-fake/storage are removed.
+ */
+ void TestClearRecentHistory1() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Generate storage data for some site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory1_Clear);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+}
+
+ /**
+ * 1. Generate some storage data.
+ * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/.
+ * 3. Pass |t| to clear recent history.
+ * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+ * $profileDir/gmp/$platform/gmp-fake/storage are removed.
+ */
+ void TestClearRecentHistory2() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Generate storage data for some site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory2_Clear);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ /**
+ * 1. Generate some storage data.
+ * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/.
+ * 3. Pass |t+1| to clear recent history.
+ * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+ * $profileDir/gmp/$platform/gmp-fake/storage remain unchanged.
+ */
+ void TestClearRecentHistory3() {
+ AssertIsOnGMPThread();
+ EXPECT_TRUE(IsGMPStorageIsEmpty());
+
+ // Generate storage data for some site.
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory3_Clear);
+ Expect(NS_LITERAL_CSTRING("test-storage complete"), r.forget());
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
+ NS_LITERAL_STRING("http://example2.com"),
+ false,
+ NS_LITERAL_CSTRING("test-storage"));
+ }
+
+ class MaxMTimeFinder {
+ public:
+ MaxMTimeFinder() : mMaxTime(0) {}
+ void operator()(nsIFile* aFile) {
+ PRTime lastModified;
+ nsresult rv = aFile->GetLastModifiedTime(&lastModified);
+ if (NS_SUCCEEDED(rv) && lastModified > mMaxTime) {
+ mMaxTime = lastModified;
+ }
+ EnumerateDir(aFile, *this);
+ }
+ PRTime GetResult() const { return mMaxTime; }
+ private:
+ PRTime mMaxTime;
+ };
+
+ void TestClearRecentHistory1_Clear() {
+ MaxMTimeFinder f;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), f);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory_CheckEmpty);
+ nsCOMPtr<nsIThread> t(GetGMPThread());
+ ClearGMPStorage(r.forget(), t, f.GetResult());
+ }
+
+ void TestClearRecentHistory2_Clear() {
+ MaxMTimeFinder f;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), f);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory_CheckEmpty);
+ nsCOMPtr<nsIThread> t(GetGMPThread());
+ ClearGMPStorage(r.forget(), t, f.GetResult());
+ }
+
+ void TestClearRecentHistory3_Clear() {
+ MaxMTimeFinder f;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), f);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ this, &GMPStorageTest::TestClearRecentHistory_CheckNonEmpty);
+ nsCOMPtr<nsIThread> t(GetGMPThread());
+ ClearGMPStorage(r.forget(), t, f.GetResult() + 1);
+ }
+
+ class FileCounter {
+ public:
+ FileCounter() : mCount(0) {}
+ void operator()(nsIFile* aFile) {
+ ++mCount;
+ }
+ int GetCount() const { return mCount; }
+ private:
+ int mCount;
+ };
+
+ void TestClearRecentHistory_CheckEmpty() {
+ FileCounter c1;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), c1);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ // There should be no files under $profileDir/gmp/$platform/gmp-fake/id/
+ EXPECT_EQ(c1.GetCount(), 0);
+
+ FileCounter c2;
+ rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), c2);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ // There should be no files under $profileDir/gmp/$platform/gmp-fake/storage/
+ EXPECT_EQ(c2.GetCount(), 0);
+
+ SetFinished();
+ }
+
+ void TestClearRecentHistory_CheckNonEmpty() {
+ FileCounter c1;
+ nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), c1);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ // There should be one directory under $profileDir/gmp/$platform/gmp-fake/id/
+ EXPECT_EQ(c1.GetCount(), 1);
+
+ FileCounter c2;
+ rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), c2);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ // There should be one directory under $profileDir/gmp/$platform/gmp-fake/storage/
+ EXPECT_EQ(c2.GetCount(), 1);
+
+ SetFinished();
+ }
+
+ void TestCrossOriginStorage() {
+ EXPECT_TRUE(!mDecryptor);
+
+ // Send the decryptor the message "store recordid $time"
+ // Wait for the decrytor to send us "stored recordid $time"
+ auto t = time(0);
+ nsCString response("stored crossOriginTestRecordId ");
+ response.AppendInt((int64_t)t);
+ Expect(response, NewRunnableMethod(this,
+ &GMPStorageTest::TestCrossOriginStorage_RecordStoredContinuation));
+
+ nsCString update("store crossOriginTestRecordId ");
+ update.AppendInt((int64_t)t);
+
+ // Open decryptor on one, origin, write a record, and test that that
+ // record can't be read on another origin.
+ CreateDecryptor(NS_LITERAL_STRING("http://example3.com"),
+ NS_LITERAL_STRING("http://example4.com"),
+ false,
+ update);
+ }
+
+ void TestCrossOriginStorage_RecordStoredContinuation() {
+ // Close the old decryptor, and create a new one on a different origin,
+ // and try to read the record.
+ Shutdown();
+
+ Expect(NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId succeeded (length 0 bytes)"),
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example5.com"),
+ NS_LITERAL_STRING("http://example6.com"),
+ false,
+ NS_LITERAL_CSTRING("retrieve crossOriginTestRecordId"));
+ }
+
+ void TestPBStorage() {
+ // Send the decryptor the message "store recordid $time"
+ // Wait for the decrytor to send us "stored recordid $time"
+ nsCString response("stored pbdata test-pb-data");
+ Expect(response, NewRunnableMethod(this,
+ &GMPStorageTest::TestPBStorage_RecordStoredContinuation));
+
+ // Open decryptor on one, origin, write a record, close decryptor,
+ // open another, and test that record can be read, close decryptor,
+ // then send pb-last-context-closed notification, then open decryptor
+ // and check that it can't read that data; it should have been purged.
+ CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"),
+ NS_LITERAL_STRING("http://pb2.com"),
+ true,
+ NS_LITERAL_CSTRING("store pbdata test-pb-data"));
+ }
+
+ void TestPBStorage_RecordStoredContinuation() {
+ Shutdown();
+
+ Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 12 bytes)"),
+ NewRunnableMethod(this,
+ &GMPStorageTest::TestPBStorage_RecordRetrievedContinuation));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"),
+ NS_LITERAL_STRING("http://pb2.com"),
+ true,
+ NS_LITERAL_CSTRING("retrieve pbdata"));
+ }
+
+ void TestPBStorage_RecordRetrievedContinuation() {
+ Shutdown();
+ SimulatePBModeExit();
+
+ Expect(NS_LITERAL_CSTRING("retrieve pbdata succeeded (length 0 bytes)"),
+ NewRunnableMethod(this,
+ &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://pb1.com"),
+ NS_LITERAL_STRING("http://pb2.com"),
+ true,
+ NS_LITERAL_CSTRING("retrieve pbdata"));
+ }
+
+ void NextAsyncShutdownTimeoutTest(nsIRunnable* aContinuation)
+ {
+ if (mDecryptor) {
+ Update(NS_LITERAL_CSTRING("shutdown-mode timeout"));
+ Shutdown();
+ }
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ thread->Dispatch(aContinuation, NS_DISPATCH_NORMAL);
+ }
+
+ void CreateAsyncShutdownTimeoutGMP(const nsAString& aOrigin1,
+ const nsAString& aOrigin2,
+ void (GMPStorageTest::*aCallback)()) {
+ nsCOMPtr<nsIRunnable> continuation(
+ NewRunnableMethod<nsCOMPtr<nsIRunnable>>(
+ this,
+ &GMPStorageTest::NextAsyncShutdownTimeoutTest,
+ NewRunnableMethod(this, aCallback)));
+
+ CreateDecryptor(GetNodeId(aOrigin1, aOrigin2, false), continuation);
+ }
+
+ void TestAsyncShutdownTimeout() {
+ // Create decryptors that timeout in their async shutdown.
+ // If the gtest hangs on shutdown, test fails!
+ CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example7.com"),
+ NS_LITERAL_STRING("http://example8.com"),
+ &GMPStorageTest::TestAsyncShutdownTimeout2);
+ };
+
+ void TestAsyncShutdownTimeout2() {
+ CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example9.com"),
+ NS_LITERAL_STRING("http://example10.com"),
+ &GMPStorageTest::TestAsyncShutdownTimeout3);
+ };
+
+ void TestAsyncShutdownTimeout3() {
+ CreateAsyncShutdownTimeoutGMP(NS_LITERAL_STRING("http://example11.com"),
+ NS_LITERAL_STRING("http://example12.com"),
+ &GMPStorageTest::SetFinished);
+ };
+
+ void TestAsyncShutdownStorage() {
+ // Instruct the GMP to write a token (the current timestamp, so it's
+ // unique) during async shutdown, then shutdown the plugin, re-create
+ // it, and check that the token was successfully stored.
+ auto t = time(0);
+ nsCString update("shutdown-mode token ");
+ nsCString token;
+ token.AppendInt((int64_t)t);
+ update.Append(token);
+
+ // Wait for a response from the GMP, so we know it's had time to receive
+ // the token.
+ nsCString response("shutdown-token received ");
+ response.Append(token);
+ Expect(response, NewRunnableMethod<nsCString>(this,
+ &GMPStorageTest::TestAsyncShutdownStorage_ReceivedShutdownToken, token));
+
+ // Test that a GMP can write to storage during shutdown, and retrieve
+ // that written data in a subsequent session.
+ CreateDecryptor(NS_LITERAL_STRING("http://example13.com"),
+ NS_LITERAL_STRING("http://example14.com"),
+ false,
+ update);
+ }
+
+ void TestAsyncShutdownStorage_ReceivedShutdownToken(const nsCString& aToken) {
+ ShutdownThen(NewRunnableMethod<nsCString>(this,
+ &GMPStorageTest::TestAsyncShutdownStorage_AsyncShutdownComplete, aToken));
+ }
+
+ void TestAsyncShutdownStorage_AsyncShutdownComplete(const nsCString& aToken) {
+ // Create a new instance of the plugin, retrieve the token written
+ // during shutdown and verify it is correct.
+ nsCString response("retrieved shutdown-token ");
+ response.Append(aToken);
+ Expect(response,
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example13.com"),
+ NS_LITERAL_STRING("http://example14.com"),
+ false,
+ NS_LITERAL_CSTRING("retrieve-shutdown-token"));
+ }
+
+#if defined(XP_WIN)
+ void TestOutputProtection() {
+ Shutdown();
+
+ Expect(NS_LITERAL_CSTRING("OP tests completed"),
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example15.com"),
+ NS_LITERAL_STRING("http://example16.com"),
+ false,
+ NS_LITERAL_CSTRING("test-op-apis"));
+ }
+#endif
+
+ void TestPluginVoucher() {
+ Expect(NS_LITERAL_CSTRING("retrieved plugin-voucher: gmp-fake placeholder voucher"),
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(NS_LITERAL_STRING("http://example17.com"),
+ NS_LITERAL_STRING("http://example18.com"),
+ false,
+ NS_LITERAL_CSTRING("retrieve-plugin-voucher"));
+ }
+
+ void TestGetRecordNamesInMemoryStorage() {
+ TestGetRecordNames(true);
+ }
+
+ nsCString mRecordNames;
+
+ void AppendIntPadded(nsACString& aString, uint32_t aInt) {
+ if (aInt > 0 && aInt < 10) {
+ aString.AppendLiteral("0");
+ }
+ aString.AppendInt(aInt);
+ }
+
+ void TestGetRecordNames(bool aPrivateBrowsing) {
+ // Create a number of records of different names.
+ const uint32_t num = 100;
+ nsTArray<nsCString> updates(num);
+ for (uint32_t i = 0; i < num; i++) {
+ nsAutoCString response;
+ response.AppendLiteral("stored data");
+ AppendIntPadded(response, i);
+ response.AppendLiteral(" test-data");
+ AppendIntPadded(response, i);
+
+ if (i != 0) {
+ mRecordNames.AppendLiteral(",");
+ }
+ mRecordNames.AppendLiteral("data");
+ AppendIntPadded(mRecordNames, i);
+
+ nsCString& update = *updates.AppendElement();
+ update.AppendLiteral("store data");
+ AppendIntPadded(update, i);
+ update.AppendLiteral(" test-data");
+ AppendIntPadded(update, i);
+
+ nsCOMPtr<nsIRunnable> continuation;
+ if (i + 1 == num) {
+ continuation =
+ NewRunnableMethod(this, &GMPStorageTest::TestGetRecordNames_QueryNames);
+ }
+ Expect(response, continuation.forget());
+ }
+
+ CreateDecryptor(NS_LITERAL_STRING("http://foo.com"),
+ NS_LITERAL_STRING("http://bar.com"),
+ aPrivateBrowsing,
+ Move(updates));
+ }
+
+ void TestGetRecordNames_QueryNames() {
+ nsCString response("record-names ");
+ response.Append(mRecordNames);
+ Expect(response,
+ NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+ Update(NS_LITERAL_CSTRING("retrieve-record-names"));
+ }
+
+ void GetRecordNamesPersistentStorage() {
+ TestGetRecordNames(false);
+ }
+
+ void TestLongRecordNames() {
+ NS_NAMED_LITERAL_CSTRING(longRecordName,
+ "A_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "very_very_very_very_very_very_very_very_very_very_very_very_very_very_very_"
+ "long_record_name");
+
+ NS_NAMED_LITERAL_CSTRING(data, "Just_some_arbitrary_data.");
+
+ MOZ_ASSERT(longRecordName.Length() < GMP_MAX_RECORD_NAME_SIZE);
+ MOZ_ASSERT(longRecordName.Length() > 260); // Windows MAX_PATH
+
+ nsCString response("stored ");
+ response.Append(longRecordName);
+ response.AppendLiteral(" ");
+ response.Append(data);
+ Expect(response, NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ nsCString update("store ");
+ update.Append(longRecordName);
+ update.AppendLiteral(" ");
+ update.Append(data);
+ CreateDecryptor(NS_LITERAL_STRING("http://fuz.com"),
+ NS_LITERAL_STRING("http://baz.com"),
+ false,
+ update);
+ }
+
+ void TestNodeId() {
+ // Calculate the nodeId, and the device bound nodeId. Start a GMP, and
+ // have it return the device bound nodeId that it's been passed. Assert
+ // they have the same value.
+ const nsString origin = NS_LITERAL_STRING("http://example-fuz-baz.com");
+ nsCString originSalt1 = GetNodeId(origin, origin, false);
+
+ nsCString salt = originSalt1;
+ std::string nodeId;
+ EXPECT_TRUE(CalculateGMPDeviceId(salt.BeginWriting(), salt.Length(), nodeId));
+
+ std::string expected = "node-id " + nodeId;
+ Expect(nsDependentCString(expected.c_str()), NewRunnableMethod(this, &GMPStorageTest::SetFinished));
+
+ CreateDecryptor(originSalt1,
+ NS_LITERAL_CSTRING("retrieve-node-id"));
+ }
+
+ void Expect(const nsCString& aMessage, already_AddRefed<nsIRunnable> aContinuation) {
+ mExpected.AppendElement(ExpectedMessage(aMessage, Move(aContinuation)));
+ }
+
+ void AwaitFinished() {
+ while (!mFinished) {
+ NS_ProcessNextEvent(nullptr, true);
+ }
+ mFinished = false;
+ }
+
+ void ShutdownThen(already_AddRefed<nsIRunnable> aContinuation) {
+ EXPECT_TRUE(!!mDecryptor);
+ if (!mDecryptor) {
+ return;
+ }
+ EXPECT_FALSE(mNodeId.IsEmpty());
+ RefPtr<GMPShutdownObserver> task(
+ new GMPShutdownObserver(NewRunnableMethod(this, &GMPStorageTest::Shutdown),
+ Move(aContinuation), mNodeId));
+ NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+ }
+
+ void Shutdown() {
+ if (mDecryptor) {
+ mDecryptor->Close();
+ mDecryptor = nullptr;
+ mNodeId = EmptyCString();
+ }
+ }
+
+ void Dummy() {
+ }
+
+ void SetFinished() {
+ mFinished = true;
+ Shutdown();
+ NS_DispatchToMainThread(NewRunnableMethod(this, &GMPStorageTest::Dummy));
+ }
+
+ void SessionMessage(const nsCString& aSessionId,
+ mozilla::dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) override
+ {
+ MonitorAutoLock mon(mMonitor);
+
+ nsCString msg((const char*)aMessage.Elements(), aMessage.Length());
+ EXPECT_TRUE(mExpected.Length() > 0);
+ bool matches = mExpected[0].mMessage.Equals(msg);
+ EXPECT_STREQ(mExpected[0].mMessage.get(), msg.get());
+ if (mExpected.Length() > 0 && matches) {
+ nsCOMPtr<nsIRunnable> continuation = mExpected[0].mContinuation;
+ mExpected.RemoveElementAt(0);
+ if (continuation) {
+ NS_DispatchToCurrentThread(continuation);
+ }
+ }
+ }
+
+ void SetDecryptorId(uint32_t aId) override
+ {
+ if (!mSetDecryptorIdContinuation) {
+ return;
+ }
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+ thread->Dispatch(mSetDecryptorIdContinuation, NS_DISPATCH_NORMAL);
+ mSetDecryptorIdContinuation = nullptr;
+ }
+
+ void SetSessionId(uint32_t aCreateSessionToken,
+ const nsCString& aSessionId) override { }
+ void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) override {}
+ void ResolvePromise(uint32_t aPromiseId) override {}
+ void RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aSessionId) override { }
+ void ExpirationChange(const nsCString& aSessionId,
+ UnixTime aExpiryTime) override {}
+ void SessionClosed(const nsCString& aSessionId) override {}
+ void SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage) override {}
+ void Decrypted(uint32_t aId,
+ mozilla::DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override { }
+
+ void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos) override { }
+
+ void Terminated() override {
+ if (mDecryptor) {
+ mDecryptor->Close();
+ mDecryptor = nullptr;
+ }
+ }
+
+private:
+ ~GMPStorageTest() { }
+
+ struct ExpectedMessage {
+ ExpectedMessage(const nsCString& aMessage, already_AddRefed<nsIRunnable> aContinuation)
+ : mMessage(aMessage)
+ , mContinuation(aContinuation)
+ {}
+ nsCString mMessage;
+ nsCOMPtr<nsIRunnable> mContinuation;
+ };
+
+ nsTArray<ExpectedMessage> mExpected;
+
+ RefPtr<nsIRunnable> mSetDecryptorIdContinuation;
+
+ GMPDecryptorProxy* mDecryptor;
+ Monitor mMonitor;
+ Atomic<bool> mFinished;
+ nsCString mNodeId;
+};
+
+void
+GMPTestRunner::DoTest(void (GMPTestRunner::*aTestMethod)(GMPTestMonitor&))
+{
+ nsCOMPtr<nsIThread> thread(GetGMPThread());
+
+ GMPTestMonitor monitor;
+ thread->Dispatch(NewRunnableMethod<GMPTestMonitor&>(this,
+ aTestMethod,
+ monitor),
+ NS_DISPATCH_NORMAL);
+ monitor.AwaitFinished();
+}
+
+TEST(GeckoMediaPlugins, GMPTestCodec) {
+ RefPtr<GMPTestRunner> runner = new GMPTestRunner();
+ runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec1);
+ runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec2);
+ runner->DoTest(&GMPTestRunner::RunTestGMPTestCodec3);
+}
+
+TEST(GeckoMediaPlugins, GMPCrossOrigin) {
+ RefPtr<GMPTestRunner> runner = new GMPTestRunner();
+ runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin1);
+ runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin2);
+ runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin3);
+ runner->DoTest(&GMPTestRunner::RunTestGMPCrossOrigin4);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageGetNodeId) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestGetNodeId);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageBasic) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestBasicStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageForgetThisSite) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestForgetThisSite);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory1) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestClearRecentHistory1);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory2) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestClearRecentHistory2);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageClearRecentHistory3) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestClearRecentHistory3);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageCrossOrigin) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestCrossOriginStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStoragePrivateBrowsing) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestPBStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageAsyncShutdownTimeout) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestAsyncShutdownTimeout);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageAsyncShutdownStorage) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestAsyncShutdownStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPPluginVoucher) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestPluginVoucher);
+}
+
+#if defined(XP_WIN)
+TEST(GeckoMediaPlugins, GMPOutputProtection) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestOutputProtection);
+}
+#endif
+
+TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesInMemoryStorage) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestGetRecordNamesInMemoryStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesPersistentStorage) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::GetRecordNamesPersistentStorage);
+}
+
+TEST(GeckoMediaPlugins, GMPStorageLongRecordNames) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestLongRecordNames);
+}
+
+TEST(GeckoMediaPlugins, GMPNodeId) {
+ RefPtr<GMPStorageTest> runner = new GMPStorageTest();
+ runner->DoTest(&GMPStorageTest::TestNodeId);
+}
diff --git a/dom/media/gtest/TestGMPRemoveAndDelete.cpp b/dom/media/gtest/TestGMPRemoveAndDelete.cpp
new file mode 100644
index 000000000..31049bb1d
--- /dev/null
+++ b/dom/media/gtest/TestGMPRemoveAndDelete.cpp
@@ -0,0 +1,490 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPService.h"
+#include "GMPTestMonitor.h"
+#include "gmp-api/gmp-video-host.h"
+#include "gtest/gtest.h"
+#include "mozilla/Services.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIObserverService.h"
+#include "GMPVideoDecoderProxy.h"
+#include "GMPServiceParent.h"
+#include "GMPService.h"
+#include "GMPUtils.h"
+#include "mozilla/StaticPtr.h"
+#include "MediaPrefs.h"
+
+#define GMP_DIR_NAME NS_LITERAL_STRING("gmp-fakeopenh264")
+#define GMP_OLD_VERSION NS_LITERAL_STRING("1.0")
+#define GMP_NEW_VERSION NS_LITERAL_STRING("1.1")
+
+#define GMP_DELETED_TOPIC "gmp-directory-deleted"
+
+#define EXPECT_OK(X) EXPECT_TRUE(NS_SUCCEEDED(X))
+
+using namespace mozilla;
+using namespace mozilla::gmp;
+
+class GMPRemoveTest : public nsIObserver
+ , public GMPVideoDecoderCallbackProxy
+{
+public:
+ GMPRemoveTest();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // Called when a GMP plugin directory has been successfully deleted.
+ // |aData| will contain the directory path.
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override;
+
+ // Create a new GMP plugin directory that we can trash and add it to the GMP
+ // service. Remove the original plugin directory. Original plugin directory
+ // gets re-added at destruction.
+ void Setup();
+
+ bool CreateVideoDecoder(nsCString aNodeId = EmptyCString());
+ void CloseVideoDecoder();
+
+ void DeletePluginDirectory(bool aCanDefer);
+
+ // Decode a dummy frame.
+ GMPErr Decode();
+
+ // Wait until TestMonitor has been signaled.
+ void Wait();
+
+ // Did we get a Terminated() callback from the plugin?
+ bool IsTerminated();
+
+ // From GMPVideoDecoderCallbackProxy
+ // Set mDecodeResult; unblock TestMonitor.
+ virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
+ virtual void Error(GMPErr aError) override;
+
+ // From GMPVideoDecoderCallbackProxy
+ // We expect this to be called when a plugin has been forcibly closed.
+ virtual void Terminated() override;
+
+ // Ignored GMPVideoDecoderCallbackProxy members
+ virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override {}
+ virtual void ReceivedDecodedFrame(const uint64_t aPictureId) override {}
+ virtual void InputDataExhausted() override {}
+ virtual void DrainComplete() override {}
+ virtual void ResetComplete() override {}
+
+private:
+ virtual ~GMPRemoveTest();
+
+ void gmp_Decode();
+ void gmp_GetVideoDecoder(nsCString aNodeId,
+ GMPVideoDecoderProxy** aOutDecoder,
+ GMPVideoHost** aOutHost);
+ void GeneratePlugin();
+
+ GMPTestMonitor mTestMonitor;
+ nsCOMPtr<nsIThread> mGMPThread;
+
+ bool mIsTerminated;
+
+ // Path to the cloned GMP we have created.
+ nsString mTmpPath;
+ nsCOMPtr<nsIFile> mTmpDir;
+
+ // Path to the original GMP. Store so that we can re-add it after we're done
+ // testing.
+ nsString mOriginalPath;
+
+ GMPVideoDecoderProxy* mDecoder;
+ GMPVideoHost* mHost;
+ GMPErr mDecodeResult;
+};
+
+/*
+ * Simple test that the plugin is deleted when forcibly removed and deleted.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteForcedSimple)
+{
+ RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+ test->Setup();
+ test->DeletePluginDirectory(false /* force immediate */);
+ test->Wait();
+}
+
+/*
+ * Simple test that the plugin is deleted when deferred deletion is allowed.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredSimple)
+{
+ RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+ test->Setup();
+ test->DeletePluginDirectory(true /* can defer */);
+ test->Wait();
+}
+
+/*
+ * Test that the plugin is unavailable immediately after a forced
+ * RemoveAndDelete, and that the plugin is deleted afterwards.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteForcedInUse)
+{
+ RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+ test->Setup();
+ EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+ // Test that we can decode a frame.
+ GMPErr err = test->Decode();
+ EXPECT_EQ(err, GMPNoErr);
+
+ test->DeletePluginDirectory(false /* force immediate */);
+ test->Wait();
+
+ // Test that the VideoDecoder is no longer available.
+ EXPECT_FALSE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+ // Test that we were notified of the plugin's destruction.
+ EXPECT_TRUE(test->IsTerminated());
+}
+
+/*
+ * Test that the plugin is still usable after a deferred RemoveAndDelete, and
+ * that the plugin is deleted afterwards.
+ */
+TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredInUse)
+{
+ RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
+
+ test->Setup();
+ EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+ // Make sure decoding works before we do anything.
+ GMPErr err = test->Decode();
+ EXPECT_EQ(err, GMPNoErr);
+
+ test->DeletePluginDirectory(true /* can defer */);
+
+ // Test that decoding still works.
+ err = test->Decode();
+ EXPECT_EQ(err, GMPNoErr);
+
+ // Test that this origin is still able to fetch the video decoder.
+ EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
+
+ test->CloseVideoDecoder();
+ test->Wait();
+}
+
+static StaticRefPtr<GeckoMediaPluginService> gService;
+static StaticRefPtr<GeckoMediaPluginServiceParent> gServiceParent;
+
+static GeckoMediaPluginService*
+GetService()
+{
+ if (!gService) {
+ RefPtr<GeckoMediaPluginService> service =
+ GeckoMediaPluginService::GetGeckoMediaPluginService();
+ gService = service;
+ }
+
+ return gService.get();
+}
+
+static GeckoMediaPluginServiceParent*
+GetServiceParent()
+{
+ if (!gServiceParent) {
+ RefPtr<GeckoMediaPluginServiceParent> parent =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ gServiceParent = parent;
+ }
+
+ return gServiceParent.get();
+}
+
+NS_IMPL_ISUPPORTS(GMPRemoveTest, nsIObserver)
+
+GMPRemoveTest::GMPRemoveTest()
+ : mIsTerminated(false)
+ , mDecoder(nullptr)
+ , mHost(nullptr)
+{
+}
+
+GMPRemoveTest::~GMPRemoveTest()
+{
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(mTmpDir->Exists(&exists)) && !exists);
+
+ EXPECT_OK(GetServiceParent()->AddPluginDirectory(mOriginalPath));
+}
+
+void
+GMPRemoveTest::Setup()
+{
+ // Initialize media preferences.
+ MediaPrefs::GetSingleton();
+ GeneratePlugin();
+ GetService()->GetThread(getter_AddRefs(mGMPThread));
+
+ // Spin the event loop until the GMP service has had a chance to complete
+ // adding GMPs from MOZ_GMP_PATH. Otherwise, the RemovePluginDirectory()
+ // below may complete before we're finished adding GMPs from MOZ_GMP_PATH,
+ // and we'll end up not removing the GMP, and the test will fail.
+ RefPtr<AbstractThread> thread(GetServiceParent()->GetAbstractGMPThread());
+ EXPECT_TRUE(thread);
+ GMPTestMonitor* mon = &mTestMonitor;
+ GetServiceParent()->EnsureInitialized()->Then(thread, __func__,
+ [mon]() { mon->SetFinished(); },
+ [mon]() { mon->SetFinished(); }
+ );
+ mTestMonitor.AwaitFinished();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(this, GMP_DELETED_TOPIC, false /* strong ref */);
+ EXPECT_OK(GetServiceParent()->RemovePluginDirectory(mOriginalPath));
+
+ GetServiceParent()->AsyncAddPluginDirectory(mTmpPath)->Then(thread, __func__,
+ [mon]() { mon->SetFinished(); },
+ [mon]() { mon->SetFinished(); }
+ );
+ mTestMonitor.AwaitFinished();
+}
+
+bool
+GMPRemoveTest::CreateVideoDecoder(nsCString aNodeId)
+{
+ GMPVideoHost* host;
+ GMPVideoDecoderProxy* decoder = nullptr;
+
+ mGMPThread->Dispatch(
+ NewNonOwningRunnableMethod<nsCString, GMPVideoDecoderProxy**, GMPVideoHost**>(
+ this, &GMPRemoveTest::gmp_GetVideoDecoder, aNodeId, &decoder, &host),
+ NS_DISPATCH_NORMAL);
+
+ mTestMonitor.AwaitFinished();
+
+ if (!decoder) {
+ return false;
+ }
+
+ GMPVideoCodec codec;
+ memset(&codec, 0, sizeof(codec));
+ codec.mGMPApiVersion = 33;
+
+ nsTArray<uint8_t> empty;
+ mGMPThread->Dispatch(
+ NewNonOwningRunnableMethod<const GMPVideoCodec&, const nsTArray<uint8_t>&, GMPVideoDecoderCallbackProxy*, int32_t>(
+ decoder, &GMPVideoDecoderProxy::InitDecode,
+ codec, empty, this, 1 /* core count */),
+ NS_DISPATCH_SYNC);
+
+ if (mDecoder) {
+ CloseVideoDecoder();
+ }
+
+ mDecoder = decoder;
+ mHost = host;
+
+ return true;
+}
+
+void
+GMPRemoveTest::gmp_GetVideoDecoder(nsCString aNodeId,
+ GMPVideoDecoderProxy** aOutDecoder,
+ GMPVideoHost** aOutHost)
+{
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ tags.AppendElement(NS_LITERAL_CSTRING("fake"));
+
+ class Callback : public GetGMPVideoDecoderCallback
+ {
+ public:
+ Callback(GMPTestMonitor* aMonitor, GMPVideoDecoderProxy** aDecoder, GMPVideoHost** aHost)
+ : mMonitor(aMonitor), mDecoder(aDecoder), mHost(aHost) { }
+ virtual void Done(GMPVideoDecoderProxy* aDecoder, GMPVideoHost* aHost) override {
+ *mDecoder = aDecoder;
+ *mHost = aHost;
+ mMonitor->SetFinished();
+ }
+ private:
+ GMPTestMonitor* mMonitor;
+ GMPVideoDecoderProxy** mDecoder;
+ GMPVideoHost** mHost;
+ };
+
+ UniquePtr<GetGMPVideoDecoderCallback>
+ cb(new Callback(&mTestMonitor, aOutDecoder, aOutHost));
+
+ if (NS_FAILED(GetService()->GetGMPVideoDecoder(nullptr, &tags, aNodeId, Move(cb)))) {
+ mTestMonitor.SetFinished();
+ }
+}
+
+void
+GMPRemoveTest::CloseVideoDecoder()
+{
+ mGMPThread->Dispatch(
+ NewNonOwningRunnableMethod(mDecoder, &GMPVideoDecoderProxy::Close),
+ NS_DISPATCH_SYNC);
+
+ mDecoder = nullptr;
+ mHost = nullptr;
+}
+
+void
+GMPRemoveTest::DeletePluginDirectory(bool aCanDefer)
+{
+ GetServiceParent()->RemoveAndDeletePluginDirectory(mTmpPath, aCanDefer);
+}
+
+GMPErr
+GMPRemoveTest::Decode()
+{
+ mGMPThread->Dispatch(
+ NewNonOwningRunnableMethod(this, &GMPRemoveTest::gmp_Decode),
+ NS_DISPATCH_NORMAL);
+
+ mTestMonitor.AwaitFinished();
+ return mDecodeResult;
+}
+
+void
+GMPRemoveTest::gmp_Decode()
+{
+ // from gmp-fake.cpp
+ struct EncodedFrame {
+ uint32_t length_;
+ uint8_t h264_compat_;
+ uint32_t magic_;
+ uint32_t width_;
+ uint32_t height_;
+ uint8_t y_;
+ uint8_t u_;
+ uint8_t v_;
+ uint32_t timestamp_;
+ };
+
+ GMPVideoFrame* absFrame;
+ GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &absFrame);
+ EXPECT_EQ(err, GMPNoErr);
+
+ GMPUniquePtr<GMPVideoEncodedFrame>
+ frame(static_cast<GMPVideoEncodedFrame*>(absFrame));
+ err = frame->CreateEmptyFrame(sizeof(EncodedFrame) /* size */);
+ EXPECT_EQ(err, GMPNoErr);
+
+ EncodedFrame* frameData = reinterpret_cast<EncodedFrame*>(frame->Buffer());
+ frameData->magic_ = 0x4652414d;
+ frameData->width_ = frameData->height_ = 16;
+
+ nsTArray<uint8_t> empty;
+ nsresult rv = mDecoder->Decode(Move(frame), false /* aMissingFrames */, empty);
+ EXPECT_OK(rv);
+}
+
+void
+GMPRemoveTest::Wait()
+{
+ mTestMonitor.AwaitFinished();
+}
+
+bool
+GMPRemoveTest::IsTerminated()
+{
+ return mIsTerminated;
+}
+
+// nsIObserver
+NS_IMETHODIMP
+GMPRemoveTest::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ EXPECT_TRUE(!strcmp(GMP_DELETED_TOPIC, aTopic));
+
+ nsString data(aData);
+ if (mTmpPath.Equals(data)) {
+ mTestMonitor.SetFinished();
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->RemoveObserver(this, GMP_DELETED_TOPIC);
+ }
+
+ return NS_OK;
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+ aDecodedFrame->Destroy();
+ mDecodeResult = GMPNoErr;
+ mTestMonitor.SetFinished();
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Error(GMPErr aError)
+{
+ mDecodeResult = aError;
+ mTestMonitor.SetFinished();
+}
+
+// GMPVideoDecoderCallbackProxy
+void
+GMPRemoveTest::Terminated()
+{
+ mIsTerminated = true;
+ if (mDecoder) {
+ mDecoder->Close();
+ mDecoder = nullptr;
+ }
+}
+
+void
+GMPRemoveTest::GeneratePlugin()
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> gmpDir;
+ nsCOMPtr<nsIFile> origDir;
+ nsCOMPtr<nsIFile> tmpDir;
+
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR,
+ getter_AddRefs(gmpDir));
+ EXPECT_OK(rv);
+ rv = gmpDir->Append(GMP_DIR_NAME);
+ EXPECT_OK(rv);
+
+ rv = gmpDir->Clone(getter_AddRefs(origDir));
+ EXPECT_OK(rv);
+ rv = origDir->Append(GMP_OLD_VERSION);
+ EXPECT_OK(rv);
+
+ rv = gmpDir->Clone(getter_AddRefs(tmpDir));
+ EXPECT_OK(rv);
+ rv = tmpDir->Append(GMP_NEW_VERSION);
+ EXPECT_OK(rv);
+ bool exists = false;
+ rv = tmpDir->Exists(&exists);
+ EXPECT_OK(rv);
+ if (exists) {
+ rv = tmpDir->Remove(true);
+ EXPECT_OK(rv);
+ }
+ rv = origDir->CopyTo(gmpDir, GMP_NEW_VERSION);
+ EXPECT_OK(rv);
+
+ rv = gmpDir->Clone(getter_AddRefs(tmpDir));
+ EXPECT_OK(rv);
+ rv = tmpDir->Append(GMP_NEW_VERSION);
+ EXPECT_OK(rv);
+
+ EXPECT_OK(origDir->GetPath(mOriginalPath));
+ EXPECT_OK(tmpDir->GetPath(mTmpPath));
+ mTmpDir = tmpDir;
+}
diff --git a/dom/media/gtest/TestGMPUtils.cpp b/dom/media/gtest/TestGMPUtils.cpp
new file mode 100644
index 000000000..00f09f812
--- /dev/null
+++ b/dom/media/gtest/TestGMPUtils.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "GMPUtils.h"
+#include "nsString.h"
+#include "MediaPrefs.h"
+
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace mozilla;
+
+void TestSplitAt(const char* aInput,
+ const char* aDelims,
+ size_t aNumExpectedTokens,
+ const char* aExpectedTokens[])
+{
+ // Initialize media preferences.
+ MediaPrefs::GetSingleton();
+ nsCString input(aInput);
+ nsTArray<nsCString> tokens;
+ SplitAt(aDelims, input, tokens);
+ EXPECT_EQ(tokens.Length(), aNumExpectedTokens) << "Should get expected number of tokens";
+ for (size_t i = 0; i < tokens.Length(); i++) {
+ EXPECT_TRUE(tokens[i].EqualsASCII(aExpectedTokens[i]))
+ << "Tokenize fail; expected=" << aExpectedTokens[i] << " got=" <<
+ tokens[i].BeginReading();
+ }
+}
+
+TEST(GeckoMediaPlugins, GMPUtils) {
+ {
+ const char* input = "1,2,3,4";
+ const char* delims = ",";
+ const char* tokens[] = { "1", "2", "3", "4" };
+ TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
+ }
+
+ {
+ const char* input = "a simple, comma, seperated, list";
+ const char* delims = ",";
+ const char* tokens[] = { "a simple", " comma", " seperated", " list" };
+ TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
+ }
+
+ {
+ const char* input = // Various platform line endings...
+ "line1\r\n" // Windows
+ "line2\r" // Old MacOSX
+ "line3\n" // Unix
+ "line4";
+ const char* delims = "\r\n";
+ const char* tokens[] = { "line1", "line2", "line3", "line4" };
+ TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
+ }
+}
diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build
index b7bd4b068..512d0a087 100644
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -10,6 +10,9 @@ UNIFIED_SOURCES += [
'TestAudioMixer.cpp',
'TestAudioPacketizer.cpp',
'TestAudioSegment.cpp',
+ 'TestGMPCrossOrigin.cpp',
+ 'TestGMPRemoveAndDelete.cpp',
+ 'TestGMPUtils.cpp',
'TestIntervalSet.cpp',
'TestMediaDataDecoder.cpp',
'TestMediaEventSource.cpp',
@@ -56,6 +59,7 @@ LOCAL_INCLUDES += [
'/dom/media',
'/dom/media/encoder',
'/dom/media/fmp4',
+ '/dom/media/gmp',
'/security/certverifier',
'/security/pkix/include',
]
diff --git a/dom/media/mediasource/TrackBuffersManager.cpp b/dom/media/mediasource/TrackBuffersManager.cpp
index da21e0b39..8663c1a8f 100644
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -58,6 +58,34 @@ AppendStateToStr(SourceBufferAttributes::AppendState aState)
static Atomic<uint32_t> sStreamSourceID(0u);
+#ifdef MOZ_EME
+class DispatchKeyNeededEvent : public Runnable {
+public:
+ DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
+ nsTArray<uint8_t>& aInitData,
+ const nsString& aInitDataType)
+ : mDecoder(aDecoder)
+ , mInitData(aInitData)
+ , mInitDataType(aInitDataType)
+ {
+ }
+ NS_IMETHOD Run() override {
+ // Note: Null check the owner, as the decoder could have been shutdown
+ // since this event was dispatched.
+ MediaDecoderOwner* owner = mDecoder->GetOwner();
+ if (owner) {
+ owner->DispatchEncrypted(mInitData, mInitDataType);
+ }
+ mDecoder = nullptr;
+ return NS_OK;
+ }
+private:
+ RefPtr<AbstractMediaDecoder> mDecoder;
+ nsTArray<uint8_t> mInitData;
+ nsString mInitDataType;
+};
+#endif // MOZ_EME
+
TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
const nsACString& aType)
: mInputBuffer(new MediaByteBuffer)
@@ -1073,6 +1101,14 @@ TrackBuffersManager::OnDemuxerInitDone(nsresult)
UniquePtr<EncryptionInfo> crypto = mInputDemuxer->GetCrypto();
if (crypto && crypto->IsEncrypted()) {
+#ifdef MOZ_EME
+ // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
+ for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
+ NS_DispatchToMainThread(
+ new DispatchKeyNeededEvent(mParentDecoder, crypto->mInitDatas[i].mInitData,
+ crypto->mInitDatas[i].mType));
+ }
+#endif
info.mCrypto = *crypto;
// We clear our crypto init data array, so the MediaFormatReader will
// not emit an encrypted event for the same init data again.
diff --git a/dom/media/moz.build b/dom/media/moz.build
index 6b0efc32b..fb162be81 100644
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -3,9 +3,15 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+with Files('*'):
+ BUG_COMPONENT = ('Core', 'Video/Audio')
+
DIRS += [
'encoder',
'flac',
+ 'gmp',
+ 'gmp-plugin',
+ 'gmp-plugin-openh264',
'imagecapture',
'ipc',
'mediasink',
@@ -25,6 +31,9 @@ DIRS += [
if CONFIG['MOZ_FMP4']:
DIRS += ['fmp4']
+if CONFIG['MOZ_EME']:
+ DIRS += ['eme']
+
TEST_DIRS += [
'gtest',
]
diff --git a/dom/media/platforms/PDMFactory.cpp b/dom/media/platforms/PDMFactory.cpp
index ecae512be..214fc80c8 100644
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -15,6 +16,7 @@
#ifdef MOZ_FFMPEG
#include "FFmpegRuntimeLinker.h"
#endif
+#include "GMPDecoderModule.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/SharedThreadPool.h"
@@ -29,6 +31,11 @@
#include "AgnosticDecoderModule.h"
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#include "EMEDecoderModule.h"
+#endif
+
#include "DecoderDoctorDiagnostics.h"
#include "MP4Decoder.h"
@@ -208,6 +215,9 @@ PDMFactory::CreateDecoder(const CreateDecoderParams& aParams)
if (mFFmpegFailedToLoad) {
diagnostics->SetFFmpegFailedToLoad();
}
+ if (mGMPPDMFailedToStartup) {
+ diagnostics->SetGMPPDMFailedToStartup();
+ }
}
for (auto& current : mCurrentPDMs) {
@@ -363,6 +373,13 @@ PDMFactory::CreatePDMs()
m = new AgnosticDecoderModule();
StartupPDM(m);
+
+ if (MediaPrefs::PDMGMPEnabled()) {
+ m = new GMPDecoderModule();
+ mGMPPDMFailedToStartup = !StartupPDM(m);
+ } else {
+ mGMPPDMFailedToStartup = false;
+ }
}
void
@@ -395,6 +412,9 @@ PDMFactory::GetDecoder(const TrackInfo& aTrackInfo,
if (mFFmpegFailedToLoad) {
aDiagnostics->SetFFmpegFailedToLoad();
}
+ if (mGMPPDMFailedToStartup) {
+ aDiagnostics->SetGMPPDMFailedToStartup();
+ }
}
RefPtr<PlatformDecoderModule> pdm;
@@ -407,4 +427,13 @@ PDMFactory::GetDecoder(const TrackInfo& aTrackInfo,
return pdm.forget();
}
+#ifdef MOZ_EME
+void
+PDMFactory::SetCDMProxy(CDMProxy* aProxy)
+{
+ RefPtr<PDMFactory> m = new PDMFactory();
+ mEMEPDM = new EMEDecoderModule(aProxy, m);
+}
+#endif
+
} // namespace mozilla
diff --git a/dom/media/platforms/PDMFactory.h b/dom/media/platforms/PDMFactory.h
index 129876ac2..a13c99ac0 100644
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -10,6 +11,10 @@
#include "mozilla/Function.h"
#include "mozilla/StaticMutex.h"
+#ifdef MOZ_EME
+class CDMProxy;
+#endif
+
namespace mozilla {
class DecoderDoctorDiagnostics;
@@ -35,6 +40,15 @@ public:
bool Supports(const TrackInfo& aTrackInfo,
DecoderDoctorDiagnostics* aDiagnostics) const;
+#ifdef MOZ_EME
+ // Creates a PlatformDecoderModule that uses a CDMProxy to decrypt or
+ // decrypt-and-decode EME encrypted content. If the CDM only decrypts and
+ // does not decode, we create a PDM and use that to create MediaDataDecoders
+ // that we use on on aTaskQueue to decode the decrypted stream.
+ // This is called on the decode task queue.
+ void SetCDMProxy(CDMProxy* aProxy);
+#endif
+
static const int kYUV400 = 0;
static const int kYUV420 = 1;
static const int kYUV422 = 2;
@@ -61,6 +75,7 @@ private:
bool mWMFFailedToLoad = false;
bool mFFmpegFailedToLoad = false;
+ bool mGMPPDMFailedToStartup = false;
void EnsureInit() const;
template<class T> friend class StaticAutoPtr;
diff --git a/dom/media/platforms/PlatformDecoderModule.h b/dom/media/platforms/PlatformDecoderModule.h
index e2ef1f752..62855335f 100644
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -16,6 +16,7 @@
#include "mozilla/layers/KnowsCompositor.h"
#include "nsTArray.h"
#include "mozilla/RefPtr.h"
+#include "GMPService.h"
#include <queue>
#include "MediaResult.h"
@@ -37,6 +38,7 @@ class RemoteDecoderModule;
class MediaDataDecoder;
class MediaDataDecoderCallback;
class TaskQueue;
+class CDMProxy;
static LazyLogModule sPDMLog("PlatformDecoderModule");
@@ -79,6 +81,7 @@ struct MOZ_STACK_CLASS CreateDecoderParams final {
layers::ImageContainer* mImageContainer = nullptr;
MediaResult* mError = nullptr;
RefPtr<layers::KnowsCompositor> mKnowsCompositor;
+ RefPtr<GMPCrashHelper> mCrashHelper;
bool mUseBlankDecoder = false;
private:
@@ -87,6 +90,7 @@ private:
void Set(DecoderDoctorDiagnostics* aDiagnostics) { mDiagnostics = aDiagnostics; }
void Set(layers::ImageContainer* aImageContainer) { mImageContainer = aImageContainer; }
void Set(MediaResult* aError) { mError = aError; }
+ void Set(GMPCrashHelper* aCrashHelper) { mCrashHelper = aCrashHelper; }
void Set(bool aUseBlankDecoder) { mUseBlankDecoder = aUseBlankDecoder; }
void Set(layers::KnowsCompositor* aKnowsCompositor) { mKnowsCompositor = aKnowsCompositor; }
template <typename T1, typename T2, typename... Ts>
diff --git a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp
new file mode 100644
index 000000000..25e502025
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EMEAudioDecoder.h"
+#include "mozilla/CDMProxy.h"
+
+namespace mozilla {
+
+void
+EMEAudioCallbackAdapter::Error(GMPErr aErr)
+{
+ if (aErr == GMPNoKeyErr) {
+ // The GMP failed to decrypt a frame due to not having a key. This can
+ // happen if a key expires or a session is closed during playback.
+ NS_WARNING("GMP failed to decrypt due to lack of key");
+ return;
+ }
+ AudioCallbackAdapter::Error(aErr);
+}
+
+EMEAudioDecoder::EMEAudioDecoder(CDMProxy* aProxy,
+ const GMPAudioDecoderParams& aParams)
+ : GMPAudioDecoder(GMPAudioDecoderParams(aParams).WithAdapter(
+ new EMEAudioCallbackAdapter(aParams.mCallback)))
+ , mProxy(aProxy)
+{}
+
+void
+EMEAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
+ aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+}
+
+nsCString
+EMEAudioDecoder::GetNodeId()
+{
+ return mProxy->GetNodeId();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h
new file mode 100644
index 000000000..c5944cf8c
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef EMEAudioDecoder_h_
+#define EMEAudioDecoder_h_
+
+#include "GMPAudioDecoder.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class EMEAudioCallbackAdapter : public AudioCallbackAdapter {
+public:
+ explicit EMEAudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback)
+ : AudioCallbackAdapter(aCallback)
+ {}
+
+ void Error(GMPErr aErr) override;
+};
+
+class EMEAudioDecoder : public GMPAudioDecoder {
+public:
+ EMEAudioDecoder(CDMProxy* aProxy, const GMPAudioDecoderParams& aParams);
+
+private:
+ void InitTags(nsTArray<nsCString>& aTags) override;
+ nsCString GetNodeId() override;
+
+ RefPtr<CDMProxy> mProxy;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
new file mode 100644
index 000000000..7d523d926
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EMEDecoderModule.h"
+#include "EMEAudioDecoder.h"
+#include "EMEVideoDecoder.h"
+#include "MediaDataDecoderProxy.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/Unused.h"
+#include "nsAutoPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "MediaInfo.h"
+#include "nsClassHashtable.h"
+#include "GMPDecoderModule.h"
+#include "MP4Decoder.h"
+
+namespace mozilla {
+
+typedef MozPromiseRequestHolder<CDMProxy::DecryptPromise> DecryptPromiseRequestHolder;
+
+class EMEDecryptor : public MediaDataDecoder {
+
+public:
+
+ EMEDecryptor(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ CDMProxy* aProxy,
+ TaskQueue* aDecodeTaskQueue)
+ : mDecoder(aDecoder)
+ , mCallback(aCallback)
+ , mTaskQueue(aDecodeTaskQueue)
+ , mProxy(aProxy)
+ , mSamplesWaitingForKey(new SamplesWaitingForKey(this, this->mCallback,
+ mTaskQueue, mProxy))
+ , mIsShutdown(false)
+ {
+ }
+
+ RefPtr<InitPromise> Init() override {
+ MOZ_ASSERT(!mIsShutdown);
+ return mDecoder->Init();
+ }
+
+ void Input(MediaRawData* aSample) override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (mIsShutdown) {
+ NS_WARNING("EME encrypted sample arrived after shutdown");
+ return;
+ }
+ if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+ return;
+ }
+
+ nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+ mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
+ writer->mCrypto.mSessionIds);
+
+ mDecrypts.Put(aSample, new DecryptPromiseRequestHolder());
+ mDecrypts.Get(aSample)->Begin(mProxy->Decrypt(aSample)->Then(
+ mTaskQueue, __func__, this,
+ &EMEDecryptor::Decrypted,
+ &EMEDecryptor::Decrypted));
+ return;
+ }
+
+ void Decrypted(const DecryptResult& aDecrypted) {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(aDecrypted.mSample);
+
+ nsAutoPtr<DecryptPromiseRequestHolder> holder;
+ mDecrypts.RemoveAndForget(aDecrypted.mSample, holder);
+ if (holder) {
+ holder->Complete();
+ } else {
+ // Decryption is not in the list of decrypt operations waiting
+ // for a result. It must have been flushed or drained. Ignore result.
+ return;
+ }
+
+ if (mIsShutdown) {
+ NS_WARNING("EME decrypted sample arrived after shutdown");
+ return;
+ }
+
+ if (aDecrypted.mStatus == NoKeyErr) {
+ // Key became unusable after we sent the sample to CDM to decrypt.
+ // Call Input() again, so that the sample is enqueued for decryption
+ // if the key becomes usable again.
+ Input(aDecrypted.mSample);
+ } else if (aDecrypted.mStatus != Ok) {
+ if (mCallback) {
+ mCallback->Error(MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("decrypted.mStatus=%u", uint32_t(aDecrypted.mStatus))));
+ }
+ } else {
+ MOZ_ASSERT(!mIsShutdown);
+ // The Adobe GMP AAC decoder gets confused if we pass it non-encrypted
+ // samples with valid crypto data. So clear the crypto data, since the
+ // sample should be decrypted now anyway. If we don't do this and we're
+ // using the Adobe GMP for unencrypted decoding of data that is decrypted
+ // by gmp-clearkey, decoding will fail.
+ UniquePtr<MediaRawDataWriter> writer(aDecrypted.mSample->CreateWriter());
+ writer->mCrypto = CryptoSample();
+ mDecoder->Input(aDecrypted.mSample);
+ }
+ }
+
+ void Flush() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
+ holder->DisconnectIfExists();
+ iter.Remove();
+ }
+ mDecoder->Flush();
+ mSamplesWaitingForKey->Flush();
+ }
+
+ void Drain() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
+ holder->DisconnectIfExists();
+ iter.Remove();
+ }
+ mDecoder->Drain();
+ }
+
+ void Shutdown() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ mIsShutdown = true;
+ mDecoder->Shutdown();
+ mSamplesWaitingForKey->BreakCycles();
+ mSamplesWaitingForKey = nullptr;
+ mDecoder = nullptr;
+ mProxy = nullptr;
+ mCallback = nullptr;
+ }
+
+ const char* GetDescriptionName() const override {
+ return mDecoder->GetDescriptionName();
+ }
+
+private:
+
+ RefPtr<MediaDataDecoder> mDecoder;
+ MediaDataDecoderCallback* mCallback;
+ RefPtr<TaskQueue> mTaskQueue;
+ RefPtr<CDMProxy> mProxy;
+ nsClassHashtable<nsRefPtrHashKey<MediaRawData>, DecryptPromiseRequestHolder> mDecrypts;
+ RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+ bool mIsShutdown;
+};
+
+class EMEMediaDataDecoderProxy : public MediaDataDecoderProxy {
+public:
+ EMEMediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,
+ MediaDataDecoderCallback* aCallback,
+ CDMProxy* aProxy,
+ TaskQueue* aTaskQueue)
+ : MediaDataDecoderProxy(Move(aProxyThread), aCallback)
+ , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback,
+ aTaskQueue, aProxy))
+ , mProxy(aProxy)
+ {
+ }
+
+ void Input(MediaRawData* aSample) override;
+ void Shutdown() override;
+
+private:
+ RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+ RefPtr<CDMProxy> mProxy;
+};
+
+void
+EMEMediaDataDecoderProxy::Input(MediaRawData* aSample)
+{
+ if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+ return;
+ }
+
+ nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+ mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
+ writer->mCrypto.mSessionIds);
+
+ MediaDataDecoderProxy::Input(aSample);
+}
+
+void
+EMEMediaDataDecoderProxy::Shutdown()
+{
+ MediaDataDecoderProxy::Shutdown();
+
+ mSamplesWaitingForKey->BreakCycles();
+ mSamplesWaitingForKey = nullptr;
+ mProxy = nullptr;
+}
+
+EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM)
+ : mProxy(aProxy)
+ , mPDM(aPDM)
+{
+}
+
+EMEDecoderModule::~EMEDecoderModule()
+{
+}
+
+static already_AddRefed<MediaDataDecoderProxy>
+CreateDecoderWrapper(MediaDataDecoderCallback* aCallback, CDMProxy* aProxy, TaskQueue* aTaskQueue)
+{
+ RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (!s) {
+ return nullptr;
+ }
+ RefPtr<AbstractThread> thread(s->GetAbstractGMPThread());
+ if (!thread) {
+ return nullptr;
+ }
+ RefPtr<MediaDataDecoderProxy> decoder(
+ new EMEMediaDataDecoderProxy(thread.forget(), aCallback, aProxy, aTaskQueue));
+ return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+ MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
+
+ if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
+ // GMP decodes. Assume that means it can decrypt too.
+ RefPtr<MediaDataDecoderProxy> wrapper =
+ CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue);
+ auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
+ return wrapper.forget();
+ }
+
+ MOZ_ASSERT(mPDM);
+ RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
+ if (!decoder) {
+ return nullptr;
+ }
+
+ RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
+ aParams.mCallback,
+ mProxy,
+ AbstractThread::GetCurrent()->AsTaskQueue()));
+ return emeDecoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
+{
+ MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
+
+ if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
+ // GMP decodes. Assume that means it can decrypt too.
+ RefPtr<MediaDataDecoderProxy> wrapper =
+ CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue);
+ auto gmpParams = GMPAudioDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy, gmpParams));
+ return wrapper.forget();
+ }
+
+ MOZ_ASSERT(mPDM);
+ RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
+ if (!decoder) {
+ return nullptr;
+ }
+
+ RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
+ aParams.mCallback,
+ mProxy,
+ AbstractThread::GetCurrent()->AsTaskQueue()));
+ return emeDecoder.forget();
+}
+
+PlatformDecoderModule::ConversionRequired
+EMEDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
+{
+ if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) {
+ return ConversionRequired::kNeedAVCC;
+ } else {
+ return ConversionRequired::kNeedNone;
+ }
+}
+
+bool
+EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const
+{
+ Maybe<nsCString> gmp;
+ gmp.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+ return GMPDecoderModule::SupportsMimeType(aMimeType, gmp);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
new file mode 100644
index 000000000..32074ae8c
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(EMEDecoderModule_h_)
+#define EMEDecoderModule_h_
+
+#include "PlatformDecoderModule.h"
+#include "PDMFactory.h"
+#include "gmp-decryption.h"
+
+namespace mozilla {
+
+class CDMProxy;
+
+class EMEDecoderModule : public PlatformDecoderModule {
+private:
+
+public:
+ EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM);
+
+ virtual ~EMEDecoderModule();
+
+protected:
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateVideoDecoder(const CreateDecoderParams& aParams) override;
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateAudioDecoder(const CreateDecoderParams& aParams) override;
+
+ ConversionRequired
+ DecoderNeedsConversion(const TrackInfo& aConfig) const override;
+
+ bool
+ SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+private:
+ RefPtr<CDMProxy> mProxy;
+ // Will be null if CDM has decoding capability.
+ RefPtr<PDMFactory> mPDM;
+ // We run the PDM on its own task queue.
+ RefPtr<TaskQueue> mTaskQueue;
+};
+
+} // namespace mozilla
+
+#endif // EMEDecoderModule_h_
diff --git a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp
new file mode 100644
index 000000000..bddb89660
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EMEVideoDecoder.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "mozilla/CDMProxy.h"
+#include "MediaData.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+
+namespace mozilla {
+
+void
+EMEVideoCallbackAdapter::Error(GMPErr aErr)
+{
+ if (aErr == GMPNoKeyErr) {
+ // The GMP failed to decrypt a frame due to not having a key. This can
+ // happen if a key expires or a session is closed during playback.
+ NS_WARNING("GMP failed to decrypt due to lack of key");
+ return;
+ }
+ VideoCallbackAdapter::Error(aErr);
+}
+
+EMEVideoDecoder::EMEVideoDecoder(CDMProxy* aProxy,
+ const GMPVideoDecoderParams& aParams)
+ : GMPVideoDecoder(GMPVideoDecoderParams(aParams).WithAdapter(
+ new EMEVideoCallbackAdapter(aParams.mCallback,
+ VideoInfo(aParams.mConfig.mDisplay),
+ aParams.mImageContainer)))
+ , mProxy(aProxy)
+ , mDecryptorId(aProxy->GetDecryptorId())
+{}
+
+void
+EMEVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ VideoInfo config = GetConfig();
+ if (MP4Decoder::IsH264(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ } else if (VPXDecoder::IsVP8(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp8"));
+ } else if (VPXDecoder::IsVP9(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp9"));
+ }
+ aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+}
+
+nsCString
+EMEVideoDecoder::GetNodeId()
+{
+ return mProxy->GetNodeId();
+}
+
+GMPUniquePtr<GMPVideoEncodedFrame>
+EMEVideoDecoder::CreateFrame(MediaRawData* aSample)
+{
+ GMPUniquePtr<GMPVideoEncodedFrame> frame = GMPVideoDecoder::CreateFrame(aSample);
+ if (frame && aSample->mCrypto.mValid) {
+ static_cast<gmp::GMPVideoEncodedFrameImpl*>(frame.get())->InitCrypto(aSample->mCrypto);
+ }
+ return frame;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h
new file mode 100644
index 000000000..a0f23f867
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef EMEVideoDecoder_h_
+#define EMEVideoDecoder_h_
+
+#include "GMPVideoDecoder.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class CDMProxy;
+class TaskQueue;
+
+class EMEVideoCallbackAdapter : public VideoCallbackAdapter {
+public:
+ EMEVideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback,
+ VideoInfo aVideoInfo,
+ layers::ImageContainer* aImageContainer)
+ : VideoCallbackAdapter(aCallback, aVideoInfo, aImageContainer)
+ {}
+
+ void Error(GMPErr aErr) override;
+};
+
+class EMEVideoDecoder : public GMPVideoDecoder {
+public:
+ EMEVideoDecoder(CDMProxy* aProxy, const GMPVideoDecoderParams& aParams);
+
+private:
+ void InitTags(nsTArray<nsCString>& aTags) override;
+ nsCString GetNodeId() override;
+ uint32_t DecryptorId() const override { return mDecryptorId; }
+ GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample) override;
+
+ RefPtr<CDMProxy> mProxy;
+ uint32_t mDecryptorId;
+};
+
+} // namespace mozilla
+
+#endif // EMEVideoDecoder_h_
diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
new file mode 100644
index 000000000..532fc63c0
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SamplesWaitingForKey.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+SamplesWaitingForKey::SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ TaskQueue* aTaskQueue,
+ CDMProxy* aProxy)
+ : mMutex("SamplesWaitingForKey")
+ , mDecoder(aDecoder)
+ , mDecoderCallback(aCallback)
+ , mTaskQueue(aTaskQueue)
+ , mProxy(aProxy)
+{
+}
+
+SamplesWaitingForKey::~SamplesWaitingForKey()
+{
+}
+
+bool
+SamplesWaitingForKey::WaitIfKeyNotUsable(MediaRawData* aSample)
+{
+ if (!aSample || !aSample->mCrypto.mValid || !mProxy) {
+ return false;
+ }
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ const auto& keyid = aSample->mCrypto.mKeyId;
+ if (!caps.IsKeyUsable(keyid)) {
+ {
+ MutexAutoLock lock(mMutex);
+ mSamples.AppendElement(aSample);
+ }
+ mDecoderCallback->WaitingForKey();
+ caps.NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this);
+ return true;
+ }
+ return false;
+}
+
+void
+SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId)
+{
+ MutexAutoLock lock(mMutex);
+ size_t i = 0;
+ while (i < mSamples.Length()) {
+ if (aKeyId == mSamples[i]->mCrypto.mKeyId) {
+ RefPtr<nsIRunnable> task;
+ task = NewRunnableMethod<RefPtr<MediaRawData>>(mDecoder,
+ &MediaDataDecoder::Input,
+ RefPtr<MediaRawData>(mSamples[i]));
+ mSamples.RemoveElementAt(i);
+ mTaskQueue->Dispatch(task.forget());
+ } else {
+ i++;
+ }
+ }
+}
+
+void
+SamplesWaitingForKey::Flush()
+{
+ MutexAutoLock lock(mMutex);
+ mSamples.Clear();
+}
+
+void
+SamplesWaitingForKey::BreakCycles()
+{
+ MutexAutoLock lock(mMutex);
+ mDecoder = nullptr;
+ mTaskQueue = nullptr;
+ mProxy = nullptr;
+ mSamples.Clear();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
new file mode 100644
index 000000000..65bb14403
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SamplesWaitingForKey_h_
+#define SamplesWaitingForKey_h_
+
+#include "mozilla/TaskQueue.h"
+
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+typedef nsTArray<uint8_t> CencKeyId;
+
+class CDMProxy;
+
+// Encapsulates the task of waiting for the CDMProxy to have the necessary
+// keys to decrypt a given sample.
+class SamplesWaitingForKey {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
+
+ explicit SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ TaskQueue* aTaskQueue,
+ CDMProxy* aProxy);
+
+ // Returns true if we need to wait for a key to become usable.
+ // Will callback MediaDataDecoder::Input(aSample) on mDecoder once the
+ // sample is ready to be decrypted. The order of input samples is
+ // preserved.
+ bool WaitIfKeyNotUsable(MediaRawData* aSample);
+
+ void NotifyUsable(const CencKeyId& aKeyId);
+
+ void Flush();
+
+ void BreakCycles();
+
+protected:
+ ~SamplesWaitingForKey();
+
+private:
+ Mutex mMutex;
+ RefPtr<MediaDataDecoder> mDecoder;
+ MediaDataDecoderCallback* mDecoderCallback;
+ RefPtr<TaskQueue> mTaskQueue;
+ RefPtr<CDMProxy> mProxy;
+ nsTArray<RefPtr<MediaRawData>> mSamples;
+};
+
+} // namespace mozilla
+
+#endif // SamplesWaitingForKey_h_
diff --git a/dom/media/platforms/agnostic/eme/moz.build b/dom/media/platforms/agnostic/eme/moz.build
new file mode 100644
index 000000000..97156f33a
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'EMEAudioDecoder.h',
+ 'EMEDecoderModule.h',
+ 'EMEVideoDecoder.h',
+ 'SamplesWaitingForKey.h',
+]
+
+SOURCES += [
+ 'EMEAudioDecoder.cpp',
+ 'EMEDecoderModule.cpp',
+ 'EMEVideoDecoder.cpp',
+ 'SamplesWaitingForKey.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
new file mode 100644
index 000000000..d863d44d4
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPAudioDecoder.h"
+#include "nsServiceManagerUtils.h"
+#include "MediaInfo.h"
+#include "GMPDecoderModule.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+bool IsOnGMPThread()
+{
+ nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mps);
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = mps->GetThread(getter_AddRefs(gmpThread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread);
+ return NS_GetCurrentThread() == gmpThread;
+}
+#endif
+
+void
+AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (aRate == 0 || aChannels == 0) {
+ mCallback->Error(MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL(
+ "Invalid rate or num channels returned on GMP audio samples")));
+ return;
+ }
+
+ size_t numFrames = aPCM.Length() / aChannels;
+ MOZ_ASSERT((aPCM.Length() % aChannels) == 0);
+ AlignedAudioBuffer audioData(aPCM.Length());
+ if (!audioData) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Unable to allocate audio buffer")));
+ return;
+ }
+
+ for (size_t i = 0; i < aPCM.Length(); ++i) {
+ audioData[i] = AudioSampleToFloat(aPCM[i]);
+ }
+
+ if (mMustRecaptureAudioPosition) {
+ mAudioFrameSum = 0;
+ auto timestamp = UsecsToFrames(aTimeStamp, aRate);
+ if (!timestamp.isValid()) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid timestamp")));
+ return;
+ }
+ mAudioFrameOffset = timestamp.value();
+ mMustRecaptureAudioPosition = false;
+ }
+
+ auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate);
+ if (!timestamp.isValid()) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid timestamp on audio samples")));
+ return;
+ }
+ mAudioFrameSum += numFrames;
+
+ auto duration = FramesToUsecs(numFrames, aRate);
+ if (!duration.isValid()) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid duration on audio samples")));
+ return;
+ }
+
+ RefPtr<AudioData> audio(new AudioData(mLastStreamOffset,
+ timestamp.value(),
+ duration.value(),
+ numFrames,
+ Move(audioData),
+ aChannels,
+ aRate));
+
+#ifdef LOG_SAMPLE_DECODE
+ LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u",
+ timestamp, duration, currentLength);
+#endif
+
+ mCallback->Output(audio);
+}
+
+void
+AudioCallbackAdapter::InputDataExhausted()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->InputExhausted();
+}
+
+void
+AudioCallbackAdapter::DrainComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->DrainComplete();
+}
+
+void
+AudioCallbackAdapter::ResetComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mMustRecaptureAudioPosition = true;
+ mCallback->FlushComplete();
+}
+
+void
+AudioCallbackAdapter::Error(GMPErr aErr)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->Error(MediaResult(aErr == GMPDecodeErr
+ ? NS_ERROR_DOM_MEDIA_DECODE_ERR
+ : NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("GMPErr:%x", aErr)));
+}
+
+void
+AudioCallbackAdapter::Terminated()
+{
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Audio GMP decoder terminated.")));
+}
+
+GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams)
+ : mConfig(aParams.AudioConfig())
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(nullptr)
+ , mAdapter(nullptr)
+ , mCrashHelper(aParams.mCrashHelper)
+{}
+
+GMPAudioDecoderParams&
+GMPAudioDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper)
+{
+ MOZ_ASSERT(aWrapper);
+ MOZ_ASSERT(!mCallback); // Should only be called once per instance.
+ mCallback = aWrapper->Callback();
+ mAdapter = nullptr;
+ return *this;
+}
+
+GMPAudioDecoderParams&
+GMPAudioDecoderParams::WithAdapter(AudioCallbackAdapter* aAdapter)
+{
+ MOZ_ASSERT(aAdapter);
+ MOZ_ASSERT(!mAdapter); // Should only be called once per instance.
+ mCallback = aAdapter->Callback();
+ mAdapter = aAdapter;
+ return *this;
+}
+
+GMPAudioDecoder::GMPAudioDecoder(const GMPAudioDecoderParams& aParams)
+ : mConfig(aParams.mConfig)
+ , mCallback(aParams.mCallback)
+ , mGMP(nullptr)
+ , mAdapter(aParams.mAdapter)
+ , mCrashHelper(aParams.mCrashHelper)
+{
+ MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback());
+ if (!mAdapter) {
+ mAdapter = new AudioCallbackAdapter(mCallback);
+ }
+}
+
+void
+GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
+ const Maybe<nsCString> gmp(
+ GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm")));
+ if (gmp.isSome()) {
+ aTags.AppendElement(gmp.value());
+ }
+}
+
+nsCString
+GMPAudioDecoder::GetNodeId()
+{
+ return SHARED_GMP_DECODING_NODE_ID;
+}
+
+void
+GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!aGMP) {
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ if (mInitPromise.IsEmpty()) {
+ // GMP must have been shutdown while we were waiting for Init operation
+ // to complete.
+ aGMP->Close();
+ return;
+ }
+ nsTArray<uint8_t> codecSpecific;
+ codecSpecific.AppendElements(mConfig.mCodecSpecificConfig->Elements(),
+ mConfig.mCodecSpecificConfig->Length());
+
+ nsresult rv = aGMP->InitDecode(kGMPAudioCodecAAC,
+ mConfig.mChannels,
+ mConfig.mBitDepth,
+ mConfig.mRate,
+ codecSpecific,
+ mAdapter);
+ if (NS_FAILED(rv)) {
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+
+ mGMP = aGMP;
+ mInitPromise.Resolve(TrackInfo::kAudioTrack, __func__);
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+GMPAudioDecoder::Init()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mMPS);
+
+ RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__));
+
+ nsTArray<nsCString> tags;
+ InitTags(tags);
+ UniquePtr<GetGMPAudioDecoderCallback> callback(new GMPInitDoneCallback(this));
+ if (NS_FAILED(mMPS->GetGMPAudioDecoder(mCrashHelper, &tags, GetNodeId(), Move(callback)))) {
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ return promise;
+}
+
+void
+GMPAudioDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ RefPtr<MediaRawData> sample(aSample);
+ if (!mGMP) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("mGMP not initialized")));
+ return;
+ }
+
+ mAdapter->SetLastStreamOffset(sample->mOffset);
+
+ gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate);
+ nsresult rv = mGMP->Decode(samples);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(MediaResult(rv, __func__));
+ }
+}
+
+void
+GMPAudioDecoder::Flush()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Reset())) {
+ // Abort the flush.
+ mCallback->FlushComplete();
+ }
+}
+
+void
+GMPAudioDecoder::Drain()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Drain())) {
+ mCallback->DrainComplete();
+ }
+}
+
+void
+GMPAudioDecoder::Shutdown()
+{
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ if (!mGMP) {
+ return;
+ }
+ // Note this unblocks flush and drain operations waiting for callbacks.
+ mGMP->Close();
+ mGMP = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h
new file mode 100644
index 000000000..90e3ebdb6
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPAudioDecoder_h_)
+#define GMPAudioDecoder_h_
+
+#include "GMPAudioDecoderProxy.h"
+#include "MediaDataDecoderProxy.h"
+#include "PlatformDecoderModule.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+class AudioCallbackAdapter : public GMPAudioDecoderCallbackProxy {
+public:
+ explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback)
+ : mCallback(aCallback)
+ , mLastStreamOffset(0)
+ , mAudioFrameSum(0)
+ , mAudioFrameOffset(0)
+ , mMustRecaptureAudioPosition(true)
+ {}
+
+ MediaDataDecoderCallbackProxy* Callback() const { return mCallback; }
+
+ // GMPAudioDecoderCallbackProxy
+ void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aErr) override;
+ void Terminated() override;
+
+ void SetLastStreamOffset(int64_t aStreamOffset) {
+ mLastStreamOffset = aStreamOffset;
+ }
+
+private:
+ MediaDataDecoderCallbackProxy* mCallback;
+ int64_t mLastStreamOffset;
+
+ int64_t mAudioFrameSum;
+ int64_t mAudioFrameOffset;
+ bool mMustRecaptureAudioPosition;
+};
+
+struct GMPAudioDecoderParams {
+ explicit GMPAudioDecoderParams(const CreateDecoderParams& aParams);
+ GMPAudioDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper);
+ GMPAudioDecoderParams& WithAdapter(AudioCallbackAdapter* aAdapter);
+
+ const AudioInfo& mConfig;
+ TaskQueue* mTaskQueue;
+ MediaDataDecoderCallbackProxy* mCallback;
+ AudioCallbackAdapter* mAdapter;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+class GMPAudioDecoder : public MediaDataDecoder {
+public:
+ explicit GMPAudioDecoder(const GMPAudioDecoderParams& aParams);
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "GMP audio decoder";
+ }
+
+protected:
+ virtual void InitTags(nsTArray<nsCString>& aTags);
+ virtual nsCString GetNodeId();
+
+private:
+
+ class GMPInitDoneCallback : public GetGMPAudioDecoderCallback
+ {
+ public:
+ explicit GMPInitDoneCallback(GMPAudioDecoder* aDecoder)
+ : mDecoder(aDecoder)
+ {
+ }
+
+ void Done(GMPAudioDecoderProxy* aGMP) override
+ {
+ mDecoder->GMPInitDone(aGMP);
+ }
+
+ private:
+ RefPtr<GMPAudioDecoder> mDecoder;
+ };
+ void GMPInitDone(GMPAudioDecoderProxy* aGMP);
+
+ const AudioInfo mConfig;
+ MediaDataDecoderCallbackProxy* mCallback;
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ GMPAudioDecoderProxy* mGMP;
+ nsAutoPtr<AudioCallbackAdapter> mAdapter;
+ MozPromiseHolder<InitPromise> mInitPromise;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+} // namespace mozilla
+
+#endif // GMPAudioDecoder_h_
diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
new file mode 100644
index 000000000..50a5097ac
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPDecoderModule.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "GMPAudioDecoder.h"
+#include "GMPVideoDecoder.h"
+#include "GMPUtils.h"
+#include "MediaDataDecoderProxy.h"
+#include "MediaPrefs.h"
+#include "VideoUtils.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/StaticMutex.h"
+#include "gmp-audio-decode.h"
+#include "gmp-video-decode.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+#ifdef XP_WIN
+#include "WMFDecoderModule.h"
+#endif
+
+namespace mozilla {
+
+GMPDecoderModule::GMPDecoderModule()
+{
+}
+
+GMPDecoderModule::~GMPDecoderModule()
+{
+}
+
+static already_AddRefed<MediaDataDecoderProxy>
+CreateDecoderWrapper(MediaDataDecoderCallback* aCallback)
+{
+ RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (!s) {
+ return nullptr;
+ }
+ RefPtr<AbstractThread> thread(s->GetAbstractGMPThread());
+ if (!thread) {
+ return nullptr;
+ }
+ RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback));
+ return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+GMPDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+ if (!MP4Decoder::IsH264(aParams.mConfig.mMimeType) &&
+ !VPXDecoder::IsVP8(aParams.mConfig.mMimeType) &&
+ !VPXDecoder::IsVP9(aParams.mConfig.mMimeType)) {
+ return nullptr;
+ }
+
+ if (aParams.mDiagnostics) {
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType);
+ if (preferredGMP.isSome()) {
+ aParams.mDiagnostics->SetGMP(preferredGMP.value());
+ }
+ }
+
+ RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback);
+ auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new GMPVideoDecoder(params));
+ return wrapper.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+GMPDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
+{
+ if (!aParams.mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
+ return nullptr;
+ }
+
+ if (aParams.mDiagnostics) {
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType);
+ if (preferredGMP.isSome()) {
+ aParams.mDiagnostics->SetGMP(preferredGMP.value());
+ }
+ }
+
+ RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback);
+ auto params = GMPAudioDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new GMPAudioDecoder(params));
+ return wrapper.forget();
+}
+
+PlatformDecoderModule::ConversionRequired
+GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
+{
+ // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format.
+ if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) {
+ return ConversionRequired::kNeedAVCC;
+ } else {
+ return ConversionRequired::kNeedNone;
+ }
+}
+
+/* static */
+const Maybe<nsCString>
+GMPDecoderModule::PreferredGMP(const nsACString& aMimeType)
+{
+ Maybe<nsCString> rv;
+ if (aMimeType.EqualsLiteral("audio/mp4a-latm")) {
+ switch (MediaPrefs::GMPAACPreferred()) {
+ case 1: rv.emplace(kEMEKeySystemClearkey); break;
+ default: break;
+ }
+ }
+
+ if (MP4Decoder::IsH264(aMimeType)) {
+ switch (MediaPrefs::GMPH264Preferred()) {
+ case 1: rv.emplace(kEMEKeySystemClearkey); break;
+ default: break;
+ }
+ }
+
+ return rv;
+}
+
+/* static */
+bool
+GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ const Maybe<nsCString>& aGMP)
+{
+ if (aGMP.isNothing()) {
+ return false;
+ }
+
+ if (MP4Decoder::IsH264(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ { NS_LITERAL_CSTRING("h264"), aGMP.value()});
+ }
+
+ if (VPXDecoder::IsVP9(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ { NS_LITERAL_CSTRING("vp9"), aGMP.value()});
+ }
+
+ if (VPXDecoder::IsVP8(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ { NS_LITERAL_CSTRING("vp8"), aGMP.value()});
+ }
+
+ if (MP4Decoder::IsAAC(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
+ { NS_LITERAL_CSTRING("aac"), aGMP.value()});
+ }
+
+ return false;
+}
+
+bool
+GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const
+{
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aMimeType);
+ bool rv = SupportsMimeType(aMimeType, preferredGMP);
+ if (rv && aDiagnostics && preferredGMP.isSome()) {
+ aDiagnostics->SetGMP(preferredGMP.value());
+ }
+ return rv;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
new file mode 100644
index 000000000..b501ecb54
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPDecoderModule_h_)
+#define GMPDecoderModule_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/Maybe.h"
+
+// The special NodeId we use when doing unencrypted decoding using the GMP's
+// decoder. This ensures that each GMP MediaDataDecoder we create doesn't
+// require spinning up a new process, but instead we run all instances of
+// GMP decoders in the one process, to reduce overhead.
+//
+// Note: GMP storage is isolated by NodeId, and non persistent for this
+// special NodeId, and the only way a GMP can communicate with the outside
+// world is through the EME GMP APIs, and we never run EME with this NodeID
+// (because NodeIds are random strings which can't contain the '-' character),
+// so there's no way a malicious GMP can harvest, store, and then report any
+// privacy sensitive data about what users are watching.
+#define SHARED_GMP_DECODING_NODE_ID NS_LITERAL_CSTRING("gmp-shared-decoding")
+
+namespace mozilla {
+
+class GMPDecoderModule : public PlatformDecoderModule {
+public:
+ GMPDecoderModule();
+
+ virtual ~GMPDecoderModule();
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateVideoDecoder(const CreateDecoderParams& aParams) override;
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateAudioDecoder(const CreateDecoderParams& aParams) override;
+
+ ConversionRequired
+ DecoderNeedsConversion(const TrackInfo& aConfig) const override;
+
+ bool
+ SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+ static const Maybe<nsCString> PreferredGMP(const nsACString& aMimeType);
+
+ static bool SupportsMimeType(const nsACString& aMimeType,
+ const Maybe<nsCString>& aGMP);
+};
+
+} // namespace mozilla
+
+#endif // GMPDecoderModule_h_
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
new file mode 100644
index 000000000..26d029da0
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoDecoder.h"
+#include "GMPVideoHost.h"
+#include "mozilla/EndianUtils.h"
+#include "prsystem.h"
+#include "MediaData.h"
+#include "GMPDecoderModule.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+extern bool IsOnGMPThread();
+#endif
+
+void
+VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+ GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame);
+
+ MOZ_ASSERT(IsOnGMPThread());
+
+ VideoData::YCbCrBuffer b;
+ for (int i = 0; i < kGMPNumOfPlanes; ++i) {
+ b.mPlanes[i].mData = decodedFrame->Buffer(GMPPlaneType(i));
+ b.mPlanes[i].mStride = decodedFrame->Stride(GMPPlaneType(i));
+ if (i == kGMPYPlane) {
+ b.mPlanes[i].mWidth = decodedFrame->Width();
+ b.mPlanes[i].mHeight = decodedFrame->Height();
+ } else {
+ b.mPlanes[i].mWidth = (decodedFrame->Width() + 1) / 2;
+ b.mPlanes[i].mHeight = (decodedFrame->Height() + 1) / 2;
+ }
+ b.mPlanes[i].mOffset = 0;
+ b.mPlanes[i].mSkip = 0;
+ }
+
+ gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height());
+ RefPtr<VideoData> v =
+ VideoData::CreateAndCopyData(mVideoInfo,
+ mImageContainer,
+ mLastStreamOffset,
+ decodedFrame->Timestamp(),
+ decodedFrame->Duration(),
+ b,
+ false,
+ -1,
+ pictureRegion);
+ if (v) {
+ mCallback->Output(v);
+ } else {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+ }
+}
+
+void
+VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+}
+
+void
+VideoCallbackAdapter::ReceivedDecodedFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+}
+
+void
+VideoCallbackAdapter::InputDataExhausted()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->InputExhausted();
+}
+
+void
+VideoCallbackAdapter::DrainComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->DrainComplete();
+}
+
+void
+VideoCallbackAdapter::ResetComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->FlushComplete();
+}
+
+void
+VideoCallbackAdapter::Error(GMPErr aErr)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->Error(MediaResult(aErr == GMPDecodeErr
+ ? NS_ERROR_DOM_MEDIA_DECODE_ERR
+ : NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("GMPErr:%x", aErr)));
+}
+
+void
+VideoCallbackAdapter::Terminated()
+{
+ // Note that this *may* be called from the proxy thread also.
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Video GMP decoder terminated.")));
+}
+
+GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams)
+ : mConfig(aParams.VideoConfig())
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(nullptr)
+ , mAdapter(nullptr)
+ , mImageContainer(aParams.mImageContainer)
+ , mLayersBackend(aParams.GetLayersBackend())
+ , mCrashHelper(aParams.mCrashHelper)
+{}
+
+GMPVideoDecoderParams&
+GMPVideoDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper)
+{
+ MOZ_ASSERT(aWrapper);
+ MOZ_ASSERT(!mCallback); // Should only be called once per instance.
+ mCallback = aWrapper->Callback();
+ mAdapter = nullptr;
+ return *this;
+}
+
+GMPVideoDecoderParams&
+GMPVideoDecoderParams::WithAdapter(VideoCallbackAdapter* aAdapter)
+{
+ MOZ_ASSERT(aAdapter);
+ MOZ_ASSERT(!mAdapter); // Should only be called once per instance.
+ mCallback = aAdapter->Callback();
+ mAdapter = aAdapter;
+ return *this;
+}
+
+GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams)
+ : mConfig(aParams.mConfig)
+ , mCallback(aParams.mCallback)
+ , mGMP(nullptr)
+ , mHost(nullptr)
+ , mAdapter(aParams.mAdapter)
+ , mConvertNALUnitLengths(false)
+ , mCrashHelper(aParams.mCrashHelper)
+{
+ MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback());
+ if (!mAdapter) {
+ mAdapter = new VideoCallbackAdapter(mCallback,
+ VideoInfo(mConfig.mDisplay.width,
+ mConfig.mDisplay.height),
+ aParams.mImageContainer);
+ }
+}
+
+void
+GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ const Maybe<nsCString> gmp(
+ GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc")));
+ if (gmp.isSome()) {
+ aTags.AppendElement(gmp.value());
+ }
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp8"));
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp9"));
+ }
+}
+
+nsCString
+GMPVideoDecoder::GetNodeId()
+{
+ return SHARED_GMP_DECODING_NODE_ID;
+}
+
+GMPUniquePtr<GMPVideoEncodedFrame>
+GMPVideoDecoder::CreateFrame(MediaRawData* aSample)
+{
+ GMPVideoFrame* ftmp = nullptr;
+ GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+ if (GMP_FAILED(err)) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Host::CreateFrame:%x", err)));
+ return nullptr;
+ }
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp));
+ err = frame->CreateEmptyFrame(aSample->Size());
+ if (GMP_FAILED(err)) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("GMPVideoEncodedFrame::CreateEmptyFrame:%x", err)));
+ return nullptr;
+ }
+
+ memcpy(frame->Buffer(), aSample->Data(), frame->Size());
+
+ // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to
+ // suit the GMP API.
+ if (mConvertNALUnitLengths) {
+ const int kNALLengthSize = 4;
+ uint8_t* buf = frame->Buffer();
+ while (buf < frame->Buffer() + frame->Size() - kNALLengthSize) {
+ uint32_t length = BigEndian::readUint32(buf) + kNALLengthSize;
+ *reinterpret_cast<uint32_t *>(buf) = length;
+ buf += length;
+ }
+ }
+
+ frame->SetBufferType(GMP_BufferLength32);
+
+ frame->SetEncodedWidth(mConfig.mDisplay.width);
+ frame->SetEncodedHeight(mConfig.mDisplay.height);
+ frame->SetTimeStamp(aSample->mTime);
+ frame->SetCompleteFrame(true);
+ frame->SetDuration(aSample->mDuration);
+ frame->SetFrameType(aSample->mKeyframe ? kGMPKeyFrame : kGMPDeltaFrame);
+
+ return frame;
+}
+
+const VideoInfo&
+GMPVideoDecoder::GetConfig() const
+{
+ return mConfig;
+}
+
+void
+GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!aGMP) {
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ MOZ_ASSERT(aHost);
+
+ if (mInitPromise.IsEmpty()) {
+ // GMP must have been shutdown while we were waiting for Init operation
+ // to complete.
+ aGMP->Close();
+ return;
+ }
+
+ GMPVideoCodec codec;
+ memset(&codec, 0, sizeof(codec));
+
+ codec.mGMPApiVersion = kGMPVersion33;
+ nsTArray<uint8_t> codecSpecific;
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecH264;
+ codecSpecific.AppendElement(0); // mPacketizationMode.
+ codecSpecific.AppendElements(mConfig.mExtraData->Elements(),
+ mConfig.mExtraData->Length());
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecVP8;
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecVP9;
+ } else {
+ // Unrecognized mime type
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ codec.mWidth = mConfig.mImage.width;
+ codec.mHeight = mConfig.mImage.height;
+
+ nsresult rv = aGMP->InitDecode(codec,
+ codecSpecific,
+ mAdapter,
+ PR_GetNumberOfProcessors());
+ if (NS_FAILED(rv)) {
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+
+ mGMP = aGMP;
+ mHost = aHost;
+
+ // GMP implementations have interpreted the meaning of GMP_BufferLength32
+ // differently. The OpenH264 GMP expects GMP_BufferLength32 to behave as
+ // specified in the GMP API, where each buffer is prefixed by a 32-bit
+ // host-endian buffer length that includes the size of the buffer length
+ // field. Other existing GMPs currently expect GMP_BufferLength32 (when
+ // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to
+ // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian
+ // and do not include the length of the buffer length field.
+ mConvertNALUnitLengths = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264");
+
+ mInitPromise.Resolve(TrackInfo::kVideoTrack, __func__);
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+GMPVideoDecoder::Init()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mMPS);
+
+ RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__));
+
+ nsTArray<nsCString> tags;
+ InitTags(tags);
+ UniquePtr<GetGMPVideoDecoderCallback> callback(new GMPInitDoneCallback(this));
+ if (NS_FAILED(mMPS->GetDecryptingGMPVideoDecoder(mCrashHelper,
+ &tags,
+ GetNodeId(),
+ Move(callback),
+ DecryptorId()))) {
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ return promise;
+}
+
+void
+GMPVideoDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ RefPtr<MediaRawData> sample(aSample);
+ if (!mGMP) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("mGMP not initialized")));
+ return;
+ }
+
+ mAdapter->SetLastStreamOffset(sample->mOffset);
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
+ if (!frame) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("CreateFrame returned null")));
+ return;
+ }
+ nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
+ nsresult rv = mGMP->Decode(Move(frame), false, info, 0);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("mGMP->Decode:%x", rv)));
+ }
+}
+
+void
+GMPVideoDecoder::Flush()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Reset())) {
+ // Abort the flush.
+ mCallback->FlushComplete();
+ }
+}
+
+void
+GMPVideoDecoder::Drain()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Drain())) {
+ mCallback->DrainComplete();
+ }
+}
+
+void
+GMPVideoDecoder::Shutdown()
+{
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ // Note that this *may* be called from the proxy thread also.
+ if (!mGMP) {
+ return;
+ }
+ // Note this unblocks flush and drain operations waiting for callbacks.
+ mGMP->Close();
+ mGMP = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
new file mode 100644
index 000000000..900ef4553
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPVideoDecoder_h_)
+#define GMPVideoDecoder_h_
+
+#include "GMPVideoDecoderProxy.h"
+#include "ImageContainer.h"
+#include "MediaDataDecoderProxy.h"
+#include "PlatformDecoderModule.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "MediaInfo.h"
+
+namespace mozilla {
+
+class VideoCallbackAdapter : public GMPVideoDecoderCallbackProxy {
+public:
+ VideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback,
+ VideoInfo aVideoInfo,
+ layers::ImageContainer* aImageContainer)
+ : mCallback(aCallback)
+ , mLastStreamOffset(0)
+ , mVideoInfo(aVideoInfo)
+ , mImageContainer(aImageContainer)
+ {}
+
+ MediaDataDecoderCallbackProxy* Callback() const { return mCallback; }
+
+ // GMPVideoDecoderCallbackProxy
+ void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
+ void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override;
+ void ReceivedDecodedFrame(const uint64_t aPictureId) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aErr) override;
+ void Terminated() override;
+
+ void SetLastStreamOffset(int64_t aStreamOffset) {
+ mLastStreamOffset = aStreamOffset;
+ }
+
+private:
+ MediaDataDecoderCallbackProxy* mCallback;
+ int64_t mLastStreamOffset;
+
+ VideoInfo mVideoInfo;
+ RefPtr<layers::ImageContainer> mImageContainer;
+};
+
+struct GMPVideoDecoderParams {
+ explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams);
+ GMPVideoDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper);
+ GMPVideoDecoderParams& WithAdapter(VideoCallbackAdapter* aAdapter);
+
+ const VideoInfo& mConfig;
+ TaskQueue* mTaskQueue;
+ MediaDataDecoderCallbackProxy* mCallback;
+ VideoCallbackAdapter* mAdapter;
+ layers::ImageContainer* mImageContainer;
+ layers::LayersBackend mLayersBackend;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+class GMPVideoDecoder : public MediaDataDecoder {
+public:
+ explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams);
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "GMP video decoder";
+ }
+
+protected:
+ virtual void InitTags(nsTArray<nsCString>& aTags);
+ virtual nsCString GetNodeId();
+ virtual uint32_t DecryptorId() const { return 0; }
+ virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample);
+ virtual const VideoInfo& GetConfig() const;
+
+private:
+
+ class GMPInitDoneCallback : public GetGMPVideoDecoderCallback
+ {
+ public:
+ explicit GMPInitDoneCallback(GMPVideoDecoder* aDecoder)
+ : mDecoder(aDecoder)
+ {
+ }
+
+ void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) override
+ {
+ mDecoder->GMPInitDone(aGMP, aHost);
+ }
+
+ private:
+ RefPtr<GMPVideoDecoder> mDecoder;
+ };
+ void GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost);
+
+ const VideoInfo mConfig;
+ MediaDataDecoderCallbackProxy* mCallback;
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ GMPVideoDecoderProxy* mGMP;
+ GMPVideoHost* mHost;
+ nsAutoPtr<VideoCallbackAdapter> mAdapter;
+ bool mConvertNALUnitLengths;
+ MozPromiseHolder<InitPromise> mInitPromise;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+} // namespace mozilla
+
+#endif // GMPVideoDecoder_h_
diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
new file mode 100644
index 000000000..5a196f8e6
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDataDecoderProxy.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+void
+MediaDataDecoderCallbackProxy::Error(const MediaResult& aError)
+{
+ mProxyCallback->Error(aError);
+}
+
+void
+MediaDataDecoderCallbackProxy::FlushComplete()
+{
+ mProxyDecoder->FlushComplete();
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+MediaDataDecoderProxy::InternalInit()
+{
+ return mProxyDecoder->Init();
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+MediaDataDecoderProxy::Init()
+{
+ MOZ_ASSERT(!mIsShutdown);
+
+ return InvokeAsync(mProxyThread, this, __func__,
+ &MediaDataDecoderProxy::InternalInit);
+}
+
+void
+MediaDataDecoderProxy::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ nsCOMPtr<nsIRunnable> task(new InputTask(mProxyDecoder, aSample));
+ mProxyThread->Dispatch(task.forget());
+}
+
+void
+MediaDataDecoderProxy::Flush()
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ mFlushComplete.Set(false);
+
+ mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Flush));
+
+ mFlushComplete.WaitUntil(true);
+}
+
+void
+MediaDataDecoderProxy::Drain()
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Drain));
+}
+
+void
+MediaDataDecoderProxy::Shutdown()
+{
+ // Note that this *may* be called from the proxy thread also.
+ MOZ_ASSERT(!mIsShutdown);
+#if defined(DEBUG)
+ mIsShutdown = true;
+#endif
+ mProxyThread->AsXPCOMThread()->Dispatch(NewRunnableMethod(mProxyDecoder,
+ &MediaDataDecoder::Shutdown),
+ NS_DISPATCH_SYNC);
+}
+
+void
+MediaDataDecoderProxy::FlushComplete()
+{
+ mFlushComplete.Set(true);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
new file mode 100644
index 000000000..735b6126e
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(MediaDataDecoderProxy_h_)
+#define MediaDataDecoderProxy_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+#include "GMPService.h"
+
+namespace mozilla {
+
+class InputTask : public Runnable {
+public:
+ InputTask(MediaDataDecoder* aDecoder,
+ MediaRawData* aSample)
+ : mDecoder(aDecoder)
+ , mSample(aSample)
+ {}
+
+ NS_IMETHOD Run() override {
+ mDecoder->Input(mSample);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<MediaDataDecoder> mDecoder;
+ RefPtr<MediaRawData> mSample;
+};
+
+template<typename T>
+class Condition {
+public:
+ explicit Condition(T aValue)
+ : mMonitor("Condition")
+ , mCondition(aValue)
+ {}
+
+ void Set(T aValue) {
+ MonitorAutoLock mon(mMonitor);
+ mCondition = aValue;
+ mon.NotifyAll();
+ }
+
+ void WaitUntil(T aValue) {
+ MonitorAutoLock mon(mMonitor);
+ while (mCondition != aValue) {
+ mon.Wait();
+ }
+ }
+
+private:
+ Monitor mMonitor;
+ T mCondition;
+};
+
+class MediaDataDecoderProxy;
+
+class MediaDataDecoderCallbackProxy : public MediaDataDecoderCallback {
+public:
+ MediaDataDecoderCallbackProxy(MediaDataDecoderProxy* aProxyDecoder,
+ MediaDataDecoderCallback* aCallback)
+ : mProxyDecoder(aProxyDecoder)
+ , mProxyCallback(aCallback)
+ {
+ }
+
+ void Output(MediaData* aData) override {
+ mProxyCallback->Output(aData);
+ }
+
+ void Error(const MediaResult& aError) override;
+
+ void InputExhausted() override {
+ mProxyCallback->InputExhausted();
+ }
+
+ void DrainComplete() override {
+ mProxyCallback->DrainComplete();
+ }
+
+ void ReleaseMediaResources() override {
+ mProxyCallback->ReleaseMediaResources();
+ }
+
+ void FlushComplete();
+
+ bool OnReaderTaskQueue() override
+ {
+ return mProxyCallback->OnReaderTaskQueue();
+ }
+
+ void WaitingForKey() override
+ {
+ mProxyCallback->WaitingForKey();
+ }
+
+private:
+ MediaDataDecoderProxy* mProxyDecoder;
+ MediaDataDecoderCallback* mProxyCallback;
+};
+
+class MediaDataDecoderProxy : public MediaDataDecoder {
+public:
+ MediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,
+ MediaDataDecoderCallback* aCallback)
+ : mProxyThread(aProxyThread)
+ , mProxyCallback(this, aCallback)
+ , mFlushComplete(false)
+#if defined(DEBUG)
+ , mIsShutdown(false)
+#endif
+ {
+ }
+
+ // Ideally, this would return a regular MediaDataDecoderCallback pointer
+ // to retain the clean abstraction, but until MediaDataDecoderCallback
+ // supports the FlushComplete interface, this will have to do. When MDDC
+ // supports FlushComplete, this, the GMP*Decoders, and the
+ // *CallbackAdapters can be reverted to accepting a regular
+ // MediaDataDecoderCallback pointer.
+ MediaDataDecoderCallbackProxy* Callback()
+ {
+ return &mProxyCallback;
+ }
+
+ void SetProxyTarget(MediaDataDecoder* aProxyDecoder)
+ {
+ MOZ_ASSERT(aProxyDecoder);
+ mProxyDecoder = aProxyDecoder;
+ }
+
+ // These are called from the decoder thread pool.
+ // Init and Shutdown run synchronously on the proxy thread, all others are
+ // asynchronously and responded to via the MediaDataDecoderCallback.
+ // Note: the nsresults returned by the proxied decoder are lost.
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+
+ const char* GetDescriptionName() const override
+ {
+ return "GMP proxy data decoder";
+ }
+
+ // Called by MediaDataDecoderCallbackProxy.
+ void FlushComplete();
+
+private:
+ RefPtr<InitPromise> InternalInit();
+
+#ifdef DEBUG
+ bool IsOnProxyThread() {
+ return mProxyThread && mProxyThread->IsCurrentThreadIn();
+ }
+#endif
+
+ friend class InputTask;
+ friend class InitTask;
+
+ RefPtr<MediaDataDecoder> mProxyDecoder;
+ RefPtr<AbstractThread> mProxyThread;
+
+ MediaDataDecoderCallbackProxy mProxyCallback;
+
+ Condition<bool> mFlushComplete;
+#if defined(DEBUG)
+ bool mIsShutdown;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // MediaDataDecoderProxy_h_
diff --git a/dom/media/platforms/agnostic/gmp/moz.build b/dom/media/platforms/agnostic/gmp/moz.build
new file mode 100644
index 000000000..64e258620
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'GMPAudioDecoder.h',
+ 'GMPDecoderModule.h',
+ 'GMPVideoDecoder.h',
+ 'MediaDataDecoderProxy.h',
+]
+
+SOURCES += [
+ 'GMPAudioDecoder.cpp',
+ 'GMPDecoderModule.cpp',
+ 'GMPVideoDecoder.cpp',
+ 'MediaDataDecoderProxy.cpp',
+]
+
+# GMPVideoEncodedFrameImpl.h needs IPC
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/media/platforms/moz.build b/dom/media/platforms/moz.build
index b570eb7d9..2d295bb1e 100644
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -29,9 +29,13 @@ SOURCES += [
]
DIRS += [
+ 'agnostic/gmp',
'omx'
]
+if CONFIG['MOZ_EME']:
+ DIRS += ['agnostic/eme']
+
if CONFIG['MOZ_WMF']:
DIRS += [ 'wmf' ];
diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
index 840348315..8cf22c241 100644
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -29,6 +29,7 @@
#include "mozilla/WindowsVersion.h"
#include "nsPrintfCString.h"
#include "nsIFile.h"
+#include "GMPUtils.h" // For SplitAt. TODO: Move SplitAt to a central place.
#include "MP4Decoder.h"
#include "VPXDecoder.h"
#include "mozilla/SyncRunnable.h"
@@ -70,21 +71,6 @@ const CLSID CLSID_WebmMfVpxDec =
namespace mozilla {
-// Utility function only used here.
-// XXXMC: Perhaps make this available globally?
-void
-SplitAt(const char* aDelims,
- const nsACString& aInput,
- nsTArray<nsCString>& aOutTokens)
-{
- nsAutoCString str(aInput);
- char* end = str.BeginWriting();
- const char* start = nullptr;
- while (!!(start = NS_strtok(aDelims, &end))) {
- aOutTokens.AppendElement(nsCString(start));
- }
-}
-
LayersBackend
GetCompositorBackendType(layers::KnowsCompositor* aKnowsCompositor)
{
diff --git a/dom/media/platforms/wrappers/H264Converter.cpp b/dom/media/platforms/wrappers/H264Converter.cpp
index 3d9d2e62f..cca03fceb 100644
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -26,6 +26,7 @@ H264Converter::H264Converter(PlatformDecoderModule* aPDM,
, mTaskQueue(aParams.mTaskQueue)
, mCallback(aParams.mCallback)
, mDecoder(nullptr)
+ , mGMPCrashHelper(aParams.mCrashHelper)
, mNeedAVCC(aPDM->DecoderNeedsConversion(aParams.mConfig)
== PlatformDecoderModule::ConversionRequired::kNeedAVCC)
, mLastError(NS_OK)
@@ -201,7 +202,8 @@ H264Converter::CreateDecoder(DecoderDoctorDiagnostics* aDiagnostics)
mCallback,
aDiagnostics,
mImageContainer,
- mKnowsCompositor
+ mKnowsCompositor,
+ mGMPCrashHelper
});
if (!mDecoder) {
diff --git a/dom/media/platforms/wrappers/H264Converter.h b/dom/media/platforms/wrappers/H264Converter.h
index 627a56731..6905b1c74 100644
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -63,6 +63,7 @@ private:
MediaDataDecoderCallback* mCallback;
RefPtr<MediaDataDecoder> mDecoder;
MozPromiseRequestHolder<InitPromise> mInitPromiseRequest;
+ RefPtr<GMPCrashHelper> mGMPCrashHelper;
bool mNeedAVCC;
nsresult mLastError;
bool mNeedKeyframe = true;
diff --git a/dom/media/test/mochitest.ini b/dom/media/test/mochitest.ini
index 24f3735d4..742ac1b1c 100644
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -425,6 +425,7 @@ support-files =
dirac.ogg^headers^
dynamic_redirect.sjs
dynamic_resource.sjs
+ eme.js
file_access_controls.html
flac-s24.flac
flac-s24.flac^headers^
@@ -681,11 +682,43 @@ tags=capturestream
[test_decoder_disable.html]
[test_defaultMuted.html]
[test_delay_load.html]
+[test_eme_session_callable_value.html]
+[test_eme_canvas_blocked.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_detach_media_keys.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_initDataTypes.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_missing_pssh.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_non_mse_fails.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_request_notifications.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_playback.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_requestKeySystemAccess.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_setMediaKeys_before_attach_MediaSource.html]
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_stream_capture_blocked_case1.html]
+tags=msg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_stream_capture_blocked_case2.html]
+tags=msg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_stream_capture_blocked_case3.html]
+tags=msg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+[test_eme_waitingforkey.html]
+skip-if = toolkit == 'android' # bug 1149374
[test_empty_resource.html]
[test_error_in_video_document.html]
[test_error_on_404.html]
[test_fastSeek.html]
[test_fastSeek-forwards.html]
+[test_gmp_playback.html]
+skip-if = (os != 'win' || os_version == '5.1') # Only gmp-clearkey on Windows Vista and later decodes
[test_imagecapture.html]
[test_info_leak.html]
[test_invalid_reject.html]
diff --git a/dom/media/test/test_eme_request_notifications.html b/dom/media/test/test_eme_request_notifications.html
new file mode 100644
index 000000000..c2fab5b15
--- /dev/null
+++ b/dom/media/test/test_eme_request_notifications.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+function SetPrefs(prefs) {
+ return new Promise(function(resolve, reject) {
+ SpecialPowers.pushPrefEnv({"set": prefs}, function() { resolve(); });
+ });
+}
+
+function observe() {
+ return new Promise(function(resolve, reject) {
+ var observer = function(subject, topic, data) {
+ SpecialPowers.Services.obs.removeObserver(observer, "mediakeys-request");
+ resolve(JSON.parse(data).status);
+ };
+ SpecialPowers.Services.obs.addObserver(observer, "mediakeys-request", false);
+ });
+}
+
+function Test(test) {
+ var p = test.prefs ? SetPrefs(test.prefs) : Promise.resolve();
+ observedStatus = "nothing";
+ var name = "'" + test.keySystem + "'";
+
+ var res = observe().then((status) => {
+ is(status, test.expectedStatus, name + " expected status");
+ });
+
+ p.then(() => navigator.requestMediaKeySystemAccess(test.keySystem, gCencMediaKeySystemConfig))
+ .then((keySystemAccess) => keySystemAccess.createMediaKeys());
+
+ return res;
+}
+
+const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
+
+var tests = [
+ {
+ keySystem: CLEARKEY_KEYSYSTEM,
+ expectedStatus: 'cdm-created',
+ prefs: [["media.eme.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: 'api-disabled',
+ prefs: [["media.eme.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: (isWinXP ? 'cdm-not-supported' : 'cdm-disabled'),
+ prefs: [["media.eme.enabled", true], ["media.gmp-widevinecdm.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: (isWinXP ? 'cdm-not-supported' : 'cdm-not-installed'),
+ prefs: [["media.eme.enabled", true], , ["media.gmp-widevinecdm.enabled", true]]
+ },
+ {
+ keySystem: CLEARKEY_KEYSYSTEM,
+ expectedStatus: 'cdm-created',
+ prefs: [["media.eme.enabled", true]]
+ }
+];
+
+SetupEMEPref(function() {
+ tests.reduce(function(p,c,i,array) {
+ return p.then(function() { return Test(c); });
+ }, Promise.resolve()).then(SimpleTest.finish);
+});
+
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_gmp_playback.html b/dom/media/test/test_gmp_playback.html
new file mode 100644
index 000000000..b65a4203c
--- /dev/null
+++ b/dom/media/test/test_gmp_playback.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of media files that should play OK</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function startTest() {
+ var v = document.createElement("video");
+ ok(v.canPlayType('video/mp4; codecs="avc1.64000d,mp4a.40.2"') != "",
+ "Should be able to play MP4/H.264/AAC via <video> using GMP");
+ v.src = "short.mp4";
+ v.addEventListener("ended", function(event) {
+ ok(true, "Reached end");
+ SimpleTest.finish();
+ });
+ document.body.appendChild(v);
+ v.play();
+}
+
+var testPrefs = [
+ ['media.gmp.decoder.aac', 1], // gmp-clearkey
+ ['media.gmp.decoder.h264', 1], // gmp-clearkey
+ ['media.gmp.decoder.enabled', true]
+];
+
+SpecialPowers.pushPrefEnv({'set': testPrefs}, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/AudioContext.cpp b/dom/media/webaudio/AudioContext.cpp
index 9f807d01f..3c7958349 100755
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -370,6 +371,13 @@ AudioContext::CreateMediaElementSource(HTMLMediaElement& aMediaElement,
return nullptr;
}
+#ifdef MOZ_EME
+ if (aMediaElement.ContainsRestrictedContent()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+#endif
+
if (CheckClosed(aRv)) {
return nullptr;
}
diff --git a/dom/media/webaudio/BufferDecoder.cpp b/dom/media/webaudio/BufferDecoder.cpp
index de0a79ffd..ddd9e7d1b 100644
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -7,13 +7,15 @@
#include "nsISupports.h"
#include "MediaResource.h"
+#include "GMPService.h"
namespace mozilla {
NS_IMPL_ISUPPORTS0(BufferDecoder)
-BufferDecoder::BufferDecoder(MediaResource* aResource)
+BufferDecoder::BufferDecoder(MediaResource* aResource, GMPCrashHelper* aCrashHelper)
: mResource(aResource)
+ , mCrashHelper(aCrashHelper)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_COUNT_CTOR(BufferDecoder);
@@ -65,4 +67,10 @@ BufferDecoder::GetOwner() const
return nullptr;
}
+already_AddRefed<GMPCrashHelper>
+BufferDecoder::GetCrashHelper()
+{
+ return do_AddRef(mCrashHelper);
+}
+
} // namespace mozilla
diff --git a/dom/media/webaudio/BufferDecoder.h b/dom/media/webaudio/BufferDecoder.h
index 5b75d6405..2c6c49454 100644
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -23,7 +23,7 @@ class BufferDecoder final : public AbstractMediaDecoder
public:
// This class holds a weak pointer to MediaResource. It's the responsibility
// of the caller to manage the memory of the MediaResource object.
- explicit BufferDecoder(MediaResource* aResource);
+ explicit BufferDecoder(MediaResource* aResource, GMPCrashHelper* aCrashHelper);
NS_DECL_THREADSAFE_ISUPPORTS
@@ -39,10 +39,13 @@ public:
MediaDecoderOwner* GetOwner() const final override;
+ already_AddRefed<GMPCrashHelper> GetCrashHelper() override;
+
private:
virtual ~BufferDecoder();
RefPtr<TaskQueue> mTaskQueueIdentity;
RefPtr<MediaResource> mResource;
+ RefPtr<GMPCrashHelper> mCrashHelper;
};
} // namespace mozilla
diff --git a/dom/media/webaudio/MediaBufferDecoder.cpp b/dom/media/webaudio/MediaBufferDecoder.cpp
index c57c9b9b5..f590d2f68 100644
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -25,6 +25,7 @@
#include "WebAudioUtils.h"
#include "mozilla/dom/Promise.h"
#include "nsPrintfCString.h"
+#include "GMPService.h"
namespace mozilla {
@@ -180,6 +181,24 @@ MediaDecodeTask::Run()
return NS_OK;
}
+class BufferDecoderGMPCrashHelper : public GMPCrashHelper
+{
+public:
+ explicit BufferDecoderGMPCrashHelper(nsPIDOMWindowInner* aParent)
+ : mParent(do_GetWeakReference(aParent))
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mParent);
+ return window.forget();
+ }
+private:
+ nsWeakPtr mParent;
+};
+
bool
MediaDecodeTask::CreateReader()
{
@@ -197,7 +216,8 @@ MediaDecodeTask::CreateReader()
mLength, principal, mContentType);
MOZ_ASSERT(!mBufferDecoder);
- mBufferDecoder = new BufferDecoder(resource);
+ mBufferDecoder = new BufferDecoder(resource,
+ new BufferDecoderGMPCrashHelper(mDecodeJob.mContext->GetParentObject()));
// If you change this list to add support for new decoders, please consider
// updating HTMLMediaElement::CreateDecoder as well.
diff --git a/dom/media/webaudio/moz.build b/dom/media/webaudio/moz.build
index aac7985ac..5aba958ab 100644
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -3,6 +3,9 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+with Files('*'):
+ BUG_COMPONENT = ('Core', 'Web Audio')
+
DIRS += ['blink']
TEST_DIRS += ['gtest']
diff --git a/dom/webidl/HTMLMediaElement.webidl b/dom/webidl/HTMLMediaElement.webidl
index 7f0f9f857..ad31f38cb 100644
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -153,6 +153,24 @@ partial interface HTMLMediaElement {
attribute EventHandler onmozinterruptend;
};
+#ifdef MOZ_EME
+// Encrypted Media Extensions
+partial interface HTMLMediaElement {
+ [Pref="media.eme.apiVisible"]
+ readonly attribute MediaKeys? mediaKeys;
+
+ // void, not any: https://www.w3.org/Bugs/Public/show_bug.cgi?id=26457
+ [Pref="media.eme.apiVisible", NewObject]
+ Promise<void> setMediaKeys(MediaKeys? mediaKeys);
+
+ [Pref="media.eme.apiVisible"]
+ attribute EventHandler onencrypted;
+
+ [Pref="media.eme.apiVisible"]
+ attribute EventHandler onwaitingforkey;
+};
+#endif
+
// This is just for testing
partial interface HTMLMediaElement {
[Pref="media.useAudioChannelService.testing"]
diff --git a/dom/webidl/MediaEncryptedEvent.webidl b/dom/webidl/MediaEncryptedEvent.webidl
new file mode 100644
index 000000000..28d7a17b7
--- /dev/null
+++ b/dom/webidl/MediaEncryptedEvent.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+[Pref="media.eme.apiVisible", Constructor(DOMString type, optional MediaKeyNeededEventInit eventInitDict)]
+interface MediaEncryptedEvent : Event {
+ readonly attribute DOMString initDataType;
+ [Throws]
+ readonly attribute ArrayBuffer? initData;
+};
+
+dictionary MediaKeyNeededEventInit : EventInit {
+ DOMString initDataType = "";
+ ArrayBuffer? initData = null;
+};
diff --git a/dom/webidl/MediaKeyError.webidl b/dom/webidl/MediaKeyError.webidl
new file mode 100644
index 000000000..d0dde2032
--- /dev/null
+++ b/dom/webidl/MediaKeyError.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+// According to the spec, "The future of error events and MediaKeyError
+// is uncertain."
+// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21798
+[Pref="media.eme.apiVisible"]
+interface MediaKeyError : Event {
+ readonly attribute unsigned long systemCode;
+};
diff --git a/dom/webidl/MediaKeyMessageEvent.webidl b/dom/webidl/MediaKeyMessageEvent.webidl
new file mode 100644
index 000000000..057924bb7
--- /dev/null
+++ b/dom/webidl/MediaKeyMessageEvent.webidl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+enum MediaKeyMessageType {
+ "license-request",
+ "license-renewal",
+ "license-release",
+ "individualization-request"
+};
+
+[Pref="media.eme.apiVisible", Constructor(DOMString type, MediaKeyMessageEventInit eventInitDict)]
+interface MediaKeyMessageEvent : Event {
+ readonly attribute MediaKeyMessageType messageType;
+ [Throws]
+ readonly attribute ArrayBuffer message;
+};
+
+dictionary MediaKeyMessageEventInit : EventInit {
+ required MediaKeyMessageType messageType;
+ required ArrayBuffer message;
+};
diff --git a/dom/webidl/MediaKeySession.webidl b/dom/webidl/MediaKeySession.webidl
new file mode 100644
index 000000000..8ca5745c4
--- /dev/null
+++ b/dom/webidl/MediaKeySession.webidl
@@ -0,0 +1,47 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+[Pref="media.eme.apiVisible"]
+interface MediaKeySession : EventTarget {
+ // error state
+ readonly attribute MediaKeyError? error;
+
+ // session properties
+ readonly attribute DOMString keySystem;
+ readonly attribute DOMString sessionId;
+
+ readonly attribute unrestricted double expiration;
+
+ readonly attribute Promise<void> closed;
+
+ readonly attribute MediaKeyStatusMap keyStatuses;
+
+ attribute EventHandler onkeystatuseschange;
+
+ attribute EventHandler onmessage;
+
+ [NewObject]
+ Promise<void> generateRequest(DOMString initDataType, BufferSource initData);
+
+ [NewObject]
+ Promise<boolean> load(DOMString sessionId);
+
+ // session operations
+ [NewObject]
+ Promise<void> update(BufferSource response);
+
+ [NewObject]
+ Promise<void> close();
+
+ [NewObject]
+ Promise<void> remove();
+};
diff --git a/dom/webidl/MediaKeyStatusMap.webidl b/dom/webidl/MediaKeyStatusMap.webidl
new file mode 100644
index 000000000..1f34b5dc7
--- /dev/null
+++ b/dom/webidl/MediaKeyStatusMap.webidl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+enum MediaKeyStatus {
+ "usable",
+ "expired",
+ "released",
+ "output-restricted",
+ "output-downscaled",
+ "status-pending",
+ "internal-error"
+};
+
+[Pref="media.eme.apiVisible"]
+interface MediaKeyStatusMap {
+ iterable<ArrayBuffer,MediaKeyStatus>;
+ readonly attribute unsigned long size;
+ boolean has (BufferSource keyId);
+ [Throws]
+ any get (BufferSource keyId);
+};
diff --git a/dom/webidl/MediaKeySystemAccess.webidl b/dom/webidl/MediaKeySystemAccess.webidl
new file mode 100644
index 000000000..01552e449
--- /dev/null
+++ b/dom/webidl/MediaKeySystemAccess.webidl
@@ -0,0 +1,41 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+enum MediaKeysRequirement {
+ "required",
+ "optional",
+ "not-allowed"
+};
+
+dictionary MediaKeySystemMediaCapability {
+ DOMString contentType = "";
+ DOMString robustness = "";
+};
+
+dictionary MediaKeySystemConfiguration {
+ DOMString label = "";
+ sequence<DOMString> initDataTypes = [];
+ sequence<MediaKeySystemMediaCapability> audioCapabilities = [];
+ sequence<MediaKeySystemMediaCapability> videoCapabilities = [];
+ MediaKeysRequirement distinctiveIdentifier = "optional";
+ MediaKeysRequirement persistentState = "optional";
+ sequence<DOMString> sessionTypes;
+};
+
+[Pref="media.eme.apiVisible"]
+interface MediaKeySystemAccess {
+ readonly attribute DOMString keySystem;
+ [NewObject]
+ MediaKeySystemConfiguration getConfiguration();
+ [NewObject]
+ Promise<MediaKeys> createMediaKeys();
+};
diff --git a/dom/webidl/MediaKeys.webidl b/dom/webidl/MediaKeys.webidl
new file mode 100644
index 000000000..cb84cdab6
--- /dev/null
+++ b/dom/webidl/MediaKeys.webidl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+// Note: "persistent-usage-record" session type is unsupported yet, as
+// it's marked as "at risk" in the spec, and Chrome doesn't support it.
+enum MediaKeySessionType {
+ "temporary",
+ "persistent-license",
+ // persistent-usage-record,
+};
+
+[Pref="media.eme.apiVisible"]
+interface MediaKeys {
+ readonly attribute DOMString keySystem;
+
+ [NewObject, Throws]
+ MediaKeySession createSession(optional MediaKeySessionType sessionType = "temporary");
+
+ [NewObject]
+ Promise<void> setServerCertificate((ArrayBufferView or ArrayBuffer) serverCertificate);
+};
diff --git a/dom/webidl/MediaKeysRequestStatus.webidl b/dom/webidl/MediaKeysRequestStatus.webidl
new file mode 100644
index 000000000..737372f66
--- /dev/null
+++ b/dom/webidl/MediaKeysRequestStatus.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+enum MediaKeySystemStatus {
+ "available",
+ "api-disabled",
+ "cdm-disabled",
+ "cdm-not-supported",
+ "cdm-not-installed",
+ "cdm-created",
+};
+
+/* Note: This dictionary and enum is only used by Gecko to convey messages
+ * to chrome JS code. It is not exposed to the web.
+ */
+
+dictionary RequestMediaKeySystemAccessNotification {
+ required DOMString keySystem;
+ required MediaKeySystemStatus status;
+};
diff --git a/dom/webidl/PluginCrashedEvent.webidl b/dom/webidl/PluginCrashedEvent.webidl
index c94315385..8eed7244e 100644
--- a/dom/webidl/PluginCrashedEvent.webidl
+++ b/dom/webidl/PluginCrashedEvent.webidl
@@ -13,6 +13,7 @@ interface PluginCrashedEvent : Event
readonly attribute DOMString? browserDumpID;
readonly attribute DOMString? pluginFilename;
readonly attribute boolean submittedCrashReport;
+ readonly attribute boolean gmpPlugin;
};
dictionary PluginCrashedEventInit : EventInit
@@ -23,4 +24,5 @@ dictionary PluginCrashedEventInit : EventInit
DOMString? browserDumpID = null;
DOMString? pluginFilename = null;
boolean submittedCrashReport = false;
+ boolean gmpPlugin = false;
};
diff --git a/dom/webidl/WidevineCDMManifest.webidl b/dom/webidl/WidevineCDMManifest.webidl
new file mode 100644
index 000000000..83e14e0b0
--- /dev/null
+++ b/dom/webidl/WidevineCDMManifest.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+dictionary WidevineCDMManifest {
+ required DOMString name;
+ required DOMString description;
+ required DOMString version;
+ required DOMString x-cdm-module-versions;
+ required DOMString x-cdm-interface-versions;
+ required DOMString x-cdm-host-versions;
+ required DOMString x-cdm-codecs;
+};
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index fca26152b..02e903849 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -572,6 +572,19 @@ WEBIDL_FILES = [
'XULElement.webidl',
]
+if CONFIG['MOZ_EME']:
+ WEBIDL_FILES += [
+ 'MediaEncryptedEvent.webidl',
+ 'MediaKeyError.webidl',
+ 'MediaKeyMessageEvent.webidl',
+ 'MediaKeys.webidl',
+ 'MediaKeySession.webidl',
+ 'MediaKeysRequestStatus.webidl',
+ 'MediaKeyStatusMap.webidl',
+ 'MediaKeySystemAccess.webidl',
+ 'WidevineCDMManifest.webidl',
+ ]
+
if CONFIG['MOZ_AUDIO_CHANNEL_MANAGER']:
WEBIDL_FILES += [
'AudioChannelManager.webidl',
diff --git a/ipc/app/moz.build b/ipc/app/moz.build
index d6239c5dc..41eb8ccff 100644
--- a/ipc/app/moz.build
+++ b/ipc/app/moz.build
@@ -16,6 +16,15 @@ LOCAL_INCLUDES += [
'/xpcom/base',
]
+# We link GMPLoader into plugin-container on desktop so that its code is
+# covered by the desktop DRM vendor's voucher.
+SOURCES += [
+ '../../dom/media/gmp/GMPLoader.cpp',
+]
+USE_LIBS += [
+ 'rlz',
+]
+
# DELAYLOAD_DLLS in this block ensures that the DLL blocklist is functional
if CONFIG['OS_ARCH'] == 'WINNT':
DELAYLOAD_DLLS += [
diff --git a/ipc/contentproc/plugin-container.cpp b/ipc/contentproc/plugin-container.cpp
index a12b9bd8b..0e59e9d7a 100644
--- a/ipc/contentproc/plugin-container.cpp
+++ b/ipc/contentproc/plugin-container.cpp
@@ -20,6 +20,14 @@
#include <unistd.h>
#endif
+#include "GMPLoader.h"
+
+mozilla::gmp::SandboxStarter*
+MakeSandboxStarter()
+{
+ return nullptr;
+}
+
int
content_process_main(int argc, char* argv[])
{
@@ -29,6 +37,8 @@ content_process_main(int argc, char* argv[])
return 3;
}
+ XREChildData childData;
+
XRE_SetProcessType(argv[--argc]);
#ifdef XP_WIN
@@ -40,7 +50,15 @@ content_process_main(int argc, char* argv[])
SetDllDirectoryW(L"");
}
#endif
- nsresult rv = XRE_InitChildProcess(argc, argv);
+#ifdef MOZ_PLUGIN_CONTAINER
+ // On desktop, the GMPLoader lives in plugin-container, so that its
+ // code can be covered by an EME/GMP vendor's voucher.
+ nsAutoPtr<mozilla::gmp::SandboxStarter> starter(MakeSandboxStarter());
+ if (XRE_GetProcessType() == GeckoProcessType_GMPlugin) {
+ childData.gmpLoader = mozilla::gmp::CreateGMPLoader(starter);
+ }
+#endif
+ nsresult rv = XRE_InitChildProcess(argc, argv, &childData);
NS_ENSURE_SUCCESS(rv, 1);
return 0;
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp
index 732068ac2..ae66219cf 100644
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -7217,6 +7217,12 @@ nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement,
(aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) == 0,
"We can't support non-premultiplied alpha for video!");
+#ifdef MOZ_EME
+ if (aElement->ContainsRestrictedContent()) {
+ return result;
+ }
+#endif
+
uint16_t readyState;
if (NS_SUCCEEDED(aElement->GetReadyState(&readyState)) &&
(readyState == nsIDOMHTMLMediaElement::HAVE_NOTHING ||
diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp
index 29dafb8c2..207ce6f2f 100644
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -171,6 +171,8 @@ static void Shutdown();
#include "MediaManager.h"
+#include "GMPService.h"
+
#include "nsScriptError.h"
#include "mozilla/TextInputProcessor.h"
@@ -184,6 +186,7 @@ using mozilla::dom::workers::WorkerDebuggerManager;
using mozilla::dom::UDPSocketChild;
using mozilla::dom::time::TimeService;
using mozilla::net::StreamingProtocolControllerService;
+using mozilla::gmp::GeckoMediaPluginService;
#define NS_EDITORCOMMANDTABLE_CID \
{ 0x4f5e62b8, 0xd659, 0x4156, \
@@ -501,6 +504,8 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsStructuredCloneContainer)
NS_GENERIC_FACTORY_CONSTRUCTOR(OSFileConstantsService)
NS_GENERIC_FACTORY_CONSTRUCTOR(UDPSocketChild)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(GeckoMediaPluginService, GeckoMediaPluginService::GetGeckoMediaPluginService)
+
NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptError)
#ifdef ACCESSIBILITY
@@ -875,6 +880,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
{ &kNS_POWERMANAGERSERVICE_CID, false, nullptr, nsIPowerManagerServiceConstructor, Module::ALLOW_IN_GPU_PROCESS },
{ &kOSFILECONSTANTSSERVICE_CID, true, nullptr, OSFileConstantsServiceConstructor },
{ &kUDPSOCKETCHILD_CID, false, nullptr, UDPSocketChildConstructor },
+ { &kGECKO_MEDIA_PLUGIN_SERVICE_CID, true, nullptr, GeckoMediaPluginServiceConstructor },
{ &kNS_TIMESERVICE_CID, false, nullptr, nsITimeServiceConstructor },
{ &kNS_MEDIASTREAMCONTROLLERSERVICE_CID, false, nullptr, nsIStreamingProtocolControllerServiceConstructor },
{ &kNS_MEDIAMANAGERSERVICE_CID, false, nullptr, nsIMediaManagerServiceConstructor },
@@ -992,6 +998,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
{ "@mozilla.org/accessibilityService;1", &kNS_ACCESSIBILITY_SERVICE_CID },
{ "@mozilla.org/accessibleRetrieval;1", &kNS_ACCESSIBILITY_SERVICE_CID },
#endif
+ { "@mozilla.org/gecko-media-plugin-service;1", &kGECKO_MEDIA_PLUGIN_SERVICE_CID },
{ "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID },
{ NS_SCRIPTERROR_CONTRACTID, &kNS_SCRIPTERROR_CID },
{ nullptr }
diff --git a/libs/gmp-clearkey/0.1/AnnexB.cpp b/libs/gmp-clearkey/0.1/AnnexB.cpp
new file mode 100644
index 000000000..952936deb
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/AnnexB.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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);
+}
diff --git a/libs/gmp-clearkey/0.1/AnnexB.h b/libs/gmp-clearkey/0.1/AnnexB.h
new file mode 100644
index 000000000..81ca87e0c
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/AnnexB.h
@@ -0,0 +1,32 @@
+/*
+ * 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__
diff --git a/libs/gmp-clearkey/0.1/ArrayUtils.h b/libs/gmp-clearkey/0.1/ArrayUtils.h
new file mode 100644
index 000000000..c5e17689e
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ArrayUtils.h
@@ -0,0 +1,23 @@
+/*
+* 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 __ArrayUtils_h__
+#define __ArrayUtils_h__
+
+#define MOZ_ARRAY_LENGTH(array_) \
+ (sizeof(array_)/sizeof(array_[0]))
+
+#endif
diff --git a/libs/gmp-clearkey/0.1/AudioDecoder.cpp b/libs/gmp-clearkey/0.1/AudioDecoder.cpp
new file mode 100644
index 000000000..b02e1a854
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/AudioDecoder.cpp
@@ -0,0 +1,312 @@
+/*
+ * 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 <cstdint>
+#include <limits>
+
+#include "AudioDecoder.h"
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeyUtils.h"
+#include "gmp-task-utils.h"
+
+using namespace wmf;
+
+AudioDecoder::AudioDecoder(GMPAudioHost *aHostAPI)
+ : mHostAPI(aHostAPI)
+ , mCallback(nullptr)
+ , mWorkerThread(nullptr)
+ , mMutex(nullptr)
+ , mNumInputTasks(0)
+ , mHasShutdown(false)
+{
+ // We drop the ref in DecodingComplete().
+ AddRef();
+}
+
+AudioDecoder::~AudioDecoder()
+{
+ if (mMutex) {
+ mMutex->Destroy();
+ }
+}
+
+void
+AudioDecoder::InitDecode(const GMPAudioCodec& aConfig,
+ GMPAudioDecoderCallback* aCallback)
+{
+ mCallback = aCallback;
+ assert(mCallback);
+ mDecoder = new WMFAACDecoder();
+ HRESULT hr = mDecoder->Init(aConfig.mChannelCount,
+ aConfig.mSamplesPerSecond,
+ (BYTE*)aConfig.mExtraData,
+ aConfig.mExtraDataLen);
+ LOG("[%p] AudioDecoder::InitializeAudioDecoder() hr=0x%x\n", this, hr);
+ if (FAILED(hr)) {
+ mCallback->Error(GMPGenericErr);
+ return;
+ }
+ auto err = GetPlatform()->createmutex(&mMutex);
+ if (GMP_FAILED(err)) {
+ mCallback->Error(GMPGenericErr);
+ return;
+ }
+}
+
+void
+AudioDecoder::EnsureWorker()
+{
+ if (!mWorkerThread) {
+ GetPlatform()->createthread(&mWorkerThread);
+ if (!mWorkerThread) {
+ mCallback->Error(GMPAllocErr);
+ return;
+ }
+ }
+}
+
+void
+AudioDecoder::Decode(GMPAudioSamples* aInput)
+{
+ EnsureWorker();
+ {
+ AutoLock lock(mMutex);
+ mNumInputTasks++;
+ }
+ mWorkerThread->Post(WrapTaskRefCounted(this,
+ &AudioDecoder::DecodeTask,
+ aInput));
+}
+
+void
+AudioDecoder::DecodeTask(GMPAudioSamples* aInput)
+{
+ HRESULT hr;
+
+ {
+ AutoLock lock(mMutex);
+ mNumInputTasks--;
+ assert(mNumInputTasks >= 0);
+ }
+
+ if (!aInput || !mHostAPI || !mDecoder) {
+ LOG("Decode job not set up correctly!");
+ return;
+ }
+
+ const uint8_t* inBuffer = aInput->Buffer();
+ if (!inBuffer) {
+ LOG("No buffer for encoded samples!\n");
+ return;
+ }
+
+ const GMPEncryptedBufferMetadata* crypto = aInput->GetDecryptionData();
+ std::vector<uint8_t> buffer(inBuffer, inBuffer + aInput->Size());
+ if (crypto) {
+ // Plugin host should have set up its decryptor/key sessions
+ // before trying to decode!
+ GMPErr rv =
+ ClearKeyDecryptionManager::Get()->Decrypt(buffer, CryptoMetaData(crypto));
+
+ if (GMP_FAILED(rv)) {
+ CK_LOGE("Failed to decrypt with key id %08x...", *(uint32_t*)crypto->KeyId());
+ MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Error, rv));
+ return;
+ }
+ }
+
+ hr = mDecoder->Input(&buffer[0],
+ buffer.size(),
+ aInput->TimeStamp());
+
+ // We must delete the input sample!
+ GetPlatform()->runonmainthread(WrapTask(aInput, &GMPAudioSamples::Destroy));
+
+ SAMPLE_LOG("AudioDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
+ if (FAILED(hr)) {
+ LOG("AudioDecoder::DecodeTask() decode failed ret=0x%x%s\n",
+ hr,
+ ((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
+ return;
+ }
+
+ while (hr == S_OK) {
+ CComPtr<IMFSample> output;
+ hr = mDecoder->Output(&output);
+ SAMPLE_LOG("AudioDecoder::DecodeTask() output ret=0x%x\n", hr);
+ if (hr == S_OK) {
+ ReturnOutput(output);
+ }
+ 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, &GMPAudioDecoderCallback::InputDataExhausted));
+ }
+ } else if (FAILED(hr)) {
+ LOG("AudioDecoder::DecodeTask() output failed hr=0x%x\n", hr);
+ }
+ }
+}
+
+void
+AudioDecoder::ReturnOutput(IMFSample* aSample)
+{
+ SAMPLE_LOG("[%p] AudioDecoder::ReturnOutput()\n", this);
+ assert(aSample);
+
+ HRESULT hr;
+
+ GMPAudioSamples* samples = nullptr;
+ mHostAPI->CreateSamples(kGMPAudioIS16Samples, &samples);
+ if (!samples) {
+ LOG("Failed to create i420 frame!\n");
+ return;
+ }
+
+ hr = MFToGMPSample(aSample, samples);
+ if (FAILED(hr)) {
+ samples->Destroy();
+ LOG("Failed to prepare output sample!");
+ return;
+ }
+ ENSURE(SUCCEEDED(hr), /*void*/);
+
+ MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Decoded, samples));
+}
+
+HRESULT
+AudioDecoder::MFToGMPSample(IMFSample* aInput,
+ GMPAudioSamples* aOutput)
+{
+ ENSURE(aInput != nullptr, E_POINTER);
+ ENSURE(aOutput != nullptr, E_POINTER);
+
+ HRESULT hr;
+ CComPtr<IMFMediaBuffer> mediaBuffer;
+
+ hr = aInput->ConvertToContiguousBuffer(&mediaBuffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ BYTE* data = nullptr; // Note: *data will be owned by the IMFMediaBuffer, we don't need to free it.
+ DWORD maxLength = 0, currentLength = 0;
+ hr = mediaBuffer->Lock(&data, &maxLength, &currentLength);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ auto err = aOutput->SetBufferSize(currentLength);
+ ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+
+ memcpy(aOutput->Buffer(), data, currentLength);
+
+ mediaBuffer->Unlock();
+
+ LONGLONG hns = 0;
+ hr = aInput->GetSampleTime(&hns);
+ ENSURE(SUCCEEDED(hr), hr);
+ aOutput->SetTimeStamp(HNsToUsecs(hns));
+ aOutput->SetChannels(mDecoder->Channels());
+ aOutput->SetRate(mDecoder->Rate());
+
+ return S_OK;
+}
+
+void
+AudioDecoder::Reset()
+{
+ if (mDecoder) {
+ mDecoder->Reset();
+ }
+ if (mCallback) {
+ mCallback->ResetComplete();
+ }
+}
+
+void
+AudioDecoder::DrainTask()
+{
+ mDecoder->Drain();
+
+ // Return any pending output.
+ HRESULT hr = S_OK;
+ while (hr == S_OK) {
+ CComPtr<IMFSample> output;
+ hr = mDecoder->Output(&output);
+ SAMPLE_LOG("AudioDecoder::DrainTask() output ret=0x%x\n", hr);
+ if (hr == S_OK) {
+ ReturnOutput(output);
+ }
+ }
+ MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::DrainComplete));
+}
+
+void
+AudioDecoder::Drain()
+{
+ if (!mDecoder) {
+ return;
+ }
+ EnsureWorker();
+ mWorkerThread->Post(WrapTaskRefCounted(this,
+ &AudioDecoder::DrainTask));
+}
+
+void
+AudioDecoder::DecodingComplete()
+{
+ if (mWorkerThread) {
+ mWorkerThread->Join();
+ }
+ mHasShutdown = true;
+
+ // Release the reference we added in the constructor. There may be
+ // WrapRefCounted tasks that also hold references to us, and keep
+ // us alive a little longer.
+ Release();
+}
+
+void
+AudioDecoder::MaybeRunOnMainThread(GMPTask* aTask)
+{
+ class MaybeRunTask : public GMPTask
+ {
+ public:
+ MaybeRunTask(AudioDecoder* aDecoder, GMPTask* aTask)
+ : mDecoder(aDecoder), mTask(aTask)
+ { }
+
+ virtual void Run(void) {
+ if (mDecoder->HasShutdown()) {
+ CK_LOGD("Trying to dispatch to main thread after AudioDecoder has shut down");
+ return;
+ }
+
+ mTask->Run();
+ }
+
+ virtual void Destroy()
+ {
+ mTask->Destroy();
+ delete this;
+ }
+
+ private:
+ RefPtr<AudioDecoder> mDecoder;
+ GMPTask* mTask;
+ };
+
+ GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
+}
diff --git a/libs/gmp-clearkey/0.1/AudioDecoder.h b/libs/gmp-clearkey/0.1/AudioDecoder.h
new file mode 100644
index 000000000..98915bb7a
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/AudioDecoder.h
@@ -0,0 +1,73 @@
+/*
+ * 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 __AudioDecoder_h__
+#define __AudioDecoder_h__
+
+#include "gmp-audio-decode.h"
+#include "gmp-audio-host.h"
+#include "gmp-task-utils.h"
+#include "WMFAACDecoder.h"
+#include "RefCounted.h"
+
+#include "mfobjects.h"
+
+class AudioDecoder : public GMPAudioDecoder
+ , public RefCounted
+{
+public:
+ AudioDecoder(GMPAudioHost *aHostAPI);
+
+ virtual void InitDecode(const GMPAudioCodec& aCodecSettings,
+ GMPAudioDecoderCallback* aCallback) override;
+
+ virtual void Decode(GMPAudioSamples* aEncodedSamples);
+
+ virtual void Reset() override;
+
+ virtual void Drain() override;
+
+ virtual void DecodingComplete() override;
+
+ bool HasShutdown() { return mHasShutdown; }
+
+private:
+ virtual ~AudioDecoder();
+
+ void EnsureWorker();
+
+ void DecodeTask(GMPAudioSamples* aEncodedSamples);
+ void DrainTask();
+
+ void ReturnOutput(IMFSample* aSample);
+
+ HRESULT MFToGMPSample(IMFSample* aSample,
+ GMPAudioSamples* aAudioFrame);
+
+ void MaybeRunOnMainThread(GMPTask* aTask);
+
+ GMPAudioHost *mHostAPI; // host-owned, invalid at DecodingComplete
+ GMPAudioDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
+ GMPThread* mWorkerThread;
+ GMPMutex* mMutex;
+ wmf::AutoPtr<wmf::WMFAACDecoder> mDecoder;
+
+ int32_t mNumInputTasks;
+
+ bool mHasShutdown;
+};
+
+#endif // __AudioDecoder_h__
diff --git a/libs/gmp-clearkey/0.1/BigEndian.h b/libs/gmp-clearkey/0.1/BigEndian.h
new file mode 100644
index 000000000..853cd4a02
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/BigEndian.h
@@ -0,0 +1,68 @@
+/*
+ * 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 __BigEndian_h__
+#define __BigEndian_h__
+
+#include <stdint.h>
+
+namespace mozilla {
+
+class BigEndian {
+public:
+
+ static uint32_t readUint32(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint32_t(p[0]) << 24 |
+ uint32_t(p[1]) << 16 |
+ uint32_t(p[2]) << 8 |
+ uint32_t(p[3]);
+ }
+
+ static uint16_t readUint16(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint32_t(p[0]) << 8 |
+ uint32_t(p[1]);
+ }
+
+ static uint64_t readUint64(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint64_t(p[0]) << 56 |
+ uint64_t(p[1]) << 48 |
+ uint64_t(p[2]) << 40 |
+ uint64_t(p[3]) << 32 |
+ uint64_t(p[4]) << 24 |
+ uint64_t(p[5]) << 16 |
+ uint64_t(p[6]) << 8 |
+ uint64_t(p[7]);
+ }
+
+ static void writeUint64(void* aPtr, uint64_t aValue) {
+ uint8_t* p = reinterpret_cast<uint8_t*>(aPtr);
+ p[0] = uint8_t(aValue >> 56) & 0xff;
+ p[1] = uint8_t(aValue >> 48) & 0xff;
+ p[2] = uint8_t(aValue >> 40) & 0xff;
+ p[3] = uint8_t(aValue >> 32) & 0xff;
+ p[4] = uint8_t(aValue >> 24) & 0xff;
+ p[5] = uint8_t(aValue >> 16) & 0xff;
+ p[6] = uint8_t(aValue >> 8) & 0xff;
+ p[7] = uint8_t(aValue) & 0xff;
+ }
+};
+
+} // namespace mozilla
+
+#endif // __BigEndian_h__
diff --git a/libs/gmp-clearkey/0.1/ClearKeyAsyncShutdown.cpp b/libs/gmp-clearkey/0.1/ClearKeyAsyncShutdown.cpp
new file mode 100644
index 000000000..ed1cac738
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyAsyncShutdown.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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));
+}
diff --git a/libs/gmp-clearkey/0.1/ClearKeyAsyncShutdown.h b/libs/gmp-clearkey/0.1/ClearKeyAsyncShutdown.h
new file mode 100644
index 000000000..b828aae41
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyAsyncShutdown.h
@@ -0,0 +1,37 @@
+/*
+ * 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__
diff --git a/libs/gmp-clearkey/0.1/ClearKeyBase64.cpp b/libs/gmp-clearkey/0.1/ClearKeyBase64.cpp
new file mode 100644
index 000000000..c4c530a34
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyBase64.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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 "ClearKeyBase64.h"
+
+#include <algorithm>
+
+using namespace std;
+
+/**
+* Take a base64-encoded string, convert (in-place) each character to its
+* corresponding value in the [0x00, 0x3f] range, and truncate any padding.
+*/
+static bool
+Decode6Bit(string& aStr)
+{
+ for (size_t i = 0; i < aStr.length(); i++) {
+ if (aStr[i] >= 'A' && aStr[i] <= 'Z') {
+ aStr[i] -= 'A';
+ }
+ else if (aStr[i] >= 'a' && aStr[i] <= 'z') {
+ aStr[i] -= 'a' - 26;
+ }
+ else if (aStr[i] >= '0' && aStr[i] <= '9') {
+ aStr[i] -= '0' - 52;
+ }
+ else if (aStr[i] == '-' || aStr[i] == '+') {
+ aStr[i] = 62;
+ }
+ else if (aStr[i] == '_' || aStr[i] == '/') {
+ aStr[i] = 63;
+ }
+ else {
+ // Truncate '=' padding at the end of the aString.
+ if (aStr[i] != '=') {
+ aStr.erase(i, string::npos);
+ return false;
+ }
+ aStr[i] = '\0';
+ aStr.resize(i);
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool
+DecodeBase64(const string& aEncoded, vector<uint8_t>& aOutDecoded)
+{
+ if (aEncoded.empty()) {
+ aOutDecoded.clear();
+ return true;
+ }
+ if (aEncoded.size() == 1) {
+ // Invalid Base64 encoding.
+ return false;
+ }
+ string encoded = aEncoded;
+ if (!Decode6Bit(encoded)) {
+ return false;
+ }
+
+ // The number of bytes we haven't yet filled in the current byte, mod 8.
+ int shift = 0;
+
+ aOutDecoded.resize((encoded.size() * 3) / 4);
+ vector<uint8_t>::iterator out = aOutDecoded.begin();
+ for (size_t i = 0; i < encoded.length(); i++) {
+ if (!shift) {
+ *out = encoded[i] << 2;
+ }
+ else {
+ *out |= encoded[i] >> (6 - shift);
+ out++;
+ if (out == aOutDecoded.end()) {
+ // Hit last 6bit octed in encoded, which is padding and can be ignored.
+ break;
+ }
+ *out = encoded[i] << (shift + 2);
+ }
+ shift = (shift + 2) % 8;
+ }
+
+ return true;
+} \ No newline at end of file
diff --git a/libs/gmp-clearkey/0.1/ClearKeyBase64.h b/libs/gmp-clearkey/0.1/ClearKeyBase64.h
new file mode 100644
index 000000000..c0c928813
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyBase64.h
@@ -0,0 +1,28 @@
+/*
+ * 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 __ClearKeyBase64_h__
+#define __ClearKeyBase64_h__
+
+#include <vector>
+#include <string>
+#include <stdint.h>
+
+// Decodes a base64 encoded string. Returns true on success.
+bool
+DecodeBase64(const std::string& aEncoded, std::vector<uint8_t>& aOutDecoded);
+
+#endif
diff --git a/libs/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp b/libs/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
new file mode 100644
index 000000000..87b316563
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
@@ -0,0 +1,241 @@
+/*
+ * 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 <string.h>
+#include <vector>
+
+#include "ClearKeyDecryptionManager.h"
+#include "psshparser/PsshParser.h"
+#include "gmp-api/gmp-decryption.h"
+#include "mozilla/CheckedInt.h"
+#include <assert.h>
+
+class ClearKeyDecryptor : public RefCounted
+{
+public:
+ ClearKeyDecryptor();
+
+ void InitKey(const Key& aKey);
+ bool HasKey() const { return !!mKey.size(); }
+
+ GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata);
+
+ const Key& DecryptionKey() const { return mKey; }
+
+private:
+ ~ClearKeyDecryptor();
+
+ Key mKey;
+};
+
+
+/* static */ ClearKeyDecryptionManager* ClearKeyDecryptionManager::sInstance = nullptr;
+
+/* static */ ClearKeyDecryptionManager*
+ClearKeyDecryptionManager::Get()
+{
+ if (!sInstance) {
+ sInstance = new ClearKeyDecryptionManager();
+ }
+ return sInstance;
+}
+
+ClearKeyDecryptionManager::ClearKeyDecryptionManager()
+{
+ CK_LOGD("ClearKeyDecryptionManager::ClearKeyDecryptionManager");
+}
+
+ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
+{
+ CK_LOGD("ClearKeyDecryptionManager::~ClearKeyDecryptionManager");
+
+ sInstance = nullptr;
+
+ for (auto it = mDecryptors.begin(); it != mDecryptors.end(); it++) {
+ it->second->Release();
+ }
+ mDecryptors.clear();
+}
+
+bool
+ClearKeyDecryptionManager::HasSeenKeyId(const KeyId& aKeyId) const
+{
+ 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]);
+ const auto& decryptor = mDecryptors.find(aKeyId);
+ return decryptor != mDecryptors.end() && !decryptor->second->HasKey();
+}
+
+bool
+ClearKeyDecryptionManager::HasKeyForKeyId(const KeyId& aKeyId) const
+{
+ CK_LOGD("ClearKeyDecryptionManager::HasKeyForKeyId");
+ const auto& decryptor = mDecryptors.find(aKeyId);
+ return decryptor != mDecryptors.end() && decryptor->second->HasKey();
+}
+
+const Key&
+ClearKeyDecryptionManager::GetDecryptionKey(const KeyId& aKeyId)
+{
+ assert(HasKeyForKeyId(aKeyId));
+ return mDecryptors[aKeyId]->DecryptionKey();
+}
+
+void
+ClearKeyDecryptionManager::InitKey(KeyId aKeyId, Key aKey)
+{
+ CK_LOGD("ClearKeyDecryptionManager::InitKey %08x...", *(uint32_t*)&aKeyId[0]);
+ if (IsExpectingKeyForKeyId(aKeyId)) {
+ mDecryptors[aKeyId]->InitKey(aKey);
+ }
+}
+
+void
+ClearKeyDecryptionManager::ExpectKeyId(KeyId aKeyId)
+{
+ CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId %08x...", *(uint32_t*)&aKeyId[0]);
+ if (!HasSeenKeyId(aKeyId)) {
+ mDecryptors[aKeyId] = new ClearKeyDecryptor();
+ }
+ mDecryptors[aKeyId]->AddRef();
+}
+
+void
+ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId)
+{
+ CK_LOGD("ClearKeyDecryptionManager::ReleaseKeyId");
+ assert(HasSeenKeyId(aKeyId));
+
+ ClearKeyDecryptor* decryptor = mDecryptors[aKeyId];
+ if (!decryptor->Release()) {
+ mDecryptors.erase(aKeyId);
+ }
+}
+
+GMPErr
+ClearKeyDecryptionManager::Decrypt(std::vector<uint8_t>& aBuffer,
+ const CryptoMetaData& aMetadata)
+{
+ return Decrypt(&aBuffer[0], aBuffer.size(), aMetadata);
+}
+
+GMPErr
+ClearKeyDecryptionManager::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata)
+{
+ CK_LOGD("ClearKeyDecryptionManager::Decrypt");
+ if (!HasKeyForKeyId(aMetadata.mKeyId)) {
+ return GMPNoKeyErr;
+ }
+
+ return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer, aBufferSize, aMetadata);
+}
+
+ClearKeyDecryptor::ClearKeyDecryptor()
+{
+ CK_LOGD("ClearKeyDecryptor ctor");
+}
+
+ClearKeyDecryptor::~ClearKeyDecryptor()
+{
+ if (HasKey()) {
+ CK_LOGD("ClearKeyDecryptor dtor; key = %08x...", *(uint32_t*)&mKey[0]);
+ } else {
+ CK_LOGD("ClearKeyDecryptor dtor");
+ }
+}
+
+void
+ClearKeyDecryptor::InitKey(const Key& aKey)
+{
+ mKey = aKey;
+}
+
+GMPErr
+ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata)
+{
+ CK_LOGD("ClearKeyDecryptor::Decrypt");
+ // If the sample is split up into multiple encrypted subsamples, we need to
+ // stitch them into one continuous buffer for decryption.
+ std::vector<uint8_t> tmp(aBufferSize);
+
+ if (aMetadata.NumSubsamples()) {
+ // Take all encrypted parts of subsamples and stitch them into one
+ // continuous encrypted buffer.
+ static_assert(sizeof(uintptr_t) == sizeof(uint8_t*),
+ "We need uintptr_t to be exactly the same size as a pointer");
+ mozilla::CheckedInt<uintptr_t> data = reinterpret_cast<uintptr_t>(aBuffer);
+ const uintptr_t endBuffer =
+ reinterpret_cast<uintptr_t>(aBuffer + aBufferSize);
+ uint8_t* iter = &tmp[0];
+ for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
+ data += aMetadata.mClearBytes[i];
+ if (!data.isValid() || data.value() > endBuffer) {
+ // Trying to read past the end of the buffer!
+ return GMPCryptoErr;
+ }
+ const uint32_t& cipherBytes = aMetadata.mCipherBytes[i];
+ mozilla::CheckedInt<uintptr_t> dataAfterCipher = data + cipherBytes;
+ if (!dataAfterCipher.isValid() || dataAfterCipher.value() > endBuffer) {
+ // Trying to read past the end of the buffer!
+ return GMPCryptoErr;
+ }
+
+ memcpy(iter, reinterpret_cast<uint8_t*>(data.value()), cipherBytes);
+
+ data = dataAfterCipher;
+ iter += cipherBytes;
+ }
+
+ tmp.resize((size_t)(iter - &tmp[0]));
+ } else {
+ memcpy(&tmp[0], aBuffer, aBufferSize);
+ }
+
+ assert(aMetadata.mIV.size() == 8 || aMetadata.mIV.size() == 16);
+ std::vector<uint8_t> iv(aMetadata.mIV);
+ iv.insert(iv.end(), CENC_KEY_LEN - aMetadata.mIV.size(), 0);
+
+ ClearKeyUtils::DecryptAES(mKey, tmp, iv);
+
+ if (aMetadata.NumSubsamples()) {
+ // Take the decrypted buffer, split up into subsamples, and insert those
+ // subsamples back into their original position in the original buffer.
+ uint8_t* data = aBuffer;
+ uint8_t* iter = &tmp[0];
+ for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
+ data += aMetadata.mClearBytes[i];
+ uint32_t cipherBytes = aMetadata.mCipherBytes[i];
+
+ memcpy(data, iter, cipherBytes);
+
+ data += cipherBytes;
+ iter += cipherBytes;
+ }
+ } else {
+ memcpy(aBuffer, &tmp[0], aBufferSize);
+ }
+
+ return GMPNoErr;
+}
diff --git a/libs/gmp-clearkey/0.1/ClearKeyDecryptionManager.h b/libs/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
new file mode 100644
index 000000000..7a6c9a560
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
@@ -0,0 +1,101 @@
+/*
+ * 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 __ClearKeyDecryptionManager_h__
+#define __ClearKeyDecryptionManager_h__
+
+#include <map>
+
+#include "ClearKeyUtils.h"
+#include "RefCounted.h"
+
+class ClearKeyDecryptor;
+
+class CryptoMetaData {
+public:
+ CryptoMetaData() {}
+
+ explicit CryptoMetaData(const GMPEncryptedBufferMetadata* aCrypto)
+ {
+ Init(aCrypto);
+ }
+
+ void Init(const GMPEncryptedBufferMetadata* aCrypto)
+ {
+ if (!aCrypto) {
+ 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());
+ }
+
+ bool IsValid() const {
+ return !mKeyId.empty() &&
+ !mIV.empty() &&
+ !mCipherBytes.empty() &&
+ !mClearBytes.empty();
+ }
+
+ size_t NumSubsamples() const {
+ assert(mClearBytes.size() == mCipherBytes.size());
+ return mClearBytes.size();
+ }
+
+ std::vector<uint8_t> mKeyId;
+ std::vector<uint8_t> mIV;
+ std::vector<uint16_t> mClearBytes;
+ std::vector<uint32_t> mCipherBytes;
+};
+
+class ClearKeyDecryptionManager : public RefCounted
+{
+private:
+ ClearKeyDecryptionManager();
+ ~ClearKeyDecryptionManager();
+
+ static ClearKeyDecryptionManager* sInstance;
+
+public:
+ static ClearKeyDecryptionManager* Get();
+
+ bool HasSeenKeyId(const KeyId& aKeyId) const;
+ bool HasKeyForKeyId(const KeyId& aKeyId) const;
+
+ const Key& GetDecryptionKey(const KeyId& aKeyId);
+
+ // Create a decryptor for the given KeyId if one does not already exist.
+ void InitKey(KeyId aKeyId, Key aKey);
+ void ExpectKeyId(KeyId aKeyId);
+ 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();
+
+private:
+ bool IsExpectingKeyForKeyId(const KeyId& aKeyId) const;
+
+ std::map<KeyId, ClearKeyDecryptor*> mDecryptors;
+};
+
+#endif // __ClearKeyDecryptionManager_h__
diff --git a/libs/gmp-clearkey/0.1/ClearKeyPersistence.cpp b/libs/gmp-clearkey/0.1/ClearKeyPersistence.cpp
new file mode 100644
index 000000000..2e14d822e
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyPersistence.cpp
@@ -0,0 +1,260 @@
+/*
+ * 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 "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>
+
+using namespace std;
+
+// Whether we've loaded the persistent session ids from GMPStorage yet.
+enum PersistentKeyState {
+ UNINITIALIZED,
+ LOADING,
+ LOADED
+};
+static PersistentKeyState sPersistentKeyState = UNINITIALIZED;
+
+// Set of session Ids of the persistent sessions created or residing in
+// storage.
+static set<uint32_t> sPersistentSessionIds;
+
+static vector<GMPTask*> sTasksBlockedOnSessionIdLoad;
+
+static void
+ReadAllRecordsFromIterator(GMPRecordIterator* aRecordIterator,
+ void* aUserArg,
+ GMPErr aStatus)
+{
+ assert(sPersistentKeyState == LOADING);
+ if (GMP_SUCCEEDED(aStatus)) {
+ // Extract the record names which are valid uint32_t's; they're
+ // the persistent session ids.
+ const char* name = nullptr;
+ uint32_t len = 0;
+ while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
+ if (ClearKeyUtils::IsValidSessionId(name, len)) {
+ assert(name[len] == 0);
+ sPersistentSessionIds.insert(atoi(name));
+ }
+ aRecordIterator->NextRecord();
+ }
+ }
+ sPersistentKeyState = LOADED;
+ aRecordIterator->Close();
+
+ for (size_t i = 0; i < sTasksBlockedOnSessionIdLoad.size(); i++) {
+ sTasksBlockedOnSessionIdLoad[i]->Run();
+ sTasksBlockedOnSessionIdLoad[i]->Destroy();
+ }
+ sTasksBlockedOnSessionIdLoad.clear();
+}
+
+/* static */ void
+ClearKeyPersistence::EnsureInitialized()
+{
+ if (sPersistentKeyState == UNINITIALIZED) {
+ sPersistentKeyState = LOADING;
+ if (GMP_FAILED(EnumRecordNames(&ReadAllRecordsFromIterator))) {
+ sPersistentKeyState = LOADED;
+ }
+ }
+}
+
+/* static */ string
+ClearKeyPersistence::GetNewSessionId(GMPSessionType aSessionType)
+{
+ static uint32_t sNextSessionId = 1;
+
+ // Ensure we don't re-use a session id that was persisted.
+ while (Contains(sPersistentSessionIds, sNextSessionId)) {
+ sNextSessionId++;
+ }
+
+ string sessionId;
+ stringstream ss;
+ ss << sNextSessionId;
+ ss >> sessionId;
+
+ if (aSessionType == kGMPPersistentSession) {
+ sPersistentSessionIds.insert(sNextSessionId);
+ }
+
+ sNextSessionId++;
+
+ 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
+ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId)
+{
+ return Contains(sPersistentSessionIds, 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)
+{
+ LoadSessionFromKeysTask* loadTask =
+ new LoadSessionFromKeysTask(aInstance, aSid, aPromiseId);
+ ReadData(aSid, loadTask);
+}
+
+/* static */ void
+ClearKeyPersistence::PersistentSessionRemoved(const string& aSessionId)
+{
+ sPersistentSessionIds.erase(atoi(aSessionId.c_str()));
+}
diff --git a/libs/gmp-clearkey/0.1/ClearKeyPersistence.h b/libs/gmp-clearkey/0.1/ClearKeyPersistence.h
new file mode 100644
index 000000000..ddab9f783
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyPersistence.h
@@ -0,0 +1,53 @@
+/*
+ * 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 __ClearKeyPersistence_h__
+#define __ClearKeyPersistence_h__
+
+#include <string>
+#include "gmp-api/gmp-decryption.h"
+
+class ClearKeySessionManager;
+
+class ClearKeyPersistence {
+public:
+ static void EnsureInitialized();
+
+ static std::string GetNewSessionId(GMPSessionType aSessionType);
+
+ static bool DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
+ uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const std::string& aInitDataType,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType);
+
+ static bool DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
+ uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength);
+
+ static bool IsPersistentSessionId(const std::string& aSid);
+
+ static void LoadSessionData(ClearKeySessionManager* aInstance,
+ const std::string& aSid,
+ uint32_t aPromiseId);
+
+ static void PersistentSessionRemoved(const std::string& aSid);
+};
+
+#endif // __ClearKeyPersistence_h__
diff --git a/libs/gmp-clearkey/0.1/ClearKeySession.cpp b/libs/gmp-clearkey/0.1/ClearKeySession.cpp
new file mode 100644
index 000000000..fb45dd2eb
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -0,0 +1,94 @@
+/*
+ * 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 "BigEndian.h"
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeySession.h"
+#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;
+
+ClearKeySession::ClearKeySession(const std::string& aSessionId,
+ GMPDecryptorCallback* aCallback,
+ GMPSessionType aSessionType)
+ : mSessionId(aSessionId)
+ , mCallback(aCallback)
+ , mSessionType(aSessionType)
+{
+ CK_LOGD("ClearKeySession ctor %p", this);
+}
+
+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)
+{
+ CK_LOGD("ClearKeySession::Init");
+
+ if (aInitDataType == "cenc") {
+ ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
+ } else if (aInitDataType == "keyids") {
+ ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds);
+ } else if (aInitDataType == "webm" && aInitDataSize <= kMaxWebmInitDataSize) {
+ // "webm" initData format is simply the raw bytes of the keyId.
+ vector<uint8_t> keyId;
+ keyId.assign(aInitData, aInitData+aInitDataSize);
+ mKeyIds.push_back(keyId);
+ }
+
+ if (!mKeyIds.size()) {
+ const char message[] = "Couldn't parse init data";
+ mCallback->RejectPromise(aPromiseId, kGMPTypeError, message, strlen(message));
+ return;
+ }
+
+ mCallback->SetSessionId(aCreateSessionToken, &mSessionId[0], mSessionId.length());
+
+ mCallback->ResolvePromise(aPromiseId);
+}
+
+GMPSessionType
+ClearKeySession::Type() const
+{
+ return mSessionType;
+}
+
+void
+ClearKeySession::AddKeyId(const KeyId& aKeyId)
+{
+ mKeyIds.push_back(aKeyId);
+}
diff --git a/libs/gmp-clearkey/0.1/ClearKeySession.h b/libs/gmp-clearkey/0.1/ClearKeySession.h
new file mode 100644
index 000000000..2783b2253
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeySession.h
@@ -0,0 +1,58 @@
+/*
+ * 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 __ClearKeySession_h__
+#define __ClearKeySession_h__
+
+#include "ClearKeyUtils.h"
+#include "gmp-api/gmp-decryption.h"
+
+class GMPBuffer;
+class GMPDecryptorCallback;
+class GMPDecryptorHost;
+class GMPEncryptedBufferMetadata;
+
+class ClearKeySession
+{
+public:
+ explicit ClearKeySession(const std::string& aSessionId,
+ GMPDecryptorCallback* aCallback,
+ GMPSessionType aSessionType);
+
+ ~ClearKeySession();
+
+ const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
+
+ void Init(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const string& aInitDataType,
+ const uint8_t* aInitData, uint32_t aInitDataSize);
+
+ GMPSessionType Type() const;
+
+ void AddKeyId(const KeyId& aKeyId);
+
+ const std::string& Id() const { return mSessionId; }
+
+private:
+ const std::string mSessionId;
+ std::vector<KeyId> mKeyIds;
+
+ GMPDecryptorCallback* mCallback;
+ const GMPSessionType mSessionType;
+};
+
+#endif // __ClearKeySession_h__
diff --git a/libs/gmp-clearkey/0.1/ClearKeySessionManager.cpp b/libs/gmp-clearkey/0.1/ClearKeySessionManager.cpp
new file mode 100644
index 000000000..4dbb06299
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -0,0 +1,418 @@
+/*
+ * 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 <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"
+#include <assert.h>
+
+using namespace std;
+
+ClearKeySessionManager::ClearKeySessionManager()
+ : 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;
+ }
+}
+
+ClearKeySessionManager::~ClearKeySessionManager()
+{
+ CK_LOGD("ClearKeySessionManager dtor %p", this);
+}
+
+void
+ClearKeySessionManager::Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierAllowed,
+ bool aPersistentStateAllowed)
+{
+ CK_LOGD("ClearKeySessionManager::Init");
+ mCallback = aCallback;
+ ClearKeyPersistence::EnsureInitialized();
+}
+
+void
+ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType)
+{
+ CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
+
+ string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize);
+ // initDataType must be "cenc", "keyids", or "webm".
+ if (initDataType != "cenc" &&
+ initDataType != "keyids" &&
+ initDataType != "webm") {
+ string message = "'" + initDataType + "' is an initDataType unsupported by ClearKey";
+ mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
+ message.c_str(), message.size());
+ return;
+ }
+
+ if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
+ aCreateSessionToken,
+ aPromiseId,
+ initDataType,
+ aInitData,
+ aInitDataSize,
+ aSessionType)) {
+ return;
+ }
+
+ string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
+ assert(mSessions.find(sessionId) == mSessions.end());
+
+ ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
+ session->Init(aCreateSessionToken, aPromiseId, initDataType, aInitData, aInitDataSize);
+ 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
+ // key ID. Otherwise a script can end up waiting for another script to
+ // respond to the request (which may not necessarily happen).
+ neededKeys.push_back(*it);
+ mDecryptionManager->ExpectKeyId(*it);
+ }
+
+ if (neededKeys.empty()) {
+ CK_LOGD("No keys needed from client.");
+ return;
+ }
+
+ // 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());
+}
+
+void
+ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CK_LOGD("ClearKeySessionManager::LoadSession");
+
+ if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
+ mCallback->ResolveLoadSessionPromise(aPromiseId, false);
+ return;
+ }
+
+ if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this,
+ aPromiseId,
+ aSessionId,
+ aSessionIdLength)) {
+ return;
+ }
+
+ string sid(aSessionId, aSessionId + aSessionIdLength);
+ if (!ClearKeyPersistence::IsPersistentSessionId(sid)) {
+ mCallback->ResolveLoadSessionPromise(aPromiseId, false);
+ return;
+ }
+
+ // Callsback PersistentSessionDataLoaded with results...
+ ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
+}
+
+void
+ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
+ uint32_t aPromiseId,
+ const string& aSessionId,
+ const uint8_t* aKeyData,
+ uint32_t aKeyDataSize)
+{
+ CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
+ if (GMP_FAILED(aStatus) ||
+ Contains(mSessions, aSessionId) ||
+ (aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
+ mCallback->ResolveLoadSessionPromise(aPromiseId, false);
+ return;
+ }
+
+ ClearKeySession* session = new ClearKeySession(aSessionId,
+ mCallback,
+ kGMPPersistentSession);
+ mSessions[aSessionId] = session;
+
+ uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
+
+ vector<GMPMediaKeyInfo> key_infos;
+ vector<KeyIdPair> keyPairs;
+ for (uint32_t i = 0; i < numKeys; i ++) {
+ const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i;
+
+ KeyIdPair keyPair;
+
+ keyPair.mKeyId = KeyId(base, base + CENC_KEY_LEN);
+ assert(keyPair.mKeyId.size() == CENC_KEY_LEN);
+
+ keyPair.mKey = Key(base + CENC_KEY_LEN, base + 2 * CENC_KEY_LEN);
+ assert(keyPair.mKey.size() == CENC_KEY_LEN);
+
+ session->AddKeyId(keyPair.mKeyId);
+
+ 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);
+}
+
+void
+ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize)
+{
+ CK_LOGD("ClearKeySessionManager::UpdateSession");
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+
+ 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);
+ return;
+ }
+ ClearKeySession* session = itr->second;
+
+ // 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);
+ return;
+ }
+
+ // Parse the response for any (key ID, key) pairs.
+ vector<KeyIdPair> keyPairs;
+ if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
+ CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
+ mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
+ return;
+ }
+
+ vector<GMPMediaKeyInfo> key_infos;
+ 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);
+ return;
+ }
+
+ // Store the keys on disk. We store a record whose name is the sessionId,
+ // 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);
+}
+
+void
+ClearKeySessionManager::Serialize(const ClearKeySession* aSession,
+ std::vector<uint8_t>& aOutKeyData)
+{
+ const std::vector<KeyId>& keyIds = aSession->GetKeyIds();
+ for (size_t i = 0; i < keyIds.size(); i++) {
+ const KeyId& keyId = keyIds[i];
+ if (!mDecryptionManager->HasKeyForKeyId(keyId)) {
+ continue;
+ }
+ assert(keyId.size() == CENC_KEY_LEN);
+ aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
+ const Key& key = mDecryptionManager->GetDecryptionKey(keyId);
+ assert(key.size() == CENC_KEY_LEN);
+ aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
+ }
+}
+
+void
+ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CK_LOGD("ClearKeySessionManager::CloseSession");
+
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+ 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);
+ return;
+ }
+
+ ClearKeySession* session = itr->second;
+ assert(session);
+
+ ClearInMemorySessionData(session);
+ mCallback->SessionClosed(aSessionId, aSessionIdLength);
+ mCallback->ResolvePromise(aPromiseId);
+}
+
+void
+ClearKeySessionManager::ClearInMemorySessionData(ClearKeySession* aSession)
+{
+ mSessions.erase(aSession->Id());
+ delete aSession;
+}
+
+void
+ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CK_LOGD("ClearKeySessionManager::RemoveSession");
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+ 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);
+ return;
+ }
+
+ ClearKeySession* session = itr->second;
+ assert(session);
+ string sid = session->Id();
+ bool isPersistent = session->Type() == kGMPPersistentSession;
+ ClearInMemorySessionData(session);
+
+ if (!isPersistent) {
+ mCallback->ResolvePromise(aPromiseId);
+ return;
+ }
+
+ ClearKeyPersistence::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);
+}
+
+void
+ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize)
+{
+ // ClearKey CDM doesn't support this method by spec.
+ CK_LOGD("ClearKeySessionManager::SetServerCertificate");
+ mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
+ nullptr /* message */, 0 /* messageLen */);
+}
+
+void
+ClearKeySessionManager::Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata)
+{
+ CK_LOGD("ClearKeySessionManager::Decrypt");
+
+ if (!mThread) {
+ CK_LOGW("No decrypt thread");
+ mCallback->Decrypted(aBuffer, GMPGenericErr);
+ return;
+ }
+
+ mThread->Post(WrapTaskRefCounted(this,
+ &ClearKeySessionManager::DoDecrypt,
+ aBuffer, aMetadata));
+}
+
+void
+ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata)
+{
+ CK_LOGD("ClearKeySessionManager::DoDecrypt");
+
+ GMPErr rv = mDecryptionManager->Decrypt(aBuffer->Data(), aBuffer->Size(),
+ CryptoMetaData(aMetadata));
+ CK_LOGD("DeDecrypt finished with code %x\n", rv);
+ mCallback->Decrypted(aBuffer, rv);
+}
+
+void
+ClearKeySessionManager::Shutdown()
+{
+ CK_LOGD("ClearKeySessionManager::Shutdown %p", this);
+
+ for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
+ delete it->second;
+ }
+ mSessions.clear();
+}
+
+void
+ClearKeySessionManager::DecryptingComplete()
+{
+ CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
+
+ GMPThread* thread = mThread;
+ thread->Join();
+
+ Shutdown();
+ mDecryptionManager = nullptr;
+ Release();
+}
diff --git a/libs/gmp-clearkey/0.1/ClearKeySessionManager.h b/libs/gmp-clearkey/0.1/ClearKeySessionManager.h
new file mode 100644
index 000000000..041b3f036
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeySessionManager.h
@@ -0,0 +1,100 @@
+/*
+ * 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 "ClearKeySession.h"
+#include "ClearKeyUtils.h"
+#include "gmp-api/gmp-decryption.h"
+#include "RefCounted.h"
+
+class ClearKeySessionManager final : public GMPDecryptor
+ , public RefCounted
+{
+public:
+ ClearKeySessionManager();
+
+ virtual void Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierAllowed,
+ bool aPersistentStateAllowed) override;
+
+ 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;
+
+ virtual void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ virtual void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) override;
+
+ virtual void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ virtual void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ virtual void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) override;
+
+ virtual void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) override;
+
+ virtual void DecryptingComplete() override;
+
+ void PersistentSessionDataLoaded(GMPErr aStatus,
+ uint32_t aPromiseId,
+ const std::string& aSessionId,
+ const uint8_t* aKeyData,
+ uint32_t aKeyDataSize);
+
+private:
+ ~ClearKeySessionManager();
+
+ void DoDecrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
+ void Shutdown();
+
+ void ClearInMemorySessionData(ClearKeySession* aSession);
+ void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
+
+ RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
+
+ GMPDecryptorCallback* mCallback;
+ GMPThread* mThread;
+
+ std::set<KeyId> mKeyIds;
+ std::map<std::string, ClearKeySession*> mSessions;
+};
+
+#endif // __ClearKeyDecryptor_h__
diff --git a/libs/gmp-clearkey/0.1/ClearKeyStorage.cpp b/libs/gmp-clearkey/0.1/ClearKeyStorage.cpp
new file mode 100644
index 000000000..0db51c9b7
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyStorage.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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 "ClearKeyStorage.h"
+#include "ClearKeyUtils.h"
+
+#include "gmp-task-utils.h"
+
+#include <assert.h>
+#include "ArrayUtils.h"
+
+#include <vector>
+
+static GMPErr
+RunOnMainThread(GMPTask* aTask)
+{
+ return GetPlatform()->runonmainthread(aTask);
+}
+
+GMPErr
+OpenRecord(const char* aName,
+ uint32_t aNameLength,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ 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);
+ }
+
+ virtual void OpenComplete(GMPErr aStatus) override {
+ if (GMP_FAILED(aStatus) ||
+ GMP_FAILED(mRecord->Write(&mData.front(), mData.size()))) {
+ Done(mOnFailure, mOnSuccess);
+ }
+ }
+
+ virtual void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override {
+ assert(false); // Should not reach here.
+ }
+
+ virtual void WriteComplete(GMPErr aStatus) override {
+ if (GMP_FAILED(aStatus)) {
+ Done(mOnFailure, mOnSuccess);
+ } else {
+ Done(mOnSuccess, mOnFailure);
+ }
+ }
+
+private:
+ WriteRecordClient(const std::vector<uint8_t>& aData,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure)
+ : mRecord(nullptr)
+ , mOnSuccess(aOnSuccess)
+ , mOnFailure(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 Done(GMPTask* aToRun, GMPTask* aToDestroy) {
+ // 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();
+ }
+ aToDestroy->Destroy();
+ RunOnMainThread(aToRun);
+ delete this;
+ }
+
+ GMPRecord* mRecord;
+ GMPTask* mOnSuccess;
+ GMPTask* mOnFailure;
+ const std::vector<uint8_t> mData;
+};
+
+void
+StoreData(const std::string& aRecordName,
+ const std::vector<uint8_t>& aData,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure)
+{
+ WriteRecordClient::Write(aRecordName, aData, aOnSuccess, aOnFailure);
+}
+
+class ReadRecordClient : public GMPRecordClient {
+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);
+ }
+
+ virtual void OpenComplete(GMPErr aStatus) override {
+ auto err = aStatus;
+ if (GMP_FAILED(err) ||
+ GMP_FAILED(err = mRecord->Read())) {
+ Done(err, nullptr, 0);
+ }
+ }
+
+ virtual void ReadComplete(GMPErr 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.
+ }
+
+private:
+ explicit ReadRecordClient(ReadContinuation* aContinuation)
+ : mRecord(nullptr)
+ , mContinuation(aContinuation) {}
+
+ 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 Done(GMPErr err, 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();
+ }
+ mContinuation->ReadComplete(err, aData, aDataSize);
+ delete mContinuation;
+ delete this;
+ }
+
+ GMPRecord* mRecord;
+ ReadContinuation* mContinuation;
+};
+
+void
+ReadData(const std::string& aRecordName,
+ ReadContinuation* aContinuation)
+{
+ ReadRecordClient::Read(aRecordName, aContinuation);
+}
+
+GMPErr
+EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc)
+{
+ return GetPlatform()->getrecordenumerator(aRecvIteratorFunc, nullptr);
+}
diff --git a/libs/gmp-clearkey/0.1/ClearKeyStorage.h b/libs/gmp-clearkey/0.1/ClearKeyStorage.h
new file mode 100644
index 000000000..695f67d9e
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyStorage.h
@@ -0,0 +1,48 @@
+/*
+ * 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 __ClearKeyStorage_h__
+#define __ClearKeyStorage_h__
+
+#include "gmp-api/gmp-errors.h"
+#include "gmp-api/gmp-platform.h"
+#include <string>
+#include <vector>
+#include <stdint.h>
+
+class GMPTask;
+
+// Responsible for ensuring that both aOnSuccess and aOnFailure are destroyed.
+void StoreData(const std::string& aRecordName,
+ const std::vector<uint8_t>& aData,
+ GMPTask* aOnSuccess,
+ GMPTask* 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);
+
+#endif // __ClearKeyStorage_h__
diff --git a/libs/gmp-clearkey/0.1/ClearKeyUtils.cpp b/libs/gmp-clearkey/0.1/ClearKeyUtils.cpp
new file mode 100644
index 000000000..b99a50a4d
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyUtils.cpp
@@ -0,0 +1,541 @@
+/*
+ * 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 <algorithm>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <vector>
+
+#include "ClearKeyUtils.h"
+#include "ClearKeyBase64.h"
+#include "ArrayUtils.h"
+#include <assert.h>
+#include <memory.h>
+#include "BigEndian.h"
+#include "openaes/oaes_lib.h"
+
+using namespace std;
+
+void
+CK_Log(const char* aFmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, aFmt);
+ vprintf(aFmt, ap);
+ va_end(ap);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void
+IncrementIV(vector<uint8_t>& aIV) {
+ using mozilla::BigEndian;
+
+ assert(aIV.size() == 16);
+ BigEndian::writeUint64(&aIV[8], BigEndian::readUint64(&aIV[8]) + 1);
+}
+
+/* static */ void
+ClearKeyUtils::DecryptAES(const vector<uint8_t>& aKey,
+ vector<uint8_t>& aData, vector<uint8_t>& aIV)
+{
+ assert(aIV.size() == CENC_KEY_LEN);
+ assert(aKey.size() == CENC_KEY_LEN);
+
+ OAES_CTX* aes = oaes_alloc();
+ oaes_key_import_data(aes, &aKey[0], aKey.size());
+ oaes_set_option(aes, OAES_OPTION_ECB, nullptr);
+
+ for (size_t i = 0; i < aData.size(); i += CENC_KEY_LEN) {
+ size_t encLen;
+ oaes_encrypt(aes, &aIV[0], CENC_KEY_LEN, nullptr, &encLen);
+
+ vector<uint8_t> enc(encLen);
+ oaes_encrypt(aes, &aIV[0], CENC_KEY_LEN, &enc[0], &encLen);
+
+ assert(encLen >= 2 * OAES_BLOCK_SIZE + CENC_KEY_LEN);
+ size_t blockLen = min(aData.size() - i, CENC_KEY_LEN);
+ for (size_t j = 0; j < blockLen; j++) {
+ aData[i + j] ^= enc[2 * OAES_BLOCK_SIZE + j];
+ }
+ IncrementIV(aIV);
+ }
+
+ oaes_free(&aes);
+}
+
+/**
+ * ClearKey expects all Key IDs to be base64 encoded with non-standard alphabet
+ * and padding.
+ */
+static bool
+EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
+{
+ const char sAlphabet[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+ const uint8_t sMask = 0x3f;
+
+ aEncoded.resize((aBinary.size() * 8 + 5) / 6);
+
+ // Pad binary data in case there's rubbish past the last byte.
+ aBinary.push_back(0);
+
+ // Number of bytes not consumed in the previous character
+ uint32_t shift = 0;
+
+ auto out = aEncoded.begin();
+ auto data = aBinary.begin();
+ for (string::size_type i = 0; i < aEncoded.length(); i++) {
+ if (shift) {
+ out[i] = (*data << (6 - shift)) & sMask;
+ data++;
+ } else {
+ out[i] = 0;
+ }
+
+ out[i] += (*data >> (shift + 2)) & sMask;
+ shift = (shift + 2) % 8;
+
+ // 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[i] = sAlphabet[idx];
+ }
+
+ return true;
+}
+
+/* static */ void
+ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
+ string& aOutRequest,
+ GMPSessionType aSessionType)
+{
+ assert(aKeyIDs.size() && aOutRequest.empty());
+
+ aOutRequest.append("{\"kids\":[");
+ for (size_t i = 0; i < aKeyIDs.size(); i++) {
+ if (i) {
+ aOutRequest.append(",");
+ }
+ aOutRequest.append("\"");
+
+ string base64key;
+ EncodeBase64Web(aKeyIDs[i], base64key);
+ aOutRequest.append(base64key);
+
+ aOutRequest.append("\"");
+ }
+ aOutRequest.append("],\"type\":");
+
+ aOutRequest.append("\"");
+ aOutRequest.append(SessionTypeToString(aSessionType));
+ aOutRequest.append("\"}");
+}
+
+#define EXPECT_SYMBOL(CTX, X) do { \
+ if (GetNextSymbol(CTX) != (X)) { \
+ CK_LOGE("Unexpected symbol in JWK parser"); \
+ return false; \
+ } \
+} while (false)
+
+struct ParserContext {
+ const uint8_t* mIter;
+ const uint8_t* mEnd;
+};
+
+static uint8_t
+PeekSymbol(ParserContext& aCtx)
+{
+ for (; aCtx.mIter < aCtx.mEnd; (aCtx.mIter)++) {
+ if (!isspace(*aCtx.mIter)) {
+ return *aCtx.mIter;
+ }
+ }
+
+ return 0;
+}
+
+static uint8_t
+GetNextSymbol(ParserContext& aCtx)
+{
+ uint8_t sym = PeekSymbol(aCtx);
+ aCtx.mIter++;
+ return sym;
+}
+
+static bool SkipToken(ParserContext& aCtx);
+
+static bool
+SkipString(ParserContext& aCtx)
+{
+ EXPECT_SYMBOL(aCtx, '"');
+ for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
+ if (sym == '\\') {
+ sym = GetNextSymbol(aCtx);
+ } else if (sym == '"') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Skip whole object and values it contains.
+ */
+static bool
+SkipObject(ParserContext& aCtx)
+{
+ EXPECT_SYMBOL(aCtx, '{');
+
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+
+ while (true) {
+ if (!SkipString(aCtx)) return false;
+ EXPECT_SYMBOL(aCtx, ':');
+ if (!SkipToken(aCtx)) return false;
+
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return false;
+}
+
+/**
+ * Skip array value and the values it contains.
+ */
+static bool
+SkipArray(ParserContext& aCtx)
+{
+ EXPECT_SYMBOL(aCtx, '[');
+
+ if (PeekSymbol(aCtx) == ']') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+
+ while (SkipToken(aCtx)) {
+ if (PeekSymbol(aCtx) == ']') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return false;
+}
+
+/**
+ * Skip unquoted literals like numbers, |true|, and |null|.
+ * (XXX and anything else that matches /([:alnum:]|[+-.])+/)
+ */
+static bool
+SkipLiteral(ParserContext& aCtx)
+{
+ for (; aCtx.mIter < aCtx.mEnd; aCtx.mIter++) {
+ if (!isalnum(*aCtx.mIter) &&
+ *aCtx.mIter != '.' && *aCtx.mIter != '-' && *aCtx.mIter != '+') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool
+SkipToken(ParserContext& aCtx)
+{
+ uint8_t startSym = PeekSymbol(aCtx);
+ if (startSym == '"') {
+ CK_LOGD("JWK parser skipping string");
+ return SkipString(aCtx);
+ } else if (startSym == '{') {
+ CK_LOGD("JWK parser skipping object");
+ return SkipObject(aCtx);
+ } else if (startSym == '[') {
+ CK_LOGD("JWK parser skipping array");
+ return SkipArray(aCtx);
+ } else {
+ CK_LOGD("JWK parser skipping literal");
+ return SkipLiteral(aCtx);
+ }
+
+ return false;
+}
+
+static bool
+GetNextLabel(ParserContext& aCtx, string& aOutLabel)
+{
+ EXPECT_SYMBOL(aCtx, '"');
+
+ const uint8_t* start = aCtx.mIter;
+ for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
+ if (sym == '\\') {
+ GetNextSymbol(aCtx);
+ continue;
+ }
+
+ if (sym == '"') {
+ aOutLabel.assign(start, aCtx.mIter - 1);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool
+ParseKeyObject(ParserContext& aCtx, KeyIdPair& aOutKey)
+{
+ EXPECT_SYMBOL(aCtx, '{');
+
+ // Reject empty objects as invalid licenses.
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return false;
+ }
+
+ string keyId;
+ string key;
+
+ while (true) {
+ string label;
+ string value;
+
+ if (!GetNextLabel(aCtx, label)) {
+ return false;
+ }
+
+ EXPECT_SYMBOL(aCtx, ':');
+ if (label == "kty") {
+ if (!GetNextLabel(aCtx, value)) return false;
+ // By spec, type must be "oct".
+ if (value != "oct") return false;
+ } else if (label == "k" && PeekSymbol(aCtx) == '"') {
+ // if this isn't a string we will fall through to the SkipToken() path.
+ if (!GetNextLabel(aCtx, key)) return false;
+ } else if (label == "kid" && PeekSymbol(aCtx) == '"') {
+ if (!GetNextLabel(aCtx, keyId)) return false;
+ } else {
+ if (!SkipToken(aCtx)) return false;
+ }
+
+ uint8_t sym = PeekSymbol(aCtx);
+ if (!sym || sym == '}') {
+ break;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return !key.empty() &&
+ !keyId.empty() &&
+ DecodeBase64(keyId, aOutKey.mKeyId) &&
+ DecodeBase64(key, aOutKey.mKey) &&
+ GetNextSymbol(aCtx) == '}';
+}
+
+static bool
+ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys)
+{
+ // Consume start of array.
+ EXPECT_SYMBOL(aCtx, '[');
+
+ while (true) {
+ KeyIdPair key;
+ if (!ParseKeyObject(aCtx, key)) {
+ CK_LOGE("Failed to parse key object");
+ return false;
+ }
+
+ assert(!key.mKey.empty() && !key.mKeyId.empty());
+ aOutKeys.push_back(key);
+
+ uint8_t sym = PeekSymbol(aCtx);
+ if (!sym || sym == ']') {
+ break;
+ }
+
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return GetNextSymbol(aCtx) == ']';
+}
+
+/* static */ bool
+ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
+ vector<KeyIdPair>& aOutKeys,
+ GMPSessionType aSessionType)
+{
+ ParserContext ctx;
+ ctx.mIter = aKeyData;
+ ctx.mEnd = aKeyData + aKeyDataSize;
+
+ // Consume '{' from start of object.
+ EXPECT_SYMBOL(ctx, '{');
+
+ while (true) {
+ string label;
+ // Consume member key.
+ if (!GetNextLabel(ctx, label)) return false;
+ EXPECT_SYMBOL(ctx, ':');
+
+ if (label == "keys") {
+ // Parse "keys" array.
+ if (!ParseKeys(ctx, aOutKeys)) return false;
+ } else if (label == "type") {
+ // Consume type string.
+ string type;
+ if (!GetNextLabel(ctx, type)) return false;
+ if (type != SessionTypeToString(aSessionType)) {
+ return false;
+ }
+ } else {
+ SkipToken(ctx);
+ }
+
+ // Check for end of object.
+ if (PeekSymbol(ctx) == '}') {
+ break;
+ }
+
+ // Consume ',' between object members.
+ EXPECT_SYMBOL(ctx, ',');
+ }
+
+ // Consume '}' from end of object.
+ EXPECT_SYMBOL(ctx, '}');
+
+ return true;
+}
+
+static bool
+ParseKeyIds(ParserContext& aCtx, vector<KeyId>& aOutKeyIds)
+{
+ // Consume start of array.
+ EXPECT_SYMBOL(aCtx, '[');
+
+ while (true) {
+ string label;
+ vector<uint8_t> keyId;
+ if (!GetNextLabel(aCtx, label) || !DecodeBase64(label, keyId)) {
+ return false;
+ }
+ if (!keyId.empty() && keyId.size() <= kMaxKeyIdsLength) {
+ aOutKeyIds.push_back(keyId);
+ }
+
+ uint8_t sym = PeekSymbol(aCtx);
+ if (!sym || sym == ']') {
+ break;
+ }
+
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return GetNextSymbol(aCtx) == ']';
+}
+
+
+/* static */ bool
+ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ vector<KeyId>& aOutKeyIds)
+{
+ ParserContext ctx;
+ ctx.mIter = aInitData;
+ ctx.mEnd = aInitData + aInitDataSize;
+
+ // Consume '{' from start of object.
+ EXPECT_SYMBOL(ctx, '{');
+
+ while (true) {
+ string label;
+ // Consume member kids.
+ if (!GetNextLabel(ctx, label)) return false;
+ EXPECT_SYMBOL(ctx, ':');
+
+ if (label == "kids") {
+ // Parse "kids" array.
+ if (!ParseKeyIds(ctx, aOutKeyIds) ||
+ aOutKeyIds.empty()) {
+ return false;
+ }
+ } else {
+ SkipToken(ctx);
+ }
+
+ // Check for end of object.
+ if (PeekSymbol(ctx) == '}') {
+ break;
+ }
+
+ // Consume ',' between object members.
+ EXPECT_SYMBOL(ctx, ',');
+ }
+
+ // Consume '}' from end of object.
+ EXPECT_SYMBOL(ctx, '}');
+
+ return true;
+}
+
+/* static */ const char*
+ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
+{
+ switch (aSessionType) {
+ case kGMPTemporySession: return "temporary";
+ case kGMPPersistentSession: return "persistent-license";
+ default: {
+ assert(false); // Should not reach here.
+ return "invalid";
+ }
+ }
+}
+
+/* static */ bool
+ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength)
+{
+ if (aLength > 10) {
+ // 10 is the max number of characters in UINT32_MAX when
+ // represented as a string; ClearKey session ids are integers.
+ return false;
+ }
+ for (uint32_t i = 0; i < aLength; i++) {
+ if (!isdigit(aBuff[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+GMPMutex* GMPCreateMutex() {
+ GMPMutex* mutex;
+ auto err = GetPlatform()->createmutex(&mutex);
+ assert(mutex);
+ return GMP_FAILED(err) ? nullptr : mutex;
+}
diff --git a/libs/gmp-clearkey/0.1/ClearKeyUtils.h b/libs/gmp-clearkey/0.1/ClearKeyUtils.h
new file mode 100644
index 000000000..fc29b9211
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/ClearKeyUtils.h
@@ -0,0 +1,114 @@
+/*
+ * 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 __ClearKeyUtils_h__
+#define __ClearKeyUtils_h__
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <assert.h>
+#include "gmp-api/gmp-decryption.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__)
+#else
+#define CK_LOGE(...)
+#define CK_LOGD(...)
+#define CK_LOGW(...)
+#endif
+
+struct GMPPlatformAPI;
+extern GMPPlatformAPI* GetPlatform();
+
+typedef std::vector<uint8_t> KeyId;
+typedef std::vector<uint8_t> Key;
+
+// The session response size should be within a reasonable limit.
+// The size 64 KB is referenced from web-platform-test.
+static const uint32_t kMaxSessionResponseLength = 65536;
+
+// Provide limitation for KeyIds length and webm initData size.
+static const uint32_t kMaxWebmInitDataSize = 65536;
+static const uint32_t kMaxKeyIdsLength = 512;
+
+struct KeyIdPair
+{
+ KeyId mKeyId;
+ Key mKey;
+};
+
+class ClearKeyUtils
+{
+public:
+ static void DecryptAES(const std::vector<uint8_t>& aKey,
+ std::vector<uint8_t>& aData, std::vector<uint8_t>& aIV);
+
+ static bool ParseKeyIdsInitData(const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ std::vector<KeyId>& aOutKeyIds);
+
+ static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
+ std::string& aOutRequest,
+ GMPSessionType aSessionType);
+
+ static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
+ std::vector<KeyIdPair>& aOutKeys,
+ GMPSessionType aSessionType);
+ static const char* SessionTypeToString(GMPSessionType aSessionType);
+
+ static bool IsValidSessionId(const char* aBuff, uint32_t aLength);
+};
+
+template<class Container, class Element>
+inline bool
+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)
+{
+ aVec.assign(aData, aData + aLength);
+}
+
+#endif // __ClearKeyUtils_h__
diff --git a/libs/gmp-clearkey/0.1/RefCounted.h b/libs/gmp-clearkey/0.1/RefCounted.h
new file mode 100644
index 000000000..6da694295
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/RefCounted.h
@@ -0,0 +1,116 @@
+/*
+ * 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 __RefCount_h__
+#define __RefCount_h__
+
+#include <stdint.h>
+#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 {
+public:
+ void AddRef() {
+ ++mRefCount;
+ }
+
+ uint32_t Release() {
+ uint32_t newCount = --mRefCount;
+ if (!newCount) {
+ delete this;
+ }
+ return newCount;
+ }
+
+protected:
+ RefCounted()
+ : mRefCount(0)
+ {
+ }
+ virtual ~RefCounted()
+ {
+ assert(!mRefCount);
+ }
+ AtomicRefCount mRefCount;
+};
+
+template<class T>
+class RefPtr {
+public:
+ explicit RefPtr(T* aPtr) : mPtr(nullptr) {
+ Assign(aPtr);
+ }
+ ~RefPtr() {
+ Assign(nullptr);
+ }
+ T* operator->() const { return mPtr; }
+
+ RefPtr& operator=(T* aVal) {
+ Assign(aVal);
+ return *this;
+ }
+
+private:
+ void Assign(T* aPtr) {
+ if (mPtr) {
+ mPtr->Release();
+ }
+ mPtr = aPtr;
+ if (mPtr) {
+ aPtr->AddRef();
+ }
+ }
+ T* mPtr;
+};
+
+#endif // __RefCount_h__
diff --git a/libs/gmp-clearkey/0.1/VideoDecoder.cpp b/libs/gmp-clearkey/0.1/VideoDecoder.cpp
new file mode 100644
index 000000000..a3f25402b
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2013, 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 <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;
+
+VideoDecoder::VideoDecoder(GMPVideoHost *aHostAPI)
+ : mHostAPI(aHostAPI)
+ , mCallback(nullptr)
+ , mWorkerThread(nullptr)
+ , mMutex(nullptr)
+ , mNumInputTasks(0)
+ , mSentExtraData(false)
+ , mIsFlushing(false)
+ , mHasShutdown(false)
+{
+ // We drop the ref in DecodingComplete().
+ AddRef();
+}
+
+VideoDecoder::~VideoDecoder()
+{
+ if (mMutex) {
+ mMutex->Destroy();
+ }
+}
+
+void
+VideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount)
+{
+ mCallback = aCallback;
+ assert(mCallback);
+ mDecoder = new WMFH264Decoder();
+ HRESULT hr = mDecoder->Init(aCoreCount);
+ if (FAILED(hr)) {
+ CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
+ mCallback->Error(GMPGenericErr);
+ return;
+ }
+
+ 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);
+}
+
+void
+VideoDecoder::EnsureWorker()
+{
+ 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;
+ }
+
+ 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));
+}
+
+void
+VideoDecoder::DecodeTask(DecodeData* aData)
+{
+ CK_LOGD("VideoDecoder::DecodeTask");
+ AutoPtr<DecodeData> d(aData);
+ HRESULT hr;
+
+ {
+ AutoLock lock(mMutex);
+ mNumInputTasks--;
+ assert(mNumInputTasks >= 0);
+ }
+
+ if (mIsFlushing) {
+ CK_LOGD("VideoDecoder::DecodeTask rejecting frame: flushing.");
+ return;
+ }
+
+ if (!aData || !mHostAPI || !mDecoder) {
+ CK_LOGE("Decode job not set up correctly!");
+ return;
+ }
+
+ 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);
+
+ if (GMP_FAILED(rv)) {
+ MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::Error, rv));
+ return;
+ }
+ }
+
+ 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);
+
+ 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;
+ }
+
+ 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);
+ }
+ }
+}
+
+void
+VideoDecoder::ReturnOutput(IMFSample* aSample,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride)
+{
+ CK_LOGD("[%p] VideoDecoder::ReturnOutput()\n", this);
+ assert(aSample);
+
+ HRESULT hr;
+
+ 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;
+ }
+
+ auto vf = static_cast<GMPVideoi420Frame*>(f);
+
+ hr = SampleToVideoFrame(aSample, aWidth, aHeight, aStride, vf);
+ ENSURE(SUCCEEDED(hr), /*void*/);
+
+ mCallback->Decoded(vf);
+}
+
+HRESULT
+VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride,
+ GMPVideoi420Frame* aVideoFrame)
+{
+ ENSURE(aSample != nullptr, E_POINTER);
+ ENSURE(aVideoFrame != nullptr, E_POINTER);
+
+ HRESULT hr;
+ CComPtr<IMFMediaBuffer> mediaBuffer;
+
+ // Must convert to contiguous mediaBuffer to use IMD2DBuffer interface.
+ hr = aSample->ConvertToContiguousBuffer(&mediaBuffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ // Try and use the IMF2DBuffer interface if available, otherwise fallback
+ // to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient,
+ // but only some systems (Windows 8?) support it.
+ BYTE* data = nullptr;
+ LONG stride = 0;
+ CComPtr<IMF2DBuffer> twoDBuffer;
+ hr = mediaBuffer->QueryInterface(static_cast<IMF2DBuffer**>(&twoDBuffer));
+ if (SUCCEEDED(hr)) {
+ hr = twoDBuffer->Lock2D(&data, &stride);
+ ENSURE(SUCCEEDED(hr), hr);
+ } else {
+ hr = mediaBuffer->Lock(&data, NULL, NULL);
+ ENSURE(SUCCEEDED(hr), hr);
+ stride = aStride;
+ }
+
+ // The V and U 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.
+ 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;
+
+ auto err = aVideoFrame->CreateEmptyFrame(stride, aHeight, stride, halfStride, halfStride);
+ ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+
+ err = aVideoFrame->SetWidth(aWidth);
+ ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+ err = aVideoFrame->SetHeight(aHeight);
+ ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+
+ uint8_t* outBuffer = aVideoFrame->Buffer(kGMPYPlane);
+ ENSURE(outBuffer != nullptr, E_FAIL);
+ assert(aVideoFrame->AllocatedSize(kGMPYPlane) >= stride*aHeight);
+ memcpy(outBuffer, data, stride*aHeight);
+
+ outBuffer = aVideoFrame->Buffer(kGMPUPlane);
+ ENSURE(outBuffer != nullptr, E_FAIL);
+ assert(aVideoFrame->AllocatedSize(kGMPUPlane) >= halfStride*halfHeight);
+ memcpy(outBuffer, data+y_size, halfStride*halfHeight);
+
+ outBuffer = aVideoFrame->Buffer(kGMPVPlane);
+ ENSURE(outBuffer != nullptr, E_FAIL);
+ assert(aVideoFrame->AllocatedSize(kGMPVPlane) >= halfStride*halfHeight);
+ memcpy(outBuffer, data + y_size + v_size, halfStride*halfHeight);
+
+ if (twoDBuffer) {
+ twoDBuffer->Unlock2D();
+ } else {
+ mediaBuffer->Unlock();
+ }
+
+ 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));
+
+ return S_OK;
+}
+
+void
+VideoDecoder::ResetCompleteTask()
+{
+ mIsFlushing = false;
+ if (mCallback) {
+ MaybeRunOnMainThread(WrapTask(mCallback,
+ &GMPVideoDecoderCallback::ResetComplete));
+ }
+}
+
+void
+VideoDecoder::Reset()
+{
+ mIsFlushing = true;
+ 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));
+}
+
+void
+VideoDecoder::DrainTask()
+{
+ 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));
+}
+
+void
+VideoDecoder::DecodingComplete()
+{
+ if (mWorkerThread) {
+ mWorkerThread->Join();
+ }
+ mHasShutdown = true;
+
+ // Release the reference we added in the constructor. There may be
+ // WrapRefCounted tasks that also hold references to us, and keep
+ // 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));
+}
diff --git a/libs/gmp-clearkey/0.1/VideoDecoder.h b/libs/gmp-clearkey/0.1/VideoDecoder.h
new file mode 100644
index 000000000..cf021efd7
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/VideoDecoder.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013, 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 __VideoDecoder_h__
+#define __VideoDecoder_h__
+
+#include <atomic>
+
+#include "gmp-task-utils.h"
+#include "gmp-video-decode.h"
+#include "gmp-video-host.h"
+#include "WMFH264Decoder.h"
+
+#include "mfobjects.h"
+
+class VideoDecoder : public GMPVideoDecoder
+ , public RefCounted
+{
+public:
+ VideoDecoder(GMPVideoHost *aHostAPI);
+
+ virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount) override;
+
+ virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ int64_t aRenderTimeMs = -1);
+
+ virtual void Reset() override;
+
+ virtual void Drain() override;
+
+ virtual void DecodingComplete() override;
+
+ bool HasShutdown() { return mHasShutdown; }
+
+private:
+
+ virtual ~VideoDecoder();
+
+ void EnsureWorker();
+
+ void DrainTask();
+
+ struct DecodeData {
+ DecodeData()
+ : mTimestamp(0)
+ , mDuration(0)
+ , mIsKeyframe(false)
+ {}
+ std::vector<uint8_t> mBuffer;
+ uint64_t mTimestamp;
+ uint64_t mDuration;
+ bool mIsKeyframe;
+ CryptoMetaData mCrypto;
+ };
+
+ void DecodeTask(DecodeData* aData);
+
+ void ResetCompleteTask();
+
+ void ReturnOutput(IMFSample* aSample,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride);
+
+ HRESULT SampleToVideoFrame(IMFSample* aSample,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride,
+ GMPVideoi420Frame* aVideoFrame);
+
+ void MaybeRunOnMainThread(GMPTask* aTask);
+
+ GMPVideoHost *mHostAPI; // host-owned, invalid at DecodingComplete
+ GMPVideoDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
+ GMPThread* mWorkerThread;
+ GMPMutex* mMutex;
+ wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
+
+ std::vector<uint8_t> mExtraData;
+ std::vector<uint8_t> mAnnexB;
+
+ int32_t mNumInputTasks;
+ bool mSentExtraData;
+
+ std::atomic<bool> mIsFlushing;
+
+ bool mHasShutdown;
+};
+
+#endif // __VideoDecoder_h__
diff --git a/libs/gmp-clearkey/0.1/WMFAACDecoder.cpp b/libs/gmp-clearkey/0.1/WMFAACDecoder.cpp
new file mode 100644
index 000000000..495f840e8
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/WMFAACDecoder.cpp
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2013, 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 "WMFAACDecoder.h"
+#include <algorithm>
+#include <stdint.h>
+#include <vector>
+
+using std::vector;
+
+namespace wmf {
+
+WMFAACDecoder::WMFAACDecoder()
+ : mDecoder(nullptr)
+ , mChannels(0)
+ , mRate(0)
+{
+ memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
+ memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
+}
+
+WMFAACDecoder::~WMFAACDecoder()
+{
+ Reset();
+}
+
+HRESULT
+AACAudioSpecificConfigToUserData(BYTE* aAudioSpecConfig, UINT32 aConfigLength,
+ BYTE** aOutUserData, UINT32* aOutUserDataLength)
+{
+ // For MFAudioFormat_AAC, MF_MT_USER_DATA
+ // Contains the portion of the HEAACWAVEINFO structure that appears
+ // after the WAVEFORMATEX structure (that is, after the wfx member).
+ // This is followed by the AudioSpecificConfig() data, as defined
+ // by ISO/IEC 14496-3.
+ // ...
+ // The length of the AudioSpecificConfig() data is 2 bytes for AAC-LC
+ // or HE-AAC with implicit signaling of SBR/PS. It is more than 2 bytes
+ // for HE-AAC with explicit signaling of SBR/PS.
+ //
+ // The value of audioObjectType as defined in AudioSpecificConfig()
+ // must be 2, indicating AAC-LC. The value of extensionAudioObjectType
+ // must be 5 for SBR or 29 for PS.
+ //
+ // See:
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/dd742784%28v=vs.85%29.aspx
+ //
+ // HEAACWAVEINFO structure:
+ // typedef struct heaacwaveinfo_tag {
+ // WAVEFORMATEX wfx;
+ // WORD wPayloadType;
+ // WORD wAudioProfileLevelIndication;
+ // WORD wStructType;
+ // WORD wReserved1;
+ // DWORD dwReserved2;
+ // }
+ const UINT32 heeInfoLen = 4 * sizeof(WORD) + sizeof(DWORD);
+ BYTE heeInfo[heeInfoLen] = {0};
+ WORD* w = (WORD*)heeInfo;
+ w[0] = 0x0; // Payload type raw AAC
+ w[1] = 0; // Profile level unspecified
+
+ const UINT32 len = heeInfoLen + aConfigLength;
+ BYTE* data = new BYTE[len];
+ memcpy(data, heeInfo, heeInfoLen);
+ memcpy(data+heeInfoLen, aAudioSpecConfig, aConfigLength);
+ *aOutUserData = data;
+ *aOutUserDataLength = len;
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Init(int32_t aChannelCount,
+ int32_t aSampleRate,
+ BYTE* aAACAudioSpecificConfig,
+ UINT32 aAudioConfigLength)
+{
+ HRESULT hr;
+
+ // AAC decoder is in msauddecmft on Win8, and msmpeg2adec in earlier versions.
+ hr = CreateMFT(CLSID_CMSAACDecMFT,
+ WMFDecoderDllNameFor(AAC),
+ mDecoder);
+ if (FAILED(hr)) {
+ hr = CreateMFT(CLSID_CMSAACDecMFT,
+ WMFDecoderDllNameFor(AAC),
+ mDecoder);
+ if (FAILED(hr)) {
+ LOG("Failed to create AAC decoder\n");
+ return E_FAIL;
+ }
+ }
+
+ BYTE* userData = nullptr;
+ UINT32 userDataLength;
+ hr = AACAudioSpecificConfigToUserData(aAACAudioSpecificConfig,
+ aAudioConfigLength,
+ &userData,
+ &userDataLength);
+ ENSURE(SUCCEEDED(hr), hr);
+ hr = SetDecoderInputType(aChannelCount, aSampleRate, userData, userDataLength);
+ delete userData;
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SetDecoderOutputType();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::SetDecoderInputType(int32_t aChannelCount,
+ int32_t aSampleRate,
+ BYTE* aUserData,
+ UINT32 aUserDataLength)
+{
+ HRESULT hr;
+
+ CComPtr<IMFMediaType> type;
+ hr = MFCreateMediaType(&type);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ mRate = aSampleRate;
+ hr = type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, mRate);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ mChannels = aChannelCount;
+ hr = type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, mChannels);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetBlob(MF_MT_USER_DATA, aUserData, aUserDataLength);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->SetInputType(0, type, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::SetDecoderOutputType()
+{
+ HRESULT hr;
+
+ CComPtr<IMFMediaType> type;
+
+ UINT32 typeIndex = 0;
+ while (type = nullptr, SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, &type))) {
+ GUID subtype;
+ hr = type->GetGUID(MF_MT_SUBTYPE, &subtype);
+ if (FAILED(hr)) {
+ continue;
+ }
+ if (subtype == MFAudioFormat_PCM) {
+ hr = mDecoder->SetOutputType(0, type, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &mChannels);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &mRate);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+ }
+ }
+
+ return E_FAIL;
+}
+
+HRESULT
+WMFAACDecoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData)
+{
+ ENSURE(mDecoder != nullptr, E_POINTER);
+ HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
+ ENSURE(SUCCEEDED(hr), hr);
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::CreateInputSample(const uint8_t* aData,
+ uint32_t aDataSize,
+ Microseconds aTimestamp,
+ IMFSample** aOutSample)
+{
+ HRESULT hr;
+ CComPtr<IMFSample> sample = nullptr;
+ hr = MFCreateSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ CComPtr<IMFMediaBuffer> buffer = nullptr;
+ int32_t bufferSize = std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
+ UINT32 alignment = (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
+ hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ DWORD maxLength = 0;
+ DWORD currentLength = 0;
+ BYTE* dst = nullptr;
+ hr = buffer->Lock(&dst, &maxLength, &currentLength);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ // Copy data into sample's buffer.
+ memcpy(dst, aData, aDataSize);
+
+ hr = buffer->Unlock();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = buffer->SetCurrentLength(aDataSize);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
+ ENSURE(SUCCEEDED(hr), hr);
+
+ *aOutSample = sample.Detach();
+
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::CreateOutputSample(IMFSample** aOutSample)
+{
+ HRESULT hr;
+ CComPtr<IMFSample> sample = nullptr;
+ hr = MFCreateSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ CComPtr<IMFMediaBuffer> buffer = nullptr;
+ int32_t bufferSize = mOutputStreamInfo.cbSize;
+ UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) ? mOutputStreamInfo.cbAlignment - 1 : 0;
+ hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ *aOutSample = sample.Detach();
+
+ return S_OK;
+}
+
+
+HRESULT
+WMFAACDecoder::GetOutputSample(IMFSample** aOutSample)
+{
+ HRESULT hr;
+ // We allocate samples for MFT output.
+ MFT_OUTPUT_DATA_BUFFER output = {0};
+
+ CComPtr<IMFSample> sample = nullptr;
+ hr = CreateOutputSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ output.pSample = sample;
+
+ DWORD status = 0;
+ hr = mDecoder->ProcessOutput(0, 1, &output, &status);
+ CComPtr<IMFCollection> events = output.pEvents; // Ensure this is released.
+
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ // Type change. Probably geometric apperature change.
+ hr = SetDecoderOutputType();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return GetOutputSample(aOutSample);
+ } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT || !sample) {
+ return MF_E_TRANSFORM_NEED_MORE_INPUT;
+ }
+ // Treat other errors as fatal.
+ ENSURE(SUCCEEDED(hr), hr);
+
+ assert(sample);
+
+ *aOutSample = sample.Detach();
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Input(const uint8_t* aData,
+ uint32_t aDataSize,
+ Microseconds aTimestamp)
+{
+ CComPtr<IMFSample> input = nullptr;
+ HRESULT hr = CreateInputSample(aData, aDataSize, aTimestamp, &input);
+ ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
+
+ hr = mDecoder->ProcessInput(0, input, 0);
+ if (hr == MF_E_NOTACCEPTING) {
+ // MFT *already* has enough data to produce a sample. Retrieve it.
+ LOG("ProcessInput returned MF_E_NOTACCEPTING\n");
+ return MF_E_NOTACCEPTING;
+ }
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Output(IMFSample** aOutput)
+{
+ CComPtr<IMFSample> outputSample = nullptr;
+ HRESULT hr = GetOutputSample(&outputSample);
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return MF_E_TRANSFORM_NEED_MORE_INPUT;
+ }
+ // Treat other errors as fatal.
+ ENSURE(SUCCEEDED(hr) && outputSample, hr);
+
+ *aOutput = outputSample.Detach();
+
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Reset()
+{
+ HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Drain()
+{
+ HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+}
diff --git a/libs/gmp-clearkey/0.1/WMFAACDecoder.h b/libs/gmp-clearkey/0.1/WMFAACDecoder.h
new file mode 100644
index 000000000..3099eeea2
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/WMFAACDecoder.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013, 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.
+ */
+
+#if !defined(WMFAACDecoder_h_)
+#define WMFAACDecoder_h_
+
+#include "WMFUtils.h"
+
+namespace wmf {
+
+class WMFAACDecoder {
+public:
+ WMFAACDecoder();
+ ~WMFAACDecoder();
+
+ HRESULT Init(int32_t aChannelCount,
+ int32_t aSampleRate,
+ BYTE* aUserData,
+ UINT32 aUserDataLength);
+
+ HRESULT Input(const uint8_t* aData,
+ uint32_t aDataSize,
+ Microseconds aTimestamp);
+
+ HRESULT Output(IMFSample** aOutput);
+
+ HRESULT Reset();
+
+ HRESULT Drain();
+
+ UINT32 Channels() const { return mChannels; }
+ UINT32 Rate() const { return mRate; }
+
+private:
+
+ HRESULT GetOutputSample(IMFSample** aOutSample);
+ HRESULT CreateOutputSample(IMFSample** aOutSample);
+ HRESULT CreateInputSample(const uint8_t* aData,
+ uint32_t aDataSize,
+ Microseconds aTimestamp,
+ IMFSample** aOutSample);
+
+ HRESULT SetDecoderInputType(int32_t aChannelCount,
+ int32_t aSampleRate,
+ BYTE* aUserData,
+ UINT32 aUserDataLength);
+ HRESULT SetDecoderOutputType();
+ HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData);
+
+ MFT_INPUT_STREAM_INFO mInputStreamInfo;
+ MFT_OUTPUT_STREAM_INFO mOutputStreamInfo;
+
+ CComPtr<IMFTransform> mDecoder;
+
+ UINT32 mChannels;
+ UINT32 mRate;
+};
+
+}
+
+#endif
diff --git a/libs/gmp-clearkey/0.1/WMFH264Decoder.cpp b/libs/gmp-clearkey/0.1/WMFH264Decoder.cpp
new file mode 100644
index 000000000..8a3ed4fff
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/WMFH264Decoder.cpp
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2013, 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 "WMFH264Decoder.h"
+#include <algorithm>
+#include <codecapi.h>
+
+namespace wmf {
+
+WMFH264Decoder::WMFH264Decoder()
+ : mDecoder(nullptr)
+{
+ memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
+ memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
+}
+
+WMFH264Decoder::~WMFH264Decoder()
+{
+}
+
+HRESULT
+WMFH264Decoder::Init(int32_t aCoreCount)
+{
+ HRESULT hr;
+
+ hr = CreateMFT(__uuidof(CMSH264DecoderMFT),
+ WMFDecoderDllNameFor(H264),
+ mDecoder);
+ if (FAILED(hr)) {
+ // Windows 7 Enterprise Server N (which is what Mozilla's mochitests run
+ // on) need a different CLSID to instantiate the H.264 decoder.
+ hr = CreateMFT(CLSID_CMSH264DecMFT,
+ WMFDecoderDllNameFor(H264),
+ mDecoder);
+ }
+ ENSURE(SUCCEEDED(hr), hr);
+
+ CComPtr<IMFAttributes> attr;
+ hr = mDecoder->GetAttributes(&attr);
+ ENSURE(SUCCEEDED(hr), hr);
+ hr = attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads,
+ GetNumThreads(aCoreCount));
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SetDecoderInputType();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SetDecoderOutputType();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::ConfigureVideoFrameGeometry(IMFMediaType* aMediaType)
+{
+ ENSURE(aMediaType != nullptr, E_POINTER);
+ HRESULT hr;
+
+ IntRect pictureRegion;
+ hr = wmf::GetPictureRegion(aMediaType, pictureRegion);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ UINT32 width = 0, height = 0;
+ hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
+
+ UINT32 stride = 0;
+ hr = GetDefaultStride(aMediaType, &stride);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+
+ // Success! Save state.
+ mStride = stride;
+ mVideoWidth = width;
+ mVideoHeight = height;
+ mPictureRegion = pictureRegion;
+
+ LOG("WMFH264Decoder frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, %d, %d)\n",
+ width, height,
+ mStride,
+ mPictureRegion.x, mPictureRegion.y, mPictureRegion.width, mPictureRegion.height);
+
+ return S_OK;
+}
+
+int32_t
+WMFH264Decoder::GetFrameWidth() const
+{
+ return mVideoWidth;
+}
+
+int32_t
+WMFH264Decoder::GetFrameHeight() const
+{
+ return mVideoHeight;
+}
+
+const IntRect&
+WMFH264Decoder::GetPictureRegion() const
+{
+ return mPictureRegion;
+}
+
+int32_t
+WMFH264Decoder::GetStride() const
+{
+ return mStride;
+}
+
+HRESULT
+WMFH264Decoder::SetDecoderInputType()
+{
+ HRESULT hr;
+
+ CComPtr<IMFMediaType> type;
+ hr = MFCreateMediaType(&type);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_MixedInterlaceOrProgressive);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->SetInputType(0, type, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::SetDecoderOutputType()
+{
+ HRESULT hr;
+
+ CComPtr<IMFMediaType> type;
+
+ UINT32 typeIndex = 0;
+ while (type = nullptr, SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, &type))) {
+ GUID subtype;
+ hr = type->GetGUID(MF_MT_SUBTYPE, &subtype);
+ if (FAILED(hr)) {
+ continue;
+ }
+ if (subtype == MFVideoFormat_I420 || subtype == MFVideoFormat_IYUV) {
+ // On Windows 7 Enterprise N the MFT reports it reports IYUV instead
+ // of I420. Other Windows' report I420. The formats are the same, so
+ // support both.
+ hr = mDecoder->SetOutputType(0, type, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = ConfigureVideoFrameGeometry(type);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+ }
+ }
+
+ return E_FAIL;
+}
+
+HRESULT
+WMFH264Decoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData)
+{
+ ENSURE(mDecoder != nullptr, E_POINTER);
+ HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
+ ENSURE(SUCCEEDED(hr), hr);
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::CreateInputSample(const uint8_t* aData,
+ uint32_t aDataSize,
+ Microseconds aTimestamp,
+ Microseconds aDuration,
+ IMFSample** aOutSample)
+{
+ HRESULT hr;
+ CComPtr<IMFSample> sample;
+ hr = MFCreateSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ CComPtr<IMFMediaBuffer> buffer;
+ int32_t bufferSize = std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
+ UINT32 alignment = (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
+ hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ DWORD maxLength = 0;
+ DWORD currentLength = 0;
+ BYTE* dst = nullptr;
+ hr = buffer->Lock(&dst, &maxLength, &currentLength);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ // Copy data into sample's buffer.
+ memcpy(dst, aData, aDataSize);
+
+ hr = buffer->Unlock();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = buffer->SetCurrentLength(aDataSize);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
+ ENSURE(SUCCEEDED(hr), hr);
+
+ sample->SetSampleDuration(UsecsToHNs(aDuration));
+
+ *aOutSample = sample.Detach();
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::CreateOutputSample(IMFSample** aOutSample)
+{
+ HRESULT hr;
+ CComPtr<IMFSample> sample;
+ hr = MFCreateSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ CComPtr<IMFMediaBuffer> buffer;
+ int32_t bufferSize = mOutputStreamInfo.cbSize;
+ UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) ? mOutputStreamInfo.cbAlignment - 1 : 0;
+ hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ *aOutSample = sample.Detach();
+
+ return S_OK;
+}
+
+
+HRESULT
+WMFH264Decoder::GetOutputSample(IMFSample** aOutSample)
+{
+ HRESULT hr;
+ // We allocate samples for MFT output.
+ MFT_OUTPUT_DATA_BUFFER output = {0};
+
+ CComPtr<IMFSample> sample;
+ hr = CreateOutputSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ output.pSample = sample;
+
+ DWORD status = 0;
+ hr = mDecoder->ProcessOutput(0, 1, &output, &status);
+ //LOG(L"WMFH264Decoder::GetOutputSample() ProcessOutput returned 0x%x\n", hr);
+ CComPtr<IMFCollection> events = output.pEvents; // Ensure this is released.
+
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ // Type change. Probably geometric apperature change.
+ hr = SetDecoderOutputType();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return GetOutputSample(aOutSample);
+ } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return MF_E_TRANSFORM_NEED_MORE_INPUT;
+ }
+ // Treat other errors as fatal.
+ ENSURE(SUCCEEDED(hr), hr);
+
+ assert(sample);
+
+ // output.pSample
+ *aOutSample = sample.Detach(); // AddRefs
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Input(const uint8_t* aData,
+ uint32_t aDataSize,
+ Microseconds aTimestamp,
+ Microseconds aDuration)
+{
+ HRESULT hr;
+ CComPtr<IMFSample> input = nullptr;
+ hr = CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input);
+ ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
+
+ hr = mDecoder->ProcessInput(0, input, 0);
+ if (hr == MF_E_NOTACCEPTING) {
+ // MFT *already* has enough data to produce a sample. Retrieve it.
+ LOG("ProcessInput returned MF_E_NOTACCEPTING\n");
+ return MF_E_NOTACCEPTING;
+ }
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Output(IMFSample** aOutput)
+{
+ HRESULT hr;
+ CComPtr<IMFSample> outputSample;
+ hr = GetOutputSample(&outputSample);
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return MF_E_TRANSFORM_NEED_MORE_INPUT;
+ }
+ // Treat other errors as fatal.
+ ENSURE(SUCCEEDED(hr) && outputSample, hr);
+
+ *aOutput = outputSample.Detach();
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Reset()
+{
+ HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Drain()
+{
+ HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+} // namespace wmf
diff --git a/libs/gmp-clearkey/0.1/WMFH264Decoder.h b/libs/gmp-clearkey/0.1/WMFH264Decoder.h
new file mode 100644
index 000000000..91b7e046f
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/WMFH264Decoder.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013, 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.
+ */
+
+#if !defined(WMFH264Decoder_h_)
+#define WMFH264Decoder_h_
+
+#include "WMFUtils.h"
+
+namespace wmf {
+
+class WMFH264Decoder {
+public:
+ WMFH264Decoder();
+ ~WMFH264Decoder();
+
+ HRESULT Init(int32_t aCoreCount);
+
+ HRESULT Input(const uint8_t* aData,
+ uint32_t aDataSize,
+ Microseconds aTimestamp,
+ Microseconds aDuration);
+
+ HRESULT Output(IMFSample** aOutput);
+
+ HRESULT Reset();
+
+ int32_t GetFrameWidth() const;
+ int32_t GetFrameHeight() const;
+ const IntRect& GetPictureRegion() const;
+ int32_t GetStride() const;
+
+ HRESULT Drain();
+
+private:
+
+ HRESULT SetDecoderInputType();
+ HRESULT SetDecoderOutputType();
+ HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData);
+
+ HRESULT CreateInputSample(const uint8_t* aData,
+ uint32_t aDataSize,
+ Microseconds aTimestamp,
+ Microseconds aDuration,
+ IMFSample** aOutSample);
+
+ HRESULT CreateOutputSample(IMFSample** aOutSample);
+
+ HRESULT GetOutputSample(IMFSample** aOutSample);
+ HRESULT ConfigureVideoFrameGeometry(IMFMediaType* aMediaType);
+
+ MFT_INPUT_STREAM_INFO mInputStreamInfo;
+ MFT_OUTPUT_STREAM_INFO mOutputStreamInfo;
+
+ CComPtr<IMFTransform> mDecoder;
+
+ int32_t mVideoWidth;
+ int32_t mVideoHeight;
+ IntRect mPictureRegion;
+ int32_t mStride;
+
+};
+
+} // namespace wmf
+
+#endif
diff --git a/libs/gmp-clearkey/0.1/WMFSymbols.h b/libs/gmp-clearkey/0.1/WMFSymbols.h
new file mode 100644
index 000000000..60a0a6854
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/WMFSymbols.h
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+MFPLAT_FUNC(MFCreateSample, "mfplat.dll");
+MFPLAT_FUNC(MFCreateAlignedMemoryBuffer, "mfplat.dll");
+MFPLAT_FUNC(MFGetStrideForBitmapInfoHeader, "evr.dll");
+MFPLAT_FUNC(MFCreateMediaType, "mfplat.dll");
diff --git a/libs/gmp-clearkey/0.1/WMFUtils.cpp b/libs/gmp-clearkey/0.1/WMFUtils.cpp
new file mode 100644
index 000000000..cb3cfe9fc
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/WMFUtils.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2013, 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 "WMFUtils.h"
+#include "ClearKeyUtils.h"
+#include <versionhelpers.h>
+
+#include <algorithm>
+#include <stdio.h>
+
+#define INITGUID
+#include <guiddef.h>
+
+#pragma comment(lib, "mfuuid.lib")
+#pragma comment(lib, "wmcodecdspuuid")
+
+void LOG(const char* format, ...)
+{
+#ifdef WMF_DECODER_LOG
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+#endif
+}
+
+#ifdef WMF_MUST_DEFINE_AAC_MFT_CLSID
+// Some SDK versions don't define the AAC decoder CLSID.
+// {32D186A7-218F-4C75-8876-DD77273A8999}
+DEFINE_GUID(CLSID_CMSAACDecMFT, 0x32D186A7, 0x218F, 0x4C75, 0x88, 0x76, 0xDD, 0x77, 0x27, 0x3A, 0x89, 0x99);
+#endif
+
+DEFINE_GUID(CLSID_CMSH264DecMFT, 0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45, 0x28, 0x31, 0xA8, 0x7D, 0x9D);
+
+namespace wmf {
+
+
+#define MFPLAT_FUNC(_func, _dllname) \
+ decltype(::_func)* _func;
+#include "WMFSymbols.h"
+#undef MFPLAT_FUNC
+
+static bool
+LinkMfplat()
+{
+ static bool sInitDone = false;
+ static bool sInitOk = false;
+ if (!sInitDone) {
+ sInitDone = true;
+ HMODULE handle;
+
+#define MFPLAT_FUNC(_func, _dllname) \
+ handle = GetModuleHandleA(_dllname); \
+ if (!(_func = (decltype(_func))(GetProcAddress(handle, #_func)))) { \
+ return false; \
+ }
+
+#include "WMFSymbols.h"
+#undef MFPLAT_FUNC
+ sInitOk = true;
+ }
+ return sInitOk;
+}
+
+const char*
+WMFDecoderDllNameFor(CodecType aCodec)
+{
+ if (aCodec == H264) {
+ // For H.264 decoding, we need msmpeg2vdec.dll on Win 7 & 8,
+ // and mfh264dec.dll on Vista.
+ if (IsWindows7OrGreater()) {
+ return "msmpeg2vdec.dll";
+ } else {
+ return "mfh264dec.dll";
+ }
+ } else if (aCodec == AAC) {
+ // For AAC decoding, we need to use msauddecmft.dll on Win8,
+ // msmpeg2adec.dll on Win7, and msheaacdec.dll on Vista.
+ if (IsWindows8OrGreater()) {
+ return "msauddecmft.dll";
+ } else if (IsWindows7OrGreater()) {
+ return "msmpeg2adec.dll";
+ } else {
+ return "mfheaacdec.dll";
+ }
+ } else {
+ return "";
+ }
+}
+
+
+bool
+EnsureLibs()
+{
+ static bool sInitDone = false;
+ static bool sInitOk = false;
+ if (!sInitDone) {
+ sInitOk = LinkMfplat() &&
+ !!GetModuleHandleA(WMFDecoderDllNameFor(AAC)) &&
+ !!GetModuleHandleA(WMFDecoderDllNameFor(H264));
+ sInitDone = true;
+ }
+ return sInitOk;
+}
+
+int32_t
+MFOffsetToInt32(const MFOffset& aOffset)
+{
+ return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
+}
+
+// Gets the sub-region of the video frame that should be displayed.
+// See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion)
+{
+ // Determine if "pan and scan" is enabled for this media. If it is, we
+ // only display a region of the video frame, not the entire frame.
+ BOOL panScan = MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE);
+
+ // If pan and scan mode is enabled. Try to get the display region.
+ HRESULT hr = E_FAIL;
+ MFVideoArea videoArea;
+ memset(&videoArea, 0, sizeof(MFVideoArea));
+ if (panScan) {
+ hr = aMediaType->GetBlob(MF_MT_PAN_SCAN_APERTURE,
+ (UINT8*)&videoArea,
+ sizeof(MFVideoArea),
+ NULL);
+ }
+
+ // If we're not in pan-and-scan mode, or the pan-and-scan region is not set,
+ // check for a minimimum display aperture.
+ if (!panScan || hr == MF_E_ATTRIBUTENOTFOUND) {
+ hr = aMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
+ (UINT8*)&videoArea,
+ sizeof(MFVideoArea),
+ NULL);
+ }
+
+ if (hr == MF_E_ATTRIBUTENOTFOUND) {
+ // Minimum display aperture is not set, for "backward compatibility with
+ // some components", check for a geometric aperture.
+ hr = aMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE,
+ (UINT8*)&videoArea,
+ sizeof(MFVideoArea),
+ NULL);
+ }
+
+ if (SUCCEEDED(hr)) {
+ // The media specified a picture region, return it.
+ IntRect picture = IntRect(MFOffsetToInt32(videoArea.OffsetX),
+ MFOffsetToInt32(videoArea.OffsetY),
+ videoArea.Area.cx,
+ videoArea.Area.cy);
+ ENSURE(picture.width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(picture.height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
+ aOutPictureRegion = picture;
+ return S_OK;
+ }
+
+ // No picture region defined, fall back to using the entire video area.
+ UINT32 width = 0, height = 0;
+ hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
+ aOutPictureRegion = IntRect(0, 0, width, height);
+ return S_OK;
+}
+
+
+HRESULT
+GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride)
+{
+ // Try to get the default stride from the media type.
+ UINT32 stride = 0;
+ HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, &stride);
+ if (SUCCEEDED(hr)) {
+ ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ *aOutStride = stride;
+ return S_OK;
+ }
+
+ // Stride attribute not set, calculate it.
+ GUID subtype = GUID_NULL;
+ uint32_t width = 0;
+ uint32_t height = 0;
+
+ hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
+
+ LONG lstride = 0;
+ hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lstride);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(lstride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(lstride >= 0, E_FAIL);
+ *aOutStride = lstride;
+
+ return hr;
+}
+
+void dump(const uint8_t* data, uint32_t len, const char* filename)
+{
+ FILE* f = 0;
+ fopen_s(&f, filename, "wb");
+ fwrite(data, len, 1, f);
+ fclose(f);
+}
+
+HRESULT
+CreateMFT(const CLSID& clsid,
+ const char* aDllName,
+ CComPtr<IMFTransform>& aOutMFT)
+{
+ HMODULE module = ::GetModuleHandleA(aDllName);
+ if (!module) {
+ LOG("Failed to get %S\n", aDllName);
+ return E_FAIL;
+ }
+
+ typedef HRESULT (WINAPI* DllGetClassObjectFnPtr)(const CLSID& clsid,
+ const IID& iid,
+ void** object);
+
+ DllGetClassObjectFnPtr GetClassObjPtr =
+ reinterpret_cast<DllGetClassObjectFnPtr>(GetProcAddress(module, "DllGetClassObject"));
+ if (!GetClassObjPtr) {
+ LOG("Failed to get DllGetClassObject\n");
+ return E_FAIL;
+ }
+
+ CComPtr<IClassFactory> classFactory;
+ HRESULT hr = GetClassObjPtr(clsid,
+ __uuidof(IClassFactory),
+ reinterpret_cast<void**>(static_cast<IClassFactory**>(&classFactory)));
+ if (FAILED(hr)) {
+ LOG("Failed to get H264 IClassFactory\n");
+ return E_FAIL;
+ }
+
+ hr = classFactory->CreateInstance(NULL,
+ __uuidof(IMFTransform),
+ reinterpret_cast<void**>(static_cast<IMFTransform**>(&aOutMFT)));
+ if (FAILED(hr)) {
+ LOG("Failed to get create MFT\n");
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+int32_t
+GetNumThreads(int32_t aCoreCount)
+{
+ return aCoreCount > 4 ? -1 : (std::max)(aCoreCount - 1, 1);
+}
+
+} // namespace
diff --git a/libs/gmp-clearkey/0.1/WMFUtils.h b/libs/gmp-clearkey/0.1/WMFUtils.h
new file mode 100644
index 000000000..51d46d70d
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/WMFUtils.h
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2013, 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 __WMFUtils_h__
+#define __WMFUtils_h__
+
+#include <cstdint>
+#include <string>
+
+#include <assert.h>
+#include <mfapi.h>
+#include <mferror.h>
+#include <mfobjects.h>
+#include <mftransform.h>
+#include <wmcodecdsp.h>
+#include "VideoLimits.h"
+
+#include "gmp-platform.h"
+
+void LOG(const char* format, ...);
+
+#ifdef LOG_SAMPLE_DECODE
+#define SAMPLE_LOG LOG
+#else
+#define SAMPLE_LOG(...)
+#endif
+
+#ifndef CLSID_CMSAACDecMFT
+#define WMF_MUST_DEFINE_AAC_MFT_CLSID
+extern "C" const CLSID CLSID_CMSAACDecMFT;
+#endif
+
+extern "C" const CLSID CLSID_CMSH264DecMFT;
+
+namespace wmf {
+
+// Reimplementation of CComPtr to reduce dependence on system
+// shared libraries.
+template<class T>
+class CComPtr {
+public:
+ CComPtr(CComPtr&& aOther) : mPtr(aOther.Detach()) { }
+ CComPtr& operator=(CComPtr&& other) { mPtr = other.Detach(); }
+
+ CComPtr(const CComPtr& aOther) : mPtr(nullptr) { Set(aOther.Get()); }
+ CComPtr() : mPtr(nullptr) { }
+ CComPtr(T* const & aPtr) : mPtr(nullptr) { Set(aPtr); }
+ CComPtr(const std::nullptr_t& aNullPtr) : mPtr(aNullPtr) { }
+ T** operator&() { return &mPtr; }
+ T* operator->(){ return mPtr; }
+ operator T*() { return mPtr; }
+ T* operator=(T* const & aPtr) { return Set(aPtr); }
+ T* operator=(const std::nullptr_t& aPtr) { return mPtr = aPtr; }
+
+ T* Get() const { return mPtr; }
+
+ T* Detach() {
+ T* tmp = mPtr;
+ mPtr = nullptr;
+ return tmp;
+ }
+
+ ~CComPtr() {
+ if (mPtr) {
+ mPtr->Release();
+ }
+ mPtr = nullptr;
+ }
+
+private:
+
+ T* Set(T* aPtr) {
+ if (mPtr == aPtr) {
+ return aPtr;
+ }
+ if (mPtr) {
+ mPtr->Release();
+ }
+ mPtr = aPtr;
+ if (mPtr) {
+ mPtr->AddRef();
+ }
+ return mPtr;
+ }
+
+ T* mPtr;
+};
+
+class IntRect {
+public:
+ IntRect(int32_t _x, int32_t _y, int32_t _w, int32_t _h)
+ : x(_x), y(_y), width(_w), height(_h) {}
+ IntRect()
+ : x(0), y(0), width(0), height(0) {}
+ int32_t x;
+ int32_t y;
+ int32_t width;
+ int32_t height;
+};
+
+typedef int64_t Microseconds;
+
+#ifdef ENSURE
+#undef ENSURE
+#endif
+
+#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 MFPLAT_FUNC(_func, _dllname) \
+ extern decltype(::_func)* _func;
+#include "WMFSymbols.h"
+#undef MFPLAT_FUNC
+
+bool
+EnsureLibs();
+
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion);
+
+HRESULT
+GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride);
+
+// Converts from microseconds to hundreds of nanoseconds.
+// We use microseconds for our timestamps, whereas WMF uses
+// hundreds of nanoseconds.
+inline int64_t
+UsecsToHNs(int64_t aUsecs) {
+ return aUsecs * 10;
+}
+
+// Converts from hundreds of nanoseconds to microseconds.
+// We use microseconds for our timestamps, whereas WMF uses
+// hundreds of nanoseconds.
+inline int64_t
+HNsToUsecs(int64_t hNanoSecs) {
+ return hNanoSecs / 10;
+}
+
+inline std::string narrow(std::wstring &wide) {
+ std::string ns(wide.begin(), wide.end());
+ return ns;
+}
+
+inline std::wstring widen(std::string &narrow) {
+ std::wstring ws(narrow.begin(), narrow.end());
+ return ws;
+}
+
+#define ARRAY_LENGTH(array_) \
+ (sizeof(array_)/sizeof(array_[0]))
+
+template<class Type>
+class AutoPtr {
+public:
+ AutoPtr()
+ : mPtr(nullptr)
+ {
+ }
+
+ AutoPtr(AutoPtr<Type>& aPtr)
+ : mPtr(aPtr.Forget())
+ {
+ }
+
+ AutoPtr(Type* aPtr)
+ : mPtr(aPtr)
+ {
+ }
+
+ ~AutoPtr() {
+ if (mPtr) {
+ delete mPtr;
+ }
+ }
+
+ Type* Forget() {
+ Type* rv = mPtr;
+ mPtr = nullptr;
+ return rv;
+ }
+
+ AutoPtr<Type>& operator=(Type* aOther) {
+ Assign(aOther);
+ return *this;
+ }
+
+ AutoPtr<Type>& operator=(AutoPtr<Type>& aOther) {
+ Assign(aOther.Forget());
+ return *this;
+ }
+
+ Type* Get() const {
+ return mPtr;
+ }
+
+ Type* operator->() const {
+ assert(mPtr);
+ return Get();
+ }
+
+ operator Type*() const {
+ return Get();
+ }
+
+ Type** Receive() {
+ return &mPtr;
+ }
+private:
+
+ void Assign(Type* aPtr) {
+ if (mPtr) {
+ delete mPtr;
+ }
+ mPtr = aPtr;
+ }
+
+ Type* mPtr;
+};
+
+// Video frame microseconds are (currently) in 90kHz units, as used by RTP.
+// Use this to convert to microseconds...
+inline Microseconds RTPTimeToMicroseconds(int64_t rtptime) {
+ return (rtptime * 1000000) / 90000;
+}
+
+inline uint32_t MicrosecondsToRTPTime(Microseconds us) {
+ return uint32_t(0xffffffff & (us * 90000) / 1000000);
+}
+
+void dump(const uint8_t* data, uint32_t len, const char* filename);
+
+HRESULT
+CreateMFT(const CLSID& clsid,
+ const char* aDllName,
+ CComPtr<IMFTransform>& aOutMFT);
+
+enum CodecType {
+ H264,
+ AAC,
+};
+
+// Returns the name of the DLL that is needed to decode H.264 or AAC on
+// the given windows version we're running on.
+const char* WMFDecoderDllNameFor(CodecType aCodec);
+
+// Returns the maximum number of threads we want WMF to use for decoding
+// given the number of logical processors available.
+int32_t GetNumThreads(int32_t aCoreCount);
+
+} // namespace wmf
+
+#endif // __WMFUtils_h__
diff --git a/libs/gmp-clearkey/0.1/clearkey.info.in b/libs/gmp-clearkey/0.1/clearkey.info.in
new file mode 100644
index 000000000..8b3445d59
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/clearkey.info.in
@@ -0,0 +1,10 @@
+Name: clearkey
+Description: ClearKey Gecko Media Plugin
+Version: 1
+#ifdef ENABLE_WMF
+APIs: eme-decrypt-v9[org.w3.clearkey], decode-audio[aac: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
diff --git a/libs/gmp-clearkey/0.1/gmp-clearkey.cpp b/libs/gmp-clearkey/0.1/gmp-clearkey.cpp
new file mode 100644
index 000000000..fae7ce9f8
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/gmp-clearkey.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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 <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ClearKeyAsyncShutdown.h"
+#include "ClearKeySessionManager.h"
+#include "gmp-api/gmp-async-shutdown.h"
+#include "gmp-api/gmp-decryption.h"
+#include "gmp-api/gmp-platform.h"
+
+#if defined(ENABLE_WMF)
+#include "WMFUtils.h"
+#include "AudioDecoder.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;
+}
+
+extern "C" {
+
+GMP_EXPORT GMPErr
+GMPInit(GMPPlatformAPI* aPlatformAPI)
+{
+ sPlatform = aPlatformAPI;
+ return GMPNoErr;
+}
+
+GMP_EXPORT GMPErr
+GMPGetAPI(const char* aApiName, void* aHostAPI, void** aPluginAPI)
+{
+ 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_AUDIO_DECODER) &&
+ wmf::EnsureLibs()) {
+ *aPluginAPI = new AudioDecoder(static_cast<GMPAudioHost*>(aHostAPI));
+ } else if (!strcmp(aApiName, GMP_API_VIDEO_DECODER) &&
+ wmf::EnsureLibs()) {
+ *aPluginAPI = new VideoDecoder(static_cast<GMPVideoHost*>(aHostAPI));
+ }
+#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;
+}
+
+GMP_EXPORT GMPErr
+GMPShutdown(void)
+{
+ CK_LOGD("ClearKey GMPShutdown");
+ return GMPNoErr;
+}
+
+}
diff --git a/libs/gmp-clearkey/0.1/gmp-task-utils-generated.h b/libs/gmp-clearkey/0.1/gmp-task-utils-generated.h
new file mode 100644
index 000000000..597ed4721
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/gmp-task-utils-generated.h
@@ -0,0 +1,1938 @@
+/*
+ * 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 "RefCounted.h"
+
+// 0 arguments --
+template<typename M> class gmp_task_args_nm_0 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_0(M m) :
+ m_(m) {}
+
+ void Run() {
+ m_();
+ }
+
+ private:
+ M m_;
+};
+
+
+
+// 0 arguments --
+template<typename M, typename R> class gmp_task_args_nm_0_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_0_ret(M m, R *r) :
+ m_(m), r_(r) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_();
+ }
+
+ private:
+ M m_;
+ R* r_;
+};
+
+
+
+// 0 arguments --
+template<typename C, typename M> class gmp_task_args_m_0 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_0(C o, M m) :
+ o_(o), m_(m) {}
+
+ void Run() {
+ ((*o_).*m_)();
+ }
+
+ private:
+ C o_;
+ M m_;
+};
+
+
+
+// 0 arguments --
+template<typename C, typename M, typename R> class gmp_task_args_m_0_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_0_ret(C o, M m, R *r) :
+ o_(o), m_(m), r_(r) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)();
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+};
+
+
+
+// 1 arguments --
+template<typename M, typename A0> class gmp_task_args_nm_1 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_1(M m, A0 a0) :
+ m_(m), a0_(a0) {}
+
+ void Run() {
+ m_(a0_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+};
+
+
+
+// 1 arguments --
+template<typename M, typename A0, typename R> class gmp_task_args_nm_1_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_1_ret(M m, A0 a0, R *r) :
+ m_(m), r_(r), a0_(a0) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+};
+
+
+
+// 1 arguments --
+template<typename C, typename M, typename A0> class gmp_task_args_m_1 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_1(C o, M m, A0 a0) :
+ o_(o), m_(m), a0_(a0) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+};
+
+
+
+// 1 arguments --
+template<typename C, typename M, typename A0, typename R> class gmp_task_args_m_1_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_1_ret(C o, M m, A0 a0, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+};
+
+
+
+// 2 arguments --
+template<typename M, typename A0, typename A1> class gmp_task_args_nm_2 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_2(M m, A0 a0, A1 a1) :
+ m_(m), a0_(a0), a1_(a1) {}
+
+ void Run() {
+ m_(a0_, a1_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+};
+
+
+
+// 2 arguments --
+template<typename M, typename A0, typename A1, typename R> class gmp_task_args_nm_2_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_2_ret(M m, A0 a0, A1 a1, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+};
+
+
+
+// 2 arguments --
+template<typename C, typename M, typename A0, typename A1> class gmp_task_args_m_2 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_2(C o, M m, A0 a0, A1 a1) :
+ o_(o), m_(m), a0_(a0), a1_(a1) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+};
+
+
+
+// 2 arguments --
+template<typename C, typename M, typename A0, typename A1, typename R> class gmp_task_args_m_2_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_2_ret(C o, M m, A0 a0, A1 a1, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+};
+
+
+
+// 3 arguments --
+template<typename M, typename A0, typename A1, typename A2> class gmp_task_args_nm_3 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_3(M m, A0 a0, A1 a1, A2 a2) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+};
+
+
+
+// 3 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename R> class gmp_task_args_nm_3_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_3_ret(M m, A0 a0, A1 a1, A2 a2, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+};
+
+
+
+// 3 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2> class gmp_task_args_m_3 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_3(C o, M m, A0 a0, A1 a1, A2 a2) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+};
+
+
+
+// 3 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename R> class gmp_task_args_m_3_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_3_ret(C o, M m, A0 a0, A1 a1, A2 a2, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+};
+
+
+
+// 4 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3> class gmp_task_args_nm_4 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_4(M m, A0 a0, A1 a1, A2 a2, A3 a3) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+};
+
+
+
+// 4 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename R> class gmp_task_args_nm_4_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_4_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+};
+
+
+
+// 4 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3> class gmp_task_args_m_4 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_4(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+};
+
+
+
+// 4 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename R> class gmp_task_args_m_4_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_4_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+};
+
+
+
+// 5 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4> class gmp_task_args_nm_5 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_5(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+};
+
+
+
+// 5 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename R> class gmp_task_args_nm_5_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_5_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+};
+
+
+
+// 5 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4> class gmp_task_args_m_5 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_5(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+};
+
+
+
+// 5 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename R> class gmp_task_args_m_5_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_5_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+};
+
+
+
+// 6 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5> class gmp_task_args_nm_6 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_6(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+};
+
+
+
+// 6 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename R> class gmp_task_args_nm_6_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_6_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+};
+
+
+
+// 6 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5> class gmp_task_args_m_6 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_6(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+};
+
+
+
+// 6 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename R> class gmp_task_args_m_6_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_6_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+};
+
+
+
+// 7 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6> class gmp_task_args_nm_7 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_7(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+};
+
+
+
+// 7 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename R> class gmp_task_args_nm_7_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_7_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+};
+
+
+
+// 7 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6> class gmp_task_args_m_7 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_7(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+};
+
+
+
+// 7 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename R> class gmp_task_args_m_7_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_7_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+};
+
+
+
+// 8 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7> class gmp_task_args_nm_8 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_8(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+};
+
+
+
+// 8 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename R> class gmp_task_args_nm_8_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_8_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+};
+
+
+
+// 8 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7> class gmp_task_args_m_8 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_8(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+};
+
+
+
+// 8 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename R> class gmp_task_args_m_8_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_8_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+};
+
+
+
+// 9 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8> class gmp_task_args_nm_9 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_9(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+};
+
+
+
+// 9 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename R> class gmp_task_args_nm_9_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_9_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+};
+
+
+
+// 9 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8> class gmp_task_args_m_9 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_9(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+};
+
+
+
+// 9 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename R> class gmp_task_args_m_9_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_9_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+};
+
+
+
+// 10 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9> class gmp_task_args_nm_10 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_10(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+};
+
+
+
+// 10 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename R> class gmp_task_args_nm_10_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_10_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+};
+
+
+
+// 10 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9> class gmp_task_args_m_10 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_10(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+};
+
+
+
+// 10 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename R> class gmp_task_args_m_10_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_10_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+};
+
+
+
+// 11 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10> class gmp_task_args_nm_11 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_11(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+};
+
+
+
+// 11 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename R> class gmp_task_args_nm_11_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_11_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+};
+
+
+
+// 11 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10> class gmp_task_args_m_11 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_11(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+};
+
+
+
+// 11 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename R> class gmp_task_args_m_11_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_11_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+};
+
+
+
+// 12 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11> class gmp_task_args_nm_12 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_12(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+};
+
+
+
+// 12 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename R> class gmp_task_args_nm_12_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_12_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+};
+
+
+
+// 12 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11> class gmp_task_args_m_12 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_12(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+};
+
+
+
+// 12 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename R> class gmp_task_args_m_12_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_12_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+};
+
+
+
+// 13 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12> class gmp_task_args_nm_13 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_13(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+ A12 a12_;
+};
+
+
+
+// 13 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename R> class gmp_task_args_nm_13_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_13_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+ A12 a12_;
+};
+
+
+
+// 13 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12> class gmp_task_args_m_13 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_13(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+ A12 a12_;
+};
+
+
+
+// 13 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename R> class gmp_task_args_m_13_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_13_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+ A12 a12_;
+};
+
+
+
+// 14 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13> class gmp_task_args_nm_14 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_14(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13) :
+ m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12), a13_(a13) {}
+
+ void Run() {
+ m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_, a13_);
+ }
+
+ private:
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+ A12 a12_;
+ A13 a13_;
+};
+
+
+
+// 14 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13, typename R> class gmp_task_args_nm_14_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_nm_14_ret(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R *r) :
+ m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12), a13_(a13) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = m_(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_, a13_);
+ }
+
+ private:
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+ A12 a12_;
+ A13 a13_;
+};
+
+
+
+// 14 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13> class gmp_task_args_m_14 : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_14(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13) :
+ o_(o), m_(m), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12), a13_(a13) {}
+
+ void Run() {
+ ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_, a13_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+ A12 a12_;
+ A13 a13_;
+};
+
+
+
+// 14 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13, typename R> class gmp_task_args_m_14_ret : public gmp_task_args_base {
+ public:
+ explicit gmp_task_args_m_14_ret(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R *r) :
+ o_(o), m_(m), r_(r), a0_(a0), a1_(a1), a2_(a2), a3_(a3), a4_(a4), a5_(a5), a6_(a6), a7_(a7), a8_(a8), a9_(a9), a10_(a10), a11_(a11), a12_(a12), a13_(a13) {}
+ virtual bool returns_value() const { return true; }
+
+ void Run() {
+ *r_ = ((*o_).*m_)(a0_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_, a11_, a12_, a13_);
+ }
+
+ private:
+ C o_;
+ M m_;
+ R* r_;
+ A0 a0_;
+ A1 a1_;
+ A2 a2_;
+ A3 a3_;
+ A4 a4_;
+ A5 a5_;
+ A6 a6_;
+ A7 a7_;
+ A8 a8_;
+ A9 a9_;
+ A10 a10_;
+ A11 a11_;
+ A12 a12_;
+ A13 a13_;
+};
+
+
+
+
+
+
+// 0 arguments --
+template<typename M>
+gmp_task_args_nm_0<M>* WrapTaskNM(M m) {
+ return new gmp_task_args_nm_0<M>
+ (m);
+}
+
+// 0 arguments --
+template<typename M, typename R>
+gmp_task_args_nm_0_ret<M, R>* WrapTaskNMRet(M m, R* r) {
+ return new gmp_task_args_nm_0_ret<M, R>
+ (m, r);
+}
+
+// 0 arguments --
+template<typename C, typename M>
+gmp_task_args_m_0<C, M>* WrapTask(C o, M m) {
+ return new gmp_task_args_m_0<C, M>
+ (o, m);
+}
+
+// 0 arguments --
+template<typename C, typename M, typename R>
+gmp_task_args_m_0_ret<C, M, R>* WrapTaskRet(C o, M m, R* r) {
+ return new gmp_task_args_m_0_ret<C, M, R>
+ (o, m, r);
+}
+
+// 1 arguments --
+template<typename M, typename A0>
+gmp_task_args_nm_1<M, A0>* WrapTaskNM(M m, A0 a0) {
+ return new gmp_task_args_nm_1<M, A0>
+ (m, a0);
+}
+
+// 1 arguments --
+template<typename M, typename A0, typename R>
+gmp_task_args_nm_1_ret<M, A0, R>* WrapTaskNMRet(M m, A0 a0, R* r) {
+ return new gmp_task_args_nm_1_ret<M, A0, R>
+ (m, a0, r);
+}
+
+// 1 arguments --
+template<typename C, typename M, typename A0>
+gmp_task_args_m_1<C, M, A0>* WrapTask(C o, M m, A0 a0) {
+ return new gmp_task_args_m_1<C, M, A0>
+ (o, m, a0);
+}
+
+// 1 arguments --
+template<typename C, typename M, typename A0, typename R>
+gmp_task_args_m_1_ret<C, M, A0, R>* WrapTaskRet(C o, M m, A0 a0, R* r) {
+ return new gmp_task_args_m_1_ret<C, M, A0, R>
+ (o, m, a0, r);
+}
+
+// 2 arguments --
+template<typename M, typename A0, typename A1>
+gmp_task_args_nm_2<M, A0, A1>* WrapTaskNM(M m, A0 a0, A1 a1) {
+ return new gmp_task_args_nm_2<M, A0, A1>
+ (m, a0, a1);
+}
+
+// 2 arguments --
+template<typename M, typename A0, typename A1, typename R>
+gmp_task_args_nm_2_ret<M, A0, A1, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, R* r) {
+ return new gmp_task_args_nm_2_ret<M, A0, A1, R>
+ (m, a0, a1, r);
+}
+
+// 2 arguments --
+template<typename C, typename M, typename A0, typename A1>
+gmp_task_args_m_2<C, M, A0, A1>* WrapTask(C o, M m, A0 a0, A1 a1) {
+ return new gmp_task_args_m_2<C, M, A0, A1>
+ (o, m, a0, a1);
+}
+
+// 2 arguments --
+template<typename C, typename M, typename A0, typename A1, typename R>
+gmp_task_args_m_2_ret<C, M, A0, A1, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, R* r) {
+ return new gmp_task_args_m_2_ret<C, M, A0, A1, R>
+ (o, m, a0, a1, r);
+}
+
+// 3 arguments --
+template<typename M, typename A0, typename A1, typename A2>
+gmp_task_args_nm_3<M, A0, A1, A2>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2) {
+ return new gmp_task_args_nm_3<M, A0, A1, A2>
+ (m, a0, a1, a2);
+}
+
+// 3 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename R>
+gmp_task_args_nm_3_ret<M, A0, A1, A2, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, R* r) {
+ return new gmp_task_args_nm_3_ret<M, A0, A1, A2, R>
+ (m, a0, a1, a2, r);
+}
+
+// 3 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2>
+gmp_task_args_m_3<C, M, A0, A1, A2>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2) {
+ return new gmp_task_args_m_3<C, M, A0, A1, A2>
+ (o, m, a0, a1, a2);
+}
+
+// 3 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename R>
+gmp_task_args_m_3_ret<C, M, A0, A1, A2, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, R* r) {
+ return new gmp_task_args_m_3_ret<C, M, A0, A1, A2, R>
+ (o, m, a0, a1, a2, r);
+}
+
+// 4 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3>
+gmp_task_args_nm_4<M, A0, A1, A2, A3>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3) {
+ return new gmp_task_args_nm_4<M, A0, A1, A2, A3>
+ (m, a0, a1, a2, a3);
+}
+
+// 4 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename R>
+gmp_task_args_nm_4_ret<M, A0, A1, A2, A3, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, R* r) {
+ return new gmp_task_args_nm_4_ret<M, A0, A1, A2, A3, R>
+ (m, a0, a1, a2, a3, r);
+}
+
+// 4 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3>
+gmp_task_args_m_4<C, M, A0, A1, A2, A3>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3) {
+ return new gmp_task_args_m_4<C, M, A0, A1, A2, A3>
+ (o, m, a0, a1, a2, a3);
+}
+
+// 4 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename R>
+gmp_task_args_m_4_ret<C, M, A0, A1, A2, A3, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, R* r) {
+ return new gmp_task_args_m_4_ret<C, M, A0, A1, A2, A3, R>
+ (o, m, a0, a1, a2, a3, r);
+}
+
+// 5 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4>
+gmp_task_args_nm_5<M, A0, A1, A2, A3, A4>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4) {
+ return new gmp_task_args_nm_5<M, A0, A1, A2, A3, A4>
+ (m, a0, a1, a2, a3, a4);
+}
+
+// 5 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename R>
+gmp_task_args_nm_5_ret<M, A0, A1, A2, A3, A4, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, R* r) {
+ return new gmp_task_args_nm_5_ret<M, A0, A1, A2, A3, A4, R>
+ (m, a0, a1, a2, a3, a4, r);
+}
+
+// 5 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4>
+gmp_task_args_m_5<C, M, A0, A1, A2, A3, A4>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4) {
+ return new gmp_task_args_m_5<C, M, A0, A1, A2, A3, A4>
+ (o, m, a0, a1, a2, a3, a4);
+}
+
+// 5 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename R>
+gmp_task_args_m_5_ret<C, M, A0, A1, A2, A3, A4, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, R* r) {
+ return new gmp_task_args_m_5_ret<C, M, A0, A1, A2, A3, A4, R>
+ (o, m, a0, a1, a2, a3, a4, r);
+}
+
+// 6 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5>
+gmp_task_args_nm_6<M, A0, A1, A2, A3, A4, A5>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) {
+ return new gmp_task_args_nm_6<M, A0, A1, A2, A3, A4, A5>
+ (m, a0, a1, a2, a3, a4, a5);
+}
+
+// 6 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename R>
+gmp_task_args_nm_6_ret<M, A0, A1, A2, A3, A4, A5, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, R* r) {
+ return new gmp_task_args_nm_6_ret<M, A0, A1, A2, A3, A4, A5, R>
+ (m, a0, a1, a2, a3, a4, a5, r);
+}
+
+// 6 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5>
+gmp_task_args_m_6<C, M, A0, A1, A2, A3, A4, A5>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) {
+ return new gmp_task_args_m_6<C, M, A0, A1, A2, A3, A4, A5>
+ (o, m, a0, a1, a2, a3, a4, a5);
+}
+
+// 6 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename R>
+gmp_task_args_m_6_ret<C, M, A0, A1, A2, A3, A4, A5, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, R* r) {
+ return new gmp_task_args_m_6_ret<C, M, A0, A1, A2, A3, A4, A5, R>
+ (o, m, a0, a1, a2, a3, a4, a5, r);
+}
+
+// 7 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6>
+gmp_task_args_nm_7<M, A0, A1, A2, A3, A4, A5, A6>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) {
+ return new gmp_task_args_nm_7<M, A0, A1, A2, A3, A4, A5, A6>
+ (m, a0, a1, a2, a3, a4, a5, a6);
+}
+
+// 7 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename R>
+gmp_task_args_nm_7_ret<M, A0, A1, A2, A3, A4, A5, A6, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, R* r) {
+ return new gmp_task_args_nm_7_ret<M, A0, A1, A2, A3, A4, A5, A6, R>
+ (m, a0, a1, a2, a3, a4, a5, a6, r);
+}
+
+// 7 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6>
+gmp_task_args_m_7<C, M, A0, A1, A2, A3, A4, A5, A6>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) {
+ return new gmp_task_args_m_7<C, M, A0, A1, A2, A3, A4, A5, A6>
+ (o, m, a0, a1, a2, a3, a4, a5, a6);
+}
+
+// 7 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename R>
+gmp_task_args_m_7_ret<C, M, A0, A1, A2, A3, A4, A5, A6, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, R* r) {
+ return new gmp_task_args_m_7_ret<C, M, A0, A1, A2, A3, A4, A5, A6, R>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, r);
+}
+
+// 8 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7>
+gmp_task_args_nm_8<M, A0, A1, A2, A3, A4, A5, A6, A7>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) {
+ return new gmp_task_args_nm_8<M, A0, A1, A2, A3, A4, A5, A6, A7>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7);
+}
+
+// 8 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename R>
+gmp_task_args_nm_8_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, R* r) {
+ return new gmp_task_args_nm_8_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, R>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, r);
+}
+
+// 8 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7>
+gmp_task_args_m_8<C, M, A0, A1, A2, A3, A4, A5, A6, A7>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) {
+ return new gmp_task_args_m_8<C, M, A0, A1, A2, A3, A4, A5, A6, A7>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7);
+}
+
+// 8 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename R>
+gmp_task_args_m_8_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, R* r) {
+ return new gmp_task_args_m_8_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, R>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, r);
+}
+
+// 9 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8>
+gmp_task_args_nm_9<M, A0, A1, A2, A3, A4, A5, A6, A7, A8>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) {
+ return new gmp_task_args_nm_9<M, A0, A1, A2, A3, A4, A5, A6, A7, A8>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8);
+}
+
+// 9 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename R>
+gmp_task_args_nm_9_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, R* r) {
+ return new gmp_task_args_nm_9_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, R>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, r);
+}
+
+// 9 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8>
+gmp_task_args_m_9<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) {
+ return new gmp_task_args_m_9<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8);
+}
+
+// 9 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename R>
+gmp_task_args_m_9_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, R* r) {
+ return new gmp_task_args_m_9_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, R>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, r);
+}
+
+// 10 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9>
+gmp_task_args_nm_10<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) {
+ return new gmp_task_args_nm_10<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
+}
+
+// 10 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename R>
+gmp_task_args_nm_10_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, R* r) {
+ return new gmp_task_args_nm_10_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, R>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, r);
+}
+
+// 10 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9>
+gmp_task_args_m_10<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) {
+ return new gmp_task_args_m_10<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
+}
+
+// 10 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename R>
+gmp_task_args_m_10_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, R* r) {
+ return new gmp_task_args_m_10_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, R>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, r);
+}
+
+// 11 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10>
+gmp_task_args_nm_11<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) {
+ return new gmp_task_args_nm_11<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
+}
+
+// 11 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename R>
+gmp_task_args_nm_11_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, R* r) {
+ return new gmp_task_args_nm_11_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, R>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, r);
+}
+
+// 11 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10>
+gmp_task_args_m_11<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) {
+ return new gmp_task_args_m_11<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
+}
+
+// 11 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename R>
+gmp_task_args_m_11_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, R* r) {
+ return new gmp_task_args_m_11_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, R>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, r);
+}
+
+// 12 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11>
+gmp_task_args_nm_12<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11) {
+ return new gmp_task_args_nm_12<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11);
+}
+
+// 12 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename R>
+gmp_task_args_nm_12_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, R* r) {
+ return new gmp_task_args_nm_12_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, R>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, r);
+}
+
+// 12 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11>
+gmp_task_args_m_12<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11) {
+ return new gmp_task_args_m_12<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11);
+}
+
+// 12 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename R>
+gmp_task_args_m_12_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, R* r) {
+ return new gmp_task_args_m_12_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, R>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, r);
+}
+
+// 13 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12>
+gmp_task_args_nm_13<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12) {
+ return new gmp_task_args_nm_13<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
+}
+
+// 13 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename R>
+gmp_task_args_nm_13_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, R* r) {
+ return new gmp_task_args_nm_13_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, R>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, r);
+}
+
+// 13 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12>
+gmp_task_args_m_13<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12) {
+ return new gmp_task_args_m_13<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
+}
+
+// 13 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename R>
+gmp_task_args_m_13_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, R* r) {
+ return new gmp_task_args_m_13_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, R>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, r);
+}
+
+// 14 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13>
+gmp_task_args_nm_14<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13>* WrapTaskNM(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13) {
+ return new gmp_task_args_nm_14<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13);
+}
+
+// 14 arguments --
+template<typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13, typename R>
+gmp_task_args_nm_14_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, R>* WrapTaskNMRet(M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R* r) {
+ return new gmp_task_args_nm_14_ret<M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, R>
+ (m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, r);
+}
+
+// 14 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13>
+gmp_task_args_m_14<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13>* WrapTask(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13) {
+ return new gmp_task_args_m_14<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13);
+}
+
+// 14 arguments --
+template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13, typename R>
+gmp_task_args_m_14_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R* r) {
+ return new gmp_task_args_m_14_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, R>
+ (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, r);
+}
+
+class RefCountTaskWrapper : public gmp_task_args_base {
+public:
+ RefCountTaskWrapper(GMPTask* aTask, RefCounted* aRefCounted)
+ : mTask(aTask)
+ , mRefCounted(aRefCounted)
+ {}
+ virtual void Run() override {
+ mTask->Run();
+ }
+ virtual void Destroy() override {
+ mTask->Destroy();
+ gmp_task_args_base::Destroy();
+ }
+private:
+ ~RefCountTaskWrapper() {}
+
+ GMPTask* mTask;
+ RefPtr<RefCounted> mRefCounted;
+};
+
+template<typename Type, typename Method, typename... Args>
+GMPTask*
+WrapTaskRefCounted(Type* aType, Method aMethod, Args&&... args)
+{
+ GMPTask* t = WrapTask(aType, aMethod, std::forward<Args>(args)...);
+ return new RefCountTaskWrapper(t, aType);
+}
diff --git a/libs/gmp-clearkey/0.1/gmp-task-utils.h b/libs/gmp-clearkey/0.1/gmp-task-utils.h
new file mode 100644
index 000000000..82e08373f
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/gmp-task-utils.h
@@ -0,0 +1,47 @@
+/*
+ * 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_
diff --git a/libs/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp b/libs/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
new file mode 100644
index 000000000..021d1af68
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <stdint.h>
+#include <vector>
+
+#include "../ClearKeyBase64.cpp"
+
+using namespace std;
+
+struct B64Test {
+ string b64;
+ vector<uint8_t> raw;
+ bool shouldPass;
+};
+
+const B64Test tests[] = {
+ {
+ "AAAAADk4AU4AAAAAAAAAAA",
+ { 0x0, 0x0, 0x0, 0x0, 0x39, 0x38, 0x1, 0x4e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
+ true
+ },
+ {
+ "h2mqp1zAJjDIC34YXEXBxA==",
+ { 0x87, 0x69, 0xaa, 0xa7, 0x5c, 0xc0, 0x26, 0x30, 0xc8, 0xb, 0x7e, 0x18, 0x5c, 0x45, 0xc1, 0xc4 },
+ true
+ },
+ {
+ "flcdA35XHQN-Vx0DflcdAw",
+ { 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3 },
+ true
+ },
+ {
+ "flczM35XMzN-VzMzflczMw",
+ { 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33 },
+ true,
+ },
+ {
+ "flcdBH5XHQR-Vx0EflcdBA",
+ { 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4 },
+ true
+ },
+ {
+ "fldERH5XRER-V0REfldERA",
+ { 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44 },
+ true
+ },
+ {
+ "fuzzbiz=",
+ { 0x7e, 0xec, 0xf3, 0x6e, 0x2c },
+ true
+ },
+ {
+ "fuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbiz",
+ {
+ 0x7e, 0xec, 0xf3, 0x6e, 0x2c, 0xdf, 0xbb, 0x3c, 0xdb, 0x8b,
+ 0x37, 0xee, 0xcf, 0x36, 0xe2, 0xcd, 0xfb, 0xb3, 0xcd, 0xb8,
+ 0xb3, 0x7e, 0xec, 0xf3, 0x6e, 0x2c, 0xdf, 0xbb, 0x3c, 0xdb,
+ 0x8b, 0x37, 0xee, 0xcf, 0x36, 0xe2, 0xcd, 0xfb, 0xb3, 0xcd,
+ 0xb8, 0xb3
+ },
+ true
+ },
+ { "", { }, true },
+ { "00", { 0xd3 }, true },
+ { "000", { 0xd3, 0x4d }, true },
+
+ { "invalid", { 0x8a, 0x7b, 0xda, 0x96, 0x27 }, true },
+ { "invalic", { 0x8a, 0x7b, 0xda, 0x96, 0x27 }, true },
+
+ // Failure tests
+ { "A", { }, false }, // 1 character is too few.
+ { "_", { }, false }, // 1 character is too few.
+};
+
+TEST(ClearKey, DecodeBase64) {
+ for (const B64Test& test : tests) {
+ vector<uint8_t> v;
+ bool rv = DecodeBase64(string(test.b64), v);
+ EXPECT_EQ(test.shouldPass, rv);
+ if (test.shouldPass) {
+ EXPECT_EQ(test.raw.size(), v.size());
+ for (size_t k = 0; k < test.raw.size(); k++) {
+ EXPECT_EQ(test.raw[k], v[k]);
+ }
+ }
+ }
+}
diff --git a/libs/gmp-clearkey/0.1/gtest/moz.build b/libs/gmp-clearkey/0.1/gtest/moz.build
new file mode 100644
index 000000000..22a61d280
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/gtest/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ 'TestClearKeyUtils.cpp',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
+LOCAL_INCLUDES += [
+ '..',
+]
diff --git a/libs/gmp-clearkey/0.1/moz.build b/libs/gmp-clearkey/0.1/moz.build
new file mode 100644
index 000000000..fb25fa8fc
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/moz.build
@@ -0,0 +1,74 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SharedLibrary('clearkey')
+
+FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
+
+FINAL_TARGET_PP_FILES += ['clearkey.info.in']
+
+UNIFIED_SOURCES += [
+ 'ClearKeyAsyncShutdown.cpp',
+ 'ClearKeyBase64.cpp',
+ 'ClearKeyDecryptionManager.cpp',
+ 'ClearKeyPersistence.cpp',
+ 'ClearKeySession.cpp',
+ 'ClearKeySessionManager.cpp',
+ 'ClearKeyStorage.cpp',
+ 'ClearKeyUtils.cpp',
+ 'gmp-clearkey.cpp',
+]
+
+SOURCES += [
+ 'openaes/oaes_lib.c',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ UNIFIED_SOURCES += [
+ 'AnnexB.cpp',
+ 'AudioDecoder.cpp',
+ 'VideoDecoder.cpp',
+ 'WMFAACDecoder.cpp',
+ 'WMFH264Decoder.cpp',
+ ]
+
+ SOURCES += [
+ 'WMFUtils.cpp',
+ ]
+
+ OS_LIBS += [
+ 'mfuuid',
+ ]
+
+ DEFINES['ENABLE_WMF'] = True
+
+TEST_DIRS += [
+ 'gtest',
+]
+
+
+LOCAL_INCLUDES += [
+ '/dom/media/gmp',
+]
+
+DISABLE_STL_WRAPPING = True
+DEFINES['MOZ_NO_MOZALLOC'] = True
+
+USE_LIBS += ['psshparser']
+
+# Suppress warnings in third-party code.
+if CONFIG['GNU_CXX']:
+ CFLAGS += [
+ '-Wno-missing-braces',
+ '-Wno-pointer-to-int-cast',
+ '-Wno-sign-compare',
+ '-include', 'stdio.h', # for sprintf() prototype
+ '-include', 'unistd.h', # for getpid() prototype
+ ]
+elif CONFIG['_MSC_VER']:
+ CFLAGS += [
+ '-FI', 'stdio.h', # for sprintf() prototype
+ '-wd4090', # '=' : different 'const' qualifiers
+ ]
diff --git a/libs/gmp-clearkey/0.1/openaes/LICENSE b/libs/gmp-clearkey/0.1/openaes/LICENSE
new file mode 100644
index 000000000..d824e13dd
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/openaes/LICENSE
@@ -0,0 +1,27 @@
+---------------------------------------------------------------------------
+OpenAES Licence
+---------------------------------------------------------------------------
+Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
diff --git a/libs/gmp-clearkey/0.1/openaes/oaes_common.h b/libs/gmp-clearkey/0.1/openaes/oaes_common.h
new file mode 100644
index 000000000..7e2bf513f
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/openaes/oaes_common.h
@@ -0,0 +1,77 @@
+/*
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2013, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#ifndef _OAES_COMMON_H
+#define _OAES_COMMON_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _WIN32
+# ifdef OAES_SHARED
+# ifdef oaes_lib_EXPORTS
+# define OAES_API __declspec(dllexport)
+# else
+# define OAES_API __declspec(dllimport)
+# endif
+# else
+# define OAES_API
+# endif
+#else
+# define OAES_API
+#endif // WIN32
+
+#define OAES_VERSION "0.9.0"
+
+typedef enum
+{
+ OAES_RET_FIRST = 0,
+ OAES_RET_SUCCESS = 0,
+ OAES_RET_ERROR,
+ OAES_RET_ARG1,
+ OAES_RET_ARG2,
+ OAES_RET_ARG3,
+ OAES_RET_ARG4,
+ OAES_RET_ARG5,
+ OAES_RET_NOKEY,
+ OAES_RET_MEM,
+ OAES_RET_BUF,
+ OAES_RET_HEADER,
+ OAES_RET_COUNT
+} OAES_RET;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _OAES_COMMON_H
diff --git a/libs/gmp-clearkey/0.1/openaes/oaes_config.h b/libs/gmp-clearkey/0.1/openaes/oaes_config.h
new file mode 100644
index 000000000..cb9f4e7be
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/openaes/oaes_config.h
@@ -0,0 +1,46 @@
+/*
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#ifndef _OAES_CONFIG_H
+#define _OAES_CONFIG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef OAES_DEBUG
+#define OAES_DEBUG 0
+#endif // OAES_DEBUG
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _OAES_CONFIG_H
diff --git a/libs/gmp-clearkey/0.1/openaes/oaes_lib.c b/libs/gmp-clearkey/0.1/openaes/oaes_lib.c
new file mode 100644
index 000000000..aa13623ce
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/openaes/oaes_lib.c
@@ -0,0 +1,1393 @@
+/*
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <time.h>
+#include <string.h>
+
+#include "mozilla/Sprintf.h"
+
+#ifdef WIN32
+#include <process.h>
+#endif
+
+#include "oaes_config.h"
+#include "oaes_lib.h"
+
+#ifdef OAES_HAVE_ISAAC
+#include "rand.h"
+#define OAES_RAND(x) rand(x)
+#else
+#define OAES_RAND(x) rand()
+#endif // OAES_HAVE_ISAAC
+
+#define OAES_RKEY_LEN 4
+#define OAES_COL_LEN 4
+#define OAES_ROUND_BASE 7
+
+// the block is padded
+#define OAES_FLAG_PAD 0x01
+
+#ifndef min
+# define min(a,b) (((a)<(b)) ? (a) : (b))
+#endif /* min */
+
+typedef struct _oaes_key
+{
+ size_t data_len;
+ uint8_t *data;
+ size_t exp_data_len;
+ uint8_t *exp_data;
+ size_t num_keys;
+ size_t key_base;
+} oaes_key;
+
+typedef struct _oaes_ctx
+{
+#ifdef OAES_HAVE_ISAAC
+ randctx * rctx;
+#endif // OAES_HAVE_ISAAC
+
+#ifdef OAES_DEBUG
+ oaes_step_cb step_cb;
+#endif // OAES_DEBUG
+
+ oaes_key * key;
+ OAES_OPTION options;
+ uint8_t iv[OAES_BLOCK_SIZE];
+} oaes_ctx;
+
+// "OAES<8-bit header version><8-bit type><16-bit options><8-bit flags><56-bit reserved>"
+static uint8_t oaes_header[OAES_BLOCK_SIZE] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x4f, 0x41, 0x45, 0x53, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+static uint8_t oaes_gf_8[] = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
+
+static uint8_t oaes_sub_byte_value[16][16] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+ /*1*/ 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+ /*2*/ 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+ /*3*/ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+ /*4*/ 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+ /*5*/ 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+ /*6*/ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+ /*7*/ 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+ /*8*/ 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+ /*9*/ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+ /*a*/ 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+ /*b*/ 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+ /*c*/ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+ /*d*/ 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+ /*e*/ 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+ /*f*/ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
+};
+
+static uint8_t oaes_inv_sub_byte_value[16][16] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+ /*1*/ 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+ /*2*/ 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+ /*3*/ 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+ /*4*/ 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+ /*5*/ 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+ /*6*/ 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+ /*7*/ 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+ /*8*/ 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+ /*9*/ 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+ /*a*/ 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+ /*b*/ 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+ /*c*/ 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+ /*d*/ 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+ /*e*/ 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+ /*f*/ 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
+};
+
+static uint8_t oaes_gf_mul_2[16][16] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
+ /*1*/ 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
+ /*2*/ 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
+ /*3*/ 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e,
+ /*4*/ 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e,
+ /*5*/ 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
+ /*6*/ 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
+ /*7*/ 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
+ /*8*/ 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05,
+ /*9*/ 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25,
+ /*a*/ 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45,
+ /*b*/ 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,
+ /*c*/ 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85,
+ /*d*/ 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5,
+ /*e*/ 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5,
+ /*f*/ 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5,
+};
+
+static uint8_t oaes_gf_mul_3[16][16] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11,
+ /*1*/ 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21,
+ /*2*/ 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71,
+ /*3*/ 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41,
+ /*4*/ 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1,
+ /*5*/ 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1,
+ /*6*/ 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1,
+ /*7*/ 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81,
+ /*8*/ 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a,
+ /*9*/ 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba,
+ /*a*/ 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea,
+ /*b*/ 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda,
+ /*c*/ 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a,
+ /*d*/ 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a,
+ /*e*/ 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a,
+ /*f*/ 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a,
+};
+
+static uint8_t oaes_gf_mul_9[16][16] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
+ /*1*/ 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
+ /*2*/ 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
+ /*3*/ 0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc,
+ /*4*/ 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01,
+ /*5*/ 0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91,
+ /*6*/ 0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a,
+ /*7*/ 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa,
+ /*8*/ 0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b,
+ /*9*/ 0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b,
+ /*a*/ 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0,
+ /*b*/ 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30,
+ /*c*/ 0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed,
+ /*d*/ 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d,
+ /*e*/ 0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6,
+ /*f*/ 0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46,
+};
+
+static uint8_t oaes_gf_mul_b[16][16] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
+ /*1*/ 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
+ /*2*/ 0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,
+ /*3*/ 0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2,
+ /*4*/ 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f,
+ /*5*/ 0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f,
+ /*6*/ 0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4,
+ /*7*/ 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54,
+ /*8*/ 0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e,
+ /*9*/ 0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e,
+ /*a*/ 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5,
+ /*b*/ 0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55,
+ /*c*/ 0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68,
+ /*d*/ 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8,
+ /*e*/ 0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13,
+ /*f*/ 0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3,
+};
+
+static uint8_t oaes_gf_mul_d[16][16] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
+ /*1*/ 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
+ /*2*/ 0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,
+ /*3*/ 0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20,
+ /*4*/ 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26,
+ /*5*/ 0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6,
+ /*6*/ 0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d,
+ /*7*/ 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d,
+ /*8*/ 0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,
+ /*9*/ 0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41,
+ /*a*/ 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a,
+ /*b*/ 0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa,
+ /*c*/ 0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc,
+ /*d*/ 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c,
+ /*e*/ 0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47,
+ /*f*/ 0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97,
+};
+
+static uint8_t oaes_gf_mul_e[16][16] = {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
+ /*0*/ 0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
+ /*1*/ 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
+ /*2*/ 0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,
+ /*3*/ 0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61,
+ /*4*/ 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7,
+ /*5*/ 0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,
+ /*6*/ 0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c,
+ /*7*/ 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc,
+ /*8*/ 0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b,
+ /*9*/ 0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb,
+ /*a*/ 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0,
+ /*b*/ 0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20,
+ /*c*/ 0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6,
+ /*d*/ 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56,
+ /*e*/ 0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d,
+ /*f*/ 0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d,
+};
+
+static OAES_RET oaes_sub_byte( uint8_t * byte )
+{
+ size_t _x, _y;
+
+ if( NULL == byte )
+ return OAES_RET_ARG1;
+
+ _x = _y = *byte;
+ _x &= 0x0f;
+ _y &= 0xf0;
+ _y >>= 4;
+ *byte = oaes_sub_byte_value[_y][_x];
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_inv_sub_byte( uint8_t * byte )
+{
+ size_t _x, _y;
+
+ if( NULL == byte )
+ return OAES_RET_ARG1;
+
+ _x = _y = *byte;
+ _x &= 0x0f;
+ _y &= 0xf0;
+ _y >>= 4;
+ *byte = oaes_inv_sub_byte_value[_y][_x];
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_word_rot_left( uint8_t word[OAES_COL_LEN] )
+{
+ uint8_t _temp[OAES_COL_LEN];
+
+ if( NULL == word )
+ return OAES_RET_ARG1;
+
+ memcpy( _temp, word + 1, OAES_COL_LEN - 1 );
+ _temp[OAES_COL_LEN - 1] = word[0];
+ memcpy( word, _temp, OAES_COL_LEN );
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_shift_rows( uint8_t block[OAES_BLOCK_SIZE] )
+{
+ uint8_t _temp[OAES_BLOCK_SIZE];
+
+ if( NULL == block )
+ return OAES_RET_ARG1;
+
+ _temp[0x00] = block[0x00];
+ _temp[0x01] = block[0x05];
+ _temp[0x02] = block[0x0a];
+ _temp[0x03] = block[0x0f];
+ _temp[0x04] = block[0x04];
+ _temp[0x05] = block[0x09];
+ _temp[0x06] = block[0x0e];
+ _temp[0x07] = block[0x03];
+ _temp[0x08] = block[0x08];
+ _temp[0x09] = block[0x0d];
+ _temp[0x0a] = block[0x02];
+ _temp[0x0b] = block[0x07];
+ _temp[0x0c] = block[0x0c];
+ _temp[0x0d] = block[0x01];
+ _temp[0x0e] = block[0x06];
+ _temp[0x0f] = block[0x0b];
+ memcpy( block, _temp, OAES_BLOCK_SIZE );
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_inv_shift_rows( uint8_t block[OAES_BLOCK_SIZE] )
+{
+ uint8_t _temp[OAES_BLOCK_SIZE];
+
+ if( NULL == block )
+ return OAES_RET_ARG1;
+
+ _temp[0x00] = block[0x00];
+ _temp[0x01] = block[0x0d];
+ _temp[0x02] = block[0x0a];
+ _temp[0x03] = block[0x07];
+ _temp[0x04] = block[0x04];
+ _temp[0x05] = block[0x01];
+ _temp[0x06] = block[0x0e];
+ _temp[0x07] = block[0x0b];
+ _temp[0x08] = block[0x08];
+ _temp[0x09] = block[0x05];
+ _temp[0x0a] = block[0x02];
+ _temp[0x0b] = block[0x0f];
+ _temp[0x0c] = block[0x0c];
+ _temp[0x0d] = block[0x09];
+ _temp[0x0e] = block[0x06];
+ _temp[0x0f] = block[0x03];
+ memcpy( block, _temp, OAES_BLOCK_SIZE );
+
+ return OAES_RET_SUCCESS;
+}
+
+static uint8_t oaes_gf_mul(uint8_t left, uint8_t right)
+{
+ size_t _x, _y;
+
+ _x = _y = left;
+ _x &= 0x0f;
+ _y &= 0xf0;
+ _y >>= 4;
+
+ switch( right )
+ {
+ case 0x02:
+ return oaes_gf_mul_2[_y][_x];
+ break;
+ case 0x03:
+ return oaes_gf_mul_3[_y][_x];
+ break;
+ case 0x09:
+ return oaes_gf_mul_9[_y][_x];
+ break;
+ case 0x0b:
+ return oaes_gf_mul_b[_y][_x];
+ break;
+ case 0x0d:
+ return oaes_gf_mul_d[_y][_x];
+ break;
+ case 0x0e:
+ return oaes_gf_mul_e[_y][_x];
+ break;
+ default:
+ return left;
+ break;
+ }
+}
+
+static OAES_RET oaes_mix_cols( uint8_t word[OAES_COL_LEN] )
+{
+ uint8_t _temp[OAES_COL_LEN];
+
+ if( NULL == word )
+ return OAES_RET_ARG1;
+
+ _temp[0] = oaes_gf_mul(word[0], 0x02) ^ oaes_gf_mul( word[1], 0x03 ) ^
+ word[2] ^ word[3];
+ _temp[1] = word[0] ^ oaes_gf_mul( word[1], 0x02 ) ^
+ oaes_gf_mul( word[2], 0x03 ) ^ word[3];
+ _temp[2] = word[0] ^ word[1] ^
+ oaes_gf_mul( word[2], 0x02 ) ^ oaes_gf_mul( word[3], 0x03 );
+ _temp[3] = oaes_gf_mul( word[0], 0x03 ) ^ word[1] ^
+ word[2] ^ oaes_gf_mul( word[3], 0x02 );
+ memcpy( word, _temp, OAES_COL_LEN );
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_inv_mix_cols( uint8_t word[OAES_COL_LEN] )
+{
+ uint8_t _temp[OAES_COL_LEN];
+
+ if( NULL == word )
+ return OAES_RET_ARG1;
+
+ _temp[0] = oaes_gf_mul( word[0], 0x0e ) ^ oaes_gf_mul( word[1], 0x0b ) ^
+ oaes_gf_mul( word[2], 0x0d ) ^ oaes_gf_mul( word[3], 0x09 );
+ _temp[1] = oaes_gf_mul( word[0], 0x09 ) ^ oaes_gf_mul( word[1], 0x0e ) ^
+ oaes_gf_mul( word[2], 0x0b ) ^ oaes_gf_mul( word[3], 0x0d );
+ _temp[2] = oaes_gf_mul( word[0], 0x0d ) ^ oaes_gf_mul( word[1], 0x09 ) ^
+ oaes_gf_mul( word[2], 0x0e ) ^ oaes_gf_mul( word[3], 0x0b );
+ _temp[3] = oaes_gf_mul( word[0], 0x0b ) ^ oaes_gf_mul( word[1], 0x0d ) ^
+ oaes_gf_mul( word[2], 0x09 ) ^ oaes_gf_mul( word[3], 0x0e );
+ memcpy( word, _temp, OAES_COL_LEN );
+
+ return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_sprintf(
+ char * buf, size_t * buf_len, const uint8_t * data, size_t data_len )
+{
+ size_t _i, _buf_len_in;
+ char _temp[4];
+
+ if( NULL == buf_len )
+ return OAES_RET_ARG2;
+
+ _buf_len_in = *buf_len;
+ *buf_len = data_len * 3 + data_len / OAES_BLOCK_SIZE + 1;
+
+ if( NULL == buf )
+ return OAES_RET_SUCCESS;
+
+ if( *buf_len > _buf_len_in )
+ return OAES_RET_BUF;
+
+ if( NULL == data )
+ return OAES_RET_ARG3;
+
+ strcpy( buf, "" );
+
+ for( _i = 0; _i < data_len; _i++ )
+ {
+ snprintf( _temp, sizeof(_temp), "%02x ", data[_i] );
+ strcat( buf, _temp );
+ if( _i && 0 == ( _i + 1 ) % OAES_BLOCK_SIZE )
+ strcat( buf, "\n" );
+ }
+
+ return OAES_RET_SUCCESS;
+}
+
+/*
+#ifdef OAES_HAVE_ISAAC
+static void oaes_get_seed( char buf[RANDSIZ + 1] )
+{
+ struct timeb timer;
+ struct tm *gmTimer;
+ char * _test = NULL;
+
+ ftime (&timer);
+ gmTimer = gmtime( &timer.time );
+ _test = (char *) calloc( sizeof( char ), timer.millitm );
+ sprintf( buf, "%04d%02d%02d%02d%02d%02d%03d%p%d",
+ gmTimer->tm_year + 1900, gmTimer->tm_mon + 1, gmTimer->tm_mday,
+ gmTimer->tm_hour, gmTimer->tm_min, gmTimer->tm_sec, timer.millitm,
+ _test + timer.millitm, getpid() );
+
+ if( _test )
+ free( _test );
+}
+#else
+static uint32_t oaes_get_seed()
+{
+ struct timeb timer;
+ struct tm *gmTimer;
+ char * _test = NULL;
+ uint32_t _ret = 0;
+
+ ftime (&timer);
+ gmTimer = gmtime( &timer.time );
+ _test = (char *) calloc( sizeof( char ), timer.millitm );
+ _ret = gmTimer->tm_year + 1900 + gmTimer->tm_mon + 1 + gmTimer->tm_mday +
+ gmTimer->tm_hour + gmTimer->tm_min + gmTimer->tm_sec + timer.millitm +
+ (uint32_t) ( _test + timer.millitm ) + getpid();
+
+ if( _test )
+ free( _test );
+
+ return _ret;
+}
+#endif // OAES_HAVE_ISAAC
+*/
+
+static OAES_RET oaes_key_destroy( oaes_key ** key )
+{
+ if( NULL == *key )
+ return OAES_RET_SUCCESS;
+
+ if( (*key)->data )
+ {
+ free( (*key)->data );
+ (*key)->data = NULL;
+ }
+
+ if( (*key)->exp_data )
+ {
+ free( (*key)->exp_data );
+ (*key)->exp_data = NULL;
+ }
+
+ (*key)->data_len = 0;
+ (*key)->exp_data_len = 0;
+ (*key)->num_keys = 0;
+ (*key)->key_base = 0;
+ free( *key );
+ *key = NULL;
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_key_expand( OAES_CTX * ctx )
+{
+ size_t _i, _j;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == _ctx->key )
+ return OAES_RET_NOKEY;
+
+ _ctx->key->key_base = _ctx->key->data_len / OAES_RKEY_LEN;
+ _ctx->key->num_keys = _ctx->key->key_base + OAES_ROUND_BASE;
+
+ _ctx->key->exp_data_len = _ctx->key->num_keys * OAES_RKEY_LEN * OAES_COL_LEN;
+ _ctx->key->exp_data = (uint8_t *)
+ calloc( _ctx->key->exp_data_len, sizeof( uint8_t ));
+
+ if( NULL == _ctx->key->exp_data )
+ return OAES_RET_MEM;
+
+ // the first _ctx->key->data_len are a direct copy
+ memcpy( _ctx->key->exp_data, _ctx->key->data, _ctx->key->data_len );
+
+ // apply ExpandKey algorithm for remainder
+ for( _i = _ctx->key->key_base; _i < _ctx->key->num_keys * OAES_RKEY_LEN; _i++ )
+ {
+ uint8_t _temp[OAES_COL_LEN];
+
+ memcpy( _temp,
+ _ctx->key->exp_data + ( _i - 1 ) * OAES_RKEY_LEN, OAES_COL_LEN );
+
+ // transform key column
+ if( 0 == _i % _ctx->key->key_base )
+ {
+ oaes_word_rot_left( _temp );
+
+ for( _j = 0; _j < OAES_COL_LEN; _j++ )
+ oaes_sub_byte( _temp + _j );
+
+ _temp[0] = _temp[0] ^ oaes_gf_8[ _i / _ctx->key->key_base - 1 ];
+ }
+ else if( _ctx->key->key_base > 6 && 4 == _i % _ctx->key->key_base )
+ {
+ for( _j = 0; _j < OAES_COL_LEN; _j++ )
+ oaes_sub_byte( _temp + _j );
+ }
+
+ for( _j = 0; _j < OAES_COL_LEN; _j++ )
+ {
+ _ctx->key->exp_data[ _i * OAES_RKEY_LEN + _j ] =
+ _ctx->key->exp_data[ ( _i - _ctx->key->key_base ) *
+ OAES_RKEY_LEN + _j ] ^ _temp[_j];
+ }
+ }
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_key_gen( OAES_CTX * ctx, size_t key_size )
+{
+ size_t _i;
+ oaes_key * _key = NULL;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+ OAES_RET _rc = OAES_RET_SUCCESS;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ _key = (oaes_key *) calloc( sizeof( oaes_key ), 1 );
+
+ if( NULL == _key )
+ return OAES_RET_MEM;
+
+ if( _ctx->key )
+ oaes_key_destroy( &(_ctx->key) );
+
+ _key->data_len = key_size;
+ _key->data = (uint8_t *) calloc( key_size, sizeof( uint8_t ));
+
+ if( NULL == _key->data )
+ {
+ oaes_key_destroy( &_key );
+ return OAES_RET_MEM;
+ }
+
+ for( _i = 0; _i < key_size; _i++ )
+ _key->data[_i] = (uint8_t) OAES_RAND(_ctx->rctx);
+
+ _ctx->key = _key;
+ _rc = _rc || oaes_key_expand( ctx );
+
+ if( _rc != OAES_RET_SUCCESS )
+ {
+ oaes_key_destroy( &(_ctx->key) );
+ return _rc;
+ }
+
+ return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_key_gen_128( OAES_CTX * ctx )
+{
+ return oaes_key_gen( ctx, 16 );
+}
+
+OAES_RET oaes_key_gen_192( OAES_CTX * ctx )
+{
+ return oaes_key_gen( ctx, 24 );
+}
+
+OAES_RET oaes_key_gen_256( OAES_CTX * ctx )
+{
+ return oaes_key_gen( ctx, 32 );
+}
+
+OAES_RET oaes_key_export( OAES_CTX * ctx,
+ uint8_t * data, size_t * data_len )
+{
+ size_t _data_len_in;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == _ctx->key )
+ return OAES_RET_NOKEY;
+
+ if( NULL == data_len )
+ return OAES_RET_ARG3;
+
+ _data_len_in = *data_len;
+ // data + header
+ *data_len = _ctx->key->data_len + OAES_BLOCK_SIZE;
+
+ if( NULL == data )
+ return OAES_RET_SUCCESS;
+
+ if( _data_len_in < *data_len )
+ return OAES_RET_BUF;
+
+ // header
+ memcpy( data, oaes_header, OAES_BLOCK_SIZE );
+ data[5] = 0x01;
+ data[7] = _ctx->key->data_len;
+ memcpy( data + OAES_BLOCK_SIZE, _ctx->key->data, _ctx->key->data_len );
+
+ return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_key_export_data( OAES_CTX * ctx,
+ uint8_t * data, size_t * data_len )
+{
+ size_t _data_len_in;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == _ctx->key )
+ return OAES_RET_NOKEY;
+
+ if( NULL == data_len )
+ return OAES_RET_ARG3;
+
+ _data_len_in = *data_len;
+ *data_len = _ctx->key->data_len;
+
+ if( NULL == data )
+ return OAES_RET_SUCCESS;
+
+ if( _data_len_in < *data_len )
+ return OAES_RET_BUF;
+
+ memcpy( data, _ctx->key->data, *data_len );
+
+ return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_key_import( OAES_CTX * ctx,
+ const uint8_t * data, size_t data_len )
+{
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+ OAES_RET _rc = OAES_RET_SUCCESS;
+ int _key_length;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == data )
+ return OAES_RET_ARG2;
+
+ switch( data_len )
+ {
+ case 16 + OAES_BLOCK_SIZE:
+ case 24 + OAES_BLOCK_SIZE:
+ case 32 + OAES_BLOCK_SIZE:
+ break;
+ default:
+ return OAES_RET_ARG3;
+ }
+
+ // header
+ if( 0 != memcmp( data, oaes_header, 4 ) )
+ return OAES_RET_HEADER;
+
+ // header version
+ switch( data[4] )
+ {
+ case 0x01:
+ break;
+ default:
+ return OAES_RET_HEADER;
+ }
+
+ // header type
+ switch( data[5] )
+ {
+ case 0x01:
+ break;
+ default:
+ return OAES_RET_HEADER;
+ }
+
+ // options
+ _key_length = data[7];
+ switch( _key_length )
+ {
+ case 16:
+ case 24:
+ case 32:
+ break;
+ default:
+ return OAES_RET_HEADER;
+ }
+
+ if( data_len != _key_length + OAES_BLOCK_SIZE )
+ return OAES_RET_ARG3;
+
+ if( _ctx->key )
+ oaes_key_destroy( &(_ctx->key) );
+
+ _ctx->key = (oaes_key *) calloc( sizeof( oaes_key ), 1 );
+
+ if( NULL == _ctx->key )
+ return OAES_RET_MEM;
+
+ _ctx->key->data_len = _key_length;
+ _ctx->key->data = (uint8_t *)
+ calloc( _key_length, sizeof( uint8_t ));
+
+ if( NULL == _ctx->key->data )
+ {
+ oaes_key_destroy( &(_ctx->key) );
+ return OAES_RET_MEM;
+ }
+
+ memcpy( _ctx->key->data, data + OAES_BLOCK_SIZE, _key_length );
+ _rc = _rc || oaes_key_expand( ctx );
+
+ if( _rc != OAES_RET_SUCCESS )
+ {
+ oaes_key_destroy( &(_ctx->key) );
+ return _rc;
+ }
+
+ return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_key_import_data( OAES_CTX * ctx,
+ const uint8_t * data, size_t data_len )
+{
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+ OAES_RET _rc = OAES_RET_SUCCESS;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == data )
+ return OAES_RET_ARG2;
+
+ switch( data_len )
+ {
+ case 16:
+ case 24:
+ case 32:
+ break;
+ default:
+ return OAES_RET_ARG3;
+ }
+
+ if( _ctx->key )
+ oaes_key_destroy( &(_ctx->key) );
+
+ _ctx->key = (oaes_key *) calloc( sizeof( oaes_key ), 1 );
+
+ if( NULL == _ctx->key )
+ return OAES_RET_MEM;
+
+ _ctx->key->data_len = data_len;
+ _ctx->key->data = (uint8_t *)
+ calloc( data_len, sizeof( uint8_t ));
+
+ if( NULL == _ctx->key->data )
+ {
+ oaes_key_destroy( &(_ctx->key) );
+ return OAES_RET_MEM;
+ }
+
+ memcpy( _ctx->key->data, data, data_len );
+ _rc = _rc || oaes_key_expand( ctx );
+
+ if( _rc != OAES_RET_SUCCESS )
+ {
+ oaes_key_destroy( &(_ctx->key) );
+ return _rc;
+ }
+
+ return OAES_RET_SUCCESS;
+}
+
+OAES_CTX * oaes_alloc()
+{
+ oaes_ctx * _ctx = (oaes_ctx *) calloc( sizeof( oaes_ctx ), 1 );
+
+ if( NULL == _ctx )
+ return NULL;
+
+#ifdef OAES_HAVE_ISAAC
+ {
+ ub4 _i = 0;
+ char _seed[RANDSIZ + 1];
+
+ _ctx->rctx = (randctx *) calloc( sizeof( randctx ), 1 );
+
+ if( NULL == _ctx->rctx )
+ {
+ free( _ctx );
+ return NULL;
+ }
+
+ oaes_get_seed( _seed );
+ memset( _ctx->rctx->randrsl, 0, RANDSIZ );
+ memcpy( _ctx->rctx->randrsl, _seed, RANDSIZ );
+ randinit( _ctx->rctx, TRUE);
+ }
+#else
+ //srand( oaes_get_seed() );
+#endif // OAES_HAVE_ISAAC
+
+ _ctx->key = NULL;
+ oaes_set_option( _ctx, OAES_OPTION_CBC, NULL );
+
+#ifdef OAES_DEBUG
+ _ctx->step_cb = NULL;
+ oaes_set_option( _ctx, OAES_OPTION_STEP_OFF, NULL );
+#endif // OAES_DEBUG
+
+ return (OAES_CTX *) _ctx;
+}
+
+OAES_RET oaes_free( OAES_CTX ** ctx )
+{
+ oaes_ctx ** _ctx = (oaes_ctx **) ctx;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == *_ctx )
+ return OAES_RET_SUCCESS;
+
+ if( (*_ctx)->key )
+ oaes_key_destroy( &((*_ctx)->key) );
+
+#ifdef OAES_HAVE_ISAAC
+ if( (*_ctx)->rctx )
+ {
+ free( (*_ctx)->rctx );
+ (*_ctx)->rctx = NULL;
+ }
+#endif // OAES_HAVE_ISAAC
+
+ free( *_ctx );
+ *_ctx = NULL;
+
+ return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_set_option( OAES_CTX * ctx,
+ OAES_OPTION option, const void * value )
+{
+ size_t _i;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ switch( option )
+ {
+ case OAES_OPTION_ECB:
+ _ctx->options &= ~OAES_OPTION_CBC;
+ memset( _ctx->iv, 0, OAES_BLOCK_SIZE );
+ break;
+
+ case OAES_OPTION_CBC:
+ _ctx->options &= ~OAES_OPTION_ECB;
+ if( value )
+ memcpy( _ctx->iv, value, OAES_BLOCK_SIZE );
+ else
+ {
+ for( _i = 0; _i < OAES_BLOCK_SIZE; _i++ )
+ _ctx->iv[_i] = (uint8_t) OAES_RAND(_ctx->rctx);
+ }
+ break;
+
+#ifdef OAES_DEBUG
+
+ case OAES_OPTION_STEP_ON:
+ if( value )
+ {
+ _ctx->options &= ~OAES_OPTION_STEP_OFF;
+ _ctx->step_cb = value;
+ }
+ else
+ {
+ _ctx->options &= ~OAES_OPTION_STEP_ON;
+ _ctx->options |= OAES_OPTION_STEP_OFF;
+ _ctx->step_cb = NULL;
+ return OAES_RET_ARG3;
+ }
+ break;
+
+ case OAES_OPTION_STEP_OFF:
+ _ctx->options &= ~OAES_OPTION_STEP_ON;
+ _ctx->step_cb = NULL;
+ break;
+
+#endif // OAES_DEBUG
+
+ default:
+ return OAES_RET_ARG2;
+ }
+
+ _ctx->options |= option;
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_encrypt_block(
+ OAES_CTX * ctx, uint8_t * c, size_t c_len )
+{
+ size_t _i, _j;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == c )
+ return OAES_RET_ARG2;
+
+ if( c_len != OAES_BLOCK_SIZE )
+ return OAES_RET_ARG3;
+
+ if( NULL == _ctx->key )
+ return OAES_RET_NOKEY;
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "input", 1, NULL );
+#endif // OAES_DEBUG
+
+ // AddRoundKey(State, K0)
+ for( _i = 0; _i < c_len; _i++ )
+ c[_i] = c[_i] ^ _ctx->key->exp_data[_i];
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ {
+ _ctx->step_cb( _ctx->key->exp_data, "k_sch", 1, NULL );
+ _ctx->step_cb( c, "k_add", 1, NULL );
+ }
+#endif // OAES_DEBUG
+
+ // for round = 1 step 1 to Nr–1
+ for( _i = 1; _i < _ctx->key->num_keys - 1; _i++ )
+ {
+ // SubBytes(state)
+ for( _j = 0; _j < c_len; _j++ )
+ oaes_sub_byte( c + _j );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "s_box", _i, NULL );
+#endif // OAES_DEBUG
+
+ // ShiftRows(state)
+ oaes_shift_rows( c );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "s_row", _i, NULL );
+#endif // OAES_DEBUG
+
+ // MixColumns(state)
+ oaes_mix_cols( c );
+ oaes_mix_cols( c + 4 );
+ oaes_mix_cols( c + 8 );
+ oaes_mix_cols( c + 12 );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "m_col", _i, NULL );
+#endif // OAES_DEBUG
+
+ // AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+ for( _j = 0; _j < c_len; _j++ )
+ c[_j] = c[_j] ^
+ _ctx->key->exp_data[_i * OAES_RKEY_LEN * OAES_COL_LEN + _j];
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ {
+ _ctx->step_cb( _ctx->key->exp_data + _i * OAES_RKEY_LEN * OAES_COL_LEN,
+ "k_sch", _i, NULL );
+ _ctx->step_cb( c, "k_add", _i, NULL );
+ }
+#endif // OAES_DEBUG
+
+ }
+
+ // SubBytes(state)
+ for( _i = 0; _i < c_len; _i++ )
+ oaes_sub_byte( c + _i );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "s_box", _ctx->key->num_keys - 1, NULL );
+#endif // OAES_DEBUG
+
+ // ShiftRows(state)
+ oaes_shift_rows( c );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "s_row", _ctx->key->num_keys - 1, NULL );
+#endif // OAES_DEBUG
+
+ // AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+ for( _i = 0; _i < c_len; _i++ )
+ c[_i] = c[_i] ^ _ctx->key->exp_data[
+ ( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN + _i ];
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ {
+ _ctx->step_cb( _ctx->key->exp_data +
+ ( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN,
+ "k_sch", _ctx->key->num_keys - 1, NULL );
+ _ctx->step_cb( c, "output", _ctx->key->num_keys - 1, NULL );
+ }
+#endif // OAES_DEBUG
+
+ return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_decrypt_block(
+ OAES_CTX * ctx, uint8_t * c, size_t c_len )
+{
+ size_t _i, _j;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == c )
+ return OAES_RET_ARG2;
+
+ if( c_len != OAES_BLOCK_SIZE )
+ return OAES_RET_ARG3;
+
+ if( NULL == _ctx->key )
+ return OAES_RET_NOKEY;
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "iinput", _ctx->key->num_keys - 1, NULL );
+#endif // OAES_DEBUG
+
+ // AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+ for( _i = 0; _i < c_len; _i++ )
+ c[_i] = c[_i] ^ _ctx->key->exp_data[
+ ( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN + _i ];
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ {
+ _ctx->step_cb( _ctx->key->exp_data +
+ ( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN,
+ "ik_sch", _ctx->key->num_keys - 1, NULL );
+ _ctx->step_cb( c, "ik_add", _ctx->key->num_keys - 1, NULL );
+ }
+#endif // OAES_DEBUG
+
+ for( _i = _ctx->key->num_keys - 2; _i > 0; _i-- )
+ {
+ // InvShiftRows(state)
+ oaes_inv_shift_rows( c );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "is_row", _i, NULL );
+#endif // OAES_DEBUG
+
+ // InvSubBytes(state)
+ for( _j = 0; _j < c_len; _j++ )
+ oaes_inv_sub_byte( c + _j );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "is_box", _i, NULL );
+#endif // OAES_DEBUG
+
+ // AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+ for( _j = 0; _j < c_len; _j++ )
+ c[_j] = c[_j] ^
+ _ctx->key->exp_data[_i * OAES_RKEY_LEN * OAES_COL_LEN + _j];
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ {
+ _ctx->step_cb( _ctx->key->exp_data + _i * OAES_RKEY_LEN * OAES_COL_LEN,
+ "ik_sch", _i, NULL );
+ _ctx->step_cb( c, "ik_add", _i, NULL );
+ }
+#endif // OAES_DEBUG
+
+ // InvMixColums(state)
+ oaes_inv_mix_cols( c );
+ oaes_inv_mix_cols( c + 4 );
+ oaes_inv_mix_cols( c + 8 );
+ oaes_inv_mix_cols( c + 12 );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "im_col", _i, NULL );
+#endif // OAES_DEBUG
+
+ }
+
+ // InvShiftRows(state)
+ oaes_inv_shift_rows( c );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "is_row", 1, NULL );
+#endif // OAES_DEBUG
+
+ // InvSubBytes(state)
+ for( _i = 0; _i < c_len; _i++ )
+ oaes_inv_sub_byte( c + _i );
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ _ctx->step_cb( c, "is_box", 1, NULL );
+#endif // OAES_DEBUG
+
+ // AddRoundKey(state, w[0, Nb-1])
+ for( _i = 0; _i < c_len; _i++ )
+ c[_i] = c[_i] ^ _ctx->key->exp_data[_i];
+
+#ifdef OAES_DEBUG
+ if( _ctx->step_cb )
+ {
+ _ctx->step_cb( _ctx->key->exp_data, "ik_sch", 1, NULL );
+ _ctx->step_cb( c, "ioutput", 1, NULL );
+ }
+#endif // OAES_DEBUG
+
+ return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_encrypt( OAES_CTX * ctx,
+ const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len )
+{
+ size_t _i, _j, _c_len_in, _c_data_len;
+ size_t _pad_len = m_len % OAES_BLOCK_SIZE == 0 ?
+ 0 : OAES_BLOCK_SIZE - m_len % OAES_BLOCK_SIZE;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+ OAES_RET _rc = OAES_RET_SUCCESS;
+ uint8_t _flags = _pad_len ? OAES_FLAG_PAD : 0;
+
+ if( NULL == _ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == m )
+ return OAES_RET_ARG2;
+
+ if( NULL == c_len )
+ return OAES_RET_ARG5;
+
+ _c_len_in = *c_len;
+ // data + pad
+ _c_data_len = m_len + _pad_len;
+ // header + iv + data + pad
+ *c_len = 2 * OAES_BLOCK_SIZE + m_len + _pad_len;
+
+ if( NULL == c )
+ return OAES_RET_SUCCESS;
+
+ if( _c_len_in < *c_len )
+ return OAES_RET_BUF;
+
+ if( NULL == _ctx->key )
+ return OAES_RET_NOKEY;
+
+ // fill with random data first
+ for( _i = 0; _i < OAES_BLOCK_SIZE; _i++ )
+ c[_i] = (uint8_t) OAES_RAND(_ctx->rctx);
+ // header
+ memcpy(c + 6, &_ctx->options, sizeof(_ctx->options));
+ memcpy(c + 8, &_flags, sizeof(_flags));
+ // iv
+ memcpy(c + OAES_BLOCK_SIZE, _ctx->iv, OAES_BLOCK_SIZE );
+ // data
+ memcpy(c + 2 * OAES_BLOCK_SIZE, m, m_len );
+
+ for( _i = 0; _i < _c_data_len; _i += OAES_BLOCK_SIZE )
+ {
+ uint8_t _block[OAES_BLOCK_SIZE];
+ size_t _block_size = min( m_len - _i, OAES_BLOCK_SIZE );
+
+ memcpy( _block, c + 2 * OAES_BLOCK_SIZE + _i, _block_size );
+
+ // insert pad
+ for( _j = 0; _j < OAES_BLOCK_SIZE - _block_size; _j++ )
+ _block[ _block_size + _j ] = _j + 1;
+
+ // CBC
+ if( _ctx->options & OAES_OPTION_CBC )
+ {
+ for( _j = 0; _j < OAES_BLOCK_SIZE; _j++ )
+ _block[_j] = _block[_j] ^ _ctx->iv[_j];
+ }
+
+ _rc = _rc ||
+ oaes_encrypt_block( ctx, _block, OAES_BLOCK_SIZE );
+ memcpy( c + 2 * OAES_BLOCK_SIZE + _i, _block, OAES_BLOCK_SIZE );
+
+ if( _ctx->options & OAES_OPTION_CBC )
+ memcpy( _ctx->iv, _block, OAES_BLOCK_SIZE );
+ }
+
+ return _rc;
+}
+
+OAES_RET oaes_decrypt( OAES_CTX * ctx,
+ const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len )
+{
+ size_t _i, _j, _m_len_in;
+ oaes_ctx * _ctx = (oaes_ctx *) ctx;
+ OAES_RET _rc = OAES_RET_SUCCESS;
+ uint8_t _iv[OAES_BLOCK_SIZE];
+ uint8_t _flags;
+ OAES_OPTION _options;
+
+ if( NULL == ctx )
+ return OAES_RET_ARG1;
+
+ if( NULL == c )
+ return OAES_RET_ARG2;
+
+ if( c_len % OAES_BLOCK_SIZE )
+ return OAES_RET_ARG3;
+
+ if( NULL == m_len )
+ return OAES_RET_ARG5;
+
+ _m_len_in = *m_len;
+ *m_len = c_len - 2 * OAES_BLOCK_SIZE;
+
+ if( NULL == m )
+ return OAES_RET_SUCCESS;
+
+ if( _m_len_in < *m_len )
+ return OAES_RET_BUF;
+
+ if( NULL == _ctx->key )
+ return OAES_RET_NOKEY;
+
+ // options
+ memcpy(&_options, c + 6, sizeof(_options));
+ // validate that all options are valid
+ if( _options & ~(
+ OAES_OPTION_ECB
+ | OAES_OPTION_CBC
+#ifdef OAES_DEBUG
+ | OAES_OPTION_STEP_ON
+ | OAES_OPTION_STEP_OFF
+#endif // OAES_DEBUG
+ ) )
+ return OAES_RET_HEADER;
+ if( ( _options & OAES_OPTION_ECB ) &&
+ ( _options & OAES_OPTION_CBC ) )
+ return OAES_RET_HEADER;
+ if( _options == OAES_OPTION_NONE )
+ return OAES_RET_HEADER;
+
+ // flags
+ memcpy(&_flags, c + 8, sizeof(_flags));
+ // validate that all flags are valid
+ if( _flags & ~(
+ OAES_FLAG_PAD
+ ) )
+ return OAES_RET_HEADER;
+
+ // iv
+ memcpy( _iv, c + OAES_BLOCK_SIZE, OAES_BLOCK_SIZE);
+ // data + pad
+ memcpy( m, c + 2 * OAES_BLOCK_SIZE, *m_len );
+
+ for( _i = 0; _i < *m_len; _i += OAES_BLOCK_SIZE )
+ {
+ if( ( _options & OAES_OPTION_CBC ) && _i > 0 )
+ memcpy( _iv, c + OAES_BLOCK_SIZE + _i, OAES_BLOCK_SIZE );
+
+ _rc = _rc ||
+ oaes_decrypt_block( ctx, m + _i, min( *m_len - _i, OAES_BLOCK_SIZE ) );
+
+ // CBC
+ if( _options & OAES_OPTION_CBC )
+ {
+ for( _j = 0; _j < OAES_BLOCK_SIZE; _j++ )
+ m[ _i + _j ] = m[ _i + _j ] ^ _iv[_j];
+ }
+ }
+
+ // remove pad
+ if( _flags & OAES_FLAG_PAD )
+ {
+ int _is_pad = 1;
+ size_t _temp = (size_t) m[*m_len - 1];
+
+ if( _temp <= 0x00 || _temp > 0x0f )
+ return OAES_RET_HEADER;
+ for( _i = 0; _i < _temp; _i++ )
+ if( m[*m_len - 1 - _i] != _temp - _i )
+ _is_pad = 0;
+ if( _is_pad )
+ {
+ memset( m + *m_len - _temp, 0, _temp );
+ *m_len -= _temp;
+ }
+ else
+ return OAES_RET_HEADER;
+ }
+
+ return OAES_RET_SUCCESS;
+}
diff --git a/libs/gmp-clearkey/0.1/openaes/oaes_lib.h b/libs/gmp-clearkey/0.1/openaes/oaes_lib.h
new file mode 100644
index 000000000..2344480e8
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/openaes/oaes_lib.h
@@ -0,0 +1,168 @@
+/*
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2013, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#ifndef _OAES_LIB_H
+#define _OAES_LIB_H
+
+#include "oaes_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _WIN32
+# ifdef OAES_SHARED
+# ifdef oaes_lib_EXPORTS
+# define OAES_API __declspec(dllexport)
+# else
+# define OAES_API __declspec(dllimport)
+# endif
+# else
+# define OAES_API
+# endif
+#else
+# define OAES_API
+#endif // WIN32
+
+#define OAES_BLOCK_SIZE 16
+
+typedef void OAES_CTX;
+
+/*
+ * oaes_set_option() takes one of these values for its [option] parameter
+ * some options accept either an optional or a required [value] parameter
+ */
+// no option
+#define OAES_OPTION_NONE 0
+// enable ECB mode, disable CBC mode
+#define OAES_OPTION_ECB 1
+// enable CBC mode, disable ECB mode
+// value is optional, may pass uint8_t iv[OAES_BLOCK_SIZE] to specify
+// the value of the initialization vector, iv
+#define OAES_OPTION_CBC 2
+
+#ifdef OAES_DEBUG
+typedef int ( * oaes_step_cb ) (
+ const uint8_t state[OAES_BLOCK_SIZE],
+ const char * step_name,
+ int step_count,
+ void * user_data );
+// enable state stepping mode
+// value is required, must pass oaes_step_cb to receive the state at each step
+#define OAES_OPTION_STEP_ON 4
+// disable state stepping mode
+#define OAES_OPTION_STEP_OFF 8
+#endif // OAES_DEBUG
+
+typedef uint16_t OAES_OPTION;
+
+/*
+ * // usage:
+ *
+ * OAES_CTX * ctx = oaes_alloc();
+ * .
+ * .
+ * .
+ * {
+ * oaes_gen_key_xxx( ctx );
+ * {
+ * oaes_key_export( ctx, _buf, &_buf_len );
+ * // or
+ * oaes_key_export_data( ctx, _buf, &_buf_len );\
+ * }
+ * }
+ * // or
+ * {
+ * oaes_key_import( ctx, _buf, _buf_len );
+ * // or
+ * oaes_key_import_data( ctx, _buf, _buf_len );
+ * }
+ * .
+ * .
+ * .
+ * oaes_encrypt( ctx, m, m_len, c, &c_len );
+ * .
+ * .
+ * .
+ * oaes_decrypt( ctx, c, c_len, m, &m_len );
+ * .
+ * .
+ * .
+ * oaes_free( &ctx );
+ */
+
+OAES_API OAES_CTX * oaes_alloc();
+
+OAES_API OAES_RET oaes_free( OAES_CTX ** ctx );
+
+OAES_API OAES_RET oaes_set_option( OAES_CTX * ctx,
+ OAES_OPTION option, const void * value );
+
+OAES_API OAES_RET oaes_key_gen_128( OAES_CTX * ctx );
+
+OAES_API OAES_RET oaes_key_gen_192( OAES_CTX * ctx );
+
+OAES_API OAES_RET oaes_key_gen_256( OAES_CTX * ctx );
+
+// export key with header information
+// set data == NULL to get the required data_len
+OAES_API OAES_RET oaes_key_export( OAES_CTX * ctx,
+ uint8_t * data, size_t * data_len );
+
+// directly export the data from key
+// set data == NULL to get the required data_len
+OAES_API OAES_RET oaes_key_export_data( OAES_CTX * ctx,
+ uint8_t * data, size_t * data_len );
+
+// import key with header information
+OAES_API OAES_RET oaes_key_import( OAES_CTX * ctx,
+ const uint8_t * data, size_t data_len );
+
+// directly import data into key
+OAES_API OAES_RET oaes_key_import_data( OAES_CTX * ctx,
+ const uint8_t * data, size_t data_len );
+
+// set c == NULL to get the required c_len
+OAES_API OAES_RET oaes_encrypt( OAES_CTX * ctx,
+ const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len );
+
+// set m == NULL to get the required m_len
+OAES_API OAES_RET oaes_decrypt( OAES_CTX * ctx,
+ const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len );
+
+// set buf == NULL to get the required buf_len
+OAES_API OAES_RET oaes_sprintf(
+ char * buf, size_t * buf_len, const uint8_t * data, size_t data_len );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _OAES_LIB_H
diff --git a/libs/gmp-clearkey/0.1/openaes/standard.h b/libs/gmp-clearkey/0.1/openaes/standard.h
new file mode 100644
index 000000000..fbe6ef5be
--- /dev/null
+++ b/libs/gmp-clearkey/0.1/openaes/standard.h
@@ -0,0 +1,57 @@
+/*
+------------------------------------------------------------------------------
+Standard definitions and types, Bob Jenkins
+------------------------------------------------------------------------------
+*/
+#ifndef STANDARD
+# define STANDARD
+# ifndef STDIO
+# include <stdio.h>
+# define STDIO
+# endif
+# ifndef STDDEF
+# include <stddef.h>
+# define STDDEF
+# endif
+typedef unsigned long long ub8;
+#define UB8MAXVAL 0xffffffffffffffffLL
+#define UB8BITS 64
+typedef signed long long sb8;
+#define SB8MAXVAL 0x7fffffffffffffffLL
+typedef unsigned long int ub4; /* unsigned 4-byte quantities */
+#define UB4MAXVAL 0xffffffff
+typedef signed long int sb4;
+#define UB4BITS 32
+#define SB4MAXVAL 0x7fffffff
+typedef unsigned short int ub2;
+#define UB2MAXVAL 0xffff
+#define UB2BITS 16
+typedef signed short int sb2;
+#define SB2MAXVAL 0x7fff
+typedef unsigned char ub1;
+#define UB1MAXVAL 0xff
+#define UB1BITS 8
+typedef signed char sb1; /* signed 1-byte quantities */
+#define SB1MAXVAL 0x7f
+typedef int word; /* fastest type available */
+
+#define bis(target,mask) ((target) |= (mask))
+#define bic(target,mask) ((target) &= ~(mask))
+#define bit(target,mask) ((target) & (mask))
+// #ifndef min
+// # define min(a,b) (((a)<(b)) ? (a) : (b))
+// #endif /* min */
+// #ifndef max
+// # define max(a,b) (((a)<(b)) ? (b) : (a))
+// #endif /* max */
+#ifndef align
+# define align(a) (((ub4)a+(sizeof(void *)-1))&(~(sizeof(void *)-1)))
+#endif /* align */
+// #ifndef abs
+// # define abs(a) (((a)>0) ? (a) : -(a))
+// #endif
+#define TRUE 1
+#define FALSE 0
+#define SUCCESS 0 /* 1 on VAX */
+
+#endif /* STANDARD */
diff --git a/libs/moz.build b/libs/moz.build
index 8a4a5758f..4442628c9 100644
--- a/libs/moz.build
+++ b/libs/moz.build
@@ -44,6 +44,9 @@ if CONFIG['CPU_ARCH'] == 'arm':
if CONFIG['MOZ_AV1']:
DIRS += ['libaom']
+if CONFIG['MOZ_EME']:
+ DIRS += ['gmp-clearkey/0.1']
+
if CONFIG['MOZ_ENABLE_SKIA']:
DIRS += ['skia']
diff --git a/modules/ForgetAboutSite.jsm b/modules/ForgetAboutSite.jsm
index f2f4cd58e..9d7e512a8 100644
--- a/modules/ForgetAboutSite.jsm
+++ b/modules/ForgetAboutSite.jsm
@@ -84,6 +84,15 @@ this.ForgetAboutSite = {
throw new Error("Exception thrown while clearning cookies: " + ex);
}));
+ // EME
+ promises.push(Task.spawn(function*() {
+ let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
+ getService(Ci.mozIGeckoMediaPluginChromeService);
+ mps.forgetThisSite(aDomain, JSON.stringify({}));
+ }).catch(ex => {
+ throw new Error("Exception thrown while clearing Encrypted Media Extensions: " + ex);
+ }));
+
// Plugin data
const phInterface = Ci.nsIPluginHost;
const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py
index 55da508e4..876a53a81 100644
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -925,6 +925,16 @@ class GTestCommands(MachCommandBase):
# https://code.google.com/p/googletest/wiki/AdvancedGuide#Running_Test_Programs:_Advanced_Options
gtest_env = {b'GTEST_FILTER': gtest_filter}
+ # Note: we must normalize the path here so that gtest on Windows sees
+ # a MOZ_GMP_PATH which has only Windows dir seperators, because
+ # nsILocalFile cannot open the paths with non-Windows dir seperators.
+ xre_path = os.path.join(os.path.normpath(self.topobjdir), "dist", "bin")
+ gtest_env["MOZ_XRE_DIR"] = xre_path
+ gtest_env["MOZ_GMP_PATH"] = os.pathsep.join(
+ os.path.join(xre_path, p, "1.0")
+ for p in ('gmp-fake', 'gmp-fakeopenh264')
+ )
+
gtest_env[b"MOZ_RUN_GTEST"] = b"True"
if shuffle:
diff --git a/system/graphics/layers/ipc/PAPZCTreeManager.ipdl b/system/graphics/layers/ipc/PAPZCTreeManager.ipdl
index 134a222ad..21d899f91 100644
--- a/system/graphics/layers/ipc/PAPZCTreeManager.ipdl
+++ b/system/graphics/layers/ipc/PAPZCTreeManager.ipdl
@@ -8,10 +8,6 @@ include "ipc/nsGUIEventIPC.h";
include protocol PCompositorBridge;
-// Workaround to prevent error if PContentChild.cpp & PAPZCTreeManagerChild.cpp
-// are put into different UnifiedProtocolsXX.cpp files.
-include "mozilla/dom/TabMessageUtils.h";
-
using CSSRect from "Units.h";
using LayoutDeviceCoord from "Units.h";
using LayoutDeviceIntPoint from "Units.h";
diff --git a/system/graphics/thebes/gfxPrefs.cpp b/system/graphics/thebes/gfxPrefs.cpp
index 9ec9b638a..72bc58d78 100644
--- a/system/graphics/thebes/gfxPrefs.cpp
+++ b/system/graphics/thebes/gfxPrefs.cpp
@@ -111,7 +111,7 @@ gfxPrefs::Pref::SetChangeCallback(ChangeCallback aCallback)
FireChangeCallback();
}
-// On lightweight processes such as for GPU, XPCOM is not initialized,
+// On lightweight processes such as for GMP and GPU, XPCOM is not initialized,
// and therefore we don't have access to Preferences. When XPCOM is not
// available we rely on manual synchronization of gfxPrefs values over IPC.
/* static */ bool
diff --git a/system/platform-prefs.js b/system/platform-prefs.js
index 2a8acb7bc..934796ee6 100644
--- a/system/platform-prefs.js
+++ b/system/platform-prefs.js
@@ -393,6 +393,9 @@ pref("media.libavcodec.allow-obsolete", false);
#if defined(MOZ_FFVPX)
pref("media.ffvpx.enabled", true);
#endif
+pref("media.gmp.decoder.enabled", false);
+pref("media.gmp.decoder.aac", 0);
+pref("media.gmp.decoder.h264", 0);
#ifdef MOZ_RAW
pref("media.raw.enabled", true);
#endif
@@ -401,6 +404,13 @@ pref("media.opus.enabled", true);
pref("media.wave.enabled", true);
pref("media.webm.enabled", true);
+// GMP storage version number. At startup we check the version against
+// media.gmp.storage.version.observed, and if the versions don't match,
+// we clear storage and set media.gmp.storage.version.observed=expected.
+// This provides a mechanism to clear GMP storage when non-compatible
+// changes are made.
+pref("media.gmp.storage.version.expected", 1);
+
// Filter what triggers user notifications.
// See DecoderDoctorDocumentWatcher::ReportAnalysis for details.
pref("media.decoder-doctor.notifications-allowed", "MediaWMFNeeded,MediaWidevineNoWMFNoSilverlight,MediaCannotInitializePulseAudio,MediaCannotPlayNoDecoders,MediaUnsupportedLibavcodec");
@@ -4325,6 +4335,42 @@ pref("browser.search.reset.whitelist", "");
pref("browser.search.official", true);
#endif
+// GMPInstallManager prefs
+
+// User-settable override to media.gmp-manager.url for testing purposes.
+//pref("media.gmp-manager.url.override", "");
+
+// Update service URL for GMP install/updates:
+pref("media.gmp-manager.url", "https://aus5.mozilla.org/update/3/GMP/60.0/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
+
+// When |media.gmp-manager.cert.requireBuiltIn| is true or not specified the
+// final certificate and all certificates the connection is redirected to before
+// the final certificate for the url specified in the |media.gmp-manager.url|
+// preference must be built-in.
+pref("media.gmp-manager.cert.requireBuiltIn", true);
+
+// The |media.gmp-manager.certs.| preference branch contains branches that are
+// sequentially numbered starting at 1 that contain attribute name / value
+// pairs for the certificate used by the server that hosts the update xml file
+// as specified in the |media.gmp-manager.url| preference. When these preferences are
+// present the following conditions apply for a successful update check:
+// 1. the uri scheme must be https
+// 2. the preference name must exist as an attribute name on the certificate and
+// the value for the name must be the same as the value for the attribute name
+// on the certificate.
+// If these conditions aren't met it will be treated the same as when there is
+// no update available. This validation will not be performed when the
+// |media.gmp-manager.url.override| user preference has been set for testing updates or
+// when the |media.gmp-manager.cert.checkAttributes| preference is set to false. Also,
+// the |media.gmp-manager.url.override| preference should ONLY be used for testing.
+// IMPORTANT! app.update.certs.* prefs should also be updated if these
+// are updated.
+pref("media.gmp-manager.cert.checkAttributes", true);
+pref("media.gmp-manager.certs.1.issuerName", "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US");
+pref("media.gmp-manager.certs.1.commonName", "aus5.mozilla.org");
+pref("media.gmp-manager.certs.2.issuerName", "CN=thawte SSL CA - G2,O=\"thawte, Inc.\",C=US");
+pref("media.gmp-manager.certs.2.commonName", "aus5.mozilla.org");
+
// Whether or not to perform reader mode article parsing on page load.
// If this pref is disabled, we will never show a reader mode icon in the toolbar.
pref("reader.parse-on-load.enabled", true);
diff --git a/system/runtime/nsAppRunner.cpp b/system/runtime/nsAppRunner.cpp
index 32834580b..4071e7730 100644
--- a/system/runtime/nsAppRunner.cpp
+++ b/system/runtime/nsAppRunner.cpp
@@ -783,6 +783,7 @@ SYNC_ENUMS(DEFAULT, Default)
SYNC_ENUMS(PLUGIN, Plugin)
SYNC_ENUMS(CONTENT, Content)
SYNC_ENUMS(IPDLUNITTEST, IPDLUnitTest)
+SYNC_ENUMS(GMPLUGIN, GMPlugin)
SYNC_ENUMS(GPU, GPU)
// .. and ensure that that is all of them:
diff --git a/system/runtime/nsEmbedFunctions.cpp b/system/runtime/nsEmbedFunctions.cpp
index def157cf8..841ea2a2d 100644
--- a/system/runtime/nsEmbedFunctions.cpp
+++ b/system/runtime/nsEmbedFunctions.cpp
@@ -63,6 +63,8 @@
#include "mozilla/ipc/XPCShellEnvironment.h"
#include "mozilla/WindowsDllBlocklist.h"
+#include "GMPProcessChild.h"
+#include "GMPLoader.h"
#include "mozilla/gfx/GPUProcessImpl.h"
#include "GeckoProfiler.h"
@@ -87,6 +89,10 @@ using mozilla::dom::ContentProcess;
using mozilla::dom::ContentParent;
using mozilla::dom::ContentChild;
+using mozilla::gmp::GMPLoader;
+using mozilla::gmp::CreateGMPLoader;
+using mozilla::gmp::GMPProcessChild;
+
using mozilla::ipc::TestShellParent;
using mozilla::ipc::TestShellCommandParent;
using mozilla::ipc::XPCShellEnvironment;
@@ -226,11 +232,17 @@ SetTaskbarGroupId(const nsString& aId)
nsresult
XRE_InitChildProcess(int aArgc,
- char* aArgv[])
+ char* aArgv[],
+ const XREChildData* aChildData)
{
NS_ENSURE_ARG_MIN(aArgc, 2);
NS_ENSURE_ARG_POINTER(aArgv);
NS_ENSURE_ARG_POINTER(aArgv[0]);
+ MOZ_ASSERT(aChildData);
+
+ // On non-Fennec Gecko, the GMPLoader code resides in plugin-container,
+ // and we must forward it through to the GMP code here.
+ GMPProcessChild::SetGMPLoader(aChildData->gmpLoader.get());
#if defined(XP_WIN)
// From the --attach-console support in nsNativeAppSupportWin.cpp, but
@@ -345,6 +357,9 @@ XRE_InitChildProcess(int aArgc,
// Content processes need the XPCOM/chromium frankenventloop
uiLoopType = MessageLoop::TYPE_MOZILLA_CHILD;
break;
+ case GeckoProcessType_GMPlugin:
+ uiLoopType = MessageLoop::TYPE_DEFAULT;
+ break;
default:
uiLoopType = MessageLoop::TYPE_UI;
break;
@@ -406,6 +421,10 @@ XRE_InitChildProcess(int aArgc,
#endif
break;
+ case GeckoProcessType_GMPlugin:
+ process = new gmp::GMPProcessChild(parentPID);
+ break;
+
case GeckoProcessType_GPU:
process = new gfx::GPUProcessImpl(parentPID);
break;
diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py
index f52a83672..6763952af 100644
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1499,6 +1499,14 @@ toolbar#nav-bar {
if not self.disable_leak_checking:
browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file
+ try:
+ gmp_path = self.getGMPPluginPath(options)
+ if gmp_path is not None:
+ browserEnv["MOZ_GMP_PATH"] = gmp_path
+ except EnvironmentError:
+ self.log.error('Could not find path to gmp-fake plugin!')
+ return None
+
if options.fatalAssertions:
browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
diff --git a/testing/mochitest/runtestsremote.py b/testing/mochitest/runtestsremote.py
index a6f264a36..8010703e9 100644
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -256,6 +256,10 @@ class MochiRemote(MochitestDesktop):
except mozdevice.DMError:
self.log.warning("Error getting device information")
+ def getGMPPluginPath(self, options):
+ # TODO: bug 1149374
+ return None
+
def buildBrowserEnv(self, options, debugger=False):
browserEnv = MochitestDesktop.buildBrowserEnv(
self,
diff --git a/xpcom/build/XREChildData.h b/xpcom/build/XREChildData.h
new file mode 100644
index 000000000..b4cd9895f
--- /dev/null
+++ b/xpcom/build/XREChildData.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XREChildData_h
+#define XREChildData_h
+
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace gmp {
+class GMPLoader;
+}
+}
+
+/**
+ * Data needed to start a child process.
+ */
+struct XREChildData
+{
+ /**
+ * Used to load the GMP binary.
+ */
+ mozilla::UniquePtr<mozilla::gmp::GMPLoader> gmpLoader;
+};
+
+#endif // XREChildData_h
diff --git a/xpcom/build/moz.build b/xpcom/build/moz.build
index a18751675..3e0752a85 100644
--- a/xpcom/build/moz.build
+++ b/xpcom/build/moz.build
@@ -9,6 +9,7 @@ EXPORTS += [
'nsXPCOMCIDInternal.h',
'nsXREAppData.h',
'nsXULAppAPI.h',
+ 'XREChildData.h',
'xrecore.h',
]
diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h
index 235837182..18ccca415 100644
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -18,6 +18,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/Vector.h"
#include "mozilla/TimeStamp.h"
+#include "XREChildData.h"
/**
* A directory service key which provides the platform-correct "application
@@ -396,6 +397,8 @@ enum GeckoProcessType
GeckoProcessType_IPDLUnitTest,
+ GeckoProcessType_GMPlugin, // Gecko Media Plugin
+
GeckoProcessType_GPU, // GPU and compositor process
GeckoProcessType_End,
@@ -407,6 +410,7 @@ static const char* const kGeckoProcessTypeString[] = {
"plugin",
"tab",
"ipdlunittest",
+ "geckomediaplugin",
"gpu"
};
@@ -420,9 +424,16 @@ XRE_API(const char*,
XRE_API(void,
XRE_SetProcessType, (const char* aProcessTypeString))
+namespace mozilla {
+namespace gmp {
+class GMPLoader;
+} // namespace gmp
+} // namespace mozilla
+
XRE_API(nsresult,
XRE_InitChildProcess, (int aArgc,
- char* aArgv[]))
+ char* aArgv[],
+ const XREChildData* aChildData))
XRE_API(GeckoProcessType,
XRE_GetProcessType, ())
diff --git a/xpcom/system/nsIXULRuntime.idl b/xpcom/system/nsIXULRuntime.idl
index a9f249ee7..fd92cf54a 100644
--- a/xpcom/system/nsIXULRuntime.idl
+++ b/xpcom/system/nsIXULRuntime.idl
@@ -73,7 +73,8 @@ interface nsIXULRuntime : nsISupports
const unsigned long PROCESS_TYPE_PLUGIN = 1;
const unsigned long PROCESS_TYPE_CONTENT = 2;
const unsigned long PROCESS_TYPE_IPDLUNITTEST = 3;
- const unsigned long PROCESS_TYPE_GPU = 4;
+ const unsigned long PROCESS_TYPE_GMPLUGIN = 4;
+ const unsigned long PROCESS_TYPE_GPU = 5;
/**
* The type of the caller's process. Returns one of the values above.