merge mozilla-inbound to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2017-03-24 14:24:21 +01:00
commit 7419b36815
147 changed files with 5880 additions and 2428 deletions

View File

@ -74,7 +74,7 @@ XPCOMUtils.defineLazyGetter(this, "standaloneStylesheets", () => {
});
class BasePopup {
constructor(extension, viewNode, popupURL, browserStyle, fixedWidth = false) {
constructor(extension, viewNode, popupURL, browserStyle, fixedWidth = false, blockParser = false) {
this.extension = extension;
this.popupURL = popupURL;
this.viewNode = viewNode;
@ -82,6 +82,7 @@ class BasePopup {
this.window = viewNode.ownerGlobal;
this.destroyed = false;
this.fixedWidth = fixedWidth;
this.blockParser = blockParser;
extension.callOnClose(this);
@ -117,9 +118,14 @@ class BasePopup {
this.destroyed = true;
this.browserLoadedDeferred.reject(new Error("Popup destroyed"));
BasePopup.instances.get(this.window).delete(this.extension);
return this.browserReady.then(() => {
this.destroyBrowser(this.browser, true);
this.browser.remove();
if (this.browser) {
this.destroyBrowser(this.browser, true);
this.browser.remove();
}
if (this.viewNode) {
this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
@ -131,8 +137,6 @@ class BasePopup {
this.panel.style.removeProperty("--panel-arrow-image-vertical");
}
BasePopup.instances.get(this.window).delete(this.extension);
this.browser = null;
this.viewNode = null;
});
@ -287,6 +291,7 @@ class BasePopup {
mm.sendAsyncMessage("Extension:InitBrowser", {
allowScriptsToClose: true,
blockParser: this.blockParser,
fixedWidth: this.fixedWidth,
maxWidth: 800,
maxHeight: 600,
@ -297,6 +302,12 @@ class BasePopup {
});
}
unblockParser() {
this.browserReady.then(browser => {
this.browser.messageManager.sendAsyncMessage("Extension:UnblockParser");
});
}
resizeBrowser({width, height, detail}) {
if (this.fixedWidth) {
// Figure out how much extra space we have on the side of the panel
@ -401,7 +412,7 @@ class PanelPopup extends BasePopup {
}
class ViewPopup extends BasePopup {
constructor(extension, window, popupURL, browserStyle, fixedWidth) {
constructor(extension, window, popupURL, browserStyle, fixedWidth, blockParser) {
let document = window.document;
// Create a temporary panel to hold the browser while it pre-loads its
@ -411,7 +422,7 @@ class ViewPopup extends BasePopup {
panel.setAttribute("type", "arrow");
document.getElementById("mainPopupSet").appendChild(panel);
super(extension, panel, popupURL, browserStyle, fixedWidth);
super(extension, panel, popupURL, browserStyle, fixedWidth, blockParser);
this.ignoreResizes = true;

View File

@ -122,6 +122,8 @@ BrowserAction.prototype = {
node.setAttribute("constrain-size", "true");
node.onmousedown = event => this.handleEvent(event);
node.onmouseover = event => this.handleEvent(event);
node.onmouseout = event => this.handleEvent(event);
this.updateButton(node, this.defaults);
},
@ -209,10 +211,10 @@ BrowserAction.prototype = {
let popupURL = this.getProperty(tab, "popup");
let enabled = this.getProperty(tab, "enabled");
if (popupURL && enabled) {
if (popupURL && enabled && (this.pendingPopup || !ViewPopup.for(this.extension, window))) {
// Add permission for the active tab so it will exist for the popup.
// Store the tab to revoke the permission during clearPopup.
if (!this.pendingPopup && !this.tabManager.hasActiveTabPermission(tab)) {
if (!this.tabManager.hasActiveTabPermission(tab)) {
this.tabManager.addActiveTabPermission(tab);
this.tabToRevokeDuringClearPopup = tab;
}
@ -242,6 +244,26 @@ BrowserAction.prototype = {
}
break;
case "mouseover": {
// Begin pre-loading the browser for the popup, so it's more likely to
// be ready by the time we get a complete click.
let tab = window.gBrowser.selectedTab;
let popupURL = this.getProperty(tab, "popup");
let enabled = this.getProperty(tab, "enabled");
if (popupURL && enabled && (this.pendingPopup || !ViewPopup.for(this.extension, window))) {
this.pendingPopup = this.getPopup(window, popupURL, true);
}
break;
}
case "mouseout":
if (this.pendingPopup) {
this.clearPopup();
}
break;
case "popupshowing":
const menu = event.target;
const trigger = menu.triggerNode;
@ -271,22 +293,28 @@ BrowserAction.prototype = {
* The browser window in which to create the popup.
* @param {string} popupURL
* The URL to load into the popup.
* @param {boolean} [blockParser = false]
* True if the HTML parser should initially be blocked.
* @returns {ViewPopup}
*/
getPopup(window, popupURL) {
getPopup(window, popupURL, blockParser = false) {
this.clearPopupTimeout();
let {pendingPopup} = this;
this.pendingPopup = null;
if (pendingPopup) {
if (pendingPopup.window === window && pendingPopup.popupURL === popupURL) {
if (!this.blockParser) {
pendingPopup.unblockParser();
}
return pendingPopup;
}
pendingPopup.destroy();
}
let fixedWidth = this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL;
return new ViewPopup(this.extension, window, popupURL, this.browserStyle, fixedWidth);
return new ViewPopup(this.extension, window, popupURL, this.browserStyle, fixedWidth, blockParser);
},
/**

View File

@ -7,6 +7,9 @@ let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}">
add_task(function* testBrowserActionClickCanceled() {
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
// Make sure the mouse isn't hovering over the browserAction widget.
EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "mouseover"}, window);
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {
@ -82,6 +85,9 @@ add_task(function* testBrowserActionClickCanceled() {
});
add_task(function* testBrowserActionDisabled() {
// Make sure the mouse isn't hovering over the browserAction widget.
EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "mouseover"}, window);
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {
@ -186,6 +192,9 @@ add_task(function* testBrowserActionTabPopulation() {
yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "http://example.com/");
yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
// Make sure the mouse isn't hovering over the browserAction widget.
EventUtils.synthesizeMouseAtCenter(win.gURLBar, {type: "mouseover"}, win);
yield extension.startup();
let widget = getBrowserActionWidget(extension).forWindow(win);

View File

@ -3,6 +3,9 @@
"use strict";
function* testExecuteBrowserActionWithOptions(options = {}) {
// Make sure the mouse isn't hovering over the browserAction widget.
EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "mouseover"}, window);
let extensionOptions = {};
extensionOptions.manifest = {

View File

@ -3,6 +3,9 @@
"use strict";
add_task(function* testIncognitoViews() {
// Make sure the mouse isn't hovering over the browserAction widget.
EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "mouseover"}, window);
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],

View File

@ -229,7 +229,9 @@ ContentPrincipal::GetOriginInternal(nsACString& aOrigin)
// of the spec, and the beginning of the origin attributes) is not present
// in the origin string
nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin);
NS_ENSURE_TRUE(standardURL, NS_ERROR_FAILURE);
if (!standardURL) {
return NS_ERROR_FAILURE;
}
rv = origin->GetAsciiSpec(aOrigin);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -6,9 +6,13 @@
const { addons, createClass, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react");
const { throttle } = require("devtools/client/inspector/shared/utils");
const Types = require("../types");
// The delay prior to executing the grid cell highlighting.
const GRID_CELL_MOUSEOVER_TIMEOUT = 150;
// Move SVG grid to the right 100 units, so that it is not flushed against the edge of
// layout border
const TRANSLATE_X = -100;
@ -35,6 +39,13 @@ module.exports = createClass({
};
},
componentWillMount() {
// Throttle the grid highlighting of grid cells. It makes the UX smoother by not
// lagging the grid cell highlighting if a lot of grid cells are mouseover in a
// quick succession.
this.highlightCell = throttle(this.highlightCell, GRID_CELL_MOUSEOVER_TIMEOUT);
},
componentWillReceiveProps({ grids }) {
this.setState({
selectedGrids: grids.filter(grid => grid.highlighted),
@ -66,6 +77,31 @@ module.exports = createClass({
return gridArea.name;
},
highlightCell({ target }) {
const {
grids,
onShowGridAreaHighlight,
onShowGridCellHighlight,
} = this.props;
const name = target.getAttribute("data-grid-area-name");
const id = target.getAttribute("data-grid-id");
const fragmentIndex = target.getAttribute("data-grid-fragment-index");
const color = target.getAttribute("stroke");
const rowNumber = target.getAttribute("data-grid-row");
const columnNumber = target.getAttribute("data-grid-column");
target.setAttribute("fill", color);
if (name) {
onShowGridAreaHighlight(grids[id].nodeFront, name, color);
}
if (fragmentIndex && rowNumber && columnNumber) {
onShowGridCellHighlight(grids[id].nodeFront, fragmentIndex,
rowNumber, columnNumber);
}
},
/**
* Renders the grid outline for the given grid container object.
*
@ -186,29 +222,9 @@ module.exports = createClass({
onShowGridCellHighlight(grids[id].nodeFront);
},
onMouseOverCell({ target }) {
const {
grids,
onShowGridAreaHighlight,
onShowGridCellHighlight,
} = this.props;
const name = target.getAttribute("data-grid-area-name");
const id = target.getAttribute("data-grid-id");
const fragmentIndex = target.getAttribute("data-grid-fragment-index");
const color = target.getAttribute("stroke");
const rowNumber = target.getAttribute("data-grid-row");
const columnNumber = target.getAttribute("data-grid-column");
target.setAttribute("fill", color);
if (name) {
onShowGridAreaHighlight(grids[id].nodeFront, name, color);
}
if (fragmentIndex && rowNumber && columnNumber) {
onShowGridCellHighlight(grids[id].nodeFront, fragmentIndex,
rowNumber, columnNumber);
}
onMouseOverCell(event) {
event.persist();
this.highlightCell(event);
},
render() {

View File

@ -122,6 +122,7 @@ const char* mozilla::dom::ContentPrefs::gInitPrefs[] = {
"media.decoder.recycle.enabled",
"media.dormant-on-pause-timeout-ms",
"media.eme.audio.blank",
"media.eme.chromium-api.enabled",
"media.eme.enabled",
"media.eme.video.blank",
"media.ffmpeg.enabled",

View File

@ -142,6 +142,7 @@ private:
DECL_MEDIA_PREF("media.gmp.decoder.h264", GMPH264Preferred, uint32_t, 0);
DECL_MEDIA_PREF("media.eme.audio.blank", EMEBlankAudio, bool, false);
DECL_MEDIA_PREF("media.eme.video.blank", EMEBlankVideo, bool, false);
DECL_MEDIA_PREF("media.eme.chromium-api.enabled", EMEChromiumAPIEnabled, bool, false);
// MediaDecoderStateMachine
DECL_MEDIA_PREF("media.suspend-bkgnd-video.enabled", MDSMSuspendBackgroundVideoEnabled, bool, false);

View File

@ -17,6 +17,7 @@
namespace mozilla {
class MediaRawData;
class ChromiumCDMProxy;
enum DecryptStatus {
Ok = 0,
@ -114,6 +115,7 @@ public:
// Uses the CDM to load a presistent session stored on disk.
// Calls MediaKeys::OnSessionActivated() when session is loaded.
virtual void LoadSession(PromiseId aPromiseId,
dom::MediaKeySessionType aSessionType,
const nsAString& aSessionId) = 0;
// Main thread only.
@ -223,6 +225,8 @@ public:
virtual uint32_t GetDecryptorId() { return 0; }
virtual ChromiumCDMProxy* AsChromiumCDMProxy() { return nullptr; }
protected:
virtual ~CDMProxy() {}

View File

@ -36,7 +36,7 @@ DetailedPromise::~DetailedPromise()
// GetPromiseState() == PromiseState::Rejected. But by now we've been
// unlinked, so don't have a reference to our actual JS Promise object
// anymore.
MaybeReportTelemetry(Failed);
MaybeReportTelemetry(kFailed);
}
void
@ -46,7 +46,7 @@ DetailedPromise::MaybeReject(nsresult aArg, const nsACString& aReason)
static_cast<uint32_t>(aArg), PromiseFlatCString(aReason).get());
EME_LOG("%s", msg.get());
MaybeReportTelemetry(Failed);
MaybeReportTelemetry(kFailed);
LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
@ -84,7 +84,7 @@ DetailedPromise::Create(nsIGlobalObject* aGlobal,
}
void
DetailedPromise::MaybeReportTelemetry(Status aStatus)
DetailedPromise::MaybeReportTelemetry(eStatus aStatus)
{
if (mResponded) {
return;
@ -95,8 +95,8 @@ DetailedPromise::MaybeReportTelemetry(Status aStatus)
}
uint32_t latency = (TimeStamp::Now() - mStartTime).ToMilliseconds();
EME_LOG("%s %s latency %ums reported via telemetry", mName.get(),
((aStatus == Succeeded) ? "succcess" : "failure"), latency);
Telemetry::HistogramID tid = (aStatus == Succeeded) ? mSuccessLatencyProbe.Value()
((aStatus == kSucceeded) ? "succcess" : "failure"), latency);
Telemetry::HistogramID tid = (aStatus == kSucceeded) ? mSuccessLatencyProbe.Value()
: mFailureLatencyProbe.Value();
Telemetry::Accumulate(tid, latency);
}

View File

@ -37,7 +37,7 @@ public:
void MaybeResolve(const T& aArg)
{
EME_LOG("%s promise resolved", mName.get());
MaybeReportTelemetry(Succeeded);
MaybeReportTelemetry(eStatus::kSucceeded);
Promise::MaybeResolve<T>(aArg);
}
@ -57,8 +57,8 @@ private:
Telemetry::HistogramID aFailureLatencyProbe);
virtual ~DetailedPromise();
enum Status { Succeeded, Failed };
void MaybeReportTelemetry(Status aStatus);
enum eStatus { kSucceeded, kFailed };
void MaybeReportTelemetry(eStatus aStatus);
nsCString mName;
bool mResponded;

View File

@ -396,7 +396,7 @@ MediaKeySession::Load(const nsAString& aSessionId, ErrorResult& aRv)
SetSessionId(aSessionId);
PromiseId pid = mKeys->StorePromise(promise);
mKeys->GetCDMProxy()->LoadSession(pid, aSessionId);
mKeys->GetCDMProxy()->LoadSession(pid, mSessionType, aSessionId);
EME_LOG("MediaKeySession[%p,'%s'] Load() sent to CDM, promiseId=%d",
this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);

View File

@ -99,8 +99,11 @@ MediaKeySystemAccess::CreateMediaKeys(ErrorResult& aRv)
static bool
HavePluginForKeySystem(const nsCString& aKeySystem)
{
bool havePlugin = HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
{ aKeySystem });
nsCString api = MediaPrefs::EMEChromiumAPIEnabled()
? NS_LITERAL_CSTRING(CHROMIUM_CDM_API)
: NS_LITERAL_CSTRING(GMP_API_DECRYPTOR);
bool havePlugin = HaveGMPFor(api, { aKeySystem });
#ifdef MOZ_WIDGET_ANDROID
// Check if we can use MediaDrm for this keysystem.
if (!havePlugin) {

View File

@ -32,6 +32,7 @@
#include "nsServiceManagerUtils.h"
#include "mozilla/dom/MediaKeySystemAccess.h"
#include "nsPrintfCString.h"
#include "ChromiumCDMProxy.h"
namespace mozilla {
@ -343,12 +344,23 @@ MediaKeys::CreateCDMProxy(nsIEventTarget* aMainThread)
} else
#endif
{
proxy = new GMPCDMProxy(this,
mKeySystem,
new MediaKeysGMPCrashHelper(this),
mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
mConfig.mPersistentState == MediaKeysRequirement::Required,
aMainThread);
if (MediaPrefs::EMEChromiumAPIEnabled()) {
proxy = new ChromiumCDMProxy(
this,
mKeySystem,
new MediaKeysGMPCrashHelper(this),
mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
mConfig.mPersistentState == MediaKeysRequirement::Required,
aMainThread);
} else {
proxy = new GMPCDMProxy(
this,
mKeySystem,
new MediaKeysGMPCrashHelper(this),
mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
mConfig.mPersistentState == MediaKeysRequirement::Required,
aMainThread);
}
}
return proxy.forget();
}

View File

@ -101,6 +101,7 @@ MediaDrmCDMProxy::CreateSession(uint32_t aCreateSessionToken,
void
MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId,
dom::MediaKeySessionType aSessionType,
const nsAString& aSessionId)
{
// TODO: Implement LoadSession.

View File

@ -13,6 +13,7 @@
#include "mozilla/CDMProxy.h"
#include "mozilla/CDMCaps.h"
#include "mozilla/dom/MediaKeys.h"
#include "mozilla/dom/MediaKeySession.h"
#include "mozilla/MediaDrmProxySupport.h"
#include "mozilla/UniquePtr.h"
@ -47,6 +48,7 @@ public:
nsTArray<uint8_t>& aInitData) override;
void LoadSession(PromiseId aPromiseId,
dom::MediaKeySessionType aSessionType,
const nsAString& aSessionId) override;
void SetServerCertificate(PromiseId aPromiseId,

View File

@ -0,0 +1,133 @@
/* -*- 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 "ChromiumCDMAdapter.h"
#include "content_decryption_module.h"
#include "VideoUtils.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"
#include "WidevineUtils.h"
#include "GMPLog.h"
// Declared in WidevineAdapter.cpp.
extern const GMPPlatformAPI* sPlatform;
namespace mozilla {
void
ChromiumCDMAdapter::SetAdaptee(PRLibrary* aLib)
{
mLib = aLib;
}
void*
ChromiumCdmHost(int aHostInterfaceVersion, void* aUserData)
{
CDM_LOG("ChromiumCdmHostFunc(%d, %p)", aHostInterfaceVersion, aUserData);
if (aHostInterfaceVersion != cdm::Host_8::kVersion) {
return nullptr;
}
return static_cast<cdm::Host_8*>(aUserData);
}
#define STRINGIFY(s) _STRINGIFY(s)
#define _STRINGIFY(s) #s
GMPErr
ChromiumCDMAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
{
CDM_LOG("ChromiumCDMAdapter::GMPInit");
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;
}
CDM_LOG(STRINGIFY(INITIALIZE_CDM_MODULE)"()");
init();
return GMPNoErr;
}
GMPErr
ChromiumCDMAdapter::GMPGetAPI(const char* aAPIName,
void* aHostAPI,
void** aPluginAPI,
uint32_t aDecryptorId)
{
CDM_LOG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p",
aAPIName,
aHostAPI,
aPluginAPI,
aDecryptorId,
this);
if (!strcmp(aAPIName, CHROMIUM_CDM_API)) {
auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
if (!create) {
CDM_LOG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p "
"FAILED to find CreateCdmInstance",
aAPIName,
aHostAPI,
aPluginAPI,
aDecryptorId,
this);
return GMPGenericErr;
}
auto cdm = reinterpret_cast<cdm::ContentDecryptionModule*>(
create(cdm::ContentDecryptionModule::kVersion,
kEMEKeySystemWidevine.get(),
kEMEKeySystemWidevine.Length(),
&ChromiumCdmHost,
aHostAPI));
if (!cdm) {
CDM_LOG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p "
"FAILED to create cdm",
aAPIName,
aHostAPI,
aPluginAPI,
aDecryptorId,
this);
return GMPGenericErr;
}
CDM_LOG("cdm: 0x%p", cdm);
*aPluginAPI = cdm;
}
return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
}
void
ChromiumCDMAdapter::GMPShutdown()
{
CDM_LOG("ChromiumCDMAdapter::GMPShutdown()");
decltype(::DeinitializeCdmModule)* deinit;
deinit = (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
if (deinit) {
CDM_LOG("DeinitializeCdmModule()");
deinit();
}
}
/* static */
bool
ChromiumCDMAdapter::Supports(int32_t aModuleVersion,
int32_t aInterfaceVersion,
int32_t aHostVersion)
{
return aModuleVersion == CDM_MODULE_VERSION &&
aInterfaceVersion == cdm::ContentDecryptionModule::kVersion &&
aHostVersion == cdm::Host_8::kVersion;
}
} // namespace mozilla

View File

@ -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 ChromiumAdapter_h_
#define ChromiumAdapter_h_
#include "GMPLoader.h"
#include "prlink.h"
#include "GMPUtils.h"
struct GMPPlatformAPI;
namespace mozilla {
class ChromiumCDMAdapter : 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;
static bool Supports(int32_t aModuleVersion,
int32_t aInterfaceVersion,
int32_t aHostVersion);
private:
PRLibrary* mLib = nullptr;
};
} // namespace mozilla
#endif // ChromiumAdapter_h_

View File

@ -0,0 +1,620 @@
/* -*- 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 "ChromiumCDMChild.h"
#include "GMPContentChild.h"
#include "WidevineUtils.h"
#include "WidevineFileIO.h"
#include "WidevineVideoFrame.h"
#include "GMPLog.h"
#include "GMPPlatform.h"
#include "mozilla/Unused.h"
#include "nsPrintfCString.h"
#include "base/time.h"
namespace mozilla {
namespace gmp {
ChromiumCDMChild::ChromiumCDMChild(GMPContentChild* aPlugin)
: mPlugin(aPlugin)
{
MOZ_ASSERT(IsOnMessageLoopThread());
}
void
ChromiumCDMChild::Init(cdm::ContentDecryptionModule_8* aCDM)
{
MOZ_ASSERT(IsOnMessageLoopThread());
mCDM = aCDM;
MOZ_ASSERT(mCDM);
}
void
ChromiumCDMChild::TimerExpired(void* aContext)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::TimerExpired(context=0x%p)", aContext);
if (mCDM) {
mCDM->TimerExpired(aContext);
}
}
cdm::Buffer*
ChromiumCDMChild::Allocate(uint32_t aCapacity)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::Allocate(capacity=%" PRIu32 ")", aCapacity);
return new WidevineBuffer(aCapacity);
}
void
ChromiumCDMChild::SetTimer(int64_t aDelayMs, void* aContext)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::SetTimer(delay=%" PRId64 ", context=0x%p)",
aDelayMs,
aContext);
RefPtr<ChromiumCDMChild> self(this);
SetTimerOnMainThread(NewGMPTask([self, aContext]() {
self->TimerExpired(aContext);
}), aDelayMs);
}
cdm::Time
ChromiumCDMChild::GetCurrentWallTime()
{
MOZ_ASSERT(IsOnMessageLoopThread());
return base::Time::Now().ToDoubleT();
}
void
ChromiumCDMChild::OnResolveNewSessionPromise(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%" PRIu32
", sid=%s)",
aPromiseId,
aSessionId);
if (mLoadSessionPromiseIds.Contains(aPromiseId)) {
// As laid out in the Chromium CDM API, if the CDM fails to load
// a session it calls OnResolveNewSessionPromise with nullptr as the sessionId.
// We can safely assume this means that we have failed to load a session
// as the other methods specify calling 'OnRejectPromise' when they fail.
bool loadSuccessful = aSessionId != nullptr;
GMP_LOG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%u, sid=%s) "
"resolving %s load session ",
aPromiseId,
aSessionId,
(loadSuccessful ? "successful" : "failed"));
Unused << SendResolveLoadSessionPromise(aPromiseId, loadSuccessful);
mLoadSessionPromiseIds.RemoveElement(aPromiseId);
return;
}
Unused << SendOnResolveNewSessionPromise(aPromiseId,
nsCString(aSessionId, aSessionIdSize));
}
void ChromiumCDMChild::OnResolvePromise(uint32_t aPromiseId)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::OnResolvePromise(pid=%" PRIu32 ")", aPromiseId);
Unused << SendOnResolvePromise(aPromiseId);
}
void
ChromiumCDMChild::OnRejectPromise(uint32_t aPromiseId,
cdm::Error aError,
uint32_t aSystemCode,
const char* aErrorMessage,
uint32_t aErrorMessageSize)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::OnRejectPromise(pid=%" PRIu32 ", err=%" PRIu32
" code=%" PRIu32 ", msg='%s')",
aPromiseId,
aError,
aSystemCode,
aErrorMessage);
Unused << SendOnRejectPromise(aPromiseId,
static_cast<uint32_t>(aError),
aSystemCode,
nsCString(aErrorMessage, aErrorMessageSize));
}
void
ChromiumCDMChild::OnSessionMessage(const char* aSessionId,
uint32_t aSessionIdSize,
cdm::MessageType aMessageType,
const char* aMessage,
uint32_t aMessageSize,
const char* aLegacyDestinationUrl,
uint32_t aLegacyDestinationUrlLength)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::OnSessionMessage(sid=%s, type=%" PRIu32
" size=%" PRIu32 ")",
aSessionId,
aMessageType,
aMessageSize);
nsTArray<uint8_t> message;
message.AppendElements(aMessage, aMessageSize);
Unused << SendOnSessionMessage(nsCString(aSessionId, aSessionIdSize),
static_cast<uint32_t>(aMessageType),
message);
}
static nsCString
ToString(const cdm::KeyInformation* aKeysInfo, uint32_t aKeysInfoCount)
{
nsCString str;
for (uint32_t i = 0; i < aKeysInfoCount; i++) {
nsCString keyId;
const cdm::KeyInformation& key = aKeysInfo[i];
for (size_t k = 0; k < key.key_id_size; k++) {
keyId.Append(nsPrintfCString("%hhX", key.key_id[k]));
}
if (!str.IsEmpty()) {
str.AppendLiteral(",");
}
str.Append(keyId);
str.AppendLiteral("=");
str.AppendInt(key.status);
}
return str;
}
void
ChromiumCDMChild::OnSessionKeysChange(const char *aSessionId,
uint32_t aSessionIdSize,
bool aHasAdditionalUsableKey,
const cdm::KeyInformation* aKeysInfo,
uint32_t aKeysInfoCount)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::OnSessionKeysChange(sid=%s) keys={%s}",
aSessionId,
ToString(aKeysInfo, aKeysInfoCount).get());
nsTArray<CDMKeyInformation> keys;
keys.SetCapacity(aKeysInfoCount);
for (uint32_t i = 0; i < aKeysInfoCount; i++) {
const cdm::KeyInformation& key = aKeysInfo[i];
nsTArray<uint8_t> kid;
kid.AppendElements(key.key_id, key.key_id_size);
keys.AppendElement(CDMKeyInformation(kid, key.status, key.system_code));
}
Unused << SendOnSessionKeysChange(nsCString(aSessionId, aSessionIdSize),
keys);
}
void
ChromiumCDMChild::OnExpirationChange(const char* aSessionId,
uint32_t aSessionIdSize,
cdm::Time aNewExpiryTime)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::OnExpirationChange(sid=%s, time=%lf)",
aSessionId,
aNewExpiryTime);
Unused << SendOnExpirationChange(nsCString(aSessionId, aSessionIdSize),
aNewExpiryTime);
}
void
ChromiumCDMChild::OnSessionClosed(const char* aSessionId,
uint32_t aSessionIdSize)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::OnSessionClosed(sid=%s)", aSessionId);
Unused << SendOnSessionClosed(nsCString(aSessionId, aSessionIdSize));
}
void
ChromiumCDMChild::OnLegacySessionError(const char* aSessionId,
uint32_t aSessionIdLength,
cdm::Error aError,
uint32_t aSystemCode,
const char* aErrorMessage,
uint32_t aErrorMessageLength)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::OnLegacySessionError(sid=%s, error=%" PRIu32
" msg='%s')",
aSessionId,
aError,
aErrorMessage);
Unused << SendOnLegacySessionError(
nsCString(aSessionId, aSessionIdLength),
static_cast<uint32_t>(aError),
aSystemCode,
nsCString(aErrorMessage, aErrorMessageLength));
}
cdm::FileIO*
ChromiumCDMChild::CreateFileIO(cdm::FileIOClient * aClient)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::CreateFileIO()");
if (!mPersistentStateAllowed) {
return nullptr;
}
return new WidevineFileIO(aClient);
}
bool
ChromiumCDMChild::IsOnMessageLoopThread()
{
return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvInit(const bool& aAllowDistinctiveIdentifier,
const bool& aAllowPersistentState)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvInit(distinctiveId=%d, persistentState=%d)",
aAllowDistinctiveIdentifier,
aAllowPersistentState);
mPersistentStateAllowed = aAllowPersistentState;
if (mCDM) {
mCDM->Initialize(aAllowDistinctiveIdentifier, aAllowPersistentState);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvSetServerCertificate(const uint32_t& aPromiseId,
nsTArray<uint8_t>&& aServerCert)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvSetServerCertificate() certlen=%zu",
aServerCert.Length());
if (mCDM) {
mCDM->SetServerCertificate(aPromiseId,
aServerCert.Elements(),
aServerCert.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvCreateSessionAndGenerateRequest(
const uint32_t& aPromiseId,
const uint32_t& aSessionType,
const uint32_t& aInitDataType,
nsTArray<uint8_t>&& aInitData)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvCreateSessionAndGenerateRequest("
"pid=%" PRIu32 ", sessionType=%" PRIu32 ", initDataType=%" PRIu32
") initDataLen=%zu",
aPromiseId,
aSessionType,
aInitDataType,
aInitData.Length());
MOZ_ASSERT(aSessionType <= cdm::SessionType::kPersistentKeyRelease);
MOZ_ASSERT(aInitDataType <= cdm::InitDataType::kWebM);
if (mCDM) {
mCDM->CreateSessionAndGenerateRequest(aPromiseId,
static_cast<cdm::SessionType>(aSessionType),
static_cast<cdm::InitDataType>(aInitDataType),
aInitData.Elements(),
aInitData.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvLoadSession(const uint32_t& aPromiseId,
const uint32_t& aSessionType,
const nsCString& aSessionId)
{
GMP_LOG("ChromiumCDMChild::RecvLoadSession(pid=%u, type=%u, sessionId=%s)",
aPromiseId,
aSessionType,
aSessionId.get());
if (mCDM) {
mLoadSessionPromiseIds.AppendElement(aPromiseId);
mCDM->LoadSession(aPromiseId,
static_cast<cdm::SessionType>(aSessionType),
aSessionId.get(),
aSessionId.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvUpdateSession(const uint32_t& aPromiseId,
const nsCString& aSessionId,
nsTArray<uint8_t>&& aResponse)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvUpdateSession(pid=%" PRIu32
", sid=%s) responseLen=%zu",
aPromiseId,
aSessionId.get(),
aResponse.Length());
if (mCDM) {
mCDM->UpdateSession(aPromiseId,
aSessionId.get(),
aSessionId.Length(),
aResponse.Elements(),
aResponse.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvCloseSession(const uint32_t& aPromiseId,
const nsCString& aSessionId)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvCloseSession(pid=%" PRIu32 ", sid=%s)",
aPromiseId,
aSessionId.get());
if (mCDM) {
mCDM->CloseSession(aPromiseId, aSessionId.get(), aSessionId.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvRemoveSession(const uint32_t& aPromiseId,
const nsCString& aSessionId)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvRemoveSession(pid=%" PRIu32 ", sid=%s)",
aPromiseId,
aSessionId.get());
if (mCDM) {
mCDM->RemoveSession(aPromiseId, aSessionId.get(), aSessionId.Length());
}
return IPC_OK();
}
void
ChromiumCDMChild::DecryptFailed(uint32_t aId, cdm::Status aStatus)
{
MOZ_ASSERT(IsOnMessageLoopThread());
Unused << SendDecrypted(aId, aStatus, nsTArray<uint8_t>());
}
static void
InitInputBuffer(const CDMInputBuffer& aBuffer,
nsTArray<cdm::SubsampleEntry>& aSubSamples,
cdm::InputBuffer& aInputBuffer)
{
aInputBuffer.data = aBuffer.mData().Elements();
aInputBuffer.data_size = aBuffer.mData().Length();
if (aBuffer.mIsEncrypted()) {
aInputBuffer.key_id = aBuffer.mKeyId().Elements();
aInputBuffer.key_id_size = aBuffer.mKeyId().Length();
aInputBuffer.iv = aBuffer.mIV().Elements();
aInputBuffer.iv_size = aBuffer.mIV().Length();
aSubSamples.SetCapacity(aBuffer.mClearBytes().Length());
for (size_t i = 0; i < aBuffer.mCipherBytes().Length(); i++) {
aSubSamples.AppendElement(cdm::SubsampleEntry(aBuffer.mClearBytes()[i],
aBuffer.mCipherBytes()[i]));
}
aInputBuffer.subsamples = aSubSamples.Elements();
aInputBuffer.num_subsamples = aSubSamples.Length();
}
aInputBuffer.timestamp = aBuffer.mTimestamp();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDecrypt(const uint32_t& aId,
const CDMInputBuffer& aBuffer)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvDecrypt()");
if (!mCDM) {
GMP_LOG("ChromiumCDMChild::RecvDecrypt() no CDM");
DecryptFailed(aId, cdm::kDecryptError);
return IPC_OK();
}
if (aBuffer.mClearBytes().Length() != aBuffer.mCipherBytes().Length()) {
GMP_LOG("ChromiumCDMChild::RecvDecrypt() clear/cipher bytes length doesn't "
"match");
DecryptFailed(aId, cdm::kDecryptError);
return IPC_OK();
}
cdm::InputBuffer input;
nsTArray<cdm::SubsampleEntry> subsamples;
InitInputBuffer(aBuffer, subsamples, input);
WidevineDecryptedBlock output;
cdm::Status status = mCDM->Decrypt(input, &output);
if (status != cdm::kSuccess) {
DecryptFailed(aId, status);
return IPC_OK();
}
if (!output.DecryptedBuffer() ||
output.DecryptedBuffer()->Size() != aBuffer.mData().Length()) {
// The sizes of the input and output should exactly match.
DecryptFailed(aId, cdm::kDecryptError);
return IPC_OK();
}
nsTArray<uint8_t> buf =
static_cast<WidevineBuffer*>(output.DecryptedBuffer())->ExtractBuffer();
Unused << SendDecrypted(aId, cdm::kSuccess, buf);
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvInitializeVideoDecoder(
const CDMVideoDecoderConfig& aConfig)
{
MOZ_ASSERT(IsOnMessageLoopThread());
MOZ_ASSERT(!mDecoderInitialized);
cdm::VideoDecoderConfig config;
config.codec =
static_cast<cdm::VideoDecoderConfig::VideoCodec>(aConfig.mCodec());
config.profile =
static_cast<cdm::VideoDecoderConfig::VideoCodecProfile>(aConfig.mProfile());
config.format = static_cast<cdm::VideoFormat>(aConfig.mFormat());
config.coded_size =
mCodedSize = { aConfig.mImageWidth(), aConfig.mImageHeight() };
nsTArray<uint8_t> extraData(aConfig.mExtraData());
config.extra_data = extraData.Elements();
config.extra_data_size = extraData.Length();
cdm::Status status = mCDM->InitializeVideoDecoder(config);
GMP_LOG("ChromiumCDMChild::RecvInitializeVideoDecoder() status=%u", status);
Unused << SendOnDecoderInitDone(status);
mDecoderInitialized = status == cdm::kSuccess;
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDeinitializeVideoDecoder()
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvDeinitializeVideoDecoder()");
MOZ_ASSERT(mDecoderInitialized);
if (mDecoderInitialized) {
mDecoderInitialized = false;
mCDM->DeinitializeDecoder(cdm::kStreamTypeVideo);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvResetVideoDecoder()
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvResetVideoDecoder()");
if (mDecoderInitialized) {
mCDM->ResetDecoder(cdm::kStreamTypeVideo);
}
Unused << SendResetVideoDecoderComplete();
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDecryptAndDecodeFrame(const CDMInputBuffer& aBuffer)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvDecryptAndDecodeFrame()");
MOZ_ASSERT(mDecoderInitialized);
// The output frame may not have the same timestamp as the frame we put in.
// We may need to input a number of frames before we receive output. The
// CDM's decoder reorders to ensure frames output are in presentation order.
// So we need to store the durations of the frames input, and retrieve them
// on output.
mFrameDurations.Insert(aBuffer.mTimestamp(), aBuffer.mDuration());
cdm::InputBuffer input;
nsTArray<cdm::SubsampleEntry> subsamples;
InitInputBuffer(aBuffer, subsamples, input);
WidevineVideoFrame frame;
cdm::Status rv = mCDM->DecryptAndDecodeFrame(input, &frame);
GMP_LOG("WidevineVideoDecoder::Decode(timestamp=%" PRId64 ") rv=%d",
input.timestamp,
rv);
switch (rv) {
case cdm::kNoKey:
GMP_LOG("NoKey for sample at time=%" PRId64 "!", input.timestamp);
// Somehow our key became unusable. Typically this would happen when
// a stream requires output protection, and the configuration changed
// such that output protection is no longer available. For example, a
// non-compliant monitor was attached. The JS player should notice the
// key status changing to "output-restricted", and is supposed to switch
// to a stream that doesn't require OP. In order to keep the playback
// pipeline rolling, just output a black frame. See bug 1343140.
frame.InitToBlack(mCodedSize.width, mCodedSize.height, input.timestamp);
MOZ_FALLTHROUGH;
case cdm::kSuccess:
ReturnOutput(frame);
break;
case cdm::kNeedMoreData:
Unused << SendDecoded(gmp::CDMVideoFrame());
break;
default:
Unused << SendDecodeFailed(rv);
break;
}
return IPC_OK();
}
void
ChromiumCDMChild::ReturnOutput(WidevineVideoFrame& aFrame)
{
// TODO: WidevineBuffers should hold a shmem instead of a array, and we can
// send the handle instead of copying the array here.
gmp::CDMVideoFrame output;
output.mFormat() = static_cast<cdm::VideoFormat>(aFrame.Format());
output.mImageWidth() = aFrame.Size().width;
output.mImageHeight() = aFrame.Size().height;
output.mData() = Move(
reinterpret_cast<WidevineBuffer*>(aFrame.FrameBuffer())->ExtractBuffer());
output.mYPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kYPlane),
aFrame.Stride(cdm::VideoFrame::kYPlane) };
output.mUPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kUPlane),
aFrame.Stride(cdm::VideoFrame::kUPlane) };
output.mVPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kVPlane),
aFrame.Stride(cdm::VideoFrame::kVPlane) };
output.mTimestamp() = aFrame.Timestamp();
uint64_t duration = 0;
if (mFrameDurations.Find(aFrame.Timestamp(), duration)) {
output.mDuration() = duration;
}
Unused << SendDecoded(output);
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDrain()
{
WidevineVideoFrame frame;
cdm::InputBuffer sample;
cdm::Status rv = mCDM->DecryptAndDecodeFrame(sample, &frame);
CDM_LOG("ChromiumCDMChild::RecvDrain(); DecryptAndDecodeFrame() rv=%d", rv);
if (rv == cdm::kSuccess) {
MOZ_ASSERT(frame.Format() != cdm::kUnknownVideoFormat);
ReturnOutput(frame);
} else {
Unused << SendDrainComplete();
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDestroy()
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvDestroy()");
MOZ_ASSERT(!mDecoderInitialized);
if (mCDM) {
mCDM->Destroy();
mCDM = nullptr;
}
Unused << Send__delete__(this);
return IPC_OK();
}
} // namespace gmp
} // namespace mozilla

View File

@ -0,0 +1,131 @@
/* -*- 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 ChromiumCDMChild_h_
#define ChromiumCDMChild_h_
#include "content_decryption_module.h"
#include "mozilla/gmp/PChromiumCDMChild.h"
#include "SimpleMap.h"
#include "WidevineVideoFrame.h"
namespace mozilla {
namespace gmp {
class GMPContentChild;
class ChromiumCDMChild : public PChromiumCDMChild
, public cdm::Host_8
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMChild);
explicit ChromiumCDMChild(GMPContentChild* aPlugin);
void Init(cdm::ContentDecryptionModule_8* aCDM);
void TimerExpired(void* aContext);
// cdm::Host_8
cdm::Buffer* Allocate(uint32_t aCapacity) override;
void SetTimer(int64_t aDelayMs, void* aContext) override;
cdm::Time GetCurrentWallTime() 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::Error aError,
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,
const char* aLegacyDestinationUrl,
uint32_t aLegacyDestinationUrlLength) 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 OnLegacySessionError(const char* aSessionId,
uint32_t aSessionIdLength,
cdm::Error aError,
uint32_t aSystemCode,
const char* aErrorMessage,
uint32_t aErrorMessageLength) 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::FileIO* CreateFileIO(cdm::FileIOClient* aClient) override;
protected:
~ChromiumCDMChild() {}
bool IsOnMessageLoopThread();
ipc::IPCResult RecvInit(const bool& aAllowDistinctiveIdentifier,
const bool& aAllowPersistentState) override;
ipc::IPCResult RecvSetServerCertificate(
const uint32_t& aPromiseId,
nsTArray<uint8_t>&& aServerCert) override;
ipc::IPCResult RecvCreateSessionAndGenerateRequest(
const uint32_t& aPromiseId,
const uint32_t& aSessionType,
const uint32_t& aInitDataType,
nsTArray<uint8_t>&& aInitData) override;
ipc::IPCResult RecvLoadSession(const uint32_t& aPromiseId,
const uint32_t& aSessionType,
const nsCString& aSessionId) override;
ipc::IPCResult RecvUpdateSession(const uint32_t& aPromiseId,
const nsCString& aSessionId,
nsTArray<uint8_t>&& aResponse) override;
ipc::IPCResult RecvCloseSession(const uint32_t& aPromiseId,
const nsCString& aSessionId) override;
ipc::IPCResult RecvRemoveSession(const uint32_t& aPromiseId,
const nsCString& aSessionId) override;
ipc::IPCResult RecvDecrypt(const uint32_t& aId,
const CDMInputBuffer& aBuffer) override;
ipc::IPCResult RecvInitializeVideoDecoder(
const CDMVideoDecoderConfig& aConfig) override;
ipc::IPCResult RecvDeinitializeVideoDecoder() override;
ipc::IPCResult RecvResetVideoDecoder() override;
ipc::IPCResult RecvDecryptAndDecodeFrame(
const CDMInputBuffer& aBuffer) override;
ipc::IPCResult RecvDrain() override;
ipc::IPCResult RecvDestroy() override;
void DecryptFailed(uint32_t aId, cdm::Status aStatus);
void ReturnOutput(WidevineVideoFrame& aFrame);
GMPContentChild* mPlugin = nullptr;
cdm::ContentDecryptionModule_8* mCDM = nullptr;
typedef SimpleMap<uint64_t> DurationMap;
DurationMap mFrameDurations;
nsTArray<uint32_t> mLoadSessionPromiseIds;
cdm::Size mCodedSize;
bool mDecoderInitialized = false;
bool mPersistentStateAllowed = false;
};
} // namespace gmp
} // namespace mozilla
#endif // ChromiumCDMChild_h_

View File

@ -0,0 +1,850 @@
/* -*- 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 "ChromiumCDMParent.h"
#include "mozilla/gmp/GMPTypes.h"
#include "GMPContentChild.h"
#include "GMPContentParent.h"
#include "mozilla/Unused.h"
#include "ChromiumCDMProxy.h"
#include "mozilla/dom/MediaKeyMessageEventBinding.h"
#include "content_decryption_module.h"
#include "GMPLog.h"
namespace mozilla {
namespace gmp {
ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
uint32_t aPluginId)
: mPluginId(aPluginId)
, mContentParent(aContentParent)
{
GMP_LOG(
"ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, id=%u)",
this,
aContentParent,
aPluginId);
}
bool
ChromiumCDMParent::Init(ChromiumCDMProxy* aProxy,
bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState)
{
GMP_LOG("ChromiumCDMParent::Init(this=%p)", this);
if (!aProxy) {
return false;
}
mProxy = aProxy;
return SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState);
}
void
ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken,
uint32_t aSessionType,
uint32_t aInitDataType,
uint32_t aPromiseId,
const nsTArray<uint8_t>& aInitData)
{
GMP_LOG("ChromiumCDMParent::CreateSession(this=%p)", this);
if (mIsShutdown) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("CDM is shutdown."));
return;
}
if (!SendCreateSessionAndGenerateRequest(
aPromiseId, aSessionType, aInitDataType, aInitData)) {
RejectPromise(
aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Failed to send generateRequest to CDM process."));
return;
}
mPromiseToCreateSessionToken.Put(aPromiseId, aCreateSessionToken);
}
void
ChromiumCDMParent::LoadSession(uint32_t aPromiseId,
uint32_t aSessionType,
nsString aSessionId)
{
GMP_LOG("ChromiumCDMParent::LoadSession(this=%p, pid=%u, type=%u, sid=%s)",
this,
aPromiseId,
aSessionType,
NS_ConvertUTF16toUTF8(aSessionId).get());
if (mIsShutdown) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("CDM is shutdown."));
return;
}
if (!SendLoadSession(
aPromiseId, aSessionType, NS_ConvertUTF16toUTF8(aSessionId))) {
RejectPromise(
aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Failed to send loadSession to CDM process."));
return;
}
}
void
ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId,
const nsTArray<uint8_t>& aCert)
{
GMP_LOG("ChromiumCDMParent::SetServerCertificate(this=%p)", this);
if (mIsShutdown) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("CDM is shutdown."));
return;
}
if (!SendSetServerCertificate(aPromiseId, aCert)) {
RejectPromise(
aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Failed to send setServerCertificate to CDM process"));
}
}
void
ChromiumCDMParent::UpdateSession(const nsCString& aSessionId,
uint32_t aPromiseId,
const nsTArray<uint8_t>& aResponse)
{
GMP_LOG("ChromiumCDMParent::UpdateSession(this=%p)", this);
if (mIsShutdown) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("CDM is shutdown."));
return;
}
if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) {
RejectPromise(
aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Failed to send updateSession to CDM process"));
}
}
void
ChromiumCDMParent::CloseSession(const nsCString& aSessionId,
uint32_t aPromiseId)
{
GMP_LOG("ChromiumCDMParent::CloseSession(this=%p)", this);
if (mIsShutdown) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("CDM is shutdown."));
return;
}
if (!SendCloseSession(aPromiseId, aSessionId)) {
RejectPromise(
aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Failed to send closeSession to CDM process"));
}
}
void
ChromiumCDMParent::RemoveSession(const nsCString& aSessionId,
uint32_t aPromiseId)
{
GMP_LOG("ChromiumCDMParent::RemoveSession(this=%p)", this);
if (mIsShutdown) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("CDM is shutdown."));
return;
}
if (!SendRemoveSession(aPromiseId, aSessionId)) {
RejectPromise(
aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Failed to send removeSession to CDM process"));
}
}
static bool
InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer, MediaRawData* aSample)
{
const CryptoSample& crypto = aSample->mCrypto;
if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) {
GMP_LOG("InitCDMInputBuffer clear/cipher subsamples don't match");
return false;
}
nsTArray<uint8_t> data;
data.AppendElements(aSample->Data(), aSample->Size());
aBuffer = gmp::CDMInputBuffer(data,
crypto.mKeyId,
crypto.mIV,
aSample->mTime,
aSample->mDuration,
crypto.mPlainSizes,
crypto.mEncryptedSizes,
crypto.mValid);
return true;
}
RefPtr<DecryptPromise>
ChromiumCDMParent::Decrypt(MediaRawData* aSample)
{
if (mIsShutdown) {
return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
__func__);
}
CDMInputBuffer buffer;
if (!InitCDMInputBuffer(buffer, aSample)) {
return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
__func__);
}
RefPtr<DecryptJob> job = new DecryptJob(aSample);
if (!SendDecrypt(job->mId, buffer)) {
GMP_LOG(
"ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message",
this);
return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
__func__);
}
RefPtr<DecryptPromise> promise = job->Ensure();
mDecrypts.AppendElement(job);
return promise;
}
ipc::IPCResult
ChromiumCDMParent::Recv__delete__()
{
MOZ_ASSERT(mIsShutdown);
GMP_LOG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
if (mContentParent) {
mContentParent->ChromiumCDMDestroyed(this);
mContentParent = nullptr;
}
return IPC_OK();
}
ipc::IPCResult
ChromiumCDMParent::RecvOnResolveNewSessionPromise(const uint32_t& aPromiseId,
const nsCString& aSessionId)
{
GMP_LOG("ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%u, "
"sid=%s)",
this,
aPromiseId,
aSessionId.get());
if (!mProxy || mIsShutdown) {
return IPC_OK();
}
Maybe<uint32_t> token = mPromiseToCreateSessionToken.GetAndRemove(aPromiseId);
if (token.isNothing()) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Lost session token for new session."));
return IPC_OK();
}
RefPtr<Runnable> task =
NewRunnableMethod<uint32_t, nsString>(mProxy,
&ChromiumCDMProxy::OnSetSessionId,
token.value(),
NS_ConvertUTF8toUTF16(aSessionId));
NS_DispatchToMainThread(task);
ResolvePromise(aPromiseId);
return IPC_OK();
}
ipc::IPCResult
ChromiumCDMParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
const bool& aSuccessful)
{
GMP_LOG("ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%u, "
"successful=%d)",
this,
aPromiseId,
aSuccessful);
if (!mProxy || mIsShutdown) {
return IPC_OK();
}
NS_DispatchToMainThread(NewRunnableMethod<uint32_t, bool>(
mProxy,
&ChromiumCDMProxy::OnResolveLoadSessionPromise,
aPromiseId,
aSuccessful));
return IPC_OK();
}
void
ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId)
{
GMP_LOG(
"ChromiumCDMParent::ResolvePromise(this=%p, pid=%u)", this, aPromiseId);
// Note: The MediaKeys rejects all pending DOM promises when it
// initiates shutdown.
if (!mProxy || mIsShutdown) {
return;
}
NS_DispatchToMainThread(NewRunnableMethod<uint32_t>(
mProxy, &ChromiumCDMProxy::ResolvePromise, aPromiseId));
}
ipc::IPCResult
ChromiumCDMParent::RecvOnResolvePromise(const uint32_t& aPromiseId)
{
ResolvePromise(aPromiseId);
return IPC_OK();
}
static nsresult
ToNsresult(uint32_t aError)
{
switch (static_cast<cdm::Error>(aError)) {
case cdm::kNotSupportedError:
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
case cdm::kInvalidStateError:
return NS_ERROR_DOM_INVALID_STATE_ERR;
case cdm::kInvalidAccessError:
// Note: Chrome converts kInvalidAccessError to TypeError, since the
// Chromium CDM API doesn't have a type error enum value. The EME spec
// requires TypeError in some places, so we do the same conversion.
// See bug 1313202.
return NS_ERROR_DOM_TYPE_ERR;
case cdm::kQuotaExceededError:
return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
case cdm::kUnknownError:
return NS_ERROR_DOM_UNKNOWN_ERR; // Note: Unique placeholder.
case cdm::kClientError:
return NS_ERROR_DOM_ABORT_ERR; // Note: Unique placeholder.
case cdm::kOutputError:
return NS_ERROR_DOM_SECURITY_ERR; // Note: Unique placeholder.
};
MOZ_ASSERT_UNREACHABLE("Invalid cdm::Error enum value.");
return NS_ERROR_DOM_TIMEOUT_ERR; // Note: Unique placeholder.
}
void
ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
nsresult aError,
const nsCString& aErrorMessage)
{
GMP_LOG(
"ChromiumCDMParent::RejectPromise(this=%p, pid=%u)", this, aPromiseId);
// Note: The MediaKeys rejects all pending DOM promises when it
// initiates shutdown.
if (!mProxy || mIsShutdown) {
return;
}
NS_DispatchToMainThread(NewRunnableMethod<uint32_t, nsresult, nsCString>(
mProxy,
&ChromiumCDMProxy::RejectPromise,
aPromiseId,
aError,
aErrorMessage));
}
ipc::IPCResult
ChromiumCDMParent::RecvOnRejectPromise(const uint32_t& aPromiseId,
const uint32_t& aError,
const uint32_t& aSystemCode,
const nsCString& aErrorMessage)
{
RejectPromise(aPromiseId, ToNsresult(aError), aErrorMessage);
return IPC_OK();
}
static dom::MediaKeyMessageType
ToDOMMessageType(uint32_t aMessageType)
{
switch (static_cast<cdm::MessageType>(aMessageType)) {
case cdm::kLicenseRequest:
return dom::MediaKeyMessageType::License_request;
case cdm::kLicenseRenewal:
return dom::MediaKeyMessageType::License_renewal;
case cdm::kLicenseRelease:
return dom::MediaKeyMessageType::License_release;
}
MOZ_ASSERT_UNREACHABLE("Invalid cdm::MessageType enum value.");
return dom::MediaKeyMessageType::License_request;
}
ipc::IPCResult
ChromiumCDMParent::RecvOnSessionMessage(const nsCString& aSessionId,
const uint32_t& aMessageType,
nsTArray<uint8_t>&& aMessage)
{
GMP_LOG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
this,
aSessionId.get());
if (!mProxy || mIsShutdown) {
return IPC_OK();
}
RefPtr<CDMProxy> proxy = mProxy;
nsString sid = NS_ConvertUTF8toUTF16(aSessionId);
dom::MediaKeyMessageType messageType = ToDOMMessageType(aMessageType);
nsTArray<uint8_t> msg(Move(aMessage));
NS_DispatchToMainThread(
NS_NewRunnableFunction([proxy, sid, messageType, msg]() mutable {
proxy->OnSessionMessage(sid, messageType, msg);
}));
return IPC_OK();
}
static dom::MediaKeyStatus
ToDOMMediaKeyStatus(uint32_t aStatus)
{
switch (static_cast<cdm::KeyStatus>(aStatus)) {
case cdm::kUsable:
return dom::MediaKeyStatus::Usable;
case cdm::kInternalError:
return dom::MediaKeyStatus::Internal_error;
case cdm::kExpired:
return dom::MediaKeyStatus::Expired;
case cdm::kOutputRestricted:
return dom::MediaKeyStatus::Output_restricted;
case cdm::kOutputDownscaled:
return dom::MediaKeyStatus::Output_downscaled;
case cdm::kStatusPending:
return dom::MediaKeyStatus::Status_pending;
case cdm::kReleased:
return dom::MediaKeyStatus::Released;
}
MOZ_ASSERT_UNREACHABLE("Invalid cdm::KeyStatus enum value.");
return dom::MediaKeyStatus::Internal_error;
}
ipc::IPCResult
ChromiumCDMParent::RecvOnSessionKeysChange(
const nsCString& aSessionId,
nsTArray<CDMKeyInformation>&& aKeysInfo)
{
GMP_LOG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
if (!mProxy || mIsShutdown) {
return IPC_OK();
}
bool keyStatusesChange = false;
{
CDMCaps::AutoLock caps(mProxy->Capabilites());
for (size_t i = 0; i < aKeysInfo.Length(); i++) {
keyStatusesChange |=
caps.SetKeyStatus(aKeysInfo[i].mKeyId(),
NS_ConvertUTF8toUTF16(aSessionId),
dom::Optional<dom::MediaKeyStatus>(
ToDOMMediaKeyStatus(aKeysInfo[i].mStatus())));
}
}
if (keyStatusesChange) {
NS_DispatchToMainThread(
NewRunnableMethod<nsString>(mProxy,
&ChromiumCDMProxy::OnKeyStatusesChange,
NS_ConvertUTF8toUTF16(aSessionId)));
}
return IPC_OK();
}
ipc::IPCResult
ChromiumCDMParent::RecvOnExpirationChange(const nsCString& aSessionId,
const double& aSecondsSinceEpoch)
{
GMP_LOG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
this,
aSecondsSinceEpoch);
if (!mProxy || mIsShutdown) {
return IPC_OK();
}
NS_DispatchToMainThread(NewRunnableMethod<nsString, UnixTime>(
mProxy,
&ChromiumCDMProxy::OnExpirationChange,
NS_ConvertUTF8toUTF16(aSessionId),
GMPTimestamp(aSecondsSinceEpoch * 1000)));
return IPC_OK();
}
ipc::IPCResult
ChromiumCDMParent::RecvOnSessionClosed(const nsCString& aSessionId)
{
GMP_LOG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
if (!mProxy || mIsShutdown) {
return IPC_OK();
}
NS_DispatchToMainThread(
NewRunnableMethod<nsString>(mProxy,
&ChromiumCDMProxy::OnSessionClosed,
NS_ConvertUTF8toUTF16(aSessionId)));
return IPC_OK();
}
ipc::IPCResult
ChromiumCDMParent::RecvOnLegacySessionError(const nsCString& aSessionId,
const uint32_t& aError,
const uint32_t& aSystemCode,
const nsCString& aMessage)
{
GMP_LOG("ChromiumCDMParent::RecvOnLegacySessionError(this=%p)", this);
if (!mProxy || mIsShutdown) {
return IPC_OK();
}
NS_DispatchToMainThread(
NewRunnableMethod<nsString, nsresult, uint32_t, nsString>(
mProxy,
&ChromiumCDMProxy::OnSessionError,
NS_ConvertUTF8toUTF16(aSessionId),
ToNsresult(aError),
aSystemCode,
NS_ConvertUTF8toUTF16(aMessage)));
return IPC_OK();
}
DecryptStatus
ToDecryptStatus(uint32_t aError)
{
switch (static_cast<cdm::Status>(aError)) {
case cdm::kSuccess:
return DecryptStatus::Ok;
case cdm::kNoKey:
return DecryptStatus::NoKeyErr;
default:
return DecryptStatus::GenericErr;
}
}
ipc::IPCResult
ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
const uint32_t& aStatus,
nsTArray<uint8_t>&& aData)
{
GMP_LOG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%u, status=%u)",
this,
aId,
aStatus);
if (mIsShutdown) {
MOZ_ASSERT(mDecrypts.IsEmpty());
return IPC_OK();
}
for (size_t i = 0; i < mDecrypts.Length(); i++) {
if (mDecrypts[i]->mId == aId) {
mDecrypts[i]->PostResult(ToDecryptStatus(aStatus), aData);
mDecrypts.RemoveElementAt(i);
break;
}
}
return IPC_OK();
}
ipc::IPCResult
ChromiumCDMParent::RecvDecoded(const CDMVideoFrame& aFrame)
{
if (mIsShutdown || mDecodePromise.IsEmpty()) {
return IPC_OK();
}
VideoData::YCbCrBuffer b;
nsTArray<uint8_t> data;
data = aFrame.mData();
if (data.IsEmpty()) {
mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__);
return IPC_OK();
}
b.mPlanes[0].mData = data.Elements();
b.mPlanes[0].mWidth = aFrame.mImageWidth();
b.mPlanes[0].mHeight = aFrame.mImageHeight();
b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
b.mPlanes[0].mOffset = aFrame.mYPlane().mPlaneOffset();
b.mPlanes[0].mSkip = 0;
b.mPlanes[1].mData = data.Elements();
b.mPlanes[1].mWidth = (aFrame.mImageWidth() + 1) / 2;
b.mPlanes[1].mHeight = (aFrame.mImageHeight() + 1) / 2;
b.mPlanes[1].mStride = aFrame.mUPlane().mStride();
b.mPlanes[1].mOffset = aFrame.mUPlane().mPlaneOffset();
b.mPlanes[1].mSkip = 0;
b.mPlanes[2].mData = data.Elements();
b.mPlanes[2].mWidth = (aFrame.mImageWidth() + 1) / 2;
b.mPlanes[2].mHeight = (aFrame.mImageHeight() + 1) / 2;
b.mPlanes[2].mStride = aFrame.mVPlane().mStride();
b.mPlanes[2].mOffset = aFrame.mVPlane().mPlaneOffset();
b.mPlanes[2].mSkip = 0;
gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight());
RefPtr<VideoData> v = VideoData::CreateAndCopyData(mVideoInfo,
mImageContainer,
mLastStreamOffset,
aFrame.mTimestamp(),
aFrame.mDuration(),
b,
false,
-1,
pictureRegion);
RefPtr<ChromiumCDMParent> self = this;
if (v) {
mDecodePromise.ResolveIfExists({ Move(v) }, __func__);
} else {
mDecodePromise.RejectIfExists(
MediaResult(NS_ERROR_OUT_OF_MEMORY,
RESULT_DETAIL("CallBack::CreateAndCopyData")),
__func__);
}
return IPC_OK();
}
ipc::IPCResult
ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus)
{
if (mIsShutdown) {
MOZ_ASSERT(mDecodePromise.IsEmpty());
return IPC_OK();
}
mDecodePromise.RejectIfExists(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("ChromiumCDMParent::RecvDecodeFailed")),
__func__);
return IPC_OK();
}
ipc::IPCResult
ChromiumCDMParent::RecvShutdown()
{
GMP_LOG("ChromiumCDMParent::RecvShutdown(this=%p)", this);
Shutdown();
return IPC_OK();
}
void
ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy)
{
GMP_LOG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this, aWhy);
MOZ_ASSERT(!mActorDestroyed);
mActorDestroyed = true;
if (!mIsShutdown) {
// Plugin crash.
MOZ_ASSERT(aWhy == AbnormalShutdown);
Shutdown();
}
MOZ_ASSERT(mIsShutdown);
RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
if (mContentParent) {
mContentParent->ChromiumCDMDestroyed(this);
mContentParent = nullptr;
}
bool abnormalShutdown = (aWhy == AbnormalShutdown);
if (abnormalShutdown && mProxy) {
RefPtr<Runnable> task =
NewRunnableMethod(mProxy, &ChromiumCDMProxy::Terminated);
NS_DispatchToMainThread(task);
}
MaybeDisconnect(abnormalShutdown);
}
RefPtr<MediaDataDecoder::InitPromise>
ChromiumCDMParent::InitializeVideoDecoder(
const gmp::CDMVideoDecoderConfig& aConfig,
const VideoInfo& aInfo,
RefPtr<layers::ImageContainer> aImageContainer)
{
if (mIsShutdown) {
return MediaDataDecoder::InitPromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("ChromiumCDMParent is shutdown")),
__func__);
}
if (!SendInitializeVideoDecoder(aConfig)) {
return MediaDataDecoder::InitPromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("Failed to send init video decoder to CDM")),
__func__);
}
mVideoDecoderInitialized = true;
mImageContainer = aImageContainer;
mVideoInfo = aInfo;
return mInitVideoDecoderPromise.Ensure(__func__);
}
ipc::IPCResult
ChromiumCDMParent::RecvOnDecoderInitDone(const uint32_t& aStatus)
{
GMP_LOG("ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%u)",
this,
aStatus);
if (mIsShutdown) {
MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
return IPC_OK();
}
if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
} else {
mVideoDecoderInitialized = false;
mInitVideoDecoderPromise.RejectIfExists(
MediaResult(
NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("CDM init decode failed with %" PRIu32, aStatus)),
__func__);
}
return IPC_OK();
}
RefPtr<MediaDataDecoder::DecodePromise>
ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample)
{
if (mIsShutdown) {
return MediaDataDecoder::DecodePromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("ChromiumCDMParent is shutdown")),
__func__);
}
CDMInputBuffer buffer;
if (!InitCDMInputBuffer(buffer, aSample)) {
return MediaDataDecoder::DecodePromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."),
__func__);
}
mLastStreamOffset = aSample->mOffset;
if (!SendDecryptAndDecodeFrame(buffer)) {
GMP_LOG(
"ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message.",
this);
return MediaDataDecoder::DecodePromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
"Failed to send decrypt to CDM process."),
__func__);
}
return mDecodePromise.Ensure(__func__);
}
RefPtr<MediaDataDecoder::FlushPromise>
ChromiumCDMParent::FlushVideoDecoder()
{
if (mIsShutdown) {
return MediaDataDecoder::FlushPromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("ChromiumCDMParent is shutdown")),
__func__);
}
mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
if (!SendResetVideoDecoder()) {
return MediaDataDecoder::FlushPromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to send flush to CDM."),
__func__);
}
return mFlushDecoderPromise.Ensure(__func__);
}
ipc::IPCResult
ChromiumCDMParent::RecvResetVideoDecoderComplete()
{
if (mIsShutdown) {
MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
return IPC_OK();
}
mFlushDecoderPromise.ResolveIfExists(true, __func__);
return IPC_OK();
}
RefPtr<MediaDataDecoder::DecodePromise>
ChromiumCDMParent::Drain()
{
MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete");
if (mIsShutdown) {
return MediaDataDecoder::DecodePromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("ChromiumCDMParent is shutdown")),
__func__);
}
RefPtr<MediaDataDecoder::DecodePromise> p = mDecodePromise.Ensure(__func__);
if (!SendDrain()) {
mDecodePromise.Resolve(MediaDataDecoder::DecodedData(), __func__);
}
return p;
}
ipc::IPCResult
ChromiumCDMParent::RecvDrainComplete()
{
if (mIsShutdown) {
MOZ_ASSERT(mDecodePromise.IsEmpty());
return IPC_OK();
}
mDecodePromise.ResolveIfExists(MediaDataDecoder::DecodedData(), __func__);
return IPC_OK();
}
RefPtr<ShutdownPromise>
ChromiumCDMParent::ShutdownVideoDecoder()
{
if (mIsShutdown || !mVideoDecoderInitialized) {
return ShutdownPromise::CreateAndResolve(true, __func__);
}
mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
__func__);
mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
if (!SendDeinitializeVideoDecoder()) {
return ShutdownPromise::CreateAndResolve(true, __func__);
}
mVideoDecoderInitialized = false;
return ShutdownPromise::CreateAndResolve(true, __func__);
}
void
ChromiumCDMParent::Shutdown()
{
GMP_LOG("ChromiumCDMParent::Shutdown(this=%p)", this);
if (mIsShutdown) {
return;
}
mIsShutdown = true;
for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
decrypt->PostResult(AbortedErr);
}
mDecrypts.Clear();
if (mVideoDecoderInitialized && !mActorDestroyed) {
Unused << SendDeinitializeVideoDecoder();
mVideoDecoderInitialized = false;
}
// Note: MediaKeys rejects all outstanding promises when it initiates shutdown.
mPromiseToCreateSessionToken.Clear();
mInitVideoDecoderPromise.RejectIfExists(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("ChromiumCDMParent is shutdown")),
__func__);
mDecodePromise.RejectIfExists(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("ChromiumCDMParent is shutdown")),
__func__);
mFlushDecoderPromise.RejectIfExists(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
RESULT_DETAIL("ChromiumCDMParent is shutdown")),
__func__);
if (!mActorDestroyed) {
Unused << SendDestroy();
}
}
} // namespace gmp
} // namespace mozilla

View File

@ -0,0 +1,156 @@
/* -*- 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 ChromiumCDMParent_h_
#define ChromiumCDMParent_h_
#include "DecryptJob.h"
#include "GMPCrashHelper.h"
#include "GMPCrashHelperHolder.h"
#include "GMPMessageUtils.h"
#include "mozilla/gmp/PChromiumCDMParent.h"
#include "mozilla/RefPtr.h"
#include "nsDataHashtable.h"
#include "PlatformDecoderModule.h"
#include "ImageContainer.h"
namespace mozilla {
class MediaRawData;
class ChromiumCDMProxy;
namespace gmp {
class GMPContentParent;
class ChromiumCDMParent final
: public PChromiumCDMParent
, public GMPCrashHelperHolder
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMParent)
ChromiumCDMParent(GMPContentParent* aContentParent, uint32_t aPluginId);
uint32_t PluginId() const { return mPluginId; }
bool Init(ChromiumCDMProxy* aProxy,
bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState);
void CreateSession(uint32_t aCreateSessionToken,
uint32_t aSessionType,
uint32_t aInitDataType,
uint32_t aPromiseId,
const nsTArray<uint8_t>& aInitData);
void LoadSession(uint32_t aPromiseId,
uint32_t aSessionType,
nsString aSessionId);
void SetServerCertificate(uint32_t aPromiseId,
const nsTArray<uint8_t>& aCert);
void UpdateSession(const nsCString& aSessionId,
uint32_t aPromiseId,
const nsTArray<uint8_t>& aResponse);
void CloseSession(const nsCString& aSessionId, uint32_t aPromiseId);
void RemoveSession(const nsCString& aSessionId, uint32_t aPromiseId);
RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample);
// TODO: Add functions for clients to send data to CDM, and
// a Close() function.
RefPtr<MediaDataDecoder::InitPromise> InitializeVideoDecoder(
const gmp::CDMVideoDecoderConfig& aConfig,
const VideoInfo& aInfo,
RefPtr<layers::ImageContainer> aImageContainer);
RefPtr<MediaDataDecoder::DecodePromise> DecryptAndDecodeFrame(
MediaRawData* aSample);
RefPtr<MediaDataDecoder::FlushPromise> FlushVideoDecoder();
RefPtr<MediaDataDecoder::DecodePromise> Drain();
RefPtr<ShutdownPromise> ShutdownVideoDecoder();
void Shutdown();
protected:
~ChromiumCDMParent() {}
ipc::IPCResult Recv__delete__() override;
ipc::IPCResult RecvOnResolveNewSessionPromise(
const uint32_t& aPromiseId,
const nsCString& aSessionId) override;
ipc::IPCResult RecvResolveLoadSessionPromise(
const uint32_t& aPromiseId,
const bool& aSuccessful) override;
ipc::IPCResult RecvOnResolvePromise(const uint32_t& aPromiseId) override;
ipc::IPCResult RecvOnRejectPromise(const uint32_t& aPromiseId,
const uint32_t& aError,
const uint32_t& aSystemCode,
const nsCString& aErrorMessage) override;
ipc::IPCResult RecvOnSessionMessage(const nsCString& aSessionId,
const uint32_t& aMessageType,
nsTArray<uint8_t>&& aMessage) override;
ipc::IPCResult RecvOnSessionKeysChange(
const nsCString& aSessionId,
nsTArray<CDMKeyInformation>&& aKeysInfo) override;
ipc::IPCResult RecvOnExpirationChange(
const nsCString& aSessionId,
const double& aSecondsSinceEpoch) override;
ipc::IPCResult RecvOnSessionClosed(const nsCString& aSessionId) override;
ipc::IPCResult RecvOnLegacySessionError(const nsCString& aSessionId,
const uint32_t& aError,
const uint32_t& aSystemCode,
const nsCString& aMessage) override;
ipc::IPCResult RecvDecrypted(const uint32_t& aId,
const uint32_t& aStatus,
nsTArray<uint8_t>&& aData) override;
ipc::IPCResult RecvOnDecoderInitDone(const uint32_t& aStatus) override;
ipc::IPCResult RecvDecoded(const CDMVideoFrame& aFrame) override;
ipc::IPCResult RecvDecodeFailed(const uint32_t& aStatus) override;
ipc::IPCResult RecvShutdown() override;
ipc::IPCResult RecvResetVideoDecoderComplete() override;
ipc::IPCResult RecvDrainComplete() override;
void ActorDestroy(ActorDestroyReason aWhy) override;
void RejectPromise(uint32_t aPromiseId,
nsresult aError,
const nsCString& aErrorMessage);
void ResolvePromise(uint32_t aPromiseId);
const uint32_t mPluginId;
GMPContentParent* mContentParent;
// Note: this pointer is a weak reference because otherwise it would cause
// a cycle, as ChromiumCDMProxy has a strong reference to the
// ChromiumCDMParent.
ChromiumCDMProxy* mProxy = nullptr;
nsDataHashtable<nsUint32HashKey, uint32_t> mPromiseToCreateSessionToken;
nsTArray<RefPtr<DecryptJob>> mDecrypts;
MozPromiseHolder<MediaDataDecoder::InitPromise> mInitVideoDecoderPromise;
MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
RefPtr<layers::ImageContainer> mImageContainer;
VideoInfo mVideoInfo;
uint64_t mLastStreamOffset = 0;
MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushDecoderPromise;
bool mIsShutdown = false;
bool mVideoDecoderInitialized = false;
bool mActorDestroyed = false;
};
} // namespace gmp
} // namespace mozilla
#endif // ChromiumCDMParent_h_

View File

@ -0,0 +1,589 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ChromiumCDMProxy.h"
#include "mozilla/dom/MediaKeySession.h"
#include "GMPUtils.h"
#include "nsPrintfCString.h"
#include "GMPService.h"
namespace mozilla {
ChromiumCDMProxy::ChromiumCDMProxy(dom::MediaKeys* aKeys,
const nsAString& aKeySystem,
GMPCrashHelper* aCrashHelper,
bool aDistinctiveIdentifierRequired,
bool aPersistentStateRequired,
nsIEventTarget* aMainThread)
: CDMProxy(aKeys,
aKeySystem,
aDistinctiveIdentifierRequired,
aPersistentStateRequired,
aMainThread)
, mCrashHelper(aCrashHelper)
, mCDMMutex("ChromiumCDMProxy")
, mGMPThread(GetGMPAbstractThread())
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_COUNT_CTOR(ChromiumCDMProxy);
}
ChromiumCDMProxy::~ChromiumCDMProxy()
{
MOZ_COUNT_DTOR(ChromiumCDMProxy);
}
void
ChromiumCDMProxy::Init(PromiseId aPromiseId,
const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
const nsAString& aGMPName)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
EME_LOG(
"ChromiumCDMProxy::Init (pid=%u, origin=%s, topLevelOrigin=%s, gmp=%s)",
aPromiseId,
NS_ConvertUTF16toUTF8(aOrigin).get(),
NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
NS_ConvertUTF16toUTF8(aGMPName).get());
if (!mGMPThread) {
RejectPromise(
aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Couldn't get GMP thread ChromiumCDMProxy::Init"));
return;
}
if (aGMPName.IsEmpty()) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
nsPrintfCString("Unknown GMP for keysystem '%s'",
NS_ConvertUTF16toUTF8(mKeySystem).get()));
return;
}
gmp::NodeId nodeId(aOrigin, aTopLevelOrigin, aGMPName);
RefPtr<AbstractThread> thread = mGMPThread;
RefPtr<GMPCrashHelper> helper(mCrashHelper);
RefPtr<ChromiumCDMProxy> self(this);
nsCString keySystem = NS_ConvertUTF16toUTF8(mKeySystem);
RefPtr<Runnable> task(NS_NewRunnableFunction(
[self, nodeId, helper, aPromiseId, thread, keySystem]() -> void {
MOZ_ASSERT(self->IsOnOwnerThread());
RefPtr<gmp::GeckoMediaPluginService> service =
gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
if (!service) {
self->RejectPromise(
aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING(
"Couldn't get GeckoMediaPluginService in ChromiumCDMProxy::Init"));
return;
}
RefPtr<gmp::GetCDMParentPromise> promise =
service->GetCDM(nodeId, { keySystem }, helper);
promise->Then(
thread,
__func__,
[self, aPromiseId](RefPtr<gmp::ChromiumCDMParent> cdm) {
if (!cdm->Init(self,
self->mDistinctiveIdentifierRequired,
self->mPersistentStateRequired)) {
self->RejectPromise(aPromiseId,
NS_ERROR_FAILURE,
NS_LITERAL_CSTRING("GetCDM failed."));
return;
}
{
MutexAutoLock lock(self->mCDMMutex);
self->mCDM = cdm;
}
self->OnCDMCreated(aPromiseId);
},
[self, aPromiseId](nsresult rv) {
self->RejectPromise(
aPromiseId, NS_ERROR_FAILURE, NS_LITERAL_CSTRING("GetCDM failed."));
});
}));
mGMPThread->Dispatch(task.forget());
}
void
ChromiumCDMProxy::OnCDMCreated(uint32_t aPromiseId)
{
EME_LOG("ChromiumCDMProxy::OnCDMCreated(pid=%u) isMainThread=%d this=%p",
aPromiseId,
NS_IsMainThread(),
this);
if (!NS_IsMainThread()) {
mMainThread->Dispatch(NewRunnableMethod<PromiseId>(
this, &ChromiumCDMProxy::OnCDMCreated, aPromiseId),
NS_DISPATCH_NORMAL);
return;
}
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
// This should only be called once the CDM has been created.
MOZ_ASSERT(cdm);
if (cdm) {
mKeys->OnCDMCreated(aPromiseId, cdm->PluginId());
} else {
// No CDM? Shouldn't be possible, but reject the promise anyway...
mKeys->RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Null CDM in OnCDMCreated()"));
}
}
#ifdef DEBUG
bool
ChromiumCDMProxy::IsOnOwnerThread()
{
return mGMPThread->IsCurrentThreadIn();
}
#endif
static uint32_t
ToCDMSessionType(dom::MediaKeySessionType aSessionType)
{
switch (aSessionType) {
case dom::MediaKeySessionType::Temporary:
return static_cast<uint32_t>(cdm::kTemporary);
case dom::MediaKeySessionType::Persistent_license:
return static_cast<uint32_t>(cdm::kPersistentLicense);
default:
return static_cast<uint32_t>(cdm::kTemporary);
};
};
static uint32_t
ToCDMInitDataType(const nsAString& aInitDataType)
{
if (aInitDataType.EqualsLiteral("cenc")) {
return static_cast<uint32_t>(cdm::kCenc);
}
if (aInitDataType.EqualsLiteral("webm")) {
return static_cast<uint32_t>(cdm::kWebM);
}
if (aInitDataType.EqualsLiteral("keyids")) {
return static_cast<uint32_t>(cdm::kKeyIds);
}
return static_cast<uint32_t>(cdm::kCenc);
}
void
ChromiumCDMProxy::CreateSession(uint32_t aCreateSessionToken,
dom::MediaKeySessionType aSessionType,
PromiseId aPromiseId,
const nsAString& aInitDataType,
nsTArray<uint8_t>& aInitData)
{
MOZ_ASSERT(NS_IsMainThread());
EME_LOG("ChromiumCDMProxy::CreateSession(token=%u, type=%d, pid=%u) "
"initDataLen=%zu",
aCreateSessionToken,
(int)aSessionType,
aPromiseId,
aInitData.Length());
uint32_t sessionType = ToCDMSessionType(aSessionType);
uint32_t initDataType = ToCDMInitDataType(aInitDataType);
RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
if (!cdm) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Null CDM in CreateSession"));
return;
}
mGMPThread->Dispatch(
NewRunnableMethod<uint32_t,
uint32_t,
uint32_t,
uint32_t,
nsTArray<uint8_t>>(cdm,
&gmp::ChromiumCDMParent::CreateSession,
aCreateSessionToken,
sessionType,
initDataType,
aPromiseId,
Move(aInitData)));
}
void
ChromiumCDMProxy::LoadSession(PromiseId aPromiseId,
dom::MediaKeySessionType aSessionType,
const nsAString& aSessionId)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
if (!cdm) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Null CDM in LoadSession"));
return;
}
mGMPThread->Dispatch(NewRunnableMethod<uint32_t, uint32_t, nsString>(
cdm,
&gmp::ChromiumCDMParent::LoadSession,
aPromiseId,
ToCDMSessionType(aSessionType),
aSessionId));
}
void
ChromiumCDMProxy::SetServerCertificate(PromiseId aPromiseId,
nsTArray<uint8_t>& aCert)
{
MOZ_ASSERT(NS_IsMainThread());
EME_LOG("ChromiumCDMProxy::SetServerCertificate(pid=%u) certLen=%zu",
aPromiseId,
aCert.Length());
RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
if (!cdm) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Null CDM in SetServerCertificate"));
return;
}
mGMPThread->Dispatch(NewRunnableMethod<uint32_t, nsTArray<uint8_t>>(
cdm,
&gmp::ChromiumCDMParent::SetServerCertificate,
aPromiseId,
Move(aCert)));
}
void
ChromiumCDMProxy::UpdateSession(const nsAString& aSessionId,
PromiseId aPromiseId,
nsTArray<uint8_t>& aResponse)
{
MOZ_ASSERT(NS_IsMainThread());
EME_LOG("ChromiumCDMProxy::UpdateSession(sid='%s', pid=%u) responseLen=%zu",
NS_ConvertUTF16toUTF8(aSessionId).get(),
aPromiseId,
aResponse.Length());
RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
if (!cdm) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Null CDM in UpdateSession"));
return;
}
mGMPThread->Dispatch(
NewRunnableMethod<nsCString, uint32_t, nsTArray<uint8_t>>(
cdm,
&gmp::ChromiumCDMParent::UpdateSession,
NS_ConvertUTF16toUTF8(aSessionId),
aPromiseId,
Move(aResponse)));
}
void
ChromiumCDMProxy::CloseSession(const nsAString& aSessionId,
PromiseId aPromiseId)
{
MOZ_ASSERT(NS_IsMainThread());
EME_LOG("ChromiumCDMProxy::CloseSession(sid='%s', pid=%u)",
NS_ConvertUTF16toUTF8(aSessionId).get(),
aPromiseId);
RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
if (!cdm) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Null CDM in CloseSession"));
return;
}
mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
cdm,
&gmp::ChromiumCDMParent::CloseSession,
NS_ConvertUTF16toUTF8(aSessionId),
aPromiseId));
}
void
ChromiumCDMProxy::RemoveSession(const nsAString& aSessionId,
PromiseId aPromiseId)
{
MOZ_ASSERT(NS_IsMainThread());
EME_LOG("ChromiumCDMProxy::RemoveSession(sid='%s', pid=%u)",
NS_ConvertUTF16toUTF8(aSessionId).get(),
aPromiseId);
RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
if (!cdm) {
RejectPromise(aPromiseId,
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Null CDM in RemoveSession"));
return;
}
mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
cdm,
&gmp::ChromiumCDMParent::RemoveSession,
NS_ConvertUTF16toUTF8(aSessionId),
aPromiseId));
}
void
ChromiumCDMProxy::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
EME_LOG("ChromiumCDMProxy::Shutdown()");
mKeys.Clear();
RefPtr<gmp::ChromiumCDMParent> cdm;
{
MutexAutoLock lock(mCDMMutex);
cdm.swap(mCDM);
}
if (cdm) {
nsCOMPtr<nsIRunnable> task =
NewRunnableMethod(mCDM, &gmp::ChromiumCDMParent::Shutdown);
mGMPThread->Dispatch(task.forget());
}
}
void
ChromiumCDMProxy::RejectPromise(PromiseId aId,
nsresult aCode,
const nsCString& aReason)
{
if (!NS_IsMainThread()) {
nsCOMPtr<nsIRunnable> task;
task = NewRunnableMethod<PromiseId, nsresult, nsCString>(
this, &ChromiumCDMProxy::RejectPromise, aId, aCode, aReason);
NS_DispatchToMainThread(task);
return;
}
EME_LOG("ChromiumCDMProxy::RejectPromise(pid=%u, code=0x%x, reason='%s')",
aId,
static_cast<uint32_t>(aCode),
aReason.get());
if (!mKeys.IsNull()) {
mKeys->RejectPromise(aId, aCode, aReason);
}
}
void
ChromiumCDMProxy::ResolvePromise(PromiseId aId)
{
if (!NS_IsMainThread()) {
nsCOMPtr<nsIRunnable> task;
task = NewRunnableMethod<PromiseId>(
this, &ChromiumCDMProxy::ResolvePromise, aId);
NS_DispatchToMainThread(task);
return;
}
EME_LOG("ChromiumCDMProxy::ResolvePromise(pid=%u)", aId);
if (!mKeys.IsNull()) {
mKeys->ResolvePromise(aId);
} else {
NS_WARNING("ChromiumCDMProxy unable to resolve promise!");
}
}
const nsCString&
ChromiumCDMProxy::GetNodeId() const
{
return mNodeId;
}
void
ChromiumCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
const nsAString& aSessionId)
{
MOZ_ASSERT(NS_IsMainThread());
EME_LOG("ChromiumCDMProxy::OnSetSessionId(token=%u, sid='%s')",
aCreateSessionToken,
NS_ConvertUTF16toUTF8(aSessionId).get());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(
mKeys->GetPendingSession(aCreateSessionToken));
if (session) {
session->SetSessionId(aSessionId);
}
}
void
ChromiumCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId,
bool aSuccess)
{
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
mKeys->OnSessionLoaded(aPromiseId, aSuccess);
}
void
ChromiumCDMProxy::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
ChromiumCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId)
{
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->DispatchKeyStatusesChange();
}
}
void
ChromiumCDMProxy::OnExpirationChange(const nsAString& aSessionId,
GMPTimestamp aExpiryTime)
{
MOZ_ASSERT(NS_IsMainThread());
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
// Expiry of 0 is interpreted as "never expire". See bug 1345341.
double t = (aExpiryTime == 0) ? std::numeric_limits<double>::quiet_NaN()
: static_cast<double>(aExpiryTime);
session->SetExpiration(t);
}
}
void
ChromiumCDMProxy::OnSessionClosed(const nsAString& aSessionId)
{
MOZ_ASSERT(NS_IsMainThread());
bool keyStatusesChange = false;
{
CDMCaps::AutoLock caps(Capabilites());
keyStatusesChange = caps.RemoveKeysForSession(nsString(aSessionId));
}
if (keyStatusesChange) {
OnKeyStatusesChange(aSessionId);
}
if (mKeys.IsNull()) {
return;
}
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->OnClosed();
}
}
void
ChromiumCDMProxy::OnDecrypted(uint32_t aId,
DecryptStatus aResult,
const nsTArray<uint8_t>& aDecryptedData)
{
}
void
ChromiumCDMProxy::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
ChromiumCDMProxy::OnRejectPromise(uint32_t aPromiseId,
nsresult aDOMException,
const nsCString& aMsg)
{
MOZ_ASSERT(NS_IsMainThread());
RejectPromise(aPromiseId, aDOMException, aMsg);
}
const nsString&
ChromiumCDMProxy::KeySystem() const
{
return mKeySystem;
}
CDMCaps&
ChromiumCDMProxy::Capabilites()
{
return mCapabilites;
}
RefPtr<DecryptPromise>
ChromiumCDMProxy::Decrypt(MediaRawData* aSample)
{
RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
if (!cdm) {
return DecryptPromise::CreateAndReject(DecryptResult(AbortedErr, aSample),
__func__);
}
RefPtr<MediaRawData> sample = aSample;
return InvokeAsync(
mGMPThread, __func__, [cdm, sample]() { return cdm->Decrypt(sample); });
}
void
ChromiumCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
nsTArray<nsCString>& aSessionIds)
{
CDMCaps::AutoLock caps(Capabilites());
caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
}
void
ChromiumCDMProxy::Terminated()
{
if (!mKeys.IsNull()) {
mKeys->Terminated();
}
}
already_AddRefed<gmp::ChromiumCDMParent>
ChromiumCDMProxy::GetCDMParent()
{
MutexAutoLock lock(mCDMMutex);
RefPtr<gmp::ChromiumCDMParent> cdm = mCDM;
return cdm.forget();
}
} // namespace mozilla

View File

@ -0,0 +1,132 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 ChromiumCDMProxy_h_
#define ChromiumCDMProxy_h_
#include "mozilla/CDMProxy.h"
#include "mozilla/AbstractThread.h"
#include "ChromiumCDMParent.h"
namespace mozilla {
class MediaRawData;
class DecryptJob;
class ChromiumCDMProxy : public CDMProxy
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMProxy, override)
ChromiumCDMProxy(dom::MediaKeys* aKeys,
const nsAString& aKeySystem,
GMPCrashHelper* aCrashHelper,
bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState,
nsIEventTarget* aMainThread);
void Init(PromiseId aPromiseId,
const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
const nsAString& aGMPName) override;
void CreateSession(uint32_t aCreateSessionToken,
dom::MediaKeySessionType aSessionType,
PromiseId aPromiseId,
const nsAString& aInitDataType,
nsTArray<uint8_t>& aInitData) override;
void LoadSession(PromiseId aPromiseId,
dom::MediaKeySessionType aSessionType,
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
ChromiumCDMProxy* AsChromiumCDMProxy() override { return this; }
// Threadsafe. Note this may return a reference to a shutdown
// CDM, which will fail on all operations.
already_AddRefed<gmp::ChromiumCDMParent> GetCDMParent();
private:
void OnCDMCreated(uint32_t aPromiseId);
~ChromiumCDMProxy();
GMPCrashHelper* mCrashHelper;
Mutex mCDMMutex;
RefPtr<gmp::ChromiumCDMParent> mCDM;
RefPtr<AbstractThread> mGMPThread;
};
} // namespace mozilla
#endif // GMPCDMProxy_h_

View File

@ -328,6 +328,7 @@ GMPCDMProxy::gmp_CreateSession(UniquePtr<CreateSessionData>&& aData)
void
GMPCDMProxy::LoadSession(PromiseId aPromiseId,
dom::MediaKeySessionType aSessionType,
const nsAString& aSessionId)
{
MOZ_ASSERT(NS_IsMainThread());

View File

@ -43,6 +43,7 @@ public:
nsTArray<uint8_t>& aInitData) override;
void LoadSession(PromiseId aPromiseId,
dom::MediaKeySessionType aSessionType,
const nsAString& aSessionId) override;
void SetServerCertificate(PromiseId aPromiseId,

View File

@ -23,6 +23,7 @@
#include "prio.h"
#include "base/task.h"
#include "widevine-adapter/WidevineAdapter.h"
#include "ChromiumCDMAdapter.h"
using namespace mozilla::ipc;
@ -346,9 +347,10 @@ GMPChild::AnswerStartPlugin(const nsString& aAdapter)
#endif
bool isWidevine = aAdapter.EqualsLiteral("widevine");
bool isChromium = aAdapter.EqualsLiteral("chromium");
#if defined(MOZ_GMP_SANDBOX) && defined(XP_MACOSX)
MacSandboxPluginType pluginType = MacSandboxPluginType_GMPlugin_Default;
if (isWidevine) {
if (isWidevine || isChromium) {
pluginType = MacSandboxPluginType_GMPlugin_EME_Widevine;
}
if (!SetMacSandboxInfo(pluginType)) {
@ -358,7 +360,13 @@ GMPChild::AnswerStartPlugin(const nsString& aAdapter)
}
#endif
GMPAdapter* adapter = (isWidevine) ? new WidevineAdapter() : nullptr;
GMPAdapter* adapter = nullptr;
if (isWidevine) {
adapter = new WidevineAdapter();
} else if (isChromium) {
adapter = new ChromiumCDMAdapter();
}
if (!mGMPLoader->Load(libPath.get(),
libPath.Length(),
platformAPI,

View File

@ -8,6 +8,7 @@
#include "GMPDecryptorChild.h"
#include "GMPVideoDecoderChild.h"
#include "GMPVideoEncoderChild.h"
#include "ChromiumCDMChild.h"
#include "base/task.h"
namespace mozilla {
@ -93,6 +94,21 @@ GMPContentChild::DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor)
return true;
}
PChromiumCDMChild*
GMPContentChild::AllocPChromiumCDMChild()
{
ChromiumCDMChild* actor = new ChromiumCDMChild(this);
actor->AddRef();
return actor;
}
bool
GMPContentChild::DeallocPChromiumCDMChild(PChromiumCDMChild* aActor)
{
static_cast<ChromiumCDMChild*>(aActor)->Release();
return true;
}
mozilla::ipc::IPCResult
GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
{
@ -144,6 +160,24 @@ GMPContentChild::RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor)
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPContentChild::RecvPChromiumCDMConstructor(PChromiumCDMChild* aActor)
{
ChromiumCDMChild* child = static_cast<ChromiumCDMChild*>(aActor);
cdm::Host_8* host = child;
void* cdm = nullptr;
GMPErr err = mGMPChild->GetAPI(CHROMIUM_CDM_API, host, &cdm);
if (err != GMPNoErr || !cdm) {
NS_WARNING("GMPGetAPI call failed trying to get CDM.");
return IPC_FAIL_NO_REASON(this);
}
child->Init(static_cast<cdm::ContentDecryptionModule_8*>(cdm));
return IPC_OK();
}
void
GMPContentChild::CloseActive()
{
@ -165,6 +199,11 @@ GMPContentChild::CloseActive()
for (auto iter = videoEncoders.ConstIter(); !iter.Done(); iter.Next()) {
iter.Get()->GetKey()->SendShutdown();
}
const ManagedContainer<PChromiumCDMChild>& cdms = ManagedPChromiumCDMChild();
for (auto iter = cdms.ConstIter(); !iter.Done(); iter.Next()) {
iter.Get()->GetKey()->SendShutdown();
}
}
bool
@ -172,7 +211,8 @@ GMPContentChild::IsUsed()
{
return !ManagedPGMPDecryptorChild().IsEmpty() ||
!ManagedPGMPVideoDecoderChild().IsEmpty() ||
!ManagedPGMPVideoEncoderChild().IsEmpty();
!ManagedPGMPVideoEncoderChild().IsEmpty() ||
!ManagedPChromiumCDMChild().IsEmpty();
}
} // namespace gmp

View File

@ -26,6 +26,8 @@ public:
mozilla::ipc::IPCResult RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor) override;
mozilla::ipc::IPCResult RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor, const uint32_t& aDecryptorId) override;
mozilla::ipc::IPCResult RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor) override;
mozilla::ipc::IPCResult RecvPChromiumCDMConstructor(
PChromiumCDMChild* aActor) override;
PGMPDecryptorChild* AllocPGMPDecryptorChild() override;
bool DeallocPGMPDecryptorChild(PGMPDecryptorChild* aActor) override;
@ -36,6 +38,9 @@ public:
PGMPVideoEncoderChild* AllocPGMPVideoEncoderChild() override;
bool DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor) override;
PChromiumCDMChild* AllocPChromiumCDMChild() override;
bool DeallocPChromiumCDMChild(PChromiumCDMChild* aActor) override;
void ActorDestroy(ActorDestroyReason aWhy) override;
void ProcessingError(Result aCode, const char* aReason) override;

View File

@ -9,6 +9,7 @@
#include "GMPServiceChild.h"
#include "GMPVideoDecoderParent.h"
#include "GMPVideoEncoderParent.h"
#include "ChromiumCDMParent.h"
#include "mozIGeckoMediaPluginService.h"
#include "mozilla/Logging.h"
#include "mozilla/Unused.h"
@ -65,9 +66,8 @@ private:
void
GMPContentParent::ActorDestroy(ActorDestroyReason aWhy)
{
MOZ_ASSERT(mDecryptors.IsEmpty() &&
mVideoDecoders.IsEmpty() &&
mVideoEncoders.IsEmpty());
MOZ_ASSERT(mDecryptors.IsEmpty() && mVideoDecoders.IsEmpty() &&
mVideoEncoders.IsEmpty() && mChromiumCDMs.IsEmpty());
NS_DispatchToCurrentThread(new ReleaseGMPContentParent(this));
}
@ -77,6 +77,15 @@ GMPContentParent::CheckThread()
MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
}
void
GMPContentParent::ChromiumCDMDestroyed(ChromiumCDMParent* aDecoder)
{
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
MOZ_ALWAYS_TRUE(mChromiumCDMs.RemoveElement(aDecoder));
CloseIfUnused();
}
void
GMPContentParent::VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder)
{
@ -124,9 +133,8 @@ GMPContentParent::RemoveCloseBlocker()
void
GMPContentParent::CloseIfUnused()
{
if (mDecryptors.IsEmpty() &&
mVideoDecoders.IsEmpty() &&
mVideoEncoders.IsEmpty() &&
if (mDecryptors.IsEmpty() && mVideoDecoders.IsEmpty() &&
mVideoEncoders.IsEmpty() && mChromiumCDMs.IsEmpty() &&
mCloseBlockerCount == 0) {
RefPtr<GMPContentParent> toClose;
if (mParent) {
@ -159,7 +167,7 @@ GMPContentParent::GetGMPDecryptor(GMPDecryptorParent** aGMPDP)
return NS_OK;
}
nsIThread*
nsCOMPtr<nsIThread>
GMPContentParent::GMPThread()
{
if (!mGMPThread) {
@ -180,6 +188,21 @@ GMPContentParent::GMPThread()
return mGMPThread;
}
already_AddRefed<ChromiumCDMParent>
GMPContentParent::GetChromiumCDM()
{
PChromiumCDMParent* actor = SendPChromiumCDMConstructor();
if (!actor) {
return nullptr;
}
RefPtr<ChromiumCDMParent> parent = static_cast<ChromiumCDMParent*>(actor);
// TODO: Remove parent from mChromiumCDMs in ChromiumCDMParent::Destroy().
mChromiumCDMs.AppendElement(parent);
return parent.forget();
}
nsresult
GMPContentParent::GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD,
uint32_t aDecryptorId)
@ -217,6 +240,14 @@ GMPContentParent::GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE)
return NS_OK;
}
PChromiumCDMParent*
GMPContentParent::AllocPChromiumCDMParent()
{
ChromiumCDMParent* parent = new ChromiumCDMParent(this, GetPluginId());
NS_ADDREF(parent);
return parent;
}
PGMPVideoDecoderParent*
GMPContentParent::AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId)
{
@ -225,6 +256,14 @@ GMPContentParent::AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId)
return vdp;
}
bool
GMPContentParent::DeallocPChromiumCDMParent(PChromiumCDMParent* aActor)
{
ChromiumCDMParent* parent = static_cast<ChromiumCDMParent*>(aActor);
NS_RELEASE(parent);
return true;
}
bool
GMPContentParent::DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor)
{

View File

@ -17,6 +17,7 @@ class GMPDecryptorParent;
class GMPParent;
class GMPVideoDecoderParent;
class GMPVideoEncoderParent;
class ChromiumCDMParent;
class GMPContentParent final : public PGMPContentParent,
public GMPSharedMem
@ -36,7 +37,10 @@ public:
nsresult GetGMPDecryptor(GMPDecryptorParent** aGMPKS);
void DecryptorDestroyed(GMPDecryptorParent* aSession);
nsIThread* GMPThread();
already_AddRefed<ChromiumCDMParent> GetChromiumCDM();
void ChromiumCDMDestroyed(ChromiumCDMParent* aCDM);
nsCOMPtr<nsIThread> GMPThread();
// GMPSharedMem
void CheckThread() override;
@ -92,6 +96,9 @@ private:
PGMPDecryptorParent* AllocPGMPDecryptorParent() override;
bool DeallocPGMPDecryptorParent(PGMPDecryptorParent* aActor) override;
PChromiumCDMParent* AllocPChromiumCDMParent() override;
bool DeallocPChromiumCDMParent(PChromiumCDMParent* 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.
@ -103,6 +110,7 @@ private:
nsTArray<RefPtr<GMPVideoDecoderParent>> mVideoDecoders;
nsTArray<RefPtr<GMPVideoEncoderParent>> mVideoEncoders;
nsTArray<RefPtr<GMPDecryptorParent>> mDecryptors;
nsTArray<RefPtr<ChromiumCDMParent>> mChromiumCDMs;
nsCOMPtr<nsIThread> mGMPThread;
RefPtr<GMPParent> mParent;
nsCString mDisplayName;

View File

@ -119,7 +119,7 @@ private:
uint32_t mPluginId;
GMPDecryptorProxyCallback* mCallback;
#ifdef DEBUG
nsIThread* const mGMPThread;
nsCOMPtr<nsIThread> const mGMPThread;
#endif
};

19
dom/media/gmp/GMPLog.h Normal file
View File

@ -0,0 +1,19 @@
/* -*- 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 GMP_LOG_h_
#define GMP_LOG_h_
#include "mozilla/Logging.h"
namespace mozilla {
extern LogModule* GetGMPLog();
#define GMP_LOG(msg, ...) MOZ_LOG(GetGMPLog(), LogLevel::Debug, (msg, ##__VA_ARGS__))
} // namespace mozilla
#endif // GMP_LOG_h_

View File

@ -9,6 +9,7 @@
#include "gmp-video-codec.h"
#include "gmp-video-frame-encoded.h"
#include "gmp-decryption.h"
#include "IPCMessageUtils.h"
namespace IPC {

View File

@ -43,6 +43,7 @@ using CrashReporter::GetIDFromMinidump;
#include "mozilla/dom/WidevineCDMManifestBinding.h"
#include "widevine-adapter/WidevineAdapter.h"
#include "ChromiumCDMAdapter.h"
namespace mozilla {
@ -323,7 +324,7 @@ void
GMPParent::ChildTerminated()
{
RefPtr<GMPParent> self(this);
nsIThread* gmpThread = GMPThread();
nsCOMPtr<nsIThread> gmpThread = GMPThread();
if (!gmpThread) {
// Bug 1163239 - this can happen on shutdown.
@ -372,26 +373,20 @@ GMPParent::State() const
return mState;
}
// Not changing to use mService since we'll be removing it
nsIThread*
nsCOMPtr<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);
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
MOZ_ASSERT(mps);
if (!mps) {
return nullptr;
}
return mGMPThread;
// Note: GeckoMediaPluginService::GetThread() is threadsafe, and returns
// nullptr if the GeckoMediaPluginService has started shutdown.
nsCOMPtr<nsIThread> gmpThread;
mps->GetThread(getter_AddRefs(gmpThread));
return gmpThread;
}
/* static */
@ -589,7 +584,8 @@ GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
PGMPTimerParent*
GMPParent::AllocPGMPTimerParent()
{
GMPTimerParent* p = new GMPTimerParent(GMPThread());
nsCOMPtr<nsIThread> thread = GMPThread();
GMPTimerParent* p = new GMPTimerParent(thread);
mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown.
return p;
}
@ -734,6 +730,22 @@ GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
&GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
}
static bool
IsCDMAPISupported(const mozilla::dom::WidevineCDMManifest& aManifest)
{
nsresult ignored; // Note: ToInteger returns 0 on failure.
int32_t moduleVersion = aManifest.mX_cdm_module_versions.ToInteger(&ignored);
int32_t interfaceVersion =
aManifest.mX_cdm_interface_versions.ToInteger(&ignored);
int32_t hostVersion = aManifest.mX_cdm_host_versions.ToInteger(&ignored);
if (MediaPrefs::EMEChromiumAPIEnabled()) {
return ChromiumCDMAdapter::Supports(
moduleVersion, interfaceVersion, hostVersion);
}
return WidevineAdapter::Supports(
moduleVersion, interfaceVersion, hostVersion);
}
RefPtr<GenericPromise>
GMPParent::ParseChromiumManifest(const nsAString& aJSON)
{
@ -745,10 +757,7 @@ GMPParent::ParseChromiumManifest(const nsAString& 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))) {
if (!IsCDMAPISupported(m)) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
@ -786,7 +795,7 @@ GMPParent::ParseChromiumManifest(const nsAString& aJSON)
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
GMPCapability video(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER));
GMPCapability video;
nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs);
nsTArray<nsCString> codecs;
@ -808,15 +817,20 @@ GMPParent::ParseChromiumManifest(const nsAString& aJSON)
}
video.mAPITags.AppendElement(kEMEKeySystem);
if (MediaPrefs::EMEChromiumAPIEnabled()) {
video.mAPIName = NS_LITERAL_CSTRING(CHROMIUM_CDM_API);
mAdapter = NS_LITERAL_STRING("chromium");
} else {
video.mAPIName = NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
mAdapter = NS_LITERAL_STRING("widevine");
GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
decrypt.mAPITags.AppendElement(kEMEKeySystem);
mCapabilities.AppendElement(Move(decrypt));
}
mCapabilities.AppendElement(Move(video));
GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
decrypt.mAPITags.AppendElement(kEMEKeySystem);
mCapabilities.AppendElement(Move(decrypt));
mAdapter = NS_LITERAL_STRING("widevine");
return GenericPromise::CreateAndResolve(true, __func__);
}

View File

@ -100,7 +100,7 @@ public:
void DeleteProcess();
GMPState State() const;
nsIThread* GMPThread();
nsCOMPtr<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
@ -207,7 +207,6 @@ private:
nsTArray<RefPtr<GMPTimerParent>> mTimers;
nsTArray<RefPtr<GMPStorageParent>> mStorage;
nsCOMPtr<nsIThread> mGMPThread;
// NodeId the plugin is assigned to, or empty if the the plugin is not
// assigned to a NodeId.
nsCString mNodeId;

View File

@ -229,6 +229,47 @@ GeckoMediaPluginService::Init()
return GetThread(getter_AddRefs(thread));
}
RefPtr<GetCDMParentPromise>
GeckoMediaPluginService::GetCDM(const NodeId& aNodeId,
nsTArray<nsCString> aTags,
GMPCrashHelper* aHelper)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
if (mShuttingDownOnGMPThread || aTags.IsEmpty()) {
return GetCDMParentPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
typedef MozPromiseHolder<GetCDMParentPromise> PromiseHolder;
PromiseHolder* rawHolder(new PromiseHolder());
RefPtr<GetCDMParentPromise> promise = rawHolder->Ensure(__func__);
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
RefPtr<GMPCrashHelper> helper(aHelper);
GetContentParent(
aHelper, aNodeId, NS_LITERAL_CSTRING(CHROMIUM_CDM_API), aTags)
->Then(thread,
__func__,
[rawHolder, helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) {
RefPtr<GMPContentParent> parent = wrapper->mParent;
UniquePtr<PromiseHolder> holder(rawHolder);
RefPtr<ChromiumCDMParent> cdm = parent->GetChromiumCDM();
if (!parent) {
holder->Reject(NS_ERROR_FAILURE, __func__);
return;
}
if (helper) {
cdm->SetCrashHelper(helper);
}
holder->Resolve(cdm, __func__);
},
[rawHolder] {
UniquePtr<PromiseHolder> holder(rawHolder);
holder->Reject(NS_ERROR_FAILURE, __func__);
});
return promise;
}
void
GeckoMediaPluginService::ShutdownGMPThread()
{

View File

@ -24,6 +24,7 @@
#include "mozilla/MozPromise.h"
#include "GMPContentParent.h"
#include "GMPCrashHelper.h"
#include "ChromiumCDMParent.h"
template <class> struct already_AddRefed;
@ -35,7 +36,29 @@ extern LogModule* GetGMPLog();
namespace gmp {
typedef MozPromise<RefPtr<GMPContentParent::CloseBlocker>, nsresult, /* IsExclusive = */ true> GetGMPContentParentPromise;
struct NodeId
{
NodeId(const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
const nsAString& aGMPName)
: mOrigin(aOrigin)
, mTopLevelOrigin(aTopLevelOrigin)
, mGMPName(aGMPName)
{
}
nsString mOrigin;
nsString mTopLevelOrigin;
nsString mGMPName;
};
typedef MozPromise<RefPtr<GMPContentParent::CloseBlocker>,
nsresult,
/* IsExclusive = */ true>
GetGMPContentParentPromise;
typedef MozPromise<RefPtr<ChromiumCDMParent>,
nsresult,
/* IsExclusive = */ true>
GetCDMParentPromise;
class GeckoMediaPluginService : public mozIGeckoMediaPluginService
, public nsIObserver
@ -47,6 +70,10 @@ public:
NS_DECL_THREADSAFE_ISUPPORTS
RefPtr<GetCDMParentPromise> GetCDM(const NodeId& aNodeId,
nsTArray<nsCString> aTags,
GMPCrashHelper* aHelper);
// mozIGeckoMediaPluginService
NS_IMETHOD GetThread(nsIThread** aThread) override;
NS_IMETHOD GetDecryptingGMPVideoDecoder(GMPCrashHelper* aHelper,
@ -90,11 +117,17 @@ protected:
virtual void InitializePlugins(AbstractThread* aAbstractGMPThread) = 0;
virtual RefPtr<GetGMPContentParentPromise>
GetContentParent(GMPCrashHelper* aHelper,
const nsACString& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) = 0;
virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
GMPCrashHelper* aHelper,
const nsACString& aNodeIdString,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) = 0;
virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
GMPCrashHelper* aHelper,
const NodeId& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) = 0;
nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
nsresult GMPDispatch(already_AddRefed<nsIRunnable> event, uint32_t flags = NS_DISPATCH_NORMAL);

View File

@ -54,7 +54,7 @@ GeckoMediaPluginServiceChild::GetSingleton()
RefPtr<GetGMPContentParentPromise>
GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
const nsACString& aNodeId,
const nsACString& aNodeIdString,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags)
{
@ -64,7 +64,83 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
nsCString nodeId(aNodeId);
nsCString nodeIdString(aNodeIdString);
nsCString api(aAPI);
nsTArray<nsCString> tags(aTags);
RefPtr<GMPCrashHelper> helper(aHelper);
RefPtr<GeckoMediaPluginServiceChild> self(this);
GetServiceChild()->Then(
thread,
__func__,
[self, nodeIdString, api, tags, helper, rawHolder](GMPServiceChild* child) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
nsresult rv;
nsTArray<base::ProcessId> alreadyBridgedTo;
child->GetAlreadyBridgedTo(alreadyBridgedTo);
base::ProcessId otherProcess;
nsCString displayName;
uint32_t pluginId = 0;
ipc::Endpoint<PGMPContentParent> endpoint;
bool ok = child->SendLaunchGMP(nodeIdString,
api,
tags,
alreadyBridgedTo,
&pluginId,
&otherProcess,
&displayName,
&endpoint,
&rv);
if (helper && pluginId) {
// Note: Even if the launch failed, we need to connect the crash
// helper so that if the launch failed due to the plugin crashing,
// we can report the crash via the crash reporter. The crash
// handling notification will arrive shortly if the launch failed
// due to the plugin crashing.
self->ConnectCrashHelper(pluginId, helper);
}
if (!ok || NS_FAILED(rv)) {
LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP "
"failed rv=0x%x",
static_cast<uint32_t>(rv)));
holder->Reject(rv, __func__);
return;
}
RefPtr<GMPContentParent> parent =
child->GetBridgedGMPContentParent(otherProcess, Move(endpoint));
if (!alreadyBridgedTo.Contains(otherProcess)) {
parent->SetDisplayName(displayName);
parent->SetPluginId(pluginId);
}
RefPtr<GMPContentParent::CloseBlocker> blocker(
new GMPContentParent::CloseBlocker(parent));
holder->Resolve(blocker, __func__);
},
[rawHolder](nsresult rv) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
holder->Reject(rv, __func__);
});
return promise;
}
RefPtr<GetGMPContentParentPromise>
GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
const NodeId& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
MozPromiseHolder<GetGMPContentParentPromise>* rawHolder =
new MozPromiseHolder<GetGMPContentParentPromise>();
RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
NodeIdData nodeId(aNodeId.mOrigin, aNodeId.mTopLevelOrigin, aNodeId.mGMPName);
nsCString api(aAPI);
nsTArray<nsCString> tags(aTags);
RefPtr<GMPCrashHelper> helper(aHelper);
@ -82,15 +158,15 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
uint32_t pluginId = 0;
ipc::Endpoint<PGMPContentParent> endpoint;
bool ok = child->SendLaunchGMP(nodeId,
api,
tags,
alreadyBridgedTo,
&pluginId,
&otherProcess,
&displayName,
&endpoint,
&rv);
bool ok = child->SendLaunchGMPForNodeId(nodeId,
api,
tags,
alreadyBridgedTo,
&pluginId,
&otherProcess,
&displayName,
&endpoint,
&rv);
if (helper && pluginId) {
// Note: Even if the launch failed, we need to connect the crash

View File

@ -48,11 +48,17 @@ protected:
// Nothing to do here.
}
virtual RefPtr<GetGMPContentParentPromise>
GetContentParent(GMPCrashHelper* aHelper,
const nsACString& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) override;
virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
GMPCrashHelper* aHelper,
const nsACString& aNodeIdString,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) override;
RefPtr<GetGMPContentParentPromise> GetContentParent(
GMPCrashHelper* aHelper,
const NodeId& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) override;
private:
friend class OpenPGMPServiceChild;

View File

@ -353,10 +353,11 @@ GeckoMediaPluginServiceParent::EnsureInitialized() {
}
RefPtr<GetGMPContentParentPromise>
GeckoMediaPluginServiceParent::GetContentParent(GMPCrashHelper* aHelper,
const nsACString& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags)
GeckoMediaPluginServiceParent::GetContentParent(
GMPCrashHelper* aHelper,
const nsACString& aNodeIdString,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags)
{
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
if (!thread) {
@ -367,14 +368,16 @@ GeckoMediaPluginServiceParent::GetContentParent(GMPCrashHelper* aHelper,
PromiseHolder* rawHolder = new PromiseHolder();
RefPtr<GeckoMediaPluginServiceParent> self(this);
RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
nsCString nodeId(aNodeId);
nsCString nodeIdString(aNodeIdString);
nsTArray<nsCString> tags(aTags);
nsCString api(aAPI);
RefPtr<GMPCrashHelper> helper(aHelper);
EnsureInitialized()->Then(thread, __func__,
[self, tags, api, nodeId, helper, rawHolder]() -> void {
EnsureInitialized()->Then(
thread,
__func__,
[self, tags, api, nodeIdString, helper, rawHolder]() -> void {
UniquePtr<PromiseHolder> holder(rawHolder);
RefPtr<GMPParent> gmp = self->SelectPluginForAPI(nodeId, api, tags);
RefPtr<GMPParent> gmp = self->SelectPluginForAPI(nodeIdString, api, tags);
LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)self, (void *)gmp, api.get()));
if (!gmp) {
NS_WARNING("GeckoMediaPluginServiceParent::GetContentParentFrom failed");
@ -393,6 +396,25 @@ GeckoMediaPluginServiceParent::GetContentParent(GMPCrashHelper* aHelper,
return promise;
}
RefPtr<GetGMPContentParentPromise>
GeckoMediaPluginServiceParent::GetContentParent(
GMPCrashHelper* aHelper,
const NodeId& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
nsCString nodeIdString;
nsresult rv = GetNodeId(
aNodeId.mOrigin, aNodeId.mTopLevelOrigin, aNodeId.mGMPName, nodeIdString);
if (NS_FAILED(rv)) {
return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
return GetContentParent(aHelper, nodeIdString, aAPI, aTags);
}
void
GeckoMediaPluginServiceParent::InitializePlugins(
AbstractThread* aAbstractGMPThread)
@ -1736,6 +1758,36 @@ GMPServiceParent::RecvLaunchGMP(const nsCString& aNodeId,
return IPC_OK();
}
mozilla::ipc::IPCResult
GMPServiceParent::RecvLaunchGMPForNodeId(
const NodeIdData& aNodeId,
const nsCString& aApi,
nsTArray<nsCString>&& aTags,
nsTArray<ProcessId>&& aAlreadyBridgedTo,
uint32_t* aOutPluginId,
ProcessId* aOutId,
nsCString* aOutDisplayName,
Endpoint<PGMPContentParent>* aOutEndpoint,
nsresult* aOutRv)
{
nsCString nodeId;
nsresult rv = mService->GetNodeId(
aNodeId.mOrigin(), aNodeId.mTopLevelOrigin(), aNodeId.mGMPName(), nodeId);
if (!NS_SUCCEEDED(rv)) {
*aOutRv = rv;
return IPC_OK();
}
return RecvLaunchGMP(nodeId,
aApi,
Move(aTags),
Move(aAlreadyBridgedTo),
aOutPluginId,
aOutId,
aOutDisplayName,
aOutEndpoint,
aOutRv);
}
mozilla::ipc::IPCResult
GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin,
const nsString& aTopLevelOrigin,

View File

@ -117,11 +117,17 @@ protected:
RefPtr<GenericPromise::AllPromiseType> LoadFromEnvironment();
RefPtr<GenericPromise> AddOnGMPThread(nsString aDirectory);
virtual RefPtr<GetGMPContentParentPromise>
GetContentParent(GMPCrashHelper* aHelper,
const nsACString& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) override;
virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
GMPCrashHelper* aHelper,
const nsACString& aNodeIdString,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) override;
RefPtr<GetGMPContentParentPromise> GetContentParent(
GMPCrashHelper* aHelper,
const NodeId& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags) override;
private:
// Creates a copy of aOriginal. Note that the caller is responsible for
@ -223,23 +229,34 @@ public:
}
virtual ~GMPServiceParent();
mozilla::ipc::IPCResult RecvGetGMPNodeId(const nsString& aOrigin,
const nsString& aTopLevelOrigin,
const nsString& aGMPName,
nsCString* aID) override;
ipc::IPCResult RecvGetGMPNodeId(const nsString& aOrigin,
const nsString& aTopLevelOrigin,
const nsString& aGMPName,
nsCString* aID) override;
void ActorDestroy(ActorDestroyReason aWhy) override;
static bool Create(Endpoint<PGMPServiceParent>&& aGMPService);
mozilla::ipc::IPCResult RecvLaunchGMP(const nsCString& aNodeId,
const nsCString& aAPI,
nsTArray<nsCString>&& aTags,
nsTArray<ProcessId>&& aAlreadyBridgedTo,
uint32_t* aOutPluginId,
ProcessId* aOutID,
nsCString* aOutDisplayName,
Endpoint<PGMPContentParent>* aOutEndpoint,
nsresult* aOutRv) override;
ipc::IPCResult RecvLaunchGMP(const nsCString& aNodeId,
const nsCString& aAPI,
nsTArray<nsCString>&& aTags,
nsTArray<ProcessId>&& aAlreadyBridgedTo,
uint32_t* aOutPluginId,
ProcessId* aOutID,
nsCString* aOutDisplayName,
Endpoint<PGMPContentParent>* aOutEndpoint,
nsresult* aOutRv) override;
ipc::IPCResult RecvLaunchGMPForNodeId(
const NodeIdData& nodeId,
const nsCString& aAPI,
nsTArray<nsCString>&& aTags,
nsTArray<ProcessId>&& aAlreadyBridgedTo,
uint32_t* aOutPluginId,
ProcessId* aOutID,
nsCString* aOutDisplayName,
Endpoint<PGMPContentParent>* aOutEndpoint,
nsresult* aOutRv) override;
private:
void CloseTransport(Monitor* aSyncMonitor, bool* aCompleted);

View File

@ -9,6 +9,12 @@ using GMPMediaKeyStatus from "gmp-decryption.h";
namespace mozilla {
namespace gmp {
struct NodeIdData {
nsString mOrigin;
nsString mTopLevelOrigin;
nsString mGMPName;
};
struct GMPDecryptionData {
uint8_t[] mKeyId;
uint8_t[] mIV;
@ -54,5 +60,48 @@ struct GMPKeyInformation {
GMPMediaKeyStatus status;
};
struct CDMInputBuffer {
uint8_t[] mData;
uint8_t[] mKeyId;
uint8_t[] mIV;
int64_t mTimestamp;
int64_t mDuration;
uint16_t[] mClearBytes;
uint32_t[] mCipherBytes;
bool mIsEncrypted;
};
struct CDMVideoDecoderConfig {
uint32_t mCodec;
uint32_t mProfile;
uint32_t mFormat;
int32_t mImageWidth;
int32_t mImageHeight;
uint8_t[] mExtraData;
};
struct CDMKeyInformation {
uint8_t[] mKeyId;
uint32_t mStatus;
uint32_t mSystemCode;
};
struct CDMVideoPlane {
uint32_t mPlaneOffset;
uint32_t mStride;
};
struct CDMVideoFrame {
uint32_t mFormat;
int32_t mImageWidth;
int32_t mImageHeight;
uint8_t[] mData;
CDMVideoPlane mYPlane;
CDMVideoPlane mUPlane;
CDMVideoPlane mVPlane;
int64_t mTimestamp;
int64_t mDuration;
};
}
}

View File

@ -15,6 +15,7 @@
#include "prio.h"
#include "nsIConsoleService.h"
#include "mozIGeckoMediaPluginService.h"
#include "GMPService.h"
namespace mozilla {
@ -230,4 +231,12 @@ LogToConsole(const nsAString& aMsg)
console->LogStringMessage(msg.get());
}
RefPtr<AbstractThread>
GetGMPAbstractThread()
{
RefPtr<gmp::GeckoMediaPluginService> service =
gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
return service ? service->GetAbstractGMPThread() : nullptr;
}
} // namespace mozilla

View File

@ -7,10 +7,14 @@
#define GMPUtils_h_
#include "mozilla/UniquePtr.h"
#include "mozilla/RefPtr.h"
#include "mozilla/AbstractThread.h"
#include "nsTArray.h"
#include "nsCOMPtr.h"
#include "nsClassHashtable.h"
#define CHROMIUM_CDM_API "chromium-cdm8-host4"
class nsIFile;
class nsCString;
class nsISimpleEnumerator;
@ -81,6 +85,9 @@ HaveGMPFor(const nsCString& aAPI,
void
LogToConsole(const nsAString& aMsg);
RefPtr<AbstractThread>
GetGMPAbstractThread();
} // namespace mozilla
#endif

View File

@ -209,7 +209,8 @@ GMPVideoDecoderParent::Reset()
LogToBrowserConsole(NS_LITERAL_STRING("GMPVideoDecoderParent timed out waiting for ResetComplete()"));
});
CancelResetCompleteTimeout();
mResetCompleteTimeout = SimpleTimer::Create(task, 5000, mPlugin->GMPThread());
nsCOMPtr<nsIThread> thread = mPlugin->GMPThread();
mResetCompleteTimeout = SimpleTimer::Create(task, 5000, thread);
// Async IPC, we don't have access to a return value.
return NS_OK;

View File

@ -17,6 +17,7 @@
#include "runnable_utils.h"
#include "GMPUtils.h"
#include "mozilla/SystemGroup.h"
#include "GMPCrashHelper.h"
namespace mozilla {

View File

@ -0,0 +1,106 @@
/* -*- 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;
namespace mozilla {
namespace gmp {
async protocol PChromiumCDM
{
manager PGMPContent;
child:
// cdm::ContentDecryptionModule8
async Init(bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState);
async SetServerCertificate(uint32_t aPromiseId,
uint8_t[] aServerCert);
async CreateSessionAndGenerateRequest(uint32_t aPromiseId,
uint32_t aSessionType,
uint32_t aInitDataType,
uint8_t[] aInitData);
async LoadSession(uint32_t aPromiseId,
uint32_t aSessionType,
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 Decrypt(uint32_t aId, CDMInputBuffer aBuffer);
async InitializeVideoDecoder(CDMVideoDecoderConfig aConfig);
async DeinitializeVideoDecoder();
async ResetVideoDecoder();
async DecryptAndDecodeFrame(CDMInputBuffer aBuffer);
async Drain();
async Destroy();
parent:
async __delete__();
// cdm::Host8
async OnResolveNewSessionPromise(uint32_t aPromiseId, nsCString aSessionId);
async OnResolvePromise(uint32_t aPromiseId);
async OnRejectPromise(uint32_t aPromiseId,
uint32_t aError,
uint32_t aSystemCode,
nsCString aErrorMessage);
async OnSessionMessage(nsCString aSessionId,
uint32_t aMessageType,
uint8_t[] aMessage);
async OnSessionKeysChange(nsCString aSessionId,
CDMKeyInformation[] aKeysInfo);
async OnExpirationChange(nsCString aSessionId,
double aSecondsSinceEpoch);
async OnSessionClosed(nsCString aSessionId);
async OnLegacySessionError(nsCString aSessionId,
uint32_t aError,
uint32_t aSystemCode,
nsCString aMessage);
async ResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccessful);
// Return values of cdm::ContentDecryptionModule8::Decrypt
async Decrypted(uint32_t aId, uint32_t aStatus, uint8_t[] aData);
async OnDecoderInitDone(uint32_t aStatus);
// Return values of cdm::ContentDecryptionModule8::DecryptAndDecodeFrame
async Decoded(CDMVideoFrame aFrame);
async DecodeFailed(uint32_t aStatus);
async ResetVideoDecoderComplete();
async DrainComplete();
async Shutdown();
};
} // namespace gmp
} // namespace mozilla

View File

@ -6,6 +6,7 @@
include protocol PGMPVideoDecoder;
include protocol PGMPVideoEncoder;
include protocol PGMPDecryptor;
include protocol PChromiumCDM;
namespace mozilla {
namespace gmp {
@ -15,11 +16,13 @@ intr protocol PGMPContent
manages PGMPDecryptor;
manages PGMPVideoDecoder;
manages PGMPVideoEncoder;
manages PChromiumCDM;
child:
async PGMPDecryptor();
async PGMPVideoDecoder(uint32_t aDecryptorId);
async PGMPVideoEncoder();
async PChromiumCDM();
};
} // namespace gmp

View File

@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PGMPContent;
include GMPTypes;
using base::ProcessId from "base/process.h";
@ -23,6 +24,16 @@ parent:
Endpoint<PGMPContentParent> endpoint,
nsresult aResult);
sync LaunchGMPForNodeId(NodeIdData nodeId,
nsCString api,
nsCString[] tags,
ProcessId[] alreadyBridgedTo)
returns (uint32_t pluginId,
ProcessId id,
nsCString displayName,
Endpoint<PGMPContentParent> endpoint,
nsresult aResult);
sync GetGMPNodeId(nsString origin, nsString topLevelOrigin, nsString gmpName)
returns (nsCString id);
};

View File

@ -12,6 +12,8 @@ XPIDL_SOURCES += [
]
EXPORTS += [
'ChromiumCDMParent.h',
'ChromiumCDMProxy.h',
'DecryptJob.h',
'gmp-api/gmp-decryption.h',
'gmp-api/gmp-entrypoints.h',
@ -68,6 +70,10 @@ EXPORTS += [
]
UNIFIED_SOURCES += [
'ChromiumCDMAdapter.cpp',
'ChromiumCDMChild.cpp',
'ChromiumCDMParent.cpp',
'ChromiumCDMProxy.cpp',
'DecryptJob.cpp',
'GMPCDMCallbackProxy.cpp',
'GMPCDMProxy.cpp',
@ -111,6 +117,7 @@ DIRS += [
IPDL_SOURCES += [
'GMPTypes.ipdlh',
'PChromiumCDM.ipdl',
'PGMP.ipdl',
'PGMPContent.ipdl',
'PGMPDecryptor.ipdl',

View File

@ -15,7 +15,7 @@
#include "gmp-api/gmp-video-codec.h"
#include "gmp-api/gmp-platform.h"
static const GMPPlatformAPI* sPlatform = nullptr;
const GMPPlatformAPI* sPlatform = nullptr;
namespace mozilla {

View File

@ -16,7 +16,9 @@ SOURCES += [
EXPORTS += [
'WidevineDecryptor.h',
'WidevineUtils.h'
'WidevineFileIO.h',
'WidevineUtils.h',
'WidevineVideoFrame.h'
]
FINAL_LIBRARY = 'xul'

View File

@ -0,0 +1,148 @@
/* -*- 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 "ChromiumCDMVideoDecoder.h"
#include "ChromiumCDMProxy.h"
#include "content_decryption_module.h"
#include "GMPService.h"
#include "GMPVideoDecoder.h"
#include "MP4Decoder.h"
#include "VPXDecoder.h"
namespace mozilla {
ChromiumCDMVideoDecoder::ChromiumCDMVideoDecoder(
const GMPVideoDecoderParams& aParams,
CDMProxy* aCDMProxy)
: mCDMParent(aCDMProxy->AsChromiumCDMProxy()->GetCDMParent())
, mConfig(aParams.mConfig)
, mCrashHelper(aParams.mCrashHelper)
, mGMPThread(GetGMPAbstractThread())
, mImageContainer(aParams.mImageContainer)
{
}
ChromiumCDMVideoDecoder::~ChromiumCDMVideoDecoder()
{
}
static uint32_t
ToCDMH264Profile(uint8_t aProfile)
{
switch (aProfile) {
case 66:
return cdm::VideoDecoderConfig::kH264ProfileBaseline;
case 77:
return cdm::VideoDecoderConfig::kH264ProfileMain;
case 88:
return cdm::VideoDecoderConfig::kH264ProfileExtended;
case 100:
return cdm::VideoDecoderConfig::kH264ProfileHigh;
case 110:
return cdm::VideoDecoderConfig::kH264ProfileHigh10;
case 122:
return cdm::VideoDecoderConfig::kH264ProfileHigh422;
case 144:
return cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive;
}
return cdm::VideoDecoderConfig::kUnknownVideoCodecProfile;
}
RefPtr<MediaDataDecoder::InitPromise>
ChromiumCDMVideoDecoder::Init()
{
if (!mCDMParent) {
// Must have failed to get the CDMParent from the ChromiumCDMProxy
// in our constructor; the MediaKeys must have shut down the CDM
// before we had a chance to start up the decoder.
return InitPromise::CreateAndReject(
NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
}
gmp::CDMVideoDecoderConfig config;
if (MP4Decoder::IsH264(mConfig.mMimeType)) {
config.mCodec() = cdm::VideoDecoderConfig::kCodecH264;
config.mProfile() =
ToCDMH264Profile(mConfig.mExtraData->SafeElementAt(1, 0));
config.mExtraData() = *mConfig.mExtraData;
mConvertToAnnexB = true;
} else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
config.mCodec() = cdm::VideoDecoderConfig::kCodecVp8;
config.mProfile() = cdm::VideoDecoderConfig::kProfileNotNeeded;
} else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
config.mCodec() = cdm::VideoDecoderConfig::kCodecVp9;
config.mProfile() = cdm::VideoDecoderConfig::kProfileNotNeeded;
} else {
return MediaDataDecoder::InitPromise::CreateAndReject(
NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
}
config.mImageWidth() = mConfig.mImage.width;
config.mImageHeight() = mConfig.mImage.height;
RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
VideoInfo info = mConfig;
RefPtr<layers::ImageContainer> imageContainer = mImageContainer;
return InvokeAsync(
mGMPThread, __func__, [cdm, config, info, imageContainer]() {
return cdm->InitializeVideoDecoder(config, info, imageContainer);
});
}
const char*
ChromiumCDMVideoDecoder::GetDescriptionName() const
{
return "Chromium CDM video decoder";
}
MediaDataDecoder::ConversionRequired
ChromiumCDMVideoDecoder::NeedsConversion() const
{
return mConvertToAnnexB ? ConversionRequired::kNeedAnnexB
: ConversionRequired::kNeedNone;
}
RefPtr<MediaDataDecoder::DecodePromise>
ChromiumCDMVideoDecoder::Decode(MediaRawData* aSample)
{
RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
RefPtr<MediaRawData> sample = aSample;
return InvokeAsync(mGMPThread, __func__, [cdm, sample]() {
return cdm->DecryptAndDecodeFrame(sample);
});
}
RefPtr<MediaDataDecoder::FlushPromise>
ChromiumCDMVideoDecoder::Flush()
{
MOZ_ASSERT(mCDMParent);
RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
return InvokeAsync(
mGMPThread, __func__, [cdm]() { return cdm->FlushVideoDecoder(); });
}
RefPtr<MediaDataDecoder::DecodePromise>
ChromiumCDMVideoDecoder::Drain()
{
MOZ_ASSERT(mCDMParent);
RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
return InvokeAsync(mGMPThread, __func__, [cdm]() { return cdm->Drain(); });
}
RefPtr<ShutdownPromise>
ChromiumCDMVideoDecoder::Shutdown()
{
if (!mCDMParent) {
// Must have failed to get the CDMParent from the ChromiumCDMProxy
// in our constructor; the MediaKeys must have shut down the CDM
// before we had a chance to start up the decoder.
return ShutdownPromise::CreateAndResolve(true, __func__);
}
RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
return InvokeAsync(
mGMPThread, __func__, [cdm]() { return cdm->ShutdownVideoDecoder(); });
}
} // namespace mozilla

View File

@ -0,0 +1,46 @@
/* -*- 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 ChromiumCDMVideoDecoder_h_
#define ChromiumCDMVideoDecoder_h_
#include "PlatformDecoderModule.h"
#include "ChromiumCDMParent.h"
namespace mozilla {
class CDMProxy;
struct GMPVideoDecoderParams;
class ChromiumCDMVideoDecoder : public MediaDataDecoder
{
public:
ChromiumCDMVideoDecoder(const GMPVideoDecoderParams& aParams,
CDMProxy* aCDMProxy);
RefPtr<InitPromise> Init() override;
RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
RefPtr<FlushPromise> Flush() override;
RefPtr<DecodePromise> Drain() override;
RefPtr<ShutdownPromise> Shutdown() override;
const char* GetDescriptionName() const override;
ConversionRequired NeedsConversion() const override;
private:
~ChromiumCDMVideoDecoder();
RefPtr<gmp::ChromiumCDMParent> mCDMParent;
const VideoInfo mConfig;
RefPtr<GMPCrashHelper> mCrashHelper;
RefPtr<AbstractThread> mGMPThread;
RefPtr<layers::ImageContainer> mImageContainer;
MozPromiseHolder<InitPromise> mInitPromise;
bool mConvertToAnnexB = false;
};
} // mozilla
#endif // ChromiumCDMVideoDecoder_h_

View File

@ -353,7 +353,11 @@ EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
RefPtr<MediaDataDecoderProxy> wrapper =
CreateDecoderWrapper(mProxy, aParams);
auto params = GMPVideoDecoderParams(aParams);
wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
if (MediaPrefs::EMEChromiumAPIEnabled()) {
wrapper->SetProxyTarget(new ChromiumCDMVideoDecoder(params, mProxy));
} else {
wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
}
return wrapper.forget();
}

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS += [
'ChromiumCDMVideoDecoder.h',
'DecryptThroughputLimit.h',
'EMEDecoderModule.h',
'EMEVideoDecoder.h',
@ -12,6 +13,7 @@ EXPORTS += [
]
UNIFIED_SOURCES += [
'ChromiumCDMVideoDecoder.cpp',
'EMEDecoderModule.cpp',
'EMEVideoDecoder.cpp',
'SamplesWaitingForKey.cpp',

View File

@ -79,19 +79,20 @@ GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
return false;
}
nsCString api = MediaPrefs::EMEChromiumAPIEnabled()
? NS_LITERAL_CSTRING(CHROMIUM_CDM_API)
: NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
if (MP4Decoder::IsH264(aMimeType)) {
return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
{ NS_LITERAL_CSTRING("h264"), aGMP.value()});
return HaveGMPFor(api, { 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()});
return HaveGMPFor(api, { 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()});
return HaveGMPFor(api, { NS_LITERAL_CSTRING("vp8"), aGMP.value()});
}
return false;

View File

@ -33,7 +33,7 @@ CompositionTransaction::CompositionTransaction(
, mReplaceLength(aReplaceLength)
, mRanges(aTextRangeArray)
, mStringToInsert(aStringToInsert)
, mEditorBase(aEditorBase)
, mEditorBase(&aEditorBase)
, mRangeUpdater(aRangeUpdater)
, mFixed(false)
{
@ -45,6 +45,7 @@ CompositionTransaction::~CompositionTransaction()
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(CompositionTransaction, EditTransactionBase,
mEditorBase,
mTextNode)
// mRangeList can't lead to cycles
@ -60,9 +61,13 @@ NS_IMPL_RELEASE_INHERITED(CompositionTransaction, EditTransactionBase)
NS_IMETHODIMP
CompositionTransaction::DoTransaction()
{
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_INITIALIZED;
}
// Fail before making any changes if there's no selection controller
nsCOMPtr<nsISelectionController> selCon;
mEditorBase.GetSelectionController(getter_AddRefs(selCon));
mEditorBase->GetSelectionController(getter_AddRefs(selCon));
NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
// Advance caret: This requires the presentation shell to get the selection.
@ -108,9 +113,13 @@ CompositionTransaction::DoTransaction()
NS_IMETHODIMP
CompositionTransaction::UndoTransaction()
{
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_INITIALIZED;
}
// Get the selection first so we'll fail before making any changes if we
// can't get it
RefPtr<Selection> selection = mEditorBase.GetSelection();
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
nsresult rv = mTextNode->DeleteData(mOffset, mStringToInsert.Length());
@ -171,7 +180,10 @@ CompositionTransaction::GetTxnDescription(nsAString& aString)
nsresult
CompositionTransaction::SetSelectionForRanges()
{
return SetIMESelection(mEditorBase, mTextNode, mOffset,
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_INITIALIZED;
}
return SetIMESelection(*mEditorBase, mTextNode, mOffset,
mStringToInsert.Length(), mRanges);
}

View File

@ -90,7 +90,7 @@ private:
nsString mStringToInsert;
// The editor, which is used to get the selection controller.
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
RangeUpdater* mRangeUpdater;

View File

@ -49,6 +49,7 @@ CreateElementTransaction::~CreateElementTransaction()
NS_IMPL_CYCLE_COLLECTION_INHERITED(CreateElementTransaction,
EditTransactionBase,
mEditorBase,
mParent,
mNewNode,
mRefNode)
@ -62,7 +63,9 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
CreateElementTransaction::DoTransaction()
{
MOZ_ASSERT(mEditorBase && mTag && mParent);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTag) || NS_WARN_IF(!mParent)) {
return NS_ERROR_NOT_INITIALIZED;
}
mNewNode = mEditorBase->CreateHTMLContent(mTag);
NS_ENSURE_STATE(mNewNode);
@ -105,7 +108,9 @@ CreateElementTransaction::DoTransaction()
NS_IMETHODIMP
CreateElementTransaction::UndoTransaction()
{
MOZ_ASSERT(mEditorBase && mParent);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mParent)) {
return NS_ERROR_NOT_INITIALIZED;
}
ErrorResult rv;
mParent->RemoveChild(*mNewNode, rv);
@ -116,7 +121,9 @@ CreateElementTransaction::UndoTransaction()
NS_IMETHODIMP
CreateElementTransaction::RedoTransaction()
{
MOZ_ASSERT(mEditorBase && mParent);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mParent)) {
return NS_ERROR_NOT_INITIALIZED;
}
// First, reset mNewNode so it has no attributes or content
// XXX We never actually did this, we only cleared mNewNode's contents if it

View File

@ -57,7 +57,7 @@ protected:
virtual ~CreateElementTransaction();
// The document into which the new node will be inserted.
EditorBase* mEditorBase;
RefPtr<EditorBase> mEditorBase;
// The tag (mapping to object type) for the new element.
nsCOMPtr<nsIAtom> mTag;

View File

@ -15,7 +15,7 @@ namespace mozilla {
DeleteNodeTransaction::DeleteNodeTransaction(EditorBase& aEditorBase,
nsINode& aNodeToDelete,
RangeUpdater* aRangeUpdater)
: mEditorBase(aEditorBase)
: mEditorBase(&aEditorBase)
, mNodeToDelete(&aNodeToDelete)
, mParentNode(aNodeToDelete.GetParentNode())
, mRangeUpdater(aRangeUpdater)
@ -31,6 +31,7 @@ DeleteNodeTransaction::~DeleteNodeTransaction()
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteNodeTransaction, EditTransactionBase,
mEditorBase,
mNodeToDelete,
mParentNode,
mRefNode)
@ -43,8 +44,8 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
bool
DeleteNodeTransaction::CanDoIt() const
{
if (NS_WARN_IF(!mNodeToDelete) || !mParentNode ||
!mEditorBase.IsModifiableNode(mParentNode)) {
if (NS_WARN_IF(!mNodeToDelete) || NS_WARN_IF(!mEditorBase) ||
!mParentNode || !mEditorBase->IsModifiableNode(mParentNode)) {
return false;
}
return true;

View File

@ -46,7 +46,7 @@ protected:
virtual ~DeleteNodeTransaction();
// The editor for this transaction.
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
// The element to delete.
nsCOMPtr<nsINode> mNodeToDelete;

View File

@ -27,7 +27,7 @@ using namespace dom;
DeleteRangeTransaction::DeleteRangeTransaction(EditorBase& aEditorBase,
nsRange& aRangeToDelete,
RangeUpdater* aRangeUpdater)
: mEditorBase(aEditorBase)
: mEditorBase(&aEditorBase)
, mRangeToDelete(aRangeToDelete.CloneRange())
, mRangeUpdater(aRangeUpdater)
{
@ -35,6 +35,7 @@ DeleteRangeTransaction::DeleteRangeTransaction(EditorBase& aEditorBase,
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,
EditAggregateTransaction,
mEditorBase,
mRangeToDelete)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTransaction)
@ -43,7 +44,9 @@ NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
NS_IMETHODIMP
DeleteRangeTransaction::DoTransaction()
{
MOZ_ASSERT(mRangeToDelete);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mRangeToDelete)) {
return NS_ERROR_NOT_AVAILABLE;
}
// build the child transactions
nsCOMPtr<nsINode> startParent = mRangeToDelete->GetStartParent();
@ -77,9 +80,9 @@ DeleteRangeTransaction::DoTransaction()
// only set selection to deletion point if editor gives permission
bool bAdjustSelection;
mEditorBase.ShouldTxnSetSelection(&bAdjustSelection);
mEditorBase->ShouldTxnSetSelection(&bAdjustSelection);
if (bAdjustSelection) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
rv = selection->Collapse(startParent, startOffset);
NS_ENSURE_SUCCESS(rv, rv);
@ -117,6 +120,10 @@ DeleteRangeTransaction::CreateTxnsToDeleteBetween(nsINode* aNode,
int32_t aStartOffset,
int32_t aEndOffset)
{
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_AVAILABLE;
}
// see what kind of node we have
if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
// if the node is a chardata node, then delete chardata content
@ -131,7 +138,7 @@ DeleteRangeTransaction::CreateTxnsToDeleteBetween(nsINode* aNode,
static_cast<nsGenericDOMDataNode*>(aNode);
RefPtr<DeleteTextTransaction> deleteTextTransaction =
new DeleteTextTransaction(mEditorBase, *charDataNode, aStartOffset,
new DeleteTextTransaction(*mEditorBase, *charDataNode, aStartOffset,
numToDel, mRangeUpdater);
// If the text node isn't editable, it should be never undone/redone.
// So, the transaction shouldn't be recorded.
@ -150,7 +157,7 @@ DeleteRangeTransaction::CreateTxnsToDeleteBetween(nsINode* aNode,
break;
}
RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
new DeleteNodeTransaction(mEditorBase, *child, mRangeUpdater);
new DeleteNodeTransaction(*mEditorBase, *child, mRangeUpdater);
// XXX This is odd handling. Even if some children are not editable,
// editor should append transactions because they could be editable
// at undoing/redoing. Additionally, if the transaction needs to
@ -169,6 +176,10 @@ DeleteRangeTransaction::CreateTxnsToDeleteContent(nsINode* aNode,
int32_t aOffset,
nsIEditor::EDirection aAction)
{
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_AVAILABLE;
}
// see what kind of node we have
if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
// if the node is a chardata node, then delete chardata content
@ -185,7 +196,7 @@ DeleteRangeTransaction::CreateTxnsToDeleteContent(nsINode* aNode,
RefPtr<nsGenericDOMDataNode> dataNode =
static_cast<nsGenericDOMDataNode*>(aNode);
RefPtr<DeleteTextTransaction> deleteTextTransaction =
new DeleteTextTransaction(mEditorBase, *dataNode, start, numToDelete,
new DeleteTextTransaction(*mEditorBase, *dataNode, start, numToDelete,
mRangeUpdater);
// If the text node isn't editable, it should be never undone/redone.
// So, the transaction shouldn't be recorded.
@ -202,6 +213,10 @@ DeleteRangeTransaction::CreateTxnsToDeleteContent(nsINode* aNode,
nsresult
DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween()
{
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
nsresult rv = iter->Init(mRangeToDelete);
@ -214,7 +229,7 @@ DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween()
}
RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
new DeleteNodeTransaction(mEditorBase, *node, mRangeUpdater);
new DeleteNodeTransaction(*mEditorBase, *node, mRangeUpdater);
// XXX This is odd handling. Even if some nodes in the range are not
// editable, editor should append transactions because they could
// at undoing/redoing. Additionally, if the transaction needs to

View File

@ -61,7 +61,7 @@ protected:
nsIEditor::EDirection aAction);
// The editor for this transaction.
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
// P1 in the range.
RefPtr<nsRange> mRangeToDelete;

View File

@ -25,7 +25,7 @@ DeleteTextTransaction::DeleteTextTransaction(
uint32_t aOffset,
uint32_t aNumCharsToDelete,
RangeUpdater* aRangeUpdater)
: mEditorBase(aEditorBase)
: mEditorBase(&aEditorBase)
, mCharData(&aCharData)
, mOffset(aOffset)
, mNumCharsToDelete(aNumCharsToDelete)
@ -36,6 +36,7 @@ DeleteTextTransaction::DeleteTextTransaction(
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteTextTransaction, EditTransactionBase,
mEditorBase,
mCharData)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteTextTransaction)
@ -44,16 +45,18 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
bool
DeleteTextTransaction::CanDoIt() const
{
if (NS_WARN_IF(!mCharData)) {
if (NS_WARN_IF(!mCharData) || NS_WARN_IF(!mEditorBase)) {
return false;
}
return mEditorBase.IsModifiableNode(mCharData);
return mEditorBase->IsModifiableNode(mCharData);
}
NS_IMETHODIMP
DeleteTextTransaction::DoTransaction()
{
MOZ_ASSERT(mCharData);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mCharData)) {
return NS_ERROR_NOT_AVAILABLE;
}
// Get the text that we're about to delete
nsresult rv = mCharData->SubstringData(mOffset, mNumCharsToDelete,
@ -67,8 +70,8 @@ DeleteTextTransaction::DoTransaction()
}
// Only set selection to deletion point if editor gives permission
if (mEditorBase.GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
if (mEditorBase->GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
rv = selection->Collapse(mCharData, mOffset);
NS_ASSERTION(NS_SUCCEEDED(rv),
@ -84,8 +87,9 @@ DeleteTextTransaction::DoTransaction()
NS_IMETHODIMP
DeleteTextTransaction::UndoTransaction()
{
MOZ_ASSERT(mCharData);
if (NS_WARN_IF(!mCharData)) {
return NS_ERROR_NOT_INITIALIZED;
}
return mCharData->InsertData(mOffset, mDeletedText);
}

View File

@ -57,7 +57,7 @@ public:
protected:
// The provider of basic editing operations.
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
// The CharacterData node to operate upon.
RefPtr<nsGenericDOMDataNode> mCharData;

View File

@ -465,6 +465,13 @@ EditorBase::PreDestroy(bool aDestroyingFrames)
mSpellcheckCheckboxState = eTriUnset;
mRootElement = nullptr;
// Transaction may grab this instance. Therefore, they should be released
// here for stopping the circular reference with this instance.
if (mTxnMgr) {
mTxnMgr->Clear();
mTxnMgr = nullptr;
}
mDidPreDestroy = true;
return NS_OK;
}

View File

@ -7301,14 +7301,17 @@ HTMLEditRules::PinSelectionToNewBlock(Selection* aSelection)
return NS_OK;
}
if (NS_WARN_IF(!mNewBlock)) {
return NS_ERROR_NULL_POINTER;
}
// get the (collapsed) selection location
nsCOMPtr<nsIDOMNode> selNode, temp;
nsCOMPtr<nsIDOMNode> selNode;
int32_t selOffset;
nsresult rv =
EditorBase::GetStartNodeAndOffset(aSelection,
getter_AddRefs(selNode), &selOffset);
NS_ENSURE_SUCCESS(rv, rv);
temp = selNode;
// use ranges and sRangeHelper to compare sel point to new block
nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
@ -7318,24 +7321,23 @@ HTMLEditRules::PinSelectionToNewBlock(Selection* aSelection)
NS_ENSURE_SUCCESS(rv, rv);
rv = range->SetEnd(selNode, selOffset);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContent> block = mNewBlock.get();
NS_ENSURE_TRUE(block, NS_ERROR_NO_INTERFACE);
bool nodeBefore, nodeAfter;
rv = nsRange::CompareNodeToRange(block, range, &nodeBefore, &nodeAfter);
rv = nsRange::CompareNodeToRange(mNewBlock, range, &nodeBefore, &nodeAfter);
NS_ENSURE_SUCCESS(rv, rv);
if (nodeBefore && nodeAfter) {
return NS_OK; // selection is inside block
} else if (nodeBefore) {
// selection is after block. put at end of block.
nsCOMPtr<nsIDOMNode> tmp = GetAsDOMNode(mNewBlock);
NS_ENSURE_STATE(mHTMLEditor);
tmp = GetAsDOMNode(mHTMLEditor->GetLastEditableChild(*block));
nsCOMPtr<nsINode> tmp = mHTMLEditor->GetLastEditableChild(*mNewBlock);
if (!tmp) {
tmp = mNewBlock;
}
uint32_t endPoint;
if (EditorBase::IsTextNode(tmp) ||
mHTMLEditor->IsContainer(tmp)) {
rv = EditorBase::GetLengthOfDOMNode(tmp, endPoint);
NS_ENSURE_SUCCESS(rv, rv);
endPoint = tmp->Length();
} else {
tmp = EditorBase::GetNodeLocation(tmp, (int32_t*)&endPoint);
endPoint++; // want to be after this node
@ -7343,9 +7345,11 @@ HTMLEditRules::PinSelectionToNewBlock(Selection* aSelection)
return aSelection->Collapse(tmp, (int32_t)endPoint);
} else {
// selection is before block. put at start of block.
nsCOMPtr<nsIDOMNode> tmp = GetAsDOMNode(mNewBlock);
NS_ENSURE_STATE(mHTMLEditor);
tmp = GetAsDOMNode(mHTMLEditor->GetFirstEditableChild(*block));
nsCOMPtr<nsINode> tmp = mHTMLEditor->GetFirstEditableChild(*mNewBlock);
if (!tmp) {
tmp = mNewBlock;
}
int32_t offset;
if (EditorBase::IsTextNode(tmp) ||
mHTMLEditor->IsContainer(tmp)) {

View File

@ -28,7 +28,7 @@ InsertNodeTransaction::InsertNodeTransaction(nsIContent& aNode,
: mNode(&aNode)
, mParent(&aParent)
, mOffset(aOffset)
, mEditorBase(aEditorBase)
, mEditorBase(&aEditorBase)
{
}
@ -37,6 +37,7 @@ InsertNodeTransaction::~InsertNodeTransaction()
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTransaction, EditTransactionBase,
mEditorBase,
mNode,
mParent)
@ -48,7 +49,9 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
InsertNodeTransaction::DoTransaction()
{
MOZ_ASSERT(mNode && mParent);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mNode) || NS_WARN_IF(!mParent)) {
return NS_ERROR_NOT_INITIALIZED;
}
uint32_t count = mParent->GetChildCount();
if (mOffset > static_cast<int32_t>(count) || mOffset == -1) {
@ -59,15 +62,15 @@ InsertNodeTransaction::DoTransaction()
// Note, it's ok for ref to be null. That means append.
nsCOMPtr<nsIContent> ref = mParent->GetChildAt(mOffset);
mEditorBase.MarkNodeDirty(GetAsDOMNode(mNode));
mEditorBase->MarkNodeDirty(GetAsDOMNode(mNode));
ErrorResult rv;
mParent->InsertBefore(*mNode, ref, rv);
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
// Only set selection to insertion point if editor gives permission
if (mEditorBase.GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
if (mEditorBase->GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
// Place the selection just after the inserted element
selection->Collapse(mParent, mOffset + 1);
@ -80,8 +83,9 @@ InsertNodeTransaction::DoTransaction()
NS_IMETHODIMP
InsertNodeTransaction::UndoTransaction()
{
MOZ_ASSERT(mNode && mParent);
if (NS_WARN_IF(!mNode) || NS_WARN_IF(!mParent)) {
return NS_ERROR_NOT_INITIALIZED;
}
ErrorResult rv;
mParent->RemoveChild(*mNode, rv);
return rv.StealNSResult();

View File

@ -50,7 +50,7 @@ protected:
int32_t mOffset;
// The editor for this transaction.
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
};
} // namespace mozilla

View File

@ -26,7 +26,7 @@ InsertTextTransaction::InsertTextTransaction(Text& aTextNode,
: mTextNode(&aTextNode)
, mOffset(aOffset)
, mStringToInsert(aStringToInsert)
, mEditorBase(aEditorBase)
, mEditorBase(&aEditorBase)
, mRangeUpdater(aRangeUpdater)
{
}
@ -36,6 +36,7 @@ InsertTextTransaction::~InsertTextTransaction()
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextTransaction, EditTransactionBase,
mEditorBase,
mTextNode)
NS_IMPL_ADDREF_INHERITED(InsertTextTransaction, EditTransactionBase)
@ -50,12 +51,16 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
InsertTextTransaction::DoTransaction()
{
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
NS_ENSURE_SUCCESS(rv, rv);
// Only set selection to insertion point if editor gives permission
if (mEditorBase.GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
if (mEditorBase->GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
DebugOnly<nsresult> rv =
selection->Collapse(mTextNode, mOffset + mStringToInsert.Length());

View File

@ -76,7 +76,7 @@ private:
nsString mStringToInsert;
// The editor, which we'll need to get the selection.
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
RangeUpdater* mRangeUpdater;
};

View File

@ -21,7 +21,7 @@ using namespace dom;
JoinNodeTransaction::JoinNodeTransaction(EditorBase& aEditorBase,
nsINode& aLeftNode,
nsINode& aRightNode)
: mEditorBase(aEditorBase)
: mEditorBase(&aEditorBase)
, mLeftNode(&aLeftNode)
, mRightNode(&aRightNode)
, mOffset(0)
@ -29,6 +29,7 @@ JoinNodeTransaction::JoinNodeTransaction(EditorBase& aEditorBase,
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(JoinNodeTransaction, EditTransactionBase,
mEditorBase,
mLeftNode,
mRightNode,
mParent)
@ -41,10 +42,11 @@ JoinNodeTransaction::CanDoIt() const
{
if (NS_WARN_IF(!mLeftNode) ||
NS_WARN_IF(!mRightNode) ||
NS_WARN_IF(!mEditorBase) ||
!mLeftNode->GetParentNode()) {
return false;
}
return mEditorBase.IsModifiableNode(mLeftNode->GetParentNode());
return mEditorBase->IsModifiableNode(mLeftNode->GetParentNode());
}
// After DoTransaction() and RedoTransaction(), the left node is removed from
@ -52,6 +54,12 @@ JoinNodeTransaction::CanDoIt() const
NS_IMETHODIMP
JoinNodeTransaction::DoTransaction()
{
if (NS_WARN_IF(!mEditorBase) ||
NS_WARN_IF(!mLeftNode) ||
NS_WARN_IF(!mRightNode)) {
return NS_ERROR_NOT_INITIALIZED;
}
// Get the parent node
nsCOMPtr<nsINode> leftParent = mLeftNode->GetParentNode();
NS_ENSURE_TRUE(leftParent, NS_ERROR_NULL_POINTER);
@ -67,7 +75,7 @@ JoinNodeTransaction::DoTransaction()
mParent = leftParent;
mOffset = mLeftNode->Length();
return mEditorBase.JoinNodesImpl(mRightNode, mLeftNode, mParent);
return mEditorBase->JoinNodesImpl(mRightNode, mLeftNode, mParent);
}
//XXX: What if instead of split, we just deleted the unneeded children of
@ -75,7 +83,11 @@ JoinNodeTransaction::DoTransaction()
NS_IMETHODIMP
JoinNodeTransaction::UndoTransaction()
{
MOZ_ASSERT(mParent);
if (NS_WARN_IF(!mParent) ||
NS_WARN_IF(!mLeftNode) ||
NS_WARN_IF(!mRightNode)) {
return NS_ERROR_NOT_INITIALIZED;
}
// First, massage the existing node so it is in its post-split state
ErrorResult rv;

View File

@ -48,7 +48,7 @@ public:
NS_DECL_EDITTRANSACTIONBASE
protected:
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
// The nodes to operate upon. After the merge, mRightNode remains and
// mLeftNode is removed from the content tree.

View File

@ -25,7 +25,7 @@ PlaceholderTransaction::PlaceholderTransaction(
, mCompositionTransaction(nullptr)
, mCommitted(false)
, mStartSel(Move(aSelState))
, mEditorBase(aEditorBase)
, mEditorBase(&aEditorBase)
{
mName = aName;
}
@ -41,6 +41,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PlaceholderTransaction,
if (tmp->mStartSel) {
ImplCycleCollectionUnlink(*tmp->mStartSel);
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorBase);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSel);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -49,6 +50,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PlaceholderTransaction,
if (tmp->mStartSel) {
ImplCycleCollectionTraverse(cb, *tmp->mStartSel, "mStartSel", 0);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorBase);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSel);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@ -69,6 +71,10 @@ PlaceholderTransaction::DoTransaction()
NS_IMETHODIMP
PlaceholderTransaction::UndoTransaction()
{
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_INITIALIZED;
}
// Undo transactions.
nsresult rv = EditAggregateTransaction::UndoTransaction();
NS_ENSURE_SUCCESS(rv, rv);
@ -76,7 +82,7 @@ PlaceholderTransaction::UndoTransaction()
NS_ENSURE_TRUE(mStartSel, NS_ERROR_NULL_POINTER);
// now restore selection
RefPtr<Selection> selection = mEditorBase.GetSelection();
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
return mStartSel->RestoreSelection(selection);
}
@ -84,12 +90,16 @@ PlaceholderTransaction::UndoTransaction()
NS_IMETHODIMP
PlaceholderTransaction::RedoTransaction()
{
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_INITIALIZED;
}
// Redo transactions.
nsresult rv = EditAggregateTransaction::RedoTransaction();
NS_ENSURE_SUCCESS(rv, rv);
// now restore selection
RefPtr<Selection> selection = mEditorBase.GetSelection();
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
return mEndSel.RestoreSelection(selection);
}
@ -254,7 +264,11 @@ PlaceholderTransaction::Commit()
nsresult
PlaceholderTransaction::RememberEndingSelection()
{
RefPtr<Selection> selection = mEditorBase.GetSelection();
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_INITIALIZED;
}
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
mEndSel.SaveSelection(selection);
return NS_OK;

View File

@ -82,7 +82,7 @@ protected:
SelectionState mEndSel;
// The editor for this transaction.
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
};
} // namespace mozilla

View File

@ -19,7 +19,7 @@ using namespace dom;
SplitNodeTransaction::SplitNodeTransaction(EditorBase& aEditorBase,
nsIContent& aNode,
int32_t aOffset)
: mEditorBase(aEditorBase)
: mEditorBase(&aEditorBase)
, mExistingRightNode(&aNode)
, mOffset(aOffset)
{
@ -30,6 +30,7 @@ SplitNodeTransaction::~SplitNodeTransaction()
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(SplitNodeTransaction, EditTransactionBase,
mEditorBase,
mParent,
mNewLeftNode)
@ -41,6 +42,10 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
SplitNodeTransaction::DoTransaction()
{
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_INITIALIZED;
}
// Create a new node
ErrorResult rv;
// Don't use .downcast directly because AsContent has an assertion we want
@ -48,16 +53,16 @@ SplitNodeTransaction::DoTransaction()
NS_ASSERTION(!rv.Failed() && clone, "Could not create clone");
NS_ENSURE_TRUE(!rv.Failed() && clone, rv.StealNSResult());
mNewLeftNode = dont_AddRef(clone.forget().take()->AsContent());
mEditorBase.MarkNodeDirty(mExistingRightNode->AsDOMNode());
mEditorBase->MarkNodeDirty(mExistingRightNode->AsDOMNode());
// Get the parent node
mParent = mExistingRightNode->GetParentNode();
NS_ENSURE_TRUE(mParent, NS_ERROR_NULL_POINTER);
// Insert the new node
rv = mEditorBase.SplitNodeImpl(*mExistingRightNode, mOffset, *mNewLeftNode);
if (mEditorBase.GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
rv = mEditorBase->SplitNodeImpl(*mExistingRightNode, mOffset, *mNewLeftNode);
if (mEditorBase->GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
rv = selection->Collapse(mNewLeftNode, mOffset);
}
@ -67,10 +72,14 @@ SplitNodeTransaction::DoTransaction()
NS_IMETHODIMP
SplitNodeTransaction::UndoTransaction()
{
MOZ_ASSERT(mNewLeftNode && mParent);
if (NS_WARN_IF(!mEditorBase) ||
NS_WARN_IF(!mNewLeftNode) ||
NS_WARN_IF(!mParent)) {
return NS_ERROR_NOT_INITIALIZED;
}
// This assumes Do inserted the new node in front of the prior existing node
return mEditorBase.JoinNodesImpl(mExistingRightNode, mNewLeftNode, mParent);
return mEditorBase->JoinNodesImpl(mExistingRightNode, mNewLeftNode, mParent);
}
/* Redo cannot simply resplit the right node, because subsequent transactions
@ -80,7 +89,10 @@ SplitNodeTransaction::UndoTransaction()
NS_IMETHODIMP
SplitNodeTransaction::RedoTransaction()
{
MOZ_ASSERT(mNewLeftNode && mParent);
if (NS_WARN_IF(!mNewLeftNode) ||
NS_WARN_IF(!mParent)) {
return NS_ERROR_NOT_INITIALIZED;
}
ErrorResult rv;
// First, massage the existing node so it is in its post-split state

View File

@ -49,7 +49,7 @@ public:
protected:
virtual ~SplitNodeTransaction();
EditorBase& mEditorBase;
RefPtr<EditorBase> mEditorBase;
// The node to operate upon.
nsCOMPtr<nsIContent> mExistingRightNode;

View File

@ -44,9 +44,9 @@ RemoveStyleSheet(EditorBase& aEditor, StyleSheet* aSheet)
* AddStyleSheetTransaction
******************************************************************************/
AddStyleSheetTransaction::AddStyleSheetTransaction(EditorBase& aEditor,
AddStyleSheetTransaction::AddStyleSheetTransaction(EditorBase& aEditorBase,
StyleSheet* aSheet)
: mEditor(aEditor)
: mEditorBase(&aEditorBase)
, mSheet(aSheet)
{
MOZ_ASSERT(aSheet);
@ -54,6 +54,7 @@ AddStyleSheetTransaction::AddStyleSheetTransaction(EditorBase& aEditor,
NS_IMPL_CYCLE_COLLECTION_INHERITED(AddStyleSheetTransaction,
EditTransactionBase,
mEditorBase,
mSheet)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AddStyleSheetTransaction)
@ -62,18 +63,20 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
AddStyleSheetTransaction::DoTransaction()
{
NS_ENSURE_TRUE(mSheet, NS_ERROR_NOT_INITIALIZED);
AddStyleSheet(mEditor, mSheet);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mSheet)) {
return NS_ERROR_NOT_INITIALIZED;
}
AddStyleSheet(*mEditorBase, mSheet);
return NS_OK;
}
NS_IMETHODIMP
AddStyleSheetTransaction::UndoTransaction()
{
NS_ENSURE_TRUE(mSheet, NS_ERROR_NOT_INITIALIZED);
RemoveStyleSheet(mEditor, mSheet);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mSheet)) {
return NS_ERROR_NOT_INITIALIZED;
}
RemoveStyleSheet(*mEditorBase, mSheet);
return NS_OK;
}
@ -88,9 +91,10 @@ AddStyleSheetTransaction::GetTxnDescription(nsAString& aString)
* RemoveStyleSheetTransaction
******************************************************************************/
RemoveStyleSheetTransaction::RemoveStyleSheetTransaction(EditorBase& aEditor,
StyleSheet* aSheet)
: mEditor(aEditor)
RemoveStyleSheetTransaction::RemoveStyleSheetTransaction(
EditorBase& aEditorBase,
StyleSheet* aSheet)
: mEditorBase(&aEditorBase)
, mSheet(aSheet)
{
MOZ_ASSERT(aSheet);
@ -98,6 +102,7 @@ RemoveStyleSheetTransaction::RemoveStyleSheetTransaction(EditorBase& aEditor,
NS_IMPL_CYCLE_COLLECTION_INHERITED(RemoveStyleSheetTransaction,
EditTransactionBase,
mEditorBase,
mSheet)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RemoveStyleSheetTransaction)
@ -106,18 +111,20 @@ NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
RemoveStyleSheetTransaction::DoTransaction()
{
NS_ENSURE_TRUE(mSheet, NS_ERROR_NOT_INITIALIZED);
RemoveStyleSheet(mEditor, mSheet);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mSheet)) {
return NS_ERROR_NOT_INITIALIZED;
}
RemoveStyleSheet(*mEditorBase, mSheet);
return NS_OK;
}
NS_IMETHODIMP
RemoveStyleSheetTransaction::UndoTransaction()
{
NS_ENSURE_TRUE(mSheet, NS_ERROR_NOT_INITIALIZED);
AddStyleSheet(mEditor, mSheet);
if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mSheet)) {
return NS_ERROR_NOT_INITIALIZED;
}
AddStyleSheet(*mEditorBase, mSheet);
return NS_OK;
}

View File

@ -32,7 +32,7 @@ public:
protected:
// The editor that created this transaction.
EditorBase& mEditor;
RefPtr<EditorBase> mEditorBase;
// The style sheet to add.
RefPtr<mozilla::StyleSheet> mSheet;
};
@ -55,7 +55,7 @@ public:
protected:
// The editor that created this transaction.
EditorBase& mEditor;
RefPtr<EditorBase> mEditorBase;
// The style sheet to remove.
RefPtr<StyleSheet> mSheet;

View File

@ -0,0 +1,19 @@
<!DOCTYPE>
<html>
<head>
<meta charset="UTF-8">
<script>
function boom(){
document.designMode = "on";
document.execCommand("insertlinebreak");
document.designMode = "off";
document.designMode = "on";
document.execCommand("insertunorderedlist");
}
addEventListener("DOMContentLoaded", boom);
</script>
</head>
<body style="display:flex;">
<!--comment-->
</body>
</html>

View File

@ -72,3 +72,4 @@ load 1272490.html
load 1317704.html
load 1317718.html
load 1324505.html
load 1348851.html

View File

@ -142,10 +142,12 @@ AnimationState::LoopLength() const
///////////////////////////////////////////////////////////////////////////////
Maybe<TimeStamp>
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState,
DrawableSurface& aFrames) const
{
TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime;
Maybe<FrameTimeout> timeout = GetTimeoutForFrame(aState, aState.mCurrentAnimationFrameIndex);
Maybe<FrameTimeout> timeout =
GetTimeoutForFrame(aState, aFrames, aState.mCurrentAnimationFrameIndex);
if (timeout.isNothing()) {
MOZ_ASSERT(aState.GetHasBeenDecoded() && !aState.GetIsCurrentlyDecoded());
@ -171,7 +173,9 @@ FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const
}
RefreshResult
FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
FrameAnimator::AdvanceFrame(AnimationState& aState,
DrawableSurface& aFrames,
TimeStamp aTime)
{
NS_ASSERTION(aTime <= TimeStamp::Now(),
"Given time appears to be in the future");
@ -231,7 +235,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
// the appropriate notification on the main thread. Make sure we stay in sync
// with AnimationState.
MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aFrames, nextFrameIndex);
// We should always check to see if we have the next frame even if we have
// previously finished decoding. If we needed to redecode (e.g. due to a draw
@ -243,7 +247,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
return ret;
}
Maybe<FrameTimeout> nextFrameTimeout = GetTimeoutForFrame(aState, nextFrameIndex);
Maybe<FrameTimeout> nextFrameTimeout = GetTimeoutForFrame(aState, aFrames, nextFrameIndex);
// GetTimeoutForFrame can only return none if frame doesn't exist,
// but we just got it above.
MOZ_ASSERT(nextFrameTimeout.isSome());
@ -257,11 +261,11 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
// Change frame
if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
if (!DoBlend(aFrames, &ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
// something went wrong, move on to next
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
nextFrame->SetCompositingFailed(true);
Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
MOZ_ASSERT(currentFrameEndTime.isSome());
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
@ -272,7 +276,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
nextFrame->SetCompositingFailed(false);
}
Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
MOZ_ASSERT(currentFrameEndTime.isSome());
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
@ -312,9 +316,29 @@ FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime)
return ret;
}
// Get the animation frames once now, and pass them down to callees because
// the surface could be discarded at anytime on a different thread. This is
// must easier to reason about then trying to write code that is safe to
// having the surface disappear at anytime.
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
if (!result) {
if (result.Type() == MatchType::NOT_FOUND) {
// No surface, and nothing pending, must have been discarded but
// we haven't been notified yet.
aState.SetDiscarded(true);
}
return ret;
}
// only advance the frame if the current time is greater than or
// equal to the current frame's end time.
Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
Maybe<TimeStamp> currentFrameEndTime =
GetCurrentImgFrameEndTime(aState, result.Surface());
if (currentFrameEndTime.isNothing()) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
MOZ_ASSERT(aState.GetHasBeenDecoded() && !aState.GetIsCurrentlyDecoded());
@ -327,12 +351,12 @@ FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime)
while (*currentFrameEndTime <= aTime) {
TimeStamp oldFrameEndTime = *currentFrameEndTime;
RefreshResult frameRes = AdvanceFrame(aState, aTime);
RefreshResult frameRes = AdvanceFrame(aState, result.Surface(), aTime);
// Accumulate our result for returning to callers.
ret.Accumulate(frameRes);
currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
currentFrameEndTime = GetCurrentImgFrameEndTime(aState, result.Surface());
// AdvanceFrame can't advance to a frame that doesn't exist yet.
MOZ_ASSERT(currentFrameEndTime.isSome());
@ -396,9 +420,10 @@ FrameAnimator::GetCompositedFrame(AnimationState& aState)
Maybe<FrameTimeout>
FrameAnimator::GetTimeoutForFrame(AnimationState& aState,
DrawableSurface& aFrames,
uint32_t aFrameNum) const
{
RawAccessFrameRef frame = GetRawFrame(aFrameNum);
RawAccessFrameRef frame = GetRawFrame(aFrames, aFrameNum);
if (frame) {
AnimationData data = frame->GetAnimationData();
return Some(data.mTimeout);
@ -454,37 +479,29 @@ FrameAnimator::CollectSizeOfCompositingSurfaces(
}
RawAccessFrameRef
FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
FrameAnimator::GetRawFrame(DrawableSurface& aFrames, uint32_t aFrameNum) const
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
if (!result) {
return RawAccessFrameRef();
}
// Seek to the frame we want. If seeking fails, it means we couldn't get the
// frame we're looking for, so we bail here to avoid returning the wrong frame
// to the caller.
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
if (NS_FAILED(aFrames.Seek(aFrameNum))) {
return RawAccessFrameRef(); // Not available yet.
}
return result.Surface()->RawAccessRef();
return aFrames->RawAccessRef();
}
//******************************************************************************
// DoBlend gets called when the timer for animation get fired and we have to
// update the composited frame of the animation.
bool
FrameAnimator::DoBlend(IntRect* aDirtyRect,
FrameAnimator::DoBlend(DrawableSurface& aFrames,
IntRect* aDirtyRect,
uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex)
{
RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
RawAccessFrameRef prevFrame = GetRawFrame(aFrames, aPrevFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aFrames, aNextFrameIndex);
MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");

View File

@ -21,6 +21,7 @@ namespace mozilla {
namespace image {
class RasterImage;
class DrawableSurface;
class AnimationState
{
@ -305,15 +306,19 @@ private: // methods
* @returns a RefreshResult that shows whether the frame was successfully
* advanced, and its resulting dirty rect.
*/
RefreshResult AdvanceFrame(AnimationState& aState, TimeStamp aTime);
RefreshResult AdvanceFrame(AnimationState& aState,
DrawableSurface& aFrames,
TimeStamp aTime);
/**
* Get the @aIndex-th frame in the frame index, ignoring results of blending.
*/
RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const;
RawAccessFrameRef GetRawFrame(DrawableSurface& aFrames,
uint32_t aFrameNum) const;
/// @return the given frame's timeout if it is available
Maybe<FrameTimeout> GetTimeoutForFrame(AnimationState& aState,
DrawableSurface& aFrames,
uint32_t aFrameNum) const;
/**
@ -322,9 +327,11 @@ private: // methods
* In the error case (like if the requested frame is not currently
* decoded), returns None().
*/
Maybe<TimeStamp> GetCurrentImgFrameEndTime(AnimationState& aState) const;
Maybe<TimeStamp> GetCurrentImgFrameEndTime(AnimationState& aState,
DrawableSurface& aFrames) const;
bool DoBlend(gfx::IntRect* aDirtyRect,
bool DoBlend(DrawableSurface& aFrames,
gfx::IntRect* aDirtyRect,
uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex);

View File

@ -1225,6 +1225,9 @@ RasterImage::Decode(const IntSize& aSize,
// Create a decoder.
RefPtr<IDecodingTask> task;
if (mAnimationState && aPlaybackType == PlaybackType::eAnimated) {
task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize,
decoderFlags, surfaceFlags);
mAnimationState->SetDiscarded(false);
// If the animation is finished we can draw right away because we just draw
// the final frame all the time from now on. See comment in
@ -1232,9 +1235,6 @@ RasterImage::Decode(const IntSize& aSize,
if (mAnimationFinished) {
mAnimationState->SetCompositedFrameInvalid(false);
}
task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize,
decoderFlags, surfaceFlags);
} else {
task = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize, aSize,

View File

@ -61,15 +61,7 @@ NS_DEFINE_NAMED_CID(NS_COLLATIONFACTORY_CID);
NS_DEFINE_NAMED_CID(NS_SCRIPTABLEDATEFORMAT_CID);
NS_DEFINE_NAMED_CID(NS_LANGUAGEATOMSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_PLATFORMCHARSET_CID);
#ifdef XP_WIN
NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
#endif
#ifdef USE_UNIX_LOCALE
NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
#endif
#ifdef USE_MAC_LOCALE
NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
#endif
static const mozilla::Module::CIDEntry kIntlCIDs[] = {
{ &kMOZ_LOCALESERVICE_CID, false, nullptr, mozilla::intl::LocaleServiceConstructor },
@ -89,15 +81,7 @@ static const mozilla::Module::CIDEntry kIntlCIDs[] = {
{ &kNS_SCRIPTABLEDATEFORMAT_CID, false, nullptr, NS_NewScriptableDateFormat },
{ &kNS_LANGUAGEATOMSERVICE_CID, false, nullptr, nsLanguageAtomServiceConstructor },
{ &kNS_PLATFORMCHARSET_CID, false, nullptr, nsPlatformCharsetConstructor },
#ifdef XP_WIN
{ &kNS_COLLATION_CID, false, nullptr, nsCollationWinConstructor },
#endif
#ifdef USE_UNIX_LOCALE
{ &kNS_COLLATION_CID, false, nullptr, nsCollationUnixConstructor },
#endif
#ifdef USE_MAC_LOCALE
{ &kNS_COLLATION_CID, false, nullptr, nsCollationMacUCConstructor },
#endif
{ &kNS_COLLATION_CID, false, nullptr, nsCollationConstructor },
{ nullptr }
};
@ -119,15 +103,7 @@ static const mozilla::Module::ContractIDEntry kIntlContracts[] = {
{ NS_SCRIPTABLEDATEFORMAT_CONTRACTID, &kNS_SCRIPTABLEDATEFORMAT_CID },
{ NS_LANGUAGEATOMSERVICE_CONTRACTID, &kNS_LANGUAGEATOMSERVICE_CID },
{ NS_PLATFORMCHARSET_CONTRACTID, &kNS_PLATFORMCHARSET_CID },
#ifdef XP_WIN
{ NS_COLLATION_CONTRACTID, &kNS_COLLATION_CID },
#endif
#ifdef USE_UNIX_LOCALE
{ NS_COLLATION_CONTRACTID, &kNS_COLLATION_CID },
#endif
#ifdef USE_MAC_LOCALE
{ NS_COLLATION_CONTRACTID, &kNS_COLLATION_CID },
#endif
{ nullptr }
};

View File

@ -5,7 +5,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
UNIFIED_SOURCES += [
'nsCollationMacUC.cpp',
'nsMacCharset.cpp',
]

View File

@ -1,213 +0,0 @@
/* -*- 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 "nsCollationMacUC.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsIServiceManager.h"
#include "prmem.h"
#include "nsString.h"
NS_IMPL_ISUPPORTS(nsCollationMacUC, nsICollation)
nsCollationMacUC::nsCollationMacUC()
: mInit(false)
, mHasCollator(false)
, mLastStrength(-1)
, mCollatorICU(nullptr)
{ }
nsCollationMacUC::~nsCollationMacUC()
{
#ifdef DEBUG
nsresult res =
#endif
CleanUpCollator();
NS_ASSERTION(NS_SUCCEEDED(res), "CleanUpCollator failed");
}
nsresult nsCollationMacUC::ConvertStrength(const int32_t aNSStrength,
UCollationStrength* aICUStrength,
UColAttributeValue* aCaseLevelOut)
{
NS_ENSURE_ARG_POINTER(aICUStrength);
NS_ENSURE_TRUE((aNSStrength < 4), NS_ERROR_FAILURE);
UCollationStrength strength = UCOL_DEFAULT;
UColAttributeValue caseLevel = UCOL_OFF;
switch (aNSStrength) {
case kCollationCaseInSensitive:
strength = UCOL_PRIMARY;
break;
case kCollationCaseInsensitiveAscii:
strength = UCOL_SECONDARY;
break;
case kCollationAccentInsenstive:
caseLevel = UCOL_ON;
strength = UCOL_PRIMARY;
break;
case kCollationCaseSensitive:
strength = UCOL_TERTIARY;
break;
default:
NS_WARNING("Bad aNSStrength passed to ConvertStrength.");
return NS_ERROR_FAILURE;
}
*aICUStrength = strength;
*aCaseLevelOut = caseLevel;
return NS_OK;
}
nsresult nsCollationMacUC::EnsureCollator(const int32_t newStrength)
{
NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
if (mHasCollator && (mLastStrength == newStrength))
return NS_OK;
nsresult res;
res = CleanUpCollator();
NS_ENSURE_SUCCESS(res, res);
UErrorCode status;
status = U_ZERO_ERROR;
mCollatorICU = ucol_open(mLocale.get(), &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
UCollationStrength strength;
UColAttributeValue caseLevel;
res = ConvertStrength(newStrength, &strength, &caseLevel);
NS_ENSURE_SUCCESS(res, res);
status = U_ZERO_ERROR;
ucol_setAttribute(mCollatorICU, UCOL_STRENGTH, strength, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_CASE_LEVEL, caseLevel, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_ALTERNATE_HANDLING, UCOL_DEFAULT, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_NUMERIC_COLLATION, UCOL_OFF, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_NORMALIZATION_MODE, UCOL_ON, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_CASE_FIRST, UCOL_DEFAULT, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
mHasCollator = true;
mLastStrength = newStrength;
return NS_OK;
}
nsresult nsCollationMacUC::CleanUpCollator(void)
{
if (mHasCollator) {
ucol_close(mCollatorICU);
mHasCollator = false;
}
return NS_OK;
}
NS_IMETHODIMP nsCollationMacUC::Initialize(const nsACString& locale)
{
NS_ENSURE_TRUE((!mInit), NS_ERROR_ALREADY_INITIALIZED);
nsCOMPtr<nsILocale> appLocale;
mLocale = locale;
mInit = true;
return NS_OK;
}
NS_IMETHODIMP nsCollationMacUC::AllocateRawSortKey(int32_t strength, const nsAString& stringIn,
uint8_t** key, uint32_t* outLen)
{
NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_ARG_POINTER(key);
NS_ENSURE_ARG_POINTER(outLen);
nsresult res = EnsureCollator(strength);
NS_ENSURE_SUCCESS(res, res);
uint32_t stringInLen = stringIn.Length();
const UChar* str = (const UChar*)stringIn.BeginReading();
int32_t keyLength = ucol_getSortKey(mCollatorICU, str, stringInLen, nullptr, 0);
NS_ENSURE_TRUE((stringInLen == 0 || keyLength > 0), NS_ERROR_FAILURE);
// Since key is freed elsewhere with PR_Free, allocate with PR_Malloc.
uint8_t* newKey = (uint8_t*)PR_Malloc(keyLength + 1);
if (!newKey) {
return NS_ERROR_OUT_OF_MEMORY;
}
keyLength = ucol_getSortKey(mCollatorICU, str, stringInLen, newKey, keyLength + 1);
NS_ENSURE_TRUE((stringInLen == 0 || keyLength > 0), NS_ERROR_FAILURE);
*key = newKey;
*outLen = keyLength;
return NS_OK;
}
NS_IMETHODIMP nsCollationMacUC::CompareString(int32_t strength, const nsAString& string1,
const nsAString& string2, int32_t* result)
{
NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_ARG_POINTER(result);
*result = 0;
nsresult rv = EnsureCollator(strength);
NS_ENSURE_SUCCESS(rv, rv);
UCollationResult uresult;
uresult = ucol_strcoll(mCollatorICU,
(const UChar*)string1.BeginReading(),
string1.Length(),
(const UChar*)string2.BeginReading(),
string2.Length());
int32_t res;
switch (uresult) {
case UCOL_LESS:
res = -1;
break;
case UCOL_EQUAL:
res = 0;
break;
case UCOL_GREATER:
res = 1;
break;
default:
MOZ_CRASH("ucol_strcoll returned bad UCollationResult");
}
*result = res;
return NS_OK;
}
NS_IMETHODIMP nsCollationMacUC::CompareRawSortKey(const uint8_t* key1, uint32_t len1,
const uint8_t* key2, uint32_t len2,
int32_t* result)
{
NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_ARG_POINTER(key1);
NS_ENSURE_ARG_POINTER(key2);
NS_ENSURE_ARG_POINTER(result);
*result = 0;
int32_t tmpResult = strcmp((const char*)key1, (const char*)key2);
int32_t res;
if (tmpResult < 0) {
res = -1;
} else if (tmpResult > 0) {
res = 1;
} else {
res = 0;
}
*result = res;
return NS_OK;
}

View File

@ -1,44 +0,0 @@
/* -*- 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 nsCollationMacUC_h_
#define nsCollationMacUC_h_
#include "mozilla/Attributes.h"
#include "nsICollation.h"
#include "nsCollation.h"
#include "nsString.h"
#include "unicode/ucol.h"
class nsCollationMacUC final : public nsICollation {
public:
nsCollationMacUC();
// nsISupports interface
NS_DECL_ISUPPORTS
// nsICollation interface
NS_DECL_NSICOLLATION
protected:
~nsCollationMacUC();
nsresult ConvertStrength(const int32_t aStrength,
UCollationStrength* aStrengthOut,
UColAttributeValue* aCaseLevelOut);
nsresult EnsureCollator(const int32_t newStrength);
nsresult CleanUpCollator(void);
private:
bool mInit;
bool mHasCollator;
nsCString mLocale;
int32_t mLastStrength;
UCollator* mCollatorICU;
};
#endif /* nsCollationMacUC_h_ */

View File

@ -32,7 +32,6 @@ XPIDL_MODULE = 'locale'
EXPORTS += [
'DateTimeFormat.h',
'nsCollation.h',
'nsCollationCID.h',
'nsILanguageAtomService.h',
'nsIPlatformCharset.h',
@ -48,7 +47,7 @@ EXPORTS.mozilla.intl += [
UNIFIED_SOURCES += [
'LocaleService.cpp',
'nsCollation.cpp',
'nsCollationFactory.cpp',
'nsLanguageAtomService.cpp',
'nsLocale.cpp',
'nsLocaleService.cpp',
@ -60,10 +59,12 @@ UNIFIED_SOURCES += [
if CONFIG['ENABLE_INTL_API']:
UNIFIED_SOURCES += [
'DateTimeFormat.cpp',
'nsCollation.cpp',
]
else:
UNIFIED_SOURCES += [
'DateTimeFormatAndroid.cpp',
'nsCollationAndroid.cpp',
]
EXTRA_JS_MODULES += [

View File

@ -4,142 +4,214 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCollation.h"
#include "nsCollationCID.h"
#include "nsUnicharUtils.h"
#include "nsIServiceManager.h"
#include "prmem.h"
#include "nsIUnicodeEncoder.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/intl/LocaleService.h"
#include "nsString.h"
using mozilla::dom::EncodingUtils;
////////////////////////////////////////////////////////////////////////////////
NS_DEFINE_CID(kCollationCID, NS_COLLATION_CID);
NS_IMPL_ISUPPORTS(nsCollationFactory, nsICollationFactory)
nsresult nsCollationFactory::CreateCollation(nsICollation** instancePtr)
{
nsAutoCString appLocale;
mozilla::intl::LocaleService::GetInstance()->GetAppLocaleAsLangTag(appLocale);
return CreateCollationForLocale(appLocale, instancePtr);
}
nsresult
nsCollationFactory::CreateCollationForLocale(const nsACString& locale, nsICollation** instancePtr)
{
// Create a collation interface instance.
//
nsICollation *inst;
nsresult res;
res = CallCreateInstance(kCollationCID, &inst);
if (NS_FAILED(res)) {
return res;
}
inst->Initialize(locale);
*instancePtr = inst;
return res;
}
////////////////////////////////////////////////////////////////////////////////
NS_IMPL_ISUPPORTS(nsCollation, nsICollation)
nsCollation::nsCollation()
{
MOZ_COUNT_CTOR(nsCollation);
}
: mInit(false)
, mHasCollator(false)
, mLastStrength(-1)
, mCollatorICU(nullptr)
{ }
nsCollation::~nsCollation()
{
MOZ_COUNT_DTOR(nsCollation);
#ifdef DEBUG
nsresult res =
#endif
CleanUpCollator();
NS_ASSERTION(NS_SUCCEEDED(res), "CleanUpCollator failed");
}
nsresult nsCollation::NormalizeString(const nsAString& stringIn, nsAString& stringOut)
nsresult
nsCollation::ConvertStrength(const int32_t aNSStrength,
UCollationStrength* aICUStrength,
UColAttributeValue* aCaseLevelOut)
{
int32_t aLength = stringIn.Length();
NS_ENSURE_ARG_POINTER(aICUStrength);
NS_ENSURE_TRUE((aNSStrength < 4), NS_ERROR_FAILURE);
if (aLength <= 64) {
char16_t conversionBuffer[64];
ToLowerCase(PromiseFlatString(stringIn).get(), conversionBuffer, aLength);
stringOut.Assign(conversionBuffer, aLength);
UCollationStrength strength = UCOL_DEFAULT;
UColAttributeValue caseLevel = UCOL_OFF;
switch (aNSStrength) {
case kCollationCaseInSensitive:
strength = UCOL_PRIMARY;
break;
case kCollationCaseInsensitiveAscii:
strength = UCOL_SECONDARY;
break;
case kCollationAccentInsenstive:
caseLevel = UCOL_ON;
strength = UCOL_PRIMARY;
break;
case kCollationCaseSensitive:
strength = UCOL_TERTIARY;
break;
default:
NS_WARNING("Bad aNSStrength passed to ConvertStrength.");
return NS_ERROR_FAILURE;
}
else {
char16_t* conversionBuffer;
conversionBuffer = new char16_t[aLength];
if (!conversionBuffer) {
*aICUStrength = strength;
*aCaseLevelOut = caseLevel;
return NS_OK;
}
nsresult
nsCollation::EnsureCollator(const int32_t newStrength)
{
NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
if (mHasCollator && (mLastStrength == newStrength))
return NS_OK;
nsresult res;
res = CleanUpCollator();
NS_ENSURE_SUCCESS(res, res);
UErrorCode status;
status = U_ZERO_ERROR;
mCollatorICU = ucol_open(mLocale.get(), &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
UCollationStrength strength;
UColAttributeValue caseLevel;
res = ConvertStrength(newStrength, &strength, &caseLevel);
NS_ENSURE_SUCCESS(res, res);
status = U_ZERO_ERROR;
ucol_setAttribute(mCollatorICU, UCOL_STRENGTH, strength, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_CASE_LEVEL, caseLevel, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_ALTERNATE_HANDLING, UCOL_DEFAULT, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_NUMERIC_COLLATION, UCOL_OFF, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_NORMALIZATION_MODE, UCOL_ON, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
ucol_setAttribute(mCollatorICU, UCOL_CASE_FIRST, UCOL_DEFAULT, &status);
NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
mHasCollator = true;
mLastStrength = newStrength;
return NS_OK;
}
nsresult
nsCollation::CleanUpCollator(void)
{
if (mHasCollator) {
ucol_close(mCollatorICU);
mHasCollator = false;
}
return NS_OK;
}
NS_IMETHODIMP
nsCollation::Initialize(const nsACString& locale)
{
NS_ENSURE_TRUE((!mInit), NS_ERROR_ALREADY_INITIALIZED);
mLocale = locale;
mInit = true;
return NS_OK;
}
NS_IMETHODIMP
nsCollation::AllocateRawSortKey(int32_t strength, const nsAString& stringIn,
uint8_t** key, uint32_t* outLen)
{
NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_ARG_POINTER(key);
NS_ENSURE_ARG_POINTER(outLen);
nsresult res = EnsureCollator(strength);
NS_ENSURE_SUCCESS(res, res);
uint32_t stringInLen = stringIn.Length();
const UChar* str = (const UChar*)stringIn.BeginReading();
int32_t keyLength = ucol_getSortKey(mCollatorICU, str, stringInLen, nullptr, 0);
NS_ENSURE_TRUE((stringInLen == 0 || keyLength > 0), NS_ERROR_FAILURE);
// Since key is freed elsewhere with PR_Free, allocate with PR_Malloc.
uint8_t* newKey = (uint8_t*)PR_Malloc(keyLength + 1);
if (!newKey) {
return NS_ERROR_OUT_OF_MEMORY;
}
ToLowerCase(PromiseFlatString(stringIn).get(), conversionBuffer, aLength);
stringOut.Assign(conversionBuffer, aLength);
delete [] conversionBuffer;
}
keyLength = ucol_getSortKey(mCollatorICU, str, stringInLen, newKey, keyLength + 1);
NS_ENSURE_TRUE((stringInLen == 0 || keyLength > 0), NS_ERROR_FAILURE);
*key = newKey;
*outLen = keyLength;
return NS_OK;
}
nsresult nsCollation::SetCharset(const char* aCharset)
NS_IMETHODIMP
nsCollation::CompareString(int32_t strength, const nsAString& string1,
const nsAString& string2, int32_t* result)
{
NS_ENSURE_ARG_POINTER(aCharset);
NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_ARG_POINTER(result);
*result = 0;
nsDependentCString label(aCharset);
nsAutoCString encoding;
if (!EncodingUtils::FindEncodingForLabelNoReplacement(label, encoding)) {
return NS_ERROR_UCONV_NOCONV;
nsresult rv = EnsureCollator(strength);
NS_ENSURE_SUCCESS(rv, rv);
UCollationResult uresult;
uresult = ucol_strcoll(mCollatorICU,
(const UChar*)string1.BeginReading(),
string1.Length(),
(const UChar*)string2.BeginReading(),
string2.Length());
int32_t res;
switch (uresult) {
case UCOL_LESS:
res = -1;
break;
case UCOL_EQUAL:
res = 0;
break;
case UCOL_GREATER:
res = 1;
break;
default:
MOZ_CRASH("ucol_strcoll returned bad UCollationResult");
}
mEncoder = EncodingUtils::EncoderForEncoding(encoding);
*result = res;
return NS_OK;
}
nsresult nsCollation::UnicodeToChar(const nsAString& aSrc, char** dst)
NS_IMETHODIMP
nsCollation::CompareRawSortKey(const uint8_t* key1, uint32_t len1,
const uint8_t* key2, uint32_t len2,
int32_t* result)
{
NS_ENSURE_ARG_POINTER(dst);
NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_ARG_POINTER(key1);
NS_ENSURE_ARG_POINTER(key2);
NS_ENSURE_ARG_POINTER(result);
*result = 0;
nsresult res = NS_OK;
if (!mEncoder)
res = SetCharset("ISO-8859-1");
if (NS_SUCCEEDED(res)) {
const nsPromiseFlatString& src = PromiseFlatString(aSrc);
const char16_t *unichars = src.get();
int32_t unicharLength = src.Length();
int32_t dstLength;
res = mEncoder->GetMaxLength(unichars, unicharLength, &dstLength);
if (NS_SUCCEEDED(res)) {
int32_t bufLength = dstLength + 1 + 32; // extra 32 bytes for Finish() call
*dst = (char *) PR_Malloc(bufLength);
if (*dst) {
**dst = '\0';
res = mEncoder->Convert(unichars, &unicharLength, *dst, &dstLength);
if (NS_SUCCEEDED(res) || (NS_ERROR_UENC_NOMAPPING == res)) {
// Finishes the conversion. The converter has the possibility to write some
// extra data and flush its final state.
int32_t finishLength = bufLength - dstLength; // remaining unused buffer length
if (finishLength > 0) {
res = mEncoder->Finish((*dst + dstLength), &finishLength);
if (NS_SUCCEEDED(res)) {
(*dst)[dstLength + finishLength] = '\0';
}
}
}
if (NS_FAILED(res)) {
PR_Free(*dst);
*dst = nullptr;
}
}
else {
res = NS_ERROR_OUT_OF_MEMORY;
}
}
int32_t tmpResult = strcmp((const char*)key1, (const char*)key2);
int32_t res;
if (tmpResult < 0) {
res = -1;
} else if (tmpResult > 0) {
res = 1;
} else {
res = 0;
}
return res;
*result = res;
return NS_OK;
}

View File

@ -1,52 +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
/* -*- 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 nsCollation_h__
#define nsCollation_h__
#ifndef nsCollation_h_
#define nsCollation_h_
#include "nsICollation.h"
#include "nsCOMPtr.h"
#include "mozilla/Attributes.h"
#include "nsICollation.h"
#include "nsCollationFactory.h"
#include "nsString.h"
class nsIUnicodeEncoder;
#ifdef ENABLE_INTL_API
#include "unicode/ucol.h"
#endif
// Create a collation interface for the current app's locale.
//
class nsCollationFactory final : public nsICollationFactory {
~nsCollationFactory() {}
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CreateCollation(nsICollation** instancePtr) override;
NS_IMETHOD CreateCollationForLocale(const nsACString& locale, nsICollation** instancePtr) override;
nsCollationFactory() {}
};
struct nsCollation {
public:
class nsCollation final : public nsICollation {
public:
nsCollation();
~nsCollation();
// normalize string before collation key generation
nsresult NormalizeString(const nsAString& stringIn, nsAString& stringOut);
// nsISupports interface
NS_DECL_ISUPPORTS
// charset conversion util, C string buffer is allocate by PR_Malloc, caller should call PR_Free
nsresult SetCharset(const char* aCharset);
nsresult UnicodeToChar(const nsAString& aSrc, char** dst);
// nsICollation interface
NS_DECL_NSICOLLATION
protected:
nsCOMPtr <nsIUnicodeEncoder> mEncoder;
~nsCollation();
#ifdef ENABLE_INTL_API
nsresult ConvertStrength(const int32_t aStrength,
UCollationStrength* aStrengthOut,
UColAttributeValue* aCaseLevelOut);
nsresult EnsureCollator(const int32_t newStrength);
nsresult CleanUpCollator(void);
private:
bool mInit;
bool mHasCollator;
nsCString mLocale;
int32_t mLastStrength;
UCollator* mCollatorICU;
#endif
};
#endif /* nsCollation_h__ */
#endif /* nsCollation_h_ */

View File

@ -0,0 +1,85 @@
/* -*- 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 "nsCollation.h"
#include "nsCOMPtr.h"
#include "nsUnicharUtils.h"
#include "prmem.h"
#include <string.h>
nsCollation::nsCollation()
{
}
nsCollation::~nsCollation()
{
}
NS_IMPL_ISUPPORTS(nsCollation, nsICollation)
NS_IMETHODIMP
nsCollation::Initialize(const nsACString& locale)
{
// Android doesn't have locale support
return NS_OK;
}
NS_IMETHODIMP
nsCollation::CompareString(int32_t strength,
const nsAString& string1,
const nsAString& string2,
int32_t* result)
{
NS_ENSURE_ARG_POINTER(result);
nsAutoString stringNormalized1(string1);
nsAutoString stringNormalized2(string2);
if (strength != kCollationCaseSensitive) {
ToLowerCase(stringNormalized1);
ToLowerCase(stringNormalized2);
}
*result = strcmp(NS_ConvertUTF16toUTF8(stringNormalized1).get(),
NS_ConvertUTF16toUTF8(stringNormalized2).get());
return NS_OK;
}
NS_IMETHODIMP
nsCollation::AllocateRawSortKey(int32_t strength,
const nsAString& stringIn,
uint8_t** key, uint32_t* outLen)
{
NS_ENSURE_ARG_POINTER(key);
NS_ENSURE_ARG_POINTER(outLen);
nsAutoString stringNormalized(stringIn);
if (strength != kCollationCaseSensitive) {
ToLowerCase(stringNormalized);
}
NS_ConvertUTF16toUTF8 str(stringNormalized);
size_t len = str.Length() + 1;
void *buffer = PR_Malloc(len);
memcpy(buffer, str.get(), len);
*key = (uint8_t *)buffer;
*outLen = len;
return NS_OK;
}
NS_IMETHODIMP
nsCollation::CompareRawSortKey(const uint8_t* key1, uint32_t len1,
const uint8_t* key2, uint32_t len2,
int32_t* result)
{
NS_ENSURE_ARG_POINTER(key1);
NS_ENSURE_ARG_POINTER(key2);
NS_ENSURE_ARG_POINTER(result);
*result = strcmp((const char *)key1, (const char *)key2);
return NS_OK;
}

View File

@ -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/. */
#include "nsCollationFactory.h"
#include "nsCollationCID.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/intl/LocaleService.h"
////////////////////////////////////////////////////////////////////////////////
NS_DEFINE_CID(kCollationCID, NS_COLLATION_CID);
NS_IMPL_ISUPPORTS(nsCollationFactory, nsICollationFactory)
nsresult nsCollationFactory::CreateCollation(nsICollation** instancePtr)
{
nsAutoCString appLocale;
mozilla::intl::LocaleService::GetInstance()->GetAppLocaleAsLangTag(appLocale);
return CreateCollationForLocale(appLocale, instancePtr);
}
nsresult
nsCollationFactory::CreateCollationForLocale(const nsACString& locale, nsICollation** instancePtr)
{
// Create a collation interface instance.
//
nsICollation *inst;
nsresult res;
res = CallCreateInstance(kCollationCID, &inst);
if (NS_FAILED(res)) {
return res;
}
inst->Initialize(locale);
*instancePtr = inst;
return res;
}

View File

@ -0,0 +1,28 @@
/* -*- 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 nsCollationFactory_h__
#define nsCollationFactory_h__
#include "nsICollation.h"
#include "nsCOMPtr.h"
#include "mozilla/Attributes.h"
// Create a collation interface for the current app's locale.
//
class nsCollationFactory final : public nsICollationFactory {
~nsCollationFactory() {}
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICOLLATIONFACTORY
nsCollationFactory() {}
};
#endif /* nsCollationFactory_h__ */

View File

@ -132,8 +132,7 @@ interface nsIScriptableDateFormat : nsISupports
* you depend on any specific format or language.
*
* @param locale
* Locale code of locale used to format the date or an empty string
* to follow user preference.
Ignored. If you want to use specific locale, use Intl API instead.
* @param dateFormatSelector
* Indicate which format should preferably be used for the date.
* Use one of the dateFormat* constants.

View File

@ -6,6 +6,7 @@
#ifndef nsLocaleConstructors_h__
#define nsLocaleConstructors_h__
#include "nsCollation.h"
#include "nsCollationCID.h"
#include "mozilla/ModuleUtils.h"
#include "nsILocaleService.h"
@ -16,26 +17,6 @@
#include "LocaleService.h"
#include "OSPreferences.h"
#if defined(XP_MACOSX)
#define USE_MAC_LOCALE
#endif
#if defined(XP_UNIX) && !defined(XP_MACOSX)
#define USE_UNIX_LOCALE
#endif
#ifdef XP_WIN
#include "windows/nsCollationWin.h"
#endif
#ifdef USE_MAC_LOCALE
#include "mac/nsCollationMacUC.h"
#endif
#ifdef USE_UNIX_LOCALE
#include "unix/nsCollationUnix.h"
#endif
#define NSLOCALE_MAKE_CTOR(ctor_, iface_, func_) \
static nsresult \
ctor_(nsISupports* aOuter, REFNSIID aIID, void** aResult) \
@ -54,6 +35,7 @@ ctor_(nsISupports* aOuter, REFNSIID aIID, void** aResult) \
NSLOCALE_MAKE_CTOR(CreateLocaleService, nsILocaleService, NS_NewLocaleService)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollation)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollationFactory)
//NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptableDateTimeFormat)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsLanguageAtomService)
@ -68,16 +50,4 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(OSPreferences,
}
}
#ifdef XP_WIN
NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollationWin)
#endif
#ifdef USE_UNIX_LOCALE
NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollationUnix)
#endif
#ifdef USE_MAC_LOCALE
NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollationMacUC)
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More