Merge mozilla-central to fx-team

This commit is contained in:
Carsten "Tomcat" Book 2016-07-29 12:35:16 +02:00
commit 8d03d43e97
195 changed files with 3698 additions and 2286 deletions

View File

@ -63,17 +63,16 @@ add_task(function* test() {
});
}
// make sure we have received a message
yield ContentTask.spawn(receiver.browser, channelName,
function* (name) {
yield content.window.testPromise.then(function() {});
}
);
// Since sender1 sends before sender2, if the title is exactly
// sender2's message, sender1's message must've been blocked
is(receiver.browser.contentDocument.title, sender2.message,
"should only receive messages from the same user context");
yield ContentTask.spawn(receiver.browser, sender2.message,
function* (message) {
yield content.window.testPromise.then(function() {
is(content.document.title, message,
"should only receive messages from the same user context");
});
}
);
gBrowser.removeTab(sender1.tab);
gBrowser.removeTab(sender2.tab);

View File

@ -35,7 +35,6 @@ support-files =
[browser_ext_contextMenus_urlPatterns.js]
[browser_ext_currentWindow.js]
[browser_ext_getViews.js]
[browser_ext_history.js]
[browser_ext_incognito_popup.js]
[browser_ext_lastError.js]
[browser_ext_optionsPage_privileges.js]

View File

@ -1,3 +1,7 @@
{
"extends": "../../../../../testing/xpcshell/xpcshell.eslintrc",
"globals": {
"browser": false,
},
}

View File

@ -2,12 +2,24 @@
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
/* exported createHttpServer */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
"resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
"resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils",
"resource://testing-common/ExtensionXPCShellUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
"resource://testing-common/httpd.js");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
@ -15,36 +27,29 @@ XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
/* exported normalizeManifest */
ExtensionTestUtils.init(this);
let BASE_MANIFEST = {
"applications": {"gecko": {"id": "test@web.ext"}},
"manifest_version": 2,
/**
* Creates a new HttpServer for testing, and begins listening on the
* specified port. Automatically shuts down the server when the test
* unit ends.
*
* @param {integer} [port]
* The port to listen on. If omitted, listen on a random
* port. The latter is the preferred behavior.
*
* @returns {HttpServer}
*/
function createHttpServer(port = -1) {
let server = new HttpServer();
server.start(port);
"name": "name",
"version": "0",
};
do_register_cleanup(() => {
return new Promise(resolve => {
server.stop(resolve);
});
});
function* normalizeManifest(manifest, baseManifest = BASE_MANIFEST) {
const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
yield Management.lazyInit();
let errors = [];
let context = {
url: null,
logError: error => {
errors.push(error);
},
preprocessors: {},
};
manifest = Object.assign({}, baseManifest, manifest);
let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context);
normalized.errors = errors;
return normalized;
return server;
}

View File

@ -1,16 +1,5 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebExtension test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function backgroundScript() {
@ -276,13 +265,13 @@ function backgroundScript() {
return browser.bookmarks.search("Menu Item");
}).then(results => {
browser.test.assertEq(1, results.length, "Expected number of results returned for menu item search");
checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 3, parentId: bookmarkGuids.menuGuid}, results[0]);
checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 0, parentId: bookmarkGuids.menuGuid}, results[0]);
// finds toolbar items
return browser.bookmarks.search("Toolbar Item");
}).then(results => {
browser.test.assertEq(1, results.length, "Expected number of results returned for toolbar item search");
checkBookmark({title: "Toolbar Item", url: "http://toolbar.org/", index: 2, parentId: bookmarkGuids.toolbarGuid}, results[0]);
checkBookmark({title: "Toolbar Item", url: "http://toolbar.org/", index: 0, parentId: bookmarkGuids.toolbarGuid}, results[0]);
// finds folders
return browser.bookmarks.search("Mozilla Folder");
@ -389,7 +378,7 @@ function backgroundScript() {
return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.menuGuid});
}).then(result => {
browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
browser.test.assertEq(childCount + 1, result.index, "Bookmark has the expected index");
browser.test.assertEq(childCount, result.index, "Bookmark has the expected index");
return browser.bookmarks.move(corporationBookmark.id, {index: 1});
}).then(result => {
@ -490,8 +479,3 @@ add_task(function* test_contentscript() {
yield extension.awaitFinish("bookmarks");
yield extension.unload();
});
</script>
</body>
</html>

View File

@ -85,14 +85,14 @@ add_task(function* test_delete() {
}
yield PlacesUtils.history.insertMany(visits);
is(yield PlacesTestUtils.visitsInDB(visits[0].url), 5, "5 visits for uri found in history database");
equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 5, "5 visits for uri found in history database");
let testUrl = visits[2].url;
ok(yield PlacesTestUtils.isPageInDB(testUrl), "expected url found in history database");
ok((yield PlacesTestUtils.isPageInDB(testUrl)), "expected url found in history database");
extension.sendMessage("delete-url", testUrl);
yield extension.awaitMessage("url-deleted");
is(yield PlacesTestUtils.isPageInDB(testUrl), false, "expected url not found in history database");
equal((yield PlacesTestUtils.isPageInDB(testUrl)), false, "expected url not found in history database");
// delete 3 of the 5 visits for url 1
let filter = {
@ -103,10 +103,10 @@ add_task(function* test_delete() {
extension.sendMessage("delete-range", filter);
let removedUrls = yield extension.awaitMessage("range-deleted");
ok(!removedUrls.includes(visits[0].url), `${visits[0].url} not received by onVisitRemoved`);
ok(yield PlacesTestUtils.isPageInDB(visits[0].url), "expected uri found in history database");
is(yield PlacesTestUtils.visitsInDB(visits[0].url), 2, "2 visits for uri found in history database");
ok(yield PlacesTestUtils.isPageInDB(visits[1].url), "expected uri found in history database");
is(yield PlacesTestUtils.visitsInDB(visits[1].url), 1, "1 visit for uri found in history database");
ok((yield PlacesTestUtils.isPageInDB(visits[0].url)), "expected uri found in history database");
equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 2, "2 visits for uri found in history database");
ok((yield PlacesTestUtils.isPageInDB(visits[1].url)), "expected uri found in history database");
equal((yield PlacesTestUtils.visitsInDB(visits[1].url)), 1, "1 visit for uri found in history database");
// delete the rest of the visits for url 1, and the visit for url 2
filter.startTime = visits[0].visits[0].date;
@ -115,18 +115,18 @@ add_task(function* test_delete() {
extension.sendMessage("delete-range", filter);
yield extension.awaitMessage("range-deleted");
is(yield PlacesTestUtils.isPageInDB(visits[0].url), false, "expected uri not found in history database");
is(yield PlacesTestUtils.visitsInDB(visits[0].url), 0, "0 visits for uri found in history database");
is(yield PlacesTestUtils.isPageInDB(visits[1].url), false, "expected uri not found in history database");
is(yield PlacesTestUtils.visitsInDB(visits[1].url), 0, "0 visits for uri found in history database");
equal((yield PlacesTestUtils.isPageInDB(visits[0].url)), false, "expected uri not found in history database");
equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 0, "0 visits for uri found in history database");
equal((yield PlacesTestUtils.isPageInDB(visits[1].url)), false, "expected uri not found in history database");
equal((yield PlacesTestUtils.visitsInDB(visits[1].url)), 0, "0 visits for uri found in history database");
ok(yield PlacesTestUtils.isPageInDB(visits[3].url), "expected uri found in history database");
ok((yield PlacesTestUtils.isPageInDB(visits[3].url)), "expected uri found in history database");
extension.sendMessage("delete-all");
[historyClearedCount, removedUrls] = yield extension.awaitMessage("history-cleared");
is(PlacesUtils.history.hasHistoryEntries, false, "history is empty");
is(historyClearedCount, 2, "onVisitRemoved called for each clearing of history");
is(removedUrls.length, 3, "onVisitRemoved called the expected number of times");
equal(PlacesUtils.history.hasHistoryEntries, false, "history is empty");
equal(historyClearedCount, 2, "onVisitRemoved called for each clearing of history");
equal(removedUrls.length, 3, "onVisitRemoved called the expected number of times");
for (let i = 1; i < 3; ++i) {
let url = visits[i].url;
ok(removedUrls.includes(url), `${url} received by onVisitRemoved`);
@ -215,9 +215,9 @@ add_task(function* test_search() {
function checkResult(results, url, expectedCount) {
let result = findResult(url, results);
isnot(result, null, `history.search result was found for ${url}`);
is(result.visitCount, expectedCount, `history.search reports ${expectedCount} visit(s)`);
is(result.title, `test visit for ${url}`, "title for search result is correct");
notEqual(result, null, `history.search result was found for ${url}`);
equal(result.visitCount, expectedCount, `history.search reports ${expectedCount} visit(s)`);
equal(result.title, `test visit for ${url}`, "title for search result is correct");
}
yield extension.startup();
@ -229,21 +229,21 @@ add_task(function* test_search() {
extension.sendMessage("check-history");
let results = yield extension.awaitMessage("empty-search");
is(results.length, 3, "history.search with empty text returned 3 results");
equal(results.length, 3, "history.search with empty text returned 3 results");
checkResult(results, SINGLE_VISIT_URL, 1);
checkResult(results, DOUBLE_VISIT_URL, 2);
checkResult(results, MOZILLA_VISIT_URL, 1);
results = yield extension.awaitMessage("text-search");
is(results.length, 1, "history.search with specific text returned 1 result");
equal(results.length, 1, "history.search with specific text returned 1 result");
checkResult(results, MOZILLA_VISIT_URL, 1);
results = yield extension.awaitMessage("max-results-search");
is(results.length, 1, "history.search with maxResults returned 1 result");
equal(results.length, 1, "history.search with maxResults returned 1 result");
checkResult(results, DOUBLE_VISIT_URL, 2);
results = yield extension.awaitMessage("date-range-search");
is(results.length, 2, "history.search with a date range returned 2 result");
equal(results.length, 2, "history.search with a date range returned 2 result");
checkResult(results, DOUBLE_VISIT_URL, 2);
checkResult(results, SINGLE_VISIT_URL, 1);
@ -300,11 +300,11 @@ add_task(function* test_add_url() {
];
function* checkUrl(results) {
ok(yield PlacesTestUtils.isPageInDB(results.details.url), `${results.details.url} found in history database`);
ok((yield PlacesTestUtils.isPageInDB(results.details.url)), `${results.details.url} found in history database`);
ok(PlacesUtils.isValidGuid(results.result.id), "URL was added with a valid id");
is(results.result.title, results.details.title, "URL was added with the correct title");
equal(results.result.title, results.details.title, "URL was added with the correct title");
if (results.details.visitTime) {
is(results.result.lastVisitTime,
equal(results.result.lastVisitTime,
Number(ExtensionUtils.normalizeTime(results.details.visitTime)),
"URL was added with the correct date");
}
@ -458,12 +458,12 @@ add_task(function* test_on_visited() {
function checkOnVisitedData(index, expected) {
let onVisited = onVisitedData[index];
ok(PlacesUtils.isValidGuid(onVisited.id), "onVisited received a valid id");
is(onVisited.url, expected.url, "onVisited received the expected url");
equal(onVisited.url, expected.url, "onVisited received the expected url");
// Title will be blank until bug 1287928 lands
// https://bugzilla.mozilla.org/show_bug.cgi?id=1287928
is(onVisited.title, "", "onVisited received a blank title");
is(onVisited.lastVisitTime, expected.time, "onVisited received the expected time");
is(onVisited.visitCount, expected.visitCount, "onVisited received the expected visitCount");
equal(onVisited.title, "", "onVisited received a blank title");
equal(onVisited.lastVisitTime, expected.time, "onVisited received the expected time");
equal(onVisited.visitCount, expected.visitCount, "onVisited received the expected visitCount");
}
let expected = {

View File

@ -4,7 +4,7 @@
add_task(function* test_manifest_commands() {
let normalized = yield normalizeManifest({
let normalized = yield ExtensionTestUtils.normalizeManifest({
"commands": {
"toggle-feature": {
"suggested_key": {"default": "Shifty+Y"},

View File

@ -3,4 +3,6 @@ head = head.js
tail =
firefox-appdir = browser
[test_ext_bookmarks.js]
[test_ext_history.js]
[test_ext_manifest_commands.js]

View File

@ -108,37 +108,9 @@ this.ContentLinkHandler = {
}
sizeHistogramTypes.add(sizesType);
if (uri.scheme == 'blob') {
// Blob URLs don't work cross process, work around this by sending as a data uri
let channel = NetUtil.newChannel({
uri: uri,
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE,
loadUsingSystemPrincipal: true
});
let listener = {
encoded: "",
bis: null,
onStartRequest: function(aRequest, aContext) {
this.bis = Components.classes["@mozilla.org/binaryinputstream;1"]
.createInstance(Components.interfaces.nsIBinaryInputStream);
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
let spec = "data:" + channel.contentType + ";base64," + this.encoded;
chromeGlobal.sendAsyncMessage(
"Link:SetIcon",
{url: spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
},
onDataAvailable: function(request, context, inputStream, offset, count) {
this.bis.setInputStream(inputStream);
this.encoded += btoa(this.bis.readBytes(this.bis.available()));
}
}
channel.asyncOpen2(listener);
} else {
chromeGlobal.sendAsyncMessage(
"Link:SetIcon",
{url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
}
chromeGlobal.sendAsyncMessage(
"Link:SetIcon",
{url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
iconAdded = true;
break;
case "search":

View File

@ -185,7 +185,6 @@ mt = check_prog('_MT', depends_win()(lambda: ('mt.exe',)), what='mt',
# utility".
@depends_win(mt)
@checking('whether MT is really Microsoft Manifest Tool', lambda x: bool(x))
@imports('re')
@imports('subprocess')
def valid_mt(path):
try:
@ -193,21 +192,13 @@ def valid_mt(path):
out = '\n'.join(l for l in out
if 'Microsoft (R) Manifest Tool' in l)
if out:
m = re.search(r'(?<=[^!-~])[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+',
out)
if not m:
raise FatalCheckError(
'Unknown version of the Microsoft Manifest Tool')
return namespace(
path=path,
version=Version(m.group(0)),
)
return path
except subprocess.CalledProcessError:
pass
raise FatalCheckError('%s is not Microsoft Manifest Tool')
set_config('MT', depends_if(valid_mt)(lambda x: os.path.basename(x.path)))
set_config('MT', depends_if(valid_mt)(lambda x: os.path.basename(x)))
set_config('MSMANIFEST_TOOL', depends(valid_mt)(lambda x: bool(x)))

View File

@ -2425,7 +2425,8 @@ nsDocument::FillStyleSet(StyleSetHandle aStyleSet)
}
}
} else {
NS_ERROR("stylo: nsStyleSheetService doesn't handle ServoStyleSheets yet");
NS_WARNING("stylo: Not yet checking nsStyleSheetService for Servo-backed "
"documents. See bug 1290224");
}
AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet],
@ -13350,7 +13351,8 @@ nsIDocument::FlushUserFontSet()
return;
}
} else {
NS_ERROR("stylo: ServoStyleSets cannot handle @font-face rules yet");
NS_WARNING("stylo: ServoStyleSets cannot handle @font-face rules yet. "
"See bug 1290237.");
}
}
@ -13439,6 +13441,10 @@ nsIDocument::UpdateStyleBackendType()
{
MOZ_ASSERT(mStyleBackendType == StyleBackendType(0),
"no need to call UpdateStyleBackendType now");
// Assume Gecko by default.
mStyleBackendType = StyleBackendType::Gecko;
#ifdef MOZ_STYLO
// XXX For now we use a Servo-backed style set only for (X)HTML documents
// in content docshells. This should let us avoid implementing XUL-specific
@ -13447,15 +13453,11 @@ nsIDocument::UpdateStyleBackendType()
// document before we have a pres shell (i.e. before we make the decision
// here about whether to use a Gecko- or Servo-backed style system), so
// we avoid Servo-backed style sets for SVG documents.
NS_ASSERTION(mDocumentContainer, "stylo: calling UpdateStyleBackendType "
"before we have a docshell");
mStyleBackendType =
nsLayoutUtils::SupportsServoStyleBackend(this) &&
mDocumentContainer ?
StyleBackendType::Servo :
StyleBackendType::Gecko;
#else
mStyleBackendType = StyleBackendType::Gecko;
if (!mDocumentContainer) {
NS_WARNING("stylo: No docshell yet, assuming Gecko style system");
} else if (nsLayoutUtils::SupportsServoStyleBackend(this)) {
mStyleBackendType = StyleBackendType::Servo;
}
#endif
}

View File

@ -30,7 +30,8 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeTypeArray,
mWindow,
mMimeTypes)
mMimeTypes,
mCTPMimeTypes)
nsMimeTypeArray::nsMimeTypeArray(nsPIDOMWindowInner* aWindow)
: mWindow(aWindow)
@ -57,6 +58,7 @@ void
nsMimeTypeArray::Refresh()
{
mMimeTypes.Clear();
mCTPMimeTypes.Clear();
}
nsPIDOMWindowInner*
@ -133,6 +135,10 @@ nsMimeTypeArray::NamedGetter(const nsAString& aName, bool &aFound)
aFound = true;
return mimeType;
}
nsMimeType* hiddenType = FindMimeType(mCTPMimeTypes, lowerName);
if (hiddenType) {
nsPluginArray::NotifyHiddenPluginTouched(hiddenType->GetEnabledPlugin());
}
return nullptr;
}
@ -180,6 +186,7 @@ nsMimeTypeArray::EnsurePluginMimeTypes()
}
pluginArray->GetMimeTypes(mMimeTypes);
pluginArray->GetCTPMimeTypes(mCTPMimeTypes);
}
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsMimeType, AddRef)

View File

@ -48,6 +48,7 @@ protected:
// mMimeTypes contains MIME types handled by plugins or by an OS
// PreferredApplicationHandler.
nsTArray<RefPtr<nsMimeType> > mMimeTypes;
nsTArray<RefPtr<nsMimeType> > mCTPMimeTypes;
};
class nsMimeType final : public nsWrapperCache

View File

@ -114,6 +114,24 @@ nsPluginArray::GetMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes)
aMimeTypes.Sort();
}
void
nsPluginArray::GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes)
{
aMimeTypes.Clear();
if (!AllowPlugins()) {
return;
}
EnsurePlugins();
GetPluginMimeTypes(mCTPPlugins, aMimeTypes);
// Alphabetize the enumeration order of non-hidden MIME types to reduce
// fingerprintable entropy based on plugins' installation file times.
aMimeTypes.Sort();
}
nsPluginElement*
nsPluginArray::Item(uint32_t aIndex)
{
@ -236,21 +254,26 @@ nsPluginArray::NamedGetter(const nsAString& aName, bool &aFound)
if (!aFound) {
nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
if (hiddenPlugin) {
HiddenPluginEventInit init;
init.mTag = hiddenPlugin->PluginTag();
nsCOMPtr<nsIDocument> doc = hiddenPlugin->GetParentObject()->GetDoc();
RefPtr<HiddenPluginEvent> event =
HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
event->SetTarget(doc);
event->SetTrusted(true);
event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
bool dummy;
doc->DispatchEvent(event, &dummy);
NotifyHiddenPluginTouched(hiddenPlugin);
}
}
return plugin;
}
void nsPluginArray::NotifyHiddenPluginTouched(nsPluginElement* aHiddenElement)
{
HiddenPluginEventInit init;
init.mTag = aHiddenElement->PluginTag();
nsCOMPtr<nsIDocument> doc = aHiddenElement->GetParentObject()->GetDoc();
RefPtr<HiddenPluginEvent> event =
HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
event->SetTarget(doc);
event->SetTrusted(true);
event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
bool dummy;
doc->DispatchEvent(event, &dummy);
}
uint32_t
nsPluginArray::Length()
{

View File

@ -41,6 +41,9 @@ public:
void Invalidate();
void GetMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes);
void GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes);
static void NotifyHiddenPluginTouched(nsPluginElement* aElement);
// PluginArray WebIDL methods

View File

@ -346,16 +346,10 @@ WebGLContext::GetVertexAttrib(JSContext* cx, GLuint index, GLenum pname,
return JS::Int32Value(mBoundVertexArray->mAttribs[index].stride);
case LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE:
if (!mBoundVertexArray->mAttribs[index].enabled)
return JS::Int32Value(4);
return JS::Int32Value(mBoundVertexArray->mAttribs[index].size);
case LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE:
if (!mBoundVertexArray->mAttribs[index].enabled)
return JS::NumberValue(uint32_t(LOCAL_GL_FLOAT));
return JS::NumberValue(uint32_t(mBoundVertexArray->mAttribs[index].type));
return JS::Int32Value(mBoundVertexArray->mAttribs[index].type);
case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
if (IsWebGL2())

View File

@ -262,6 +262,9 @@ WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarge
bytes = view.DataAllowShared();
byteCount = view.LengthAllowShared();
}
} else if (isSubImage) {
mContext->ErrorInvalidValue("%s: `pixels` must not be null.", funcName);
return;
}
const bool isClientData = true;

View File

@ -0,0 +1,9 @@
<canvas id='i0'></canvas>
<script>
var c=document.getElementById('i0').getContext('2d');
c.lineWidth=194.622602174;
c.miterLimit=270.273509738;
c.transform(0,0,0,0,0,0);
c.globalCompositeOperation='soft-light';
c.strokeText('a',200,273,722);
</script>

View File

@ -0,0 +1,8 @@
<canvas id='i0'></canvas>
<script>
var c=document.getElementById('i0').getContext('2d');
c.bezierCurveTo(157,351,351,44,946,701);
c.quadraticCurveTo(260,-9007199254740991,945,145);
c.translate(-9007199254740991,239);
c.isPointInPath(988,439);
</script>

View File

@ -0,0 +1,8 @@
<canvas id='i0'></canvas>
<script>
var c=document.getElementById('i0').getContext('2d');
var g=c.createLinearGradient(59,9,38.89,-75.51);
c.fillStyle=g;
c.globalAlpha=0.62;
c.fillText('a',0,24,30);
</script>

View File

@ -29,6 +29,9 @@ skip-if(azureCairo) load 1229983-1.html
load 1229932-1.html
load 1244850-1.html
load 1246775-1.html
load 1284356-1.html
load 1284578-1.html
skip-if(d2d) load 1287515-1.html
load 1287652-1.html
load 1288872-1.html

View File

@ -5838,7 +5838,6 @@ skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.
[generated/test_2_conformance__textures__misc__tex-image-with-invalid-data.html]
skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
[generated/test_2_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
fail-if = (os == 'mac') || (os == 'win')
skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
[generated/test_2_conformance__textures__misc__tex-sub-image-2d.html]
skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
@ -6722,7 +6721,6 @@ skip-if = (os == 'android')
skip-if = (os == 'android')
[generated/test_conformance__textures__misc__tex-input-validation.html]
[generated/test_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
[generated/test_conformance__textures__misc__tex-sub-image-2d.html]
[generated/test_conformance__textures__misc__texparameter-test.html]
[generated/test_conformance__textures__misc__texture-active-bind-2.html]

View File

@ -148,8 +148,6 @@ fail-if = (os == 'mac')
fail-if = (os == 'mac') || (os == 'win')
[generated/test_2_conformance2__glsl3__forbidden-operators.html]
fail-if = (os == 'mac') || (os == 'win')
[generated/test_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
[generated/test_2_conformance2__vertex_arrays__vertex-array-object.html]
fail-if = (os == 'mac') || (os == 'win')
[generated/test_2_conformance__rendering__negative-one-index.html]
@ -160,8 +158,6 @@ fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
fail-if = (os == 'mac') || (os == 'win')
[generated/test_conformance__attribs__gl-vertexattribpointer.html]
fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
[generated/test_2_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
fail-if = (os == 'mac') || (os == 'win')
[generated/test_conformance__ogles__GL__biuDepthRange__biuDepthRange_001_to_002.html]
fail-if = (os == 'android') || (os == 'linux')
[generated/test_conformance__ogles__GL__gl_FragCoord__gl_FragCoord_001_to_003.html]

View File

@ -2365,22 +2365,6 @@ HTMLInputElement::GetValueIfStepped(int32_t aStep,
value += step * Decimal(aStep);
}
// For date inputs, the value can hold a string that is not a day. We do not
// want to round it, as it might result in a step mismatch. Instead we want to
// clamp to the next valid value.
if (mType == NS_FORM_INPUT_DATE &&
NS_floorModulo(Decimal(value - GetStepBase()), GetStepScaleFactor()) != Decimal(0)) {
MOZ_ASSERT(GetStep() > Decimal(0));
Decimal validStep = EuclidLCM<Decimal>(GetStep().floor(),
GetStepScaleFactor().floor());
if (aStep > 0) {
value -= NS_floorModulo(value - GetStepBase(), validStep);
value += validStep;
} else if (aStep < 0) {
value -= NS_floorModulo(value - GetStepBase(), validStep);
}
}
if (value < minimum) {
value = minimum;
deltaFromStep = NS_floorModulo(value - stepBase, step);
@ -6903,6 +6887,11 @@ HTMLInputElement::GetStep() const
step = GetDefaultStep();
}
// For input type=date, we round the step value to have a rounded day.
if (mType == NS_FORM_INPUT_DATE) {
step = std::max(step.round(), Decimal(1));
}
return step * GetStepScaleFactor();
}
@ -7506,16 +7495,6 @@ HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
Decimal step = GetStep();
MOZ_ASSERT(step != kStepAny && step > Decimal(0));
// In case this is a date and the step is not an integer, we don't want to
// display the dates corresponding to the truncated timestamps of valueLow
// and valueHigh because they might suffer from a step mismatch as well.
// Instead we want the timestamps to correspond to a rounded day. That is,
// we want a multiple of the step scale factor (1 day) as well as of step.
if (mType == NS_FORM_INPUT_DATE) {
step = EuclidLCM<Decimal>(step.floor(),
GetStepScaleFactor().floor());
}
Decimal stepBase = GetStepBase();
Decimal valueLow = value - NS_floorModulo(value - stepBase, step);

View File

@ -3083,66 +3083,100 @@ HTMLMediaElement::ReportTelemetry()
Telemetry::Accumulate(Telemetry::VIDEO_UNLOAD_STATE, state);
LOG(LogLevel::Debug, ("%p VIDEO_UNLOAD_STATE = %d", this, state));
FrameStatisticsData data;
if (HTMLVideoElement* vid = HTMLVideoElement::FromContentOrNull(this)) {
RefPtr<VideoPlaybackQuality> quality = vid->GetVideoPlaybackQuality();
uint32_t totalFrames = quality->TotalVideoFrames();
if (totalFrames) {
uint32_t droppedFrames = quality->DroppedVideoFrames();
MOZ_ASSERT(droppedFrames <= totalFrames);
// Dropped frames <= total frames, so 'percentage' cannot be higher than
// 100 and therefore can fit in a uint32_t (that Telemetry takes).
uint32_t percentage = 100 * droppedFrames / totalFrames;
LOG(LogLevel::Debug,
("Reporting telemetry DROPPED_FRAMES_IN_VIDEO_PLAYBACK"));
Telemetry::Accumulate(Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION,
percentage);
FrameStatistics* stats = vid->GetFrameStatistics();
if (stats) {
data = stats->GetFrameStatisticsData();
if (data.mParsedFrames) {
MOZ_ASSERT(data.mDroppedFrames <= data.mParsedFrames);
// Dropped frames <= total frames, so 'percentage' cannot be higher than
// 100 and therefore can fit in a uint32_t (that Telemetry takes).
uint32_t percentage = 100 * data.mDroppedFrames / data.mParsedFrames;
LOG(LogLevel::Debug,
("Reporting telemetry DROPPED_FRAMES_IN_VIDEO_PLAYBACK"));
Telemetry::Accumulate(Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION,
percentage);
}
}
}
double playTime = mPlayTime.Total();
double hiddenPlayTime = mHiddenPlayTime.Total();
Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS, SECONDS_TO_MS(playTime));
LOG(LogLevel::Debug, ("%p VIDEO_PLAY_TIME_MS = %f", this, playTime));
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS, SECONDS_TO_MS(hiddenPlayTime));
LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_MS = %f", this, hiddenPlayTime));
if (playTime > 0.0 &&
mMediaInfo.HasVideo() &&
if (mMediaInfo.HasVideo() &&
mMediaInfo.mVideo.mImage.height > 0) {
// We have actually played some valid video -> Report hidden/total ratio.
uint32_t hiddenPercentage = uint32_t(hiddenPlayTime / playTime * 100.0 + 0.5);
// We have a valid video.
double playTime = mPlayTime.Total();
double hiddenPlayTime = mHiddenPlayTime.Total();
// Keyed by audio+video or video alone, and by a resolution range.
nsCString key(mMediaInfo.HasAudio() ? "AV," : "V,");
static const struct { int32_t mH; const char* mRes; } sResolutions[] = {
{ 240, "0<h<=240" },
{ 480, "240<h<=480" },
{ 576, "480<h<=576" },
{ 720, "576<h<=720" },
{ 1080, "720<h<=1080" },
{ 2160, "1080<h<=2160" }
};
const char* resolution = "h>2160";
int32_t height = mMediaInfo.mVideo.mImage.height;
for (const auto& res : sResolutions) {
if (height <= res.mH) {
resolution = res.mRes;
break;
Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS, SECONDS_TO_MS(playTime));
LOG(LogLevel::Debug, ("%p VIDEO_PLAY_TIME_MS = %f", this, playTime));
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS, SECONDS_TO_MS(hiddenPlayTime));
LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_MS = %f", this, hiddenPlayTime));
if (playTime > 0.0) {
// We have actually played something -> Report hidden/total ratio.
uint32_t hiddenPercentage = uint32_t(hiddenPlayTime / playTime * 100.0 + 0.5);
// Keyed by audio+video or video alone, and by a resolution range.
nsCString key(mMediaInfo.HasAudio() ? "AV," : "V,");
static const struct { int32_t mH; const char* mRes; } sResolutions[] = {
{ 240, "0<h<=240" },
{ 480, "240<h<=480" },
{ 576, "480<h<=576" },
{ 720, "576<h<=720" },
{ 1080, "720<h<=1080" },
{ 2160, "1080<h<=2160" }
};
const char* resolution = "h>2160";
int32_t height = mMediaInfo.mVideo.mImage.height;
for (const auto& res : sResolutions) {
if (height <= res.mH) {
resolution = res.mRes;
break;
}
}
key.AppendASCII(resolution);
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
key,
hiddenPercentage);
// Also accumulate all percentages in an "All" key.
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
NS_LITERAL_CSTRING("All"),
hiddenPercentage);
LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'",
this, hiddenPercentage, key.get()));
if (data.mInterKeyframeCount != 0) {
uint32_t average_ms =
uint32_t(std::min<uint64_t>(double(data.mInterKeyframeSum_us)
/ double(data.mInterKeyframeCount)
/ 1000.0
+ 0.5,
UINT32_MAX));
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS,
key,
average_ms);
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS,
NS_LITERAL_CSTRING("All"),
average_ms);
LOG(LogLevel::Debug, ("%p VIDEO_INTER_KEYFRAME_AVERAGE_MS = %u, keys: '%s' and 'All'",
this, average_ms, key.get()));
uint32_t max_ms =
uint32_t(std::min<uint64_t>((data.mInterKeyFrameMax_us + 500) / 1000,
UINT32_MAX));
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
key,
max_ms);
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
NS_LITERAL_CSTRING("All"),
max_ms);
LOG(LogLevel::Debug, ("%p VIDEO_INTER_KEYFRAME_MAX_MS = %u, keys: '%s' and 'All'",
this, max_ms, key.get()));
}
}
key.AppendASCII(resolution);
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
key,
hiddenPercentage);
// Also accumulate all percentages in an "All" key.
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
NS_LITERAL_CSTRING("All"),
hiddenPercentage);
LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'",
this, hiddenPercentage, key.get()));
}
}

View File

@ -76,6 +76,7 @@ static constexpr const nsAttrValue::EnumTable* kKindTableInvalidValueDefault = &
/** HTMLTrackElement */
HTMLTrackElement::HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
: nsGenericHTMLElement(aNodeInfo)
, mLoadResourceDispatched(false)
{
}
@ -182,9 +183,46 @@ HTMLTrackElement::ParseAttribute(int32_t aNamespaceID,
aResult);
}
void
HTMLTrackElement::SetSrc(const nsAString& aSrc, ErrorResult& aError)
{
SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
uint16_t oldReadyState = ReadyState();
SetReadyState(TextTrackReadyState::NotLoaded);
if (!mMediaParent) {
return;
}
if (mTrack && (oldReadyState != TextTrackReadyState::NotLoaded)) {
// Remove all the cues in MediaElement.
mMediaParent->RemoveTextTrack(mTrack);
// Recreate mTrack.
CreateTextTrack();
}
// Stop WebVTTListener.
mListener = nullptr;
if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED);
mChannel = nullptr;
}
DispatchLoadResource();
}
void
HTMLTrackElement::DispatchLoadResource()
{
if (!mLoadResourceDispatched) {
RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLTrackElement::LoadResource);
nsContentUtils::RunInStableState(r.forget());
mLoadResourceDispatched = true;
}
}
void
HTMLTrackElement::LoadResource()
{
mLoadResourceDispatched = false;
// Find our 'src' url
nsAutoString src;
if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
@ -258,8 +296,7 @@ HTMLTrackElement::BindToTree(nsIDocument* aDocument,
if (!mTrack) {
CreateTextTrack();
}
RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLTrackElement::LoadResource);
nsContentUtils::RunInStableState(r.forget());
DispatchLoadResource();
}
return NS_OK;

View File

@ -46,10 +46,8 @@ public:
{
GetHTMLURIAttr(nsGkAtoms::src, aSrc);
}
void SetSrc(const nsAString& aSrc, ErrorResult& aError)
{
SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
}
void SetSrc(const nsAString& aSrc, ErrorResult& aError);
void GetSrclang(DOMString& aSrclang) const
{
@ -134,6 +132,10 @@ protected:
RefPtr<WebVTTListener> mListener;
void CreateTextTrack();
private:
void DispatchLoadResource();
bool mLoadResourceDispatched;
};
} // namespace dom

View File

@ -31,6 +31,9 @@
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/VideoPlaybackQuality.h"
#include <algorithm>
#include <limits>
NS_IMPL_NS_NEW_HTML_ELEMENT(Video)
namespace mozilla {
@ -225,6 +228,12 @@ HTMLVideoElement::NotifyOwnerDocumentActivityChangedInternal()
return pauseElement;
}
FrameStatistics*
HTMLVideoElement::GetFrameStatistics()
{
return mDecoder ? &(mDecoder->GetFrameStatistics()) : nullptr;
}
already_AddRefed<VideoPlaybackQuality>
HTMLVideoElement::GetVideoPlaybackQuality()
{
@ -242,11 +251,24 @@ HTMLVideoElement::GetVideoPlaybackQuality()
}
if (mDecoder) {
FrameStatistics& stats = mDecoder->GetFrameStatistics();
static_assert(sizeof(uint32_t) >= sizeof (stats.GetParsedFrames()),
"possible truncation from FrameStatistics to VideoPlaybackQuality");
totalFrames = stats.GetParsedFrames();
droppedFrames = stats.GetDroppedFrames();
FrameStatisticsData stats =
mDecoder->GetFrameStatistics().GetFrameStatisticsData();
if (sizeof(totalFrames) >= sizeof(stats.mParsedFrames)) {
totalFrames = stats.mParsedFrames;
droppedFrames = stats.mDroppedFrames;
} else {
auto maxStat = std::max(stats.mParsedFrames, stats.mDroppedFrames);
const auto maxNumber = std::numeric_limits<uint32_t>::max();
if (maxStat <= maxNumber) {
totalFrames = static_cast<uint32_t>(stats.mParsedFrames);
droppedFrames = static_cast<uint32_t>(stats.mDroppedFrames);
} else {
// Too big number(s) -> Resize everything to fit in 32 bits.
double ratio = double(maxNumber) / double(maxStat);
totalFrames = double(stats.mParsedFrames) * ratio;
droppedFrames = double(stats.mDroppedFrames) * ratio;
}
}
corruptedFrames = 0;
}
}

View File

@ -130,6 +130,9 @@ public:
bool NotifyOwnerDocumentActivityChangedInternal() override;
// Gives access to the decoder's frame statistics, if present.
FrameStatistics* GetFrameStatistics();
already_AddRefed<VideoPlaybackQuality> GetVideoPlaybackQuality();
protected:

View File

@ -185,7 +185,7 @@ for (var test of data) {
input.min = '2009-02-01';
input.step = '1.1';
input.value = '2009-02-02';
checkValidity(input, false, apply, { low: "2009-02-01", high: "2009-02-12" });
checkValidity(input, true, apply);
// Without any step attribute the date is valid
input.removeAttribute('step');
@ -203,21 +203,21 @@ for (var test of data) {
input.step = '0.9';
input.value = '1951-01-02';
checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-10" });
input.value = '1951-01-10'
is(input.step, '0.9', "check that step value is unchanged");
checkValidity(input, true, apply);
input.step = '0.5';
input.step = '0.4';
input.value = '1951-01-02';
is(input.step, '0.4', "check that step value is unchanged");
checkValidity(input, true, apply);
input.step = '1.5';
input.value = '1951-01-03';
checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-04" });
input.value = '1951-01-02';
is(input.step, '1.5', "check that step value is unchanged");
checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-03" });
input.value = '1951-01-08';
checkValidity(input, false, apply, { low: "1951-01-07", high: "1951-01-10" });
checkValidity(input, false, apply, { low: "1951-01-07", high: "1951-01-09" });
input.step = '3000';
input.min= '1968-01-01';
@ -236,26 +236,26 @@ for (var test of data) {
input.value = '1992-08-22';
checkValidity(input, true, apply);
input.step = '1.1';
input.step = '2.1';
input.min = '1991-01-01';
input.value = '1991-01-01';
checkValidity(input, true, apply);
input.value = '1991-01-02';
checkValidity(input, false, apply, { low: "1991-01-01", high: "1991-01-12" });
checkValidity(input, false, apply, { low: "1991-01-01", high: "1991-01-03" });
input.value = '1991-01-12';
input.value = '1991-01-03';
checkValidity(input, true, apply);
input.step = '1.1';
input.step = '2.1';
input.min = '1969-12-20';
input.value = '1969-12-20';
checkValidity(input, true, apply);
input.value = '1969-12-21';
checkValidity(input, false, apply, { low: "1969-12-20", high: "1969-12-31" });
checkValidity(input, false, apply, { low: "1969-12-20", high: "1969-12-22" });
input.value = '1969-12-31';
input.value = '1969-12-22';
checkValidity(input, true, apply);
break;

View File

@ -271,12 +271,12 @@ function checkStepDown()
[ '2012-01-03', '0.5', null, null, null, '2012-01-02', false ],
[ '2012-01-02', '0.5', null, null, null, '2012-01-01', false ],
[ '2012-01-01', '2', null, null, null, '2011-12-30', false ],
[ '2012-01-02', '0.25',null, null, 4, '2012-01-01', false ],
[ '2012-01-15', '1.1', '2012-01-01', null, 1, '2012-01-12', false ],
[ '2012-01-12', '1.1', '2012-01-01', null, 2, '2012-01-01', false ],
[ '2012-01-23', '1.1', '2012-01-01', null, 10, '2012-01-12', false ],
[ '2012-01-23', '1.1', '2012-01-01', null, 11, '2012-01-01', false ],
[ '1968-01-12', '1.1', '1968-01-01', null, 8, '1968-01-01', false ],
[ '2012-01-02', '0.25',null, null, 4, '2011-12-29', false ],
[ '2012-01-15', '1.1', '2012-01-01', null, 1, '2012-01-14', false ],
[ '2012-01-12', '1.1', '2012-01-01', null, 2, '2012-01-10', false ],
[ '2012-01-23', '1.1', '2012-01-01', null, 10, '2012-01-13', false ],
[ '2012-01-23', '1.1', '2012-01-01', null, 11, '2012-01-12', false ],
[ '1968-01-12', '1.1', '1968-01-01', null, 8, '1968-01-04', false ],
// step = 0 isn't allowed (-> step = 1).
[ '2012-01-02', '0', null, null, null, '2012-01-01', false ],
// step < 0 isn't allowed (-> step = 1).
@ -588,14 +588,13 @@ function checkStepUp()
[ '2012-01-01', null, null, null, 1.9, '2012-01-02', false ],
// With step values.
[ '2012-01-01', '0.5', null, null, null, '2012-01-02', false ],
[ '2012-01-01', '0.5', null, null, null, '2012-01-02', false ],
[ '2012-01-01', '2', null, null, null, '2012-01-03', false ],
[ '2012-01-01', '0.25', null, null, 4, '2012-01-02', false ],
[ '2012-01-01', '1.1', '2012-01-01', null, 1, '2012-01-12', false ],
[ '2012-01-01', '1.1', '2012-01-01', null, 2, '2012-01-12', false ],
[ '2012-01-01', '1.1', '2012-01-01', null, 10, '2012-01-12', false ],
[ '2012-01-01', '1.1', '2012-01-01', null, 11, '2012-01-23', false ],
[ '1968-01-01', '1.1', '1968-01-01', null, 8, '1968-01-12', false ],
[ '2012-01-01', '0.25', null, null, 4, '2012-01-05', false ],
[ '2012-01-01', '1.1', '2012-01-01', null, 1, '2012-01-02', false ],
[ '2012-01-01', '1.1', '2012-01-01', null, 2, '2012-01-03', false ],
[ '2012-01-01', '1.1', '2012-01-01', null, 10, '2012-01-11', false ],
[ '2012-01-01', '1.1', '2012-01-01', null, 11, '2012-01-12', false ],
[ '1968-01-01', '1.1', '1968-01-01', null, 8, '1968-01-09', false ],
// step = 0 isn't allowed (-> step = 1).
[ '2012-01-01', '0', null, null, null, '2012-01-02', false ],
// step < 0 isn't allowed (-> step = 1).

View File

@ -10,6 +10,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/StateMirroring.h"
#include "FrameStatistics.h"
#include "MediaEventSource.h"
#include "MediaInfo.h"
#include "nsISupports.h"
@ -57,8 +58,7 @@ public:
// Increments the parsed, decoded and dropped frame counters by the passed in
// counts.
// Can be called on any thread.
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
uint32_t aDropped) = 0;
virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0;
virtual AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() { return nullptr; };
@ -99,15 +99,15 @@ public:
class AutoNotifyDecoded {
public:
explicit AutoNotifyDecoded(AbstractMediaDecoder* aDecoder)
: mParsed(0), mDecoded(0), mDropped(0), mDecoder(aDecoder) {}
: mDecoder(aDecoder)
{}
~AutoNotifyDecoded() {
if (mDecoder) {
mDecoder->NotifyDecodedFrames(mParsed, mDecoded, mDropped);
mDecoder->NotifyDecodedFrames(mStats);
}
}
uint32_t mParsed;
uint32_t mDecoded;
uint32_t mDropped;
FrameStatisticsData mStats;
private:
AbstractMediaDecoder* mDecoder;

View File

@ -7,90 +7,137 @@
#ifndef FrameStatistics_h_
#define FrameStatistics_h_
#include "mozilla/ReentrantMonitor.h"
namespace mozilla {
struct FrameStatisticsData
{
// Number of frames parsed and demuxed from media.
// Access protected by mReentrantMonitor.
uint64_t mParsedFrames = 0;
// Number of parsed frames which were actually decoded.
// Access protected by mReentrantMonitor.
uint64_t mDecodedFrames = 0;
// Number of decoded frames which were actually sent down the rendering
// pipeline to be painted ("presented"). Access protected by mReentrantMonitor.
uint64_t mPresentedFrames = 0;
// Number of frames that have been skipped because they have missed their
// composition deadline.
uint64_t mDroppedFrames = 0;
// Sum of all inter-keyframe segment durations, in microseconds.
// Dividing by count will give the average inter-keyframe time.
uint64_t mInterKeyframeSum_us = 0;
// Number of inter-keyframe segments summed so far.
size_t mInterKeyframeCount = 0;
// Maximum inter-keyframe segment duration, in microseconds.
uint64_t mInterKeyFrameMax_us = 0;
FrameStatisticsData() = default;
FrameStatisticsData(uint64_t aParsed, uint64_t aDecoded, uint64_t aDropped)
: mParsedFrames(aParsed)
, mDecodedFrames(aDecoded)
, mDroppedFrames(aDropped)
{}
void
Accumulate(const FrameStatisticsData& aStats)
{
mParsedFrames += aStats.mParsedFrames;
mDecodedFrames += aStats.mDecodedFrames;
mPresentedFrames += aStats.mPresentedFrames;
mDroppedFrames += aStats.mDroppedFrames;
mInterKeyframeSum_us += aStats.mInterKeyframeSum_us;
mInterKeyframeCount += aStats.mInterKeyframeCount;
// It doesn't make sense to add max numbers, instead keep the bigger one.
if (mInterKeyFrameMax_us < aStats.mInterKeyFrameMax_us) {
mInterKeyFrameMax_us = aStats.mInterKeyFrameMax_us;
}
}
};
// Frame decoding/painting related performance counters.
// Threadsafe.
class FrameStatistics {
class FrameStatistics
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FrameStatistics);
FrameStatistics() :
mReentrantMonitor("FrameStats"),
mParsedFrames(0),
mDecodedFrames(0),
mPresentedFrames(0),
mDroppedFrames(0) {}
FrameStatistics()
: mReentrantMonitor("FrameStats")
{}
// Returns a copy of all frame statistics data.
// Can be called on any thread.
FrameStatisticsData GetFrameStatisticsData() const
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return mFrameStatisticsData;
}
// Returns number of frames which have been parsed from the media.
// Can be called on any thread.
uint32_t GetParsedFrames() {
uint64_t GetParsedFrames() const
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return mParsedFrames;
return mFrameStatisticsData.mParsedFrames;
}
// Returns the number of parsed frames which have been decoded.
// Can be called on any thread.
uint32_t GetDecodedFrames() {
uint64_t GetDecodedFrames() const
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return mDecodedFrames;
return mFrameStatisticsData.mDecodedFrames;
}
// Returns the number of decoded frames which have been sent to the rendering
// pipeline for painting ("presented").
// Can be called on any thread.
uint32_t GetPresentedFrames() {
uint64_t GetPresentedFrames() const
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return mPresentedFrames;
return mFrameStatisticsData.mPresentedFrames;
}
// Number of frames that have been skipped because they have missed their
// compoisition deadline.
uint32_t GetDroppedFrames() {
// Returns the number of frames that have been skipped because they have
// missed their composition deadline.
uint64_t GetDroppedFrames() const
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return mDroppedFrames;
return mFrameStatisticsData.mDroppedFrames;
}
// Increments the parsed and decoded frame counters by the passed in counts.
// Can be called on any thread.
void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
uint32_t aDropped) {
if (aParsed == 0 && aDecoded == 0 && aDropped == 0)
return;
void NotifyDecodedFrames(const FrameStatisticsData& aStats)
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mParsedFrames += aParsed;
mDecodedFrames += aDecoded;
mDroppedFrames += aDropped;
mFrameStatisticsData.Accumulate(aStats);
}
// Increments the presented frame counters.
// Can be called on any thread.
void NotifyPresentedFrame() {
void NotifyPresentedFrame()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
++mPresentedFrames;
++mFrameStatisticsData.mPresentedFrames;
}
private:
~FrameStatistics() {}
// ReentrantMonitor to protect access of playback statistics.
ReentrantMonitor mReentrantMonitor;
mutable ReentrantMonitor mReentrantMonitor;
// Number of frames parsed and demuxed from media.
// Access protected by mReentrantMonitor.
uint32_t mParsedFrames;
// Number of parsed frames which were actually decoded.
// Access protected by mReentrantMonitor.
uint32_t mDecodedFrames;
// Number of decoded frames which were actually sent down the rendering
// pipeline to be painted ("presented"). Access protected by mReentrantMonitor.
uint32_t mPresentedFrames;
uint32_t mDroppedFrames;
FrameStatisticsData mFrameStatisticsData;
};
} // namespace mozilla
#endif
#endif // FrameStatistics_h_

View File

@ -27,7 +27,6 @@
#include "nsITimer.h"
#include "AbstractMediaDecoder.h"
#include "FrameStatistics.h"
#include "MediaDecoderOwner.h"
#include "MediaEventSource.h"
#include "MediaMetadataManager.h"
@ -484,10 +483,9 @@ private:
// Increments the parsed and decoded frame counters by the passed in counts.
// Can be called on any thread.
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
uint32_t aDropped) override
virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) override
{
GetFrameStatistics().NotifyDecodedFrames(aParsed, aDecoded, aDropped);
GetFrameStatistics().NotifyDecodedFrames(aStats);
}
void UpdateReadyState()

View File

@ -67,6 +67,7 @@ MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
, mDemuxer(aDemuxer)
, mDemuxerInitDone(false)
, mLastReportedNumDecodedFrames(0)
, mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
, mLayersBackendType(aLayersBackend)
, mInitDone(false)
, mIsEncrypted(false)
@ -1015,7 +1016,7 @@ MediaFormatReader::HandleDemuxedSamples(TrackType aTrack,
decoder.mNumSamplesInput++;
decoder.mSizeOfQueue++;
if (aTrack == TrackInfo::kVideoTrack) {
aA.mParsed++;
aA.mStats.mParsedFrames++;
}
if (mDemuxOnly) {
@ -1159,6 +1160,8 @@ MediaFormatReader::Update(TrackType aTrack)
if (time >= target.Time()) {
// We have reached our internal seek target.
decoder.mTimeThreshold.reset();
// We might have dropped some keyframes.
mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
}
if (time < target.Time() || (target.mDropTarget && target.Contains(time))) {
LOGV("Internal Seeking: Dropping %s frame time:%f wanted:%f (kf:%d)",
@ -1192,8 +1195,20 @@ MediaFormatReader::Update(TrackType aTrack)
if (aTrack == TrackType::kVideoTrack) {
uint64_t delta =
decoder.mNumSamplesOutputTotal - mLastReportedNumDecodedFrames;
a.mDecoded = static_cast<uint32_t>(delta);
a.mStats.mDecodedFrames = static_cast<uint32_t>(delta);
mLastReportedNumDecodedFrames = decoder.mNumSamplesOutputTotal;
if (output->mKeyframe) {
if (mPreviousDecodedKeyframeTime_us < output->mTime) {
// There is a previous keyframe -> Record inter-keyframe stats.
uint64_t segment_us = output->mTime - mPreviousDecodedKeyframeTime_us;
a.mStats.mInterKeyframeSum_us += segment_us;
a.mStats.mInterKeyframeCount += 1;
if (a.mStats.mInterKeyFrameMax_us < segment_us) {
a.mStats.mInterKeyFrameMax_us = segment_us;
}
}
mPreviousDecodedKeyframeTime_us = output->mTime;
}
nsCString error;
mVideo.mIsHardwareAccelerated =
mVideo.mDecoder && mVideo.mDecoder->IsHardwareAccelerated(error);
@ -1491,7 +1506,7 @@ MediaFormatReader::DropDecodedSamples(TrackType aTrack)
decoder.mOutput.Clear();
decoder.mSizeOfQueue -= lengthDecodedQueue;
if (aTrack == TrackInfo::kVideoTrack && mDecoder) {
mDecoder->NotifyDecodedFrames(0, 0, lengthDecodedQueue);
mDecoder->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
}
}
@ -1527,7 +1542,7 @@ MediaFormatReader::VideoSkipReset(uint32_t aSkipped)
DropDecodedSamples(TrackInfo::kVideoTrack);
// Report the pending frames as dropped.
if (mDecoder) {
mDecoder->NotifyDecodedFrames(0, 0, SizeOfVideoQueueInFrames());
mDecoder->NotifyDecodedFrames({ 0, 0, SizeOfVideoQueueInFrames() });
}
// Cancel any pending demux request and pending demuxed samples.
@ -1535,7 +1550,7 @@ MediaFormatReader::VideoSkipReset(uint32_t aSkipped)
Reset(TrackType::kVideoTrack);
if (mDecoder) {
mDecoder->NotifyDecodedFrames(aSkipped, 0, aSkipped);
mDecoder->NotifyDecodedFrames({ aSkipped, 0, aSkipped });
}
mVideo.mNumSamplesSkippedTotal += aSkipped;
@ -1757,6 +1772,8 @@ MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
LOGV("Video seeked to %lld", aTime.ToMicroseconds());
mVideo.mSeekRequest.Complete();
mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
SetVideoDecodeThreshold();
if (HasAudio() && !mOriginalSeekTarget.IsVideoOnly()) {
@ -1773,6 +1790,13 @@ MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
}
}
void
MediaFormatReader::OnVideoSeekFailed(DemuxerFailureReason aFailure)
{
mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
OnSeekFailed(TrackType::kVideoTrack, aFailure);
}
void
MediaFormatReader::SetVideoDecodeThreshold()
{
@ -1833,6 +1857,12 @@ MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime)
mSeekPromise.Resolve(aTime, __func__);
}
void
MediaFormatReader::OnAudioSeekFailed(DemuxerFailureReason aFailure)
{
OnSeekFailed(TrackType::kAudioTrack, aFailure);
}
media::TimeIntervals
MediaFormatReader::GetBuffered()
{

View File

@ -515,6 +515,11 @@ private:
// delta there.
uint64_t mLastReportedNumDecodedFrames;
// Timestamp of the previous decoded keyframe, in microseconds.
int64_t mPreviousDecodedKeyframeTime_us;
// Default mLastDecodedKeyframeTime_us value, must be bigger than anything.
static const int64_t sNoPreviousDecodedKeyframe = INT64_MAX;
layers::LayersBackend mLayersBackendType;
// Metadata objects
@ -546,18 +551,12 @@ private:
void OnSeekFailed(TrackType aTrack, DemuxerFailureReason aFailure);
void DoVideoSeek();
void OnVideoSeekCompleted(media::TimeUnit aTime);
void OnVideoSeekFailed(DemuxerFailureReason aFailure)
{
OnSeekFailed(TrackType::kVideoTrack, aFailure);
}
void OnVideoSeekFailed(DemuxerFailureReason aFailure);
bool mSeekScheduled;
void DoAudioSeek();
void OnAudioSeekCompleted(media::TimeUnit aTime);
void OnAudioSeekFailed(DemuxerFailureReason aFailure)
{
OnSeekFailed(TrackType::kAudioTrack, aFailure);
}
void OnAudioSeekFailed(DemuxerFailureReason aFailure);
// The SeekTarget that was last given to Seek()
SeekTarget mOriginalSeekTarget;
// Temporary seek information while we wait for the data

View File

@ -361,6 +361,9 @@ public:
if (mVideoDevice) {
mVideoDevice->GetSource()->GetSettings(aOutSettings);
}
if (mAudioDevice) {
mAudioDevice->GetSource()->GetSettings(aOutSettings);
}
}
// implement in .cpp to avoid circular dependency with MediaOperationTask
@ -1618,7 +1621,7 @@ already_AddRefed<MediaManager::PledgeSourceSet>
MediaManager::EnumerateRawDevices(uint64_t aWindowId,
MediaSourceEnum aVideoType,
MediaSourceEnum aAudioType,
bool aFake, bool aFakeTracks)
bool aFake)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aVideoType != MediaSourceEnum::Other ||
@ -1639,15 +1642,9 @@ MediaManager::EnumerateRawDevices(uint64_t aWindowId,
}
}
if (!aFake) {
// Fake tracks only make sense when we have a fake stream.
aFakeTracks = false;
}
MediaManager::PostTask(NewTaskFrom([id, aWindowId, audioLoopDev,
videoLoopDev, aVideoType,
aAudioType, aFake,
aFakeTracks]() mutable {
aAudioType, aFake]() mutable {
// Only enumerate what's asked for, and only fake cams and mics.
bool hasVideo = aVideoType != MediaSourceEnum::Other;
bool hasAudio = aAudioType != MediaSourceEnum::Other;
@ -1656,7 +1653,7 @@ MediaManager::EnumerateRawDevices(uint64_t aWindowId,
RefPtr<MediaEngine> fakeBackend, realBackend;
if (fakeCams || fakeMics) {
fakeBackend = new MediaEngineDefault(aFakeTracks);
fakeBackend = new MediaEngineDefault();
}
if ((!fakeCams && hasVideo) || (!fakeMics && hasAudio)) {
RefPtr<MediaManager> manager = MediaManager_GetInstance();
@ -2345,14 +2342,11 @@ MediaManager::GetUserMedia(nsPIDOMWindowInner* aWindow,
bool fake = c.mFake.WasPassed()? c.mFake.Value() :
Preferences::GetBool("media.navigator.streams.fake");
bool fakeTracks = c.mFakeTracks.WasPassed()? c.mFakeTracks.Value() : false;
bool askPermission = !privileged &&
(!fake || Preferences::GetBool("media.navigator.permission.fake"));
RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowID, videoType,
audioType, fake,
fakeTracks);
audioType, fake);
p->Then([this, onSuccess, onFailure, windowID, c, listener, askPermission,
prefs, isHTTPS, callID, origin](SourceSet*& aDevices) mutable {
@ -2539,7 +2533,7 @@ already_AddRefed<MediaManager::PledgeSourceSet>
MediaManager::EnumerateDevicesImpl(uint64_t aWindowId,
MediaSourceEnum aVideoType,
MediaSourceEnum aAudioType,
bool aFake, bool aFakeTracks)
bool aFake)
{
MOZ_ASSERT(NS_IsMainThread());
nsPIDOMWindowInner* window =
@ -2568,13 +2562,12 @@ MediaManager::EnumerateDevicesImpl(uint64_t aWindowId,
RefPtr<Pledge<nsCString>> p = media::GetOriginKey(origin, privateBrowsing,
persist);
p->Then([id, aWindowId, aVideoType, aAudioType,
aFake, aFakeTracks](const nsCString& aOriginKey) mutable {
aFake](const nsCString& aOriginKey) mutable {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<MediaManager> mgr = MediaManager_GetInstance();
RefPtr<PledgeSourceSet> p = mgr->EnumerateRawDevices(aWindowId,
aVideoType, aAudioType,
aFake, aFakeTracks);
RefPtr<PledgeSourceSet> p = mgr->EnumerateRawDevices(aWindowId, aVideoType,
aAudioType, aFake);
p->Then([id, aWindowId, aOriginKey](SourceSet*& aDevices) mutable {
UniquePtr<SourceSet> devices(aDevices); // secondary result

View File

@ -96,7 +96,7 @@ protected:
nsString mID;
dom::MediaSourceEnum mMediaSource;
RefPtr<MediaEngineSource> mSource;
RefPtr<MediaEngineSource::BaseAllocationHandle> mAllocationHandle;
RefPtr<MediaEngineSource::AllocationHandle> mAllocationHandle;
public:
dom::MediaSourceEnum GetMediaSource() {
return mMediaSource;
@ -273,12 +273,12 @@ private:
EnumerateRawDevices(uint64_t aWindowId,
dom::MediaSourceEnum aVideoType,
dom::MediaSourceEnum aAudioType,
bool aFake, bool aFakeTracks);
bool aFake);
already_AddRefed<PledgeSourceSet>
EnumerateDevicesImpl(uint64_t aWindowId,
dom::MediaSourceEnum aVideoSrcType,
dom::MediaSourceEnum aAudioSrcType,
bool aFake = false, bool aFakeTracks = false);
bool aFake = false);
already_AddRefed<PledgeChar>
SelectSettings(
dom::MediaStreamConstraints& aConstraints,

View File

@ -36,17 +36,17 @@ public:
return mCreationTime;
}
uint32_t TotalVideoFrames()
uint32_t TotalVideoFrames() const
{
return mTotalFrames;
}
uint32_t DroppedVideoFrames()
uint32_t DroppedVideoFrames() const
{
return mDroppedFrames;
}
uint32_t CorruptedVideoFrames()
uint32_t CorruptedVideoFrames() const
{
return mCorruptedFrames;
}
@ -64,4 +64,4 @@ private:
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_VideoPlaybackQuality_h_ */
#endif // mozilla_dom_VideoPlaybackQuality_h_

View File

@ -155,8 +155,8 @@ bool AndroidMediaReader::DecodeVideoFrame(bool &aKeyframeSkip,
// when a frame is a keyframe.
#if 0
if (!frame.mKeyFrame) {
++a.mParsed;
++a.mDropped;
++a.mStats.mParsedFrames;
++a.mStats.mDroppedFrames;
continue;
}
#endif
@ -244,9 +244,9 @@ bool AndroidMediaReader::DecodeVideoFrame(bool &aKeyframeSkip,
if (!v) {
return false;
}
a.mParsed++;
a.mDecoded++;
NS_ASSERTION(a.mDecoded <= a.mParsed, "Expect to decode fewer frames than parsed in AndroidMedia...");
a.mStats.mParsedFrames++;
a.mStats.mDecodedFrames++;
NS_ASSERTION(a.mStats.mDecodedFrames <= a.mStats.mParsedFrames, "Expect to decode fewer frames than parsed in AndroidMedia...");
// Since MPAPI doesn't give us the end time of frames, we keep one frame
// buffered in AndroidMediaReader and push it into the queue as soon

View File

@ -406,7 +406,7 @@ VideoSink::UpdateRenderedVideoFrames()
}
++framesRemoved;
if (!currentFrame->As<VideoData>()->mSentToCompositor) {
mFrameStats.NotifyDecodedFrames(0, 0, 1);
mFrameStats.NotifyDecodedFrames({ 0, 0, 1 });
VSINK_LOG_V("discarding video frame mTime=%lld clock_time=%lld",
currentFrame->mTime, clockTime);
}

View File

@ -921,7 +921,7 @@ bool OggReader::DecodeVideoFrame(bool &aKeyframeSkip,
}
nsAutoRef<ogg_packet> autoRelease(packet);
a.mParsed++;
a.mStats.mParsedFrames++;
NS_ASSERTION(packet && packet->granulepos != -1,
"Must know first packet's granulepos");
bool eos = packet->e_o_s;
@ -931,7 +931,7 @@ bool OggReader::DecodeVideoFrame(bool &aKeyframeSkip,
{
aKeyframeSkip = false;
nsresult res = DecodeTheora(packet, aTimeThreshold);
a.mDecoded++;
a.mStats.mDecodedFrames++;
if (NS_FAILED(res)) {
return false;
}

View File

@ -20,6 +20,7 @@
#include "AudioOffloadPlayer.h"
#include "nsComponentManagerUtils.h"
#include "nsITimer.h"
#include "MediaOmxCommonDecoder.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "VideoUtils.h"
#include "mozilla/dom/power/PowerManagerService.h"
@ -58,8 +59,7 @@ AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxCommonDecoder* aObserver) :
mSampleRate(0),
mStartPosUs(0),
mPositionTimeMediaUs(-1),
mInputBuffer(nullptr),
mObserver(aObserver)
mInputBuffer(nullptr)
{
MOZ_ASSERT(NS_IsMainThread());
@ -73,6 +73,17 @@ AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxCommonDecoder* aObserver) :
#endif
mAudioSink = new AudioOutput(mSessionId,
IPCThreadState::self()->getCallingUid());
nsCOMPtr<nsIThread> thread;
MOZ_ALWAYS_SUCCEEDS(NS_GetMainThread(getter_AddRefs(thread)));
mPositionChanged = mOnPositionChanged.Connect(
thread, aObserver, &MediaOmxCommonDecoder::NotifyOffloadPlayerPositionChanged);
mPlaybackEnded = mOnPlaybackEnded.Connect(
thread, aObserver, &MediaDecoder::PlaybackEnded);
mPlayerTearDown = mOnPlayerTearDown.Connect(
thread, aObserver, &MediaOmxCommonDecoder::AudioOffloadTearDown);
mSeekingStarted = mOnSeekingStarted.Connect(
thread, aObserver, &MediaDecoder::SeekingStarted);
}
AudioOffloadPlayer::~AudioOffloadPlayer()
@ -83,6 +94,13 @@ AudioOffloadPlayer::~AudioOffloadPlayer()
#else
AudioSystem::releaseAudioSessionId(mSessionId);
#endif
// Disconnect the listeners to prevent notifications from reaching
// the MediaOmxCommonDecoder object after shutdown.
mPositionChanged.Disconnect();
mPlaybackEnded.Disconnect();
mPlayerTearDown.Disconnect();
mSeekingStarted.Disconnect();
}
void AudioOffloadPlayer::SetSource(const sp<MediaSource> &aSource)
@ -353,12 +371,7 @@ status_t AudioOffloadPlayer::DoSeek()
mStartPosUs = mSeekTarget.GetTime().ToMicroseconds();
if (!mSeekPromise.IsEmpty()) {
nsCOMPtr<nsIRunnable> nsEvent =
NewRunnableMethod<MediaDecoderEventVisibility>(
mObserver,
&MediaDecoder::SeekingStarted,
mSeekTarget.mEventVisibility);
NS_DispatchToCurrentThread(nsEvent);
mOnSeekingStarted.Notify(mSeekTarget.mEventVisibility);
}
if (mPlaying) {
@ -425,14 +438,12 @@ void AudioOffloadPlayer::NotifyAudioEOS()
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
mSeekPromise.Resolve(val, __func__);
}
NS_DispatchToMainThread(NewRunnableMethod(mObserver,
&MediaDecoder::PlaybackEnded));
mOnPlaybackEnded.Notify();
}
void AudioOffloadPlayer::NotifyPositionChanged()
{
NS_DispatchToMainThread(NewRunnableMethod(mObserver,
&MediaOmxCommonDecoder::NotifyOffloadPlayerPositionChanged));
mOnPositionChanged.Notify();
}
void AudioOffloadPlayer::NotifyAudioTearDown()
@ -446,8 +457,7 @@ void AudioOffloadPlayer::NotifyAudioTearDown()
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
mSeekPromise.Resolve(val, __func__);
}
NS_DispatchToMainThread(NewRunnableMethod(mObserver,
&MediaOmxCommonDecoder::AudioOffloadTearDown));
mOnPlayerTearDown.Notify();
}
// static

View File

@ -29,7 +29,7 @@
#include "AudioOutput.h"
#include "AudioOffloadPlayerBase.h"
#include "MediaDecoderOwner.h"
#include "MediaOmxCommonDecoder.h"
#include "MediaEventSource.h"
namespace mozilla {
@ -58,6 +58,8 @@ class WakeLock;
* offload playback
*/
class MediaOmxCommonDecoder;
class AudioOffloadPlayer : public AudioOffloadPlayerBase
{
typedef android::Mutex Mutex;
@ -73,7 +75,7 @@ public:
SEEK_COMPLETE
};
AudioOffloadPlayer(MediaOmxCommonDecoder* aDecoder = nullptr);
AudioOffloadPlayer(MediaOmxCommonDecoder* aDecoder);
~AudioOffloadPlayer();
@ -175,9 +177,6 @@ private:
// Buffer used to get date from audio source. Used in offload callback thread
MediaBuffer* mInputBuffer;
// MediaOmxCommonDecoder object used mainly to notify the audio sink status
MediaOmxCommonDecoder* mObserver;
TimeStamp mLastFireUpdateTime;
// Timer to trigger position changed events
@ -192,6 +191,15 @@ private:
// Used only from main thread so no lock is needed.
RefPtr<mozilla::dom::WakeLock> mWakeLock;
MediaEventProducer<void> mOnPositionChanged;
MediaEventProducer<void> mOnPlaybackEnded;
MediaEventProducer<void> mOnPlayerTearDown;
MediaEventProducer<MediaDecoderEventVisibility> mOnSeekingStarted;
MediaEventListener mPositionChanged;
MediaEventListener mPlaybackEnded;
MediaEventListener mPlayerTearDown;
MediaEventListener mSeekingStarted;
// Provide the playback position in microseconds from total number of
// frames played by audio track
int64_t GetOutputPlayPositionUs_l() const;

View File

@ -165,6 +165,7 @@ void
MediaOmxCommonDecoder::AudioOffloadTearDown()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!IsShutdown());
DECODER_LOG(LogLevel::Debug, ("%s", __PRETTY_FUNCTION__));
// mAudioOffloadPlayer can be null here if ResumeStateMachine was called
@ -286,4 +287,11 @@ MediaOmxCommonDecoder::CreateStateMachine()
return CreateStateMachineFromReader(mReader);
}
void
MediaOmxCommonDecoder::Shutdown()
{
mAudioOffloadPlayer = nullptr;
MediaDecoder::Shutdown();
}
} // namespace mozilla

View File

@ -46,6 +46,8 @@ public:
void NotifyOffloadPlayerPositionChanged() { UpdateLogicalPosition(); }
void Shutdown() override;
protected:
virtual ~MediaOmxCommonDecoder();
void PauseStateMachine();

View File

@ -357,7 +357,7 @@ bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip,
continue;
}
a.mParsed++;
a.mStats.mParsedFrames++;
if (frame.mShouldSkip && mSkipCount < MAX_DROPPED_FRAMES) {
mSkipCount++;
continue;
@ -434,8 +434,8 @@ bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip,
return false;
}
a.mDecoded++;
NS_ASSERTION(a.mDecoded <= a.mParsed, "Expect to decode fewer frames than parsed in OMX decoder...");
a.mStats.mDecodedFrames++;
NS_ASSERTION(a.mStats.mDecodedFrames <= a.mStats.mParsedFrames, "Expect to decode fewer frames than parsed in OMX decoder...");
mVideoQueue.Push(v);

View File

@ -156,7 +156,7 @@ bool RawReader::DecodeVideoFrame(bool &aKeyframeSkip,
return false;
}
a.mParsed++;
a.mStats.mParsedFrames++;
if (currentFrameTime >= aTimeThreshold)
break;
@ -200,7 +200,7 @@ bool RawReader::DecodeVideoFrame(bool &aKeyframeSkip,
mVideoQueue.Push(v);
mCurrentFrame++;
a.mDecoded++;
a.mStats.mDecodedFrames++;
return true;
}

View File

@ -856,6 +856,8 @@ tags = webvtt
[test_timeupdate_small_files.html]
[test_trackelementevent.html]
tags = webvtt
[test_trackelementsrc.html]
tags = webvtt
[test_trackevent.html]
tags = webvtt
[test_unseekable.html]

View File

@ -10,8 +10,11 @@
<pre id="test">
<script class="testbody" type="text/javascript">
function startTest() {
navigator.mediaDevices.getUserMedia({audio:true, video:true, fake:true, fakeTracks:true})
.then(function(stream) {
navigator.mediaDevices.getUserMedia({audio:true, video:true, fake:true})
.then(function(orgStream) {
var a = orgStream.getAudioTracks()[0];
var v = orgStream.getVideoTracks()[0];
var stream = new MediaStream([a, a, a, a, v, v, v].map(track => track.clone()));
var element = document.createElement("video");
element.onloadedmetadata = function() {
@ -20,8 +23,7 @@ function startTest() {
SimpleTest.finish();
};
mStream = stream;
element.srcObject = mStream;
element.srcObject = stream;
element.play();
})
.catch(function(reason) {

View File

@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for Bug 1281418 - Change the src attribue for TrackElement.</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
["media.webvtt.regions.enabled", true]]}, function() {
var video = document.createElement("video");
video.src = "seek.webm";
video.preload = "metadata";
var trackElement = document.createElement("track");
trackElement.src = "basic.vtt";
trackElement.default = true;
document.getElementById("content").appendChild(video);
video.appendChild(trackElement);
video.addEventListener("loadedmetadata", function metadata() {
if (trackElement.readyState <= 1) {
return setTimeout(metadata, 0);
}
is(video.textTracks.length, 1, "Length should be 1.");
is(video.textTracks[0].cues.length, 6, "Cue length should be 6.");
trackElement.src = "sequential.vtt";
trackElement.track.mode = "showing";
video.play();
});
video.addEventListener("ended", function end() {
is(trackElement.readyState, 2, "readyState should be 2.")
is(video.textTracks.length, 1, "Length should be 1.");
is(video.textTracks[0].cues.length, 3, "Cue length should be 3.");
SimpleTest.finish();
});
});
</script>
</pre>
</body>
</html>

View File

@ -42,8 +42,7 @@ BufferDecoder::GetResource() const
}
void
BufferDecoder::NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
uint32_t aDropped)
BufferDecoder::NotifyDecodedFrames(const FrameStatisticsData& aStats)
{
// ignore
}

View File

@ -33,8 +33,7 @@ public:
MediaResource* GetResource() const final override;
void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
uint32_t aDropped) final override;
void NotifyDecodedFrames(const FrameStatisticsData& aStats) final override;
VideoFrameContainer* GetVideoFrameContainer() final override;
layers::ImageContainer* GetImageContainer() final override;

View File

@ -8,6 +8,7 @@
#include "mozilla/RefPtr.h"
#include "DOMMediaStream.h"
#include "MediaStreamGraph.h"
#include "MediaTrackConstraints.h"
#include "mozilla/dom/MediaStreamTrackBinding.h"
#include "mozilla/dom/VideoStreamTrack.h"
@ -32,7 +33,6 @@ enum {
*/
class MediaEngineVideoSource;
class MediaEngineAudioSource;
class MediaEnginePrefs;
enum MediaEngineState {
kAllocated,
@ -79,154 +79,6 @@ protected:
virtual ~MediaEngine() {}
};
/**
* Callback interface for TakePhoto(). Either PhotoComplete() or PhotoError()
* should be called.
*/
class MediaEnginePhotoCallback {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEnginePhotoCallback)
// aBlob is the image captured by MediaEngineSource. It is
// called on main thread.
virtual nsresult PhotoComplete(already_AddRefed<dom::Blob> aBlob) = 0;
// It is called on main thread. aRv is the error code.
virtual nsresult PhotoError(nsresult aRv) = 0;
protected:
virtual ~MediaEnginePhotoCallback() {}
};
/**
* Common abstract base class for audio and video sources.
*/
class MediaEngineSource : public nsISupports
{
public:
// code inside webrtc.org assumes these sizes; don't use anything smaller
// without verifying it's ok
static const unsigned int kMaxDeviceNameLength = 128;
static const unsigned int kMaxUniqueIdLength = 256;
virtual ~MediaEngineSource() {}
virtual void Shutdown() = 0;
/* Populate the human readable name of this device in the nsAString */
virtual void GetName(nsAString&) const = 0;
/* Populate the UUID of this device in the nsACString */
virtual void GetUUID(nsACString&) const = 0;
class BaseAllocationHandle
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BaseAllocationHandle);
protected:
virtual ~BaseAllocationHandle() {}
};
/* Release the device back to the system. */
virtual nsresult Deallocate(BaseAllocationHandle* aHandle) = 0;
/* Start the device and add the track to the provided SourceMediaStream, with
* the provided TrackID. You may start appending data to the track
* immediately after. */
virtual nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) = 0;
/* tell the source if there are any direct listeners attached */
virtual void SetDirectListeners(bool) = 0;
/* Called when the stream wants more data */
virtual void NotifyPull(MediaStreamGraph* aGraph,
SourceMediaStream *aSource,
TrackID aId,
StreamTime aDesiredTime,
const PrincipalHandle& aPrincipalHandle) = 0;
/* Stop the device and release the corresponding MediaStream */
virtual nsresult Stop(SourceMediaStream *aSource, TrackID aID) = 0;
/* Restart with new capability */
virtual nsresult Restart(BaseAllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) = 0;
/* Returns true if a source represents a fake capture device and
* false otherwise
*/
virtual bool IsFake() = 0;
/* Returns the type of media source (camera, microphone, screen, window, etc) */
virtual dom::MediaSourceEnum GetMediaSource() const = 0;
/* If implementation of MediaEngineSource supports TakePhoto(), the picture
* should be return via aCallback object. Otherwise, it returns NS_ERROR_NOT_IMPLEMENTED.
* Currently, only Gonk MediaEngineSource implementation supports it.
*/
virtual nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) = 0;
/* Return false if device is currently allocated or started */
bool IsAvailable() {
if (mState == kAllocated || mState == kStarted) {
return false;
} else {
return true;
}
}
/* It is an error to call Start() before an Allocate(), and Stop() before
* a Start(). Only Allocate() may be called after a Deallocate(). */
void SetHasFakeTracks(bool aHasFakeTracks) {
mHasFakeTracks = aHasFakeTracks;
}
/* This call reserves but does not start the device. */
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
const char** aOutBadConstraint) = 0;
virtual uint32_t GetBestFitnessDistance(
const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
const nsString& aDeviceId) const = 0;
void GetSettings(dom::MediaTrackSettings& aOutSettings)
{
MOZ_ASSERT(NS_IsMainThread());
aOutSettings = mSettings;
}
protected:
// Only class' own members can be initialized in constructor initializer list.
explicit MediaEngineSource(MediaEngineState aState)
: mState(aState)
#ifdef DEBUG
, mOwningThread(PR_GetCurrentThread())
#endif
, mHasFakeTracks(false)
{}
void AssertIsOnOwningThread()
{
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
}
MediaEngineState mState;
#ifdef DEBUG
PRThread* mOwningThread;
#endif
bool mHasFakeTracks;
// Main-thread only:
dom::MediaTrackSettings mSettings;
};
/**
* Video source and friends.
*/
@ -301,6 +153,291 @@ private:
}
};
/**
* Callback interface for TakePhoto(). Either PhotoComplete() or PhotoError()
* should be called.
*/
class MediaEnginePhotoCallback {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEnginePhotoCallback)
// aBlob is the image captured by MediaEngineSource. It is
// called on main thread.
virtual nsresult PhotoComplete(already_AddRefed<dom::Blob> aBlob) = 0;
// It is called on main thread. aRv is the error code.
virtual nsresult PhotoError(nsresult aRv) = 0;
protected:
virtual ~MediaEnginePhotoCallback() {}
};
/**
* Common abstract base class for audio and video sources.
*
* By default, the base class implements Allocate and Deallocate using its
* UpdateSingleSource pattern, which manages allocation handles and calculates
* net constraints from competing allocations and updates a single shared device.
*
* Classes that don't operate as a single shared device can override Allocate
* and Deallocate and simply not pass the methods up.
*/
class MediaEngineSource : public nsISupports,
protected MediaConstraintsHelper
{
public:
// code inside webrtc.org assumes these sizes; don't use anything smaller
// without verifying it's ok
static const unsigned int kMaxDeviceNameLength = 128;
static const unsigned int kMaxUniqueIdLength = 256;
virtual ~MediaEngineSource()
{
if (!mInShutdown) {
Shutdown();
}
}
virtual void Shutdown()
{
mInShutdown = true;
};
/* Populate the human readable name of this device in the nsAString */
virtual void GetName(nsAString&) const = 0;
/* Populate the UUID of this device in the nsACString */
virtual void GetUUID(nsACString&) const = 0;
class AllocationHandle
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AllocationHandle);
protected:
~AllocationHandle() {}
public:
AllocationHandle(const dom::MediaTrackConstraints& aConstraints,
const nsACString& aOrigin,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId)
: mConstraints(aConstraints),
mOrigin(aOrigin),
mPrefs(aPrefs),
mDeviceId(aDeviceId) {}
public:
NormalizedConstraints mConstraints;
nsCString mOrigin;
MediaEnginePrefs mPrefs;
nsString mDeviceId;
};
/* Release the device back to the system. */
virtual nsresult Deallocate(AllocationHandle* aHandle)
{
MOZ_ASSERT(aHandle);
RefPtr<AllocationHandle> handle = aHandle;
class Comparator {
public:
static bool Equals(const RefPtr<AllocationHandle>& a,
const RefPtr<AllocationHandle>& b) {
return a.get() == b.get();
}
};
MOZ_ASSERT(mRegisteredHandles.Contains(handle, Comparator()));
mRegisteredHandles.RemoveElementAt(mRegisteredHandles.IndexOf(handle, 0,
Comparator()));
if (mRegisteredHandles.Length() && !mInShutdown) {
// Whenever constraints are removed, other parties may get closer to ideal.
auto& first = mRegisteredHandles[0];
const char* badConstraint = nullptr;
return ReevaluateAllocation(nullptr, nullptr, first->mPrefs,
first->mDeviceId, &badConstraint);
}
return NS_OK;
}
/* Start the device and add the track to the provided SourceMediaStream, with
* the provided TrackID. You may start appending data to the track
* immediately after. */
virtual nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) = 0;
/* tell the source if there are any direct listeners attached */
virtual void SetDirectListeners(bool) = 0;
/* Called when the stream wants more data */
virtual void NotifyPull(MediaStreamGraph* aGraph,
SourceMediaStream *aSource,
TrackID aId,
StreamTime aDesiredTime,
const PrincipalHandle& aPrincipalHandle) = 0;
/* Stop the device and release the corresponding MediaStream */
virtual nsresult Stop(SourceMediaStream *aSource, TrackID aID) = 0;
/* Restart with new capability */
virtual nsresult Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) = 0;
/* Returns true if a source represents a fake capture device and
* false otherwise
*/
virtual bool IsFake() = 0;
/* Returns the type of media source (camera, microphone, screen, window, etc) */
virtual dom::MediaSourceEnum GetMediaSource() const = 0;
/* If implementation of MediaEngineSource supports TakePhoto(), the picture
* should be return via aCallback object. Otherwise, it returns NS_ERROR_NOT_IMPLEMENTED.
* Currently, only Gonk MediaEngineSource implementation supports it.
*/
virtual nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) = 0;
/* Return false if device is currently allocated or started */
bool IsAvailable() {
if (mState == kAllocated || mState == kStarted) {
return false;
} else {
return true;
}
}
/* It is an error to call Start() before an Allocate(), and Stop() before
* a Start(). Only Allocate() may be called after a Deallocate(). */
/* This call reserves but does not start the device. */
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aOutHandle);
RefPtr<AllocationHandle> handle = new AllocationHandle(aConstraints, aOrigin,
aPrefs, aDeviceId);
nsresult rv = ReevaluateAllocation(handle, nullptr, aPrefs, aDeviceId,
aOutBadConstraint);
if (NS_FAILED(rv)) {
return rv;
}
mRegisteredHandles.AppendElement(handle);
handle.forget(aOutHandle);
return NS_OK;
}
virtual uint32_t GetBestFitnessDistance(
const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
const nsString& aDeviceId) const = 0;
void GetSettings(dom::MediaTrackSettings& aOutSettings)
{
MOZ_ASSERT(NS_IsMainThread());
aOutSettings = mSettings;
}
protected:
// Only class' own members can be initialized in constructor initializer list.
explicit MediaEngineSource(MediaEngineState aState)
: mState(aState)
#ifdef DEBUG
, mOwningThread(PR_GetCurrentThread())
#endif
, mInShutdown(false)
{}
/* UpdateSingleSource - Centralized abstract function to implement in those
* cases where a single device is being shared between users. Should apply net
* constraints and restart the device as needed.
*
* aHandle - New or existing handle, or null to update after removal.
* aNetConstraints - Net constraints to be applied to the single device.
* aPrefs - As passed in (in case of changes in about:config).
* aDeviceId - As passed in (origin dependent).
* aOutBadConstraint - Result: nonzero if failed to apply. Name of culprit.
*/
virtual nsresult
UpdateSingleSource(const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) {
return NS_ERROR_NOT_IMPLEMENTED;
};
/* ReevaluateAllocation - Call to change constraints for an allocation of
* a single device. Manages allocation handles, calculates net constraints
* from all competing allocations, and calls UpdateSingleSource with the net
* result, to restart the single device as needed.
*
* aHandle - New or existing handle, or null to update after removal.
* aConstraintsUpdate - Constraints to be applied to existing handle, or null.
* aPrefs - As passed in (in case of changes from about:config).
* aDeviceId - As passed in (origin-dependent id).
* aOutBadConstraint - Result: nonzero if failed to apply. Name of culprit.
*/
nsresult
ReevaluateAllocation(AllocationHandle* aHandle,
NormalizedConstraints* aConstraintsUpdate,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint)
{
// aHandle and/or aConstraintsUpdate may be nullptr (see below)
AutoTArray<const NormalizedConstraints*, 10> allConstraints;
for (auto& registered : mRegisteredHandles) {
if (aConstraintsUpdate && registered.get() == aHandle) {
continue; // Don't count old constraints
}
allConstraints.AppendElement(&registered->mConstraints);
}
if (aConstraintsUpdate) {
allConstraints.AppendElement(aConstraintsUpdate);
} else if (aHandle) {
// In the case of AddShareOfSingleSource, the handle isn't registered yet.
allConstraints.AppendElement(&aHandle->mConstraints);
}
NormalizedConstraints netConstraints(allConstraints);
if (netConstraints.mBadConstraint) {
*aOutBadConstraint = netConstraints.mBadConstraint;
return NS_ERROR_FAILURE;
}
nsresult rv = UpdateSingleSource(aHandle, netConstraints, aPrefs, aDeviceId,
aOutBadConstraint);
if (NS_FAILED(rv)) {
return rv;
}
if (aHandle && aConstraintsUpdate) {
aHandle->mConstraints = *aConstraintsUpdate;
}
return NS_OK;
}
void AssertIsOnOwningThread()
{
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
}
MediaEngineState mState;
#ifdef DEBUG
PRThread* mOwningThread;
#endif
nsTArray<RefPtr<AllocationHandle>> mRegisteredHandles;
bool mInShutdown;
// Main-thread only:
dom::MediaTrackSettings mSettings;
};
class MediaEngineVideoSource : public MediaEngineSource
{
public:

View File

@ -6,7 +6,6 @@
#define MediaEngineCameraVideoSource_h
#include "MediaEngine.h"
#include "MediaTrackConstraints.h"
#include "nsDirectoryServiceDefs.h"
@ -21,10 +20,10 @@ bool operator == (const webrtc::CaptureCapability& a,
bool operator != (const webrtc::CaptureCapability& a,
const webrtc::CaptureCapability& b);
class MediaEngineCameraVideoSource : public MediaEngineVideoSource,
protected MediaConstraintsHelper
class MediaEngineCameraVideoSource : public MediaEngineVideoSource
{
public:
// Some subclasses use an index to track multiple instances.
explicit MediaEngineCameraVideoSource(int aIndex,
const char* aMonitorName = "Camera.Monitor")
: MediaEngineVideoSource(kReleased)
@ -33,11 +32,12 @@ public:
, mHeight(0)
, mInitDone(false)
, mHasDirectListeners(false)
, mNrAllocations(0)
, mCaptureIndex(aIndex)
, mTrackID(0)
{}
explicit MediaEngineCameraVideoSource(const char* aMonitorName = "Camera.Monitor")
: MediaEngineCameraVideoSource(0, aMonitorName) {}
void GetName(nsAString& aName) const override;
void GetUUID(nsACString& aUUID) const override;
@ -114,7 +114,6 @@ protected:
bool mInitDone;
bool mHasDirectListeners;
int mNrAllocations; // When this becomes 0, we shut down HW
int mCaptureIndex;
TrackID mTrackID;

View File

@ -32,18 +32,13 @@ namespace mozilla {
using namespace mozilla::gfx;
// Enable the testing flag fakeTracks and fake in MediaStreamConstraints, will
// return you a MediaStream with additional fake video tracks and audio tracks.
static const int kFakeVideoTrackCount = 2;
static const int kFakeAudioTrackCount = 3;
NS_IMPL_ISUPPORTS(MediaEngineDefaultVideoSource, nsITimerCallback)
/**
* Default video source.
*/
MediaEngineDefaultVideoSource::MediaEngineDefaultVideoSource()
: MediaEngineVideoSource(kReleased)
: MediaEngineCameraVideoSource("FakeVideo.Monitor")
, mTimer(nullptr)
, mMonitor("Fake video")
, mCb(16), mCr(16)
@ -89,29 +84,34 @@ MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConst
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint)
{
if (mState != kReleased) {
return NS_ERROR_FAILURE;
}
FlattenedConstraints c(aConstraints);
// Mock failure for automated tests.
if (aConstraints.mDeviceId.IsString() &&
aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) {
if (c.mDeviceId.mIdeal.find(NS_LITERAL_STRING("bad device")) !=
c.mDeviceId.mIdeal.end()) {
return NS_ERROR_FAILURE;
}
mOpts = aPrefs;
mOpts.mWidth = mOpts.mWidth ? mOpts.mWidth : MediaEngine::DEFAULT_43_VIDEO_WIDTH;
mOpts.mHeight = mOpts.mHeight ? mOpts.mHeight : MediaEngine::DEFAULT_43_VIDEO_HEIGHT;
mOpts.mWidth = c.mWidth.Get(aPrefs.mWidth ? aPrefs.mWidth :
MediaEngine::DEFAULT_43_VIDEO_WIDTH);
mOpts.mHeight = c.mHeight.Get(aPrefs.mHeight ? aPrefs.mHeight :
MediaEngine::DEFAULT_43_VIDEO_HEIGHT);
mState = kAllocated;
aOutHandle = nullptr;
return NS_OK;
}
nsresult
MediaEngineDefaultVideoSource::Deallocate(BaseAllocationHandle* aHandle)
MediaEngineDefaultVideoSource::Deallocate(AllocationHandle* aHandle)
{
MOZ_ASSERT(!aHandle);
if (mState != kStopped && mState != kAllocated) {
@ -170,12 +170,6 @@ MediaEngineDefaultVideoSource::Start(SourceMediaStream* aStream, TrackID aID,
aStream->AddTrack(aID, 0, new VideoSegment(), SourceMediaStream::ADDTRACK_QUEUED);
if (mHasFakeTracks) {
for (int i = 0; i < kFakeVideoTrackCount; ++i) {
aStream->AddTrack(kTrackCount + i, 0, new VideoSegment(), SourceMediaStream::ADDTRACK_QUEUED);
}
}
// Remember TrackID so we can end it later
mTrackID = aID;
@ -205,11 +199,6 @@ MediaEngineDefaultVideoSource::Stop(SourceMediaStream *aSource, TrackID aID)
mTimer = nullptr;
aSource->EndTrack(aID);
if (mHasFakeTracks) {
for (int i = 0; i < kFakeVideoTrackCount; ++i) {
aSource->EndTrack(kTrackCount + i);
}
}
mState = kStopped;
mImage = nullptr;
@ -218,7 +207,7 @@ MediaEngineDefaultVideoSource::Stop(SourceMediaStream *aSource, TrackID aID)
nsresult
MediaEngineDefaultVideoSource::Restart(
BaseAllocationHandle* aHandle,
AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
@ -309,14 +298,6 @@ MediaEngineDefaultVideoSource::NotifyPull(MediaStreamGraph* aGraph,
// This can fail if either a) we haven't added the track yet, or b)
// we've removed or finished the track.
aSource->AppendToTrack(aID, &segment);
// Generate null data for fake tracks.
if (mHasFakeTracks) {
for (int i = 0; i < kFakeVideoTrackCount; ++i) {
VideoSegment nullSegment;
nullSegment.AppendNullData(delta);
aSource->AppendToTrack(kTrackCount + i, &nullSegment);
}
}
}
}
@ -417,7 +398,7 @@ MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConst
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint)
{
if (mState != kReleased) {
@ -439,7 +420,7 @@ MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConst
}
nsresult
MediaEngineDefaultAudioSource::Deallocate(BaseAllocationHandle* aHandle)
MediaEngineDefaultAudioSource::Deallocate(AllocationHandle* aHandle)
{
MOZ_ASSERT(!aHandle);
if (mState != kStopped && mState != kAllocated) {
@ -473,15 +454,6 @@ MediaEngineDefaultAudioSource::Start(SourceMediaStream* aStream, TrackID aID,
AppendToSegment(*segment, mBufferSize);
mSource->AddAudioTrack(aID, AUDIO_RATE, 0, segment, SourceMediaStream::ADDTRACK_QUEUED);
if (mHasFakeTracks) {
for (int i = 0; i < kFakeAudioTrackCount; ++i) {
segment = new AudioSegment();
segment->AppendNullData(mBufferSize);
mSource->AddAudioTrack(kTrackCount + kFakeVideoTrackCount+i,
AUDIO_RATE, 0, segment, SourceMediaStream::ADDTRACK_QUEUED);
}
}
// Remember TrackID so we can finish later
mTrackID = aID;
@ -518,18 +490,13 @@ MediaEngineDefaultAudioSource::Stop(SourceMediaStream *aSource, TrackID aID)
mTimer = nullptr;
aSource->EndTrack(aID);
if (mHasFakeTracks) {
for (int i = 0; i < kFakeAudioTrackCount; ++i) {
aSource->EndTrack(kTrackCount + kFakeVideoTrackCount+i);
}
}
mState = kStopped;
return NS_OK;
}
nsresult
MediaEngineDefaultAudioSource::Restart(BaseAllocationHandle* aHandle,
MediaEngineDefaultAudioSource::Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
@ -569,14 +536,6 @@ MediaEngineDefaultAudioSource::Notify(nsITimer* aTimer)
AppendToSegment(segment, samplesToAppend);
mSource->AppendToTrack(mTrackID, &segment);
// Generate null data for fake tracks.
if (mHasFakeTracks) {
for (int i = 0; i < kFakeAudioTrackCount; ++i) {
AudioSegment nullSegment;
nullSegment.AppendNullData(samplesToAppend);
mSource->AppendToTrack(kTrackCount + kFakeVideoTrackCount+i, &nullSegment);
}
}
return NS_OK;
}
@ -594,7 +553,6 @@ MediaEngineDefault::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
// This no longer is possible since the resolution is being set in Allocate().
RefPtr<MediaEngineVideoSource> newSource = new MediaEngineDefaultVideoSource();
newSource->SetHasFakeTracks(mHasFakeTracks);
mVSources.AppendElement(newSource);
aVSources->AppendElement(newSource);
@ -620,7 +578,6 @@ MediaEngineDefault::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
if (aASources->Length() == 0) {
RefPtr<MediaEngineAudioSource> newSource =
new MediaEngineDefaultAudioSource();
newSource->SetHasFakeTracks(mHasFakeTracks);
mASources.AppendElement(newSource);
aASources->AppendElement(newSource);
}

View File

@ -18,6 +18,7 @@
#include "VideoSegment.h"
#include "AudioSegment.h"
#include "StreamTracks.h"
#include "MediaEngineCameraVideoSource.h"
#include "MediaStreamGraph.h"
#include "MediaTrackConstraints.h"
@ -33,14 +34,11 @@ class MediaEngineDefault;
* The default implementation of the MediaEngine interface.
*/
class MediaEngineDefaultVideoSource : public nsITimerCallback,
public MediaEngineVideoSource,
private MediaConstraintsHelper
public MediaEngineCameraVideoSource
{
public:
MediaEngineDefaultVideoSource();
void Shutdown() override {};
void GetName(nsAString&) const override;
void GetUUID(nsACString&) const override;
@ -48,12 +46,12 @@ public:
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint) override;
nsresult Deallocate(BaseAllocationHandle* aHandle) override;
nsresult Deallocate(AllocationHandle* aHandle) override;
nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
nsresult Stop(SourceMediaStream*, TrackID) override;
nsresult Restart(BaseAllocationHandle* aHandle,
nsresult Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
@ -108,14 +106,11 @@ protected:
class SineWaveGenerator;
class MediaEngineDefaultAudioSource : public nsITimerCallback,
public MediaEngineAudioSource,
private MediaConstraintsHelper
public MediaEngineAudioSource
{
public:
MediaEngineDefaultAudioSource();
void Shutdown() override {};
void GetName(nsAString&) const override;
void GetUUID(nsACString&) const override;
@ -123,12 +118,12 @@ public:
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint) override;
nsresult Deallocate(BaseAllocationHandle* aHandle) override;
nsresult Deallocate(AllocationHandle* aHandle) override;
nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
nsresult Stop(SourceMediaStream*, TrackID) override;
nsresult Restart(BaseAllocationHandle* aHandle,
nsresult Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
@ -197,11 +192,9 @@ protected:
class MediaEngineDefault : public MediaEngine
{
typedef MediaEngine Super;
public:
explicit MediaEngineDefault(bool aHasFakeTracks = false)
: mHasFakeTracks(aHasFakeTracks)
, mMutex("mozilla::MediaEngineDefault")
{}
explicit MediaEngineDefault() : mMutex("mozilla::MediaEngineDefault") {}
void EnumerateVideoDevices(dom::MediaSourceEnum,
nsTArray<RefPtr<MediaEngineVideoSource> >*) override;
@ -214,13 +207,8 @@ public:
mASources.Clear();
};
protected:
bool mHasFakeTracks;
private:
~MediaEngineDefault() {
Shutdown();
}
~MediaEngineDefault() {}
Mutex mMutex;
// protected with mMutex:

View File

@ -32,8 +32,7 @@ MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource(
dom::MediaSourceEnum aMediaSource, const char* aMonitorName)
: MediaEngineCameraVideoSource(aIndex, aMonitorName),
mMediaSource(aMediaSource),
mCapEngine(aCapEngine),
mInShutdown(false)
mCapEngine(aCapEngine)
{
MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other);
mSettings.mWidth.Construct(0);
@ -72,7 +71,7 @@ MediaEngineRemoteVideoSource::Shutdown()
if (!mInitDone) {
return;
}
mInShutdown = true;
Super::Shutdown();
if (mState == kStarted) {
SourceMediaStream *source;
bool empty;
@ -108,7 +107,7 @@ MediaEngineRemoteVideoSource::Allocate(
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint)
{
LOG((__PRETTY_FUNCTION__));
@ -119,10 +118,8 @@ MediaEngineRemoteVideoSource::Allocate(
return NS_ERROR_FAILURE;
}
RefPtr<AllocationHandle> handle = new AllocationHandle(aConstraints, aOrigin,
aPrefs, aDeviceId);
nsresult rv = UpdateNew(handle, aPrefs, aDeviceId, aOutBadConstraint);
nsresult rv = Super::Allocate(aConstraints, aPrefs, aDeviceId, aOrigin,
aOutHandle, aOutBadConstraint);
if (NS_FAILED(rv)) {
return rv;
}
@ -136,35 +133,18 @@ MediaEngineRemoteVideoSource::Allocate(
LOG(("Video device %d allocated shared", mCaptureIndex));
}
}
mRegisteredHandles.AppendElement(handle);
++mNrAllocations;
handle.forget(aOutHandle);
return NS_OK;
}
nsresult
MediaEngineRemoteVideoSource::Deallocate(BaseAllocationHandle* aHandle)
MediaEngineRemoteVideoSource::Deallocate(AllocationHandle* aHandle)
{
LOG((__PRETTY_FUNCTION__));
AssertIsOnOwningThread();
MOZ_ASSERT(aHandle);
RefPtr<AllocationHandle> handle = static_cast<AllocationHandle*>(aHandle);
class Comparator {
public:
static bool Equals(const RefPtr<AllocationHandle>& a,
const RefPtr<AllocationHandle>& b) {
return a.get() == b.get();
}
};
MOZ_ASSERT(mRegisteredHandles.Contains(handle, Comparator()));
mRegisteredHandles.RemoveElementAt(mRegisteredHandles.IndexOf(handle, 0,
Comparator()));
--mNrAllocations;
MOZ_ASSERT(mNrAllocations >= 0, "Double-deallocations are prohibited");
Super::Deallocate(aHandle);
if (mNrAllocations == 0) {
MOZ_ASSERT(!mRegisteredHandles.Length());
if (!mRegisteredHandles.Length()) {
if (mState != kStopped && mState != kAllocated) {
return NS_ERROR_FAILURE;
}
@ -175,13 +155,6 @@ MediaEngineRemoteVideoSource::Deallocate(BaseAllocationHandle* aHandle)
LOG(("Video device %d deallocated", mCaptureIndex));
} else {
LOG(("Video device %d deallocated but still in use", mCaptureIndex));
MOZ_ASSERT(mRegisteredHandles.Length());
if (!mInShutdown) {
// Whenever constraints are removed, other parties may get closer to ideal.
auto& first = mRegisteredHandles[0];
const char* badConstraint = nullptr;
return UpdateRemove(first->mPrefs, first->mDeviceId, &badConstraint);
}
}
return NS_OK;
}
@ -267,7 +240,7 @@ MediaEngineRemoteVideoSource::Stop(mozilla::SourceMediaStream* aSource,
}
nsresult
MediaEngineRemoteVideoSource::Restart(BaseAllocationHandle* aHandle,
MediaEngineRemoteVideoSource::Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
@ -280,49 +253,26 @@ MediaEngineRemoteVideoSource::Restart(BaseAllocationHandle* aHandle,
}
MOZ_ASSERT(aHandle);
NormalizedConstraints constraints(aConstraints);
return UpdateExisting(static_cast<AllocationHandle*>(aHandle), &constraints,
aPrefs, aDeviceId, aOutBadConstraint);
return ReevaluateAllocation(aHandle, &constraints, aPrefs, aDeviceId,
aOutBadConstraint);
}
nsresult
MediaEngineRemoteVideoSource::UpdateExisting(AllocationHandle* aHandle,
NormalizedConstraints* aNewConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint)
MediaEngineRemoteVideoSource::UpdateSingleSource(
const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint)
{
// aHandle and/or aNewConstraints may be nullptr
AutoTArray<const NormalizedConstraints*, 10> allConstraints;
for (auto& registered : mRegisteredHandles) {
if (aNewConstraints && registered.get() == aHandle) {
continue; // Don't count old constraints
}
allConstraints.AppendElement(&registered->mConstraints);
}
if (aNewConstraints) {
allConstraints.AppendElement(aNewConstraints);
} else if (aHandle) {
// In the case of UpdateNew, the handle isn't registered yet.
allConstraints.AppendElement(&aHandle->mConstraints);
}
NormalizedConstraints netConstraints(allConstraints);
if (netConstraints.mBadConstraint) {
*aOutBadConstraint = netConstraints.mBadConstraint;
return NS_ERROR_FAILURE;
}
if (!ChooseCapability(netConstraints, aPrefs, aDeviceId)) {
*aOutBadConstraint = FindBadConstraint(netConstraints, *this, aDeviceId);
if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId)) {
*aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
return NS_ERROR_FAILURE;
}
switch (mState) {
case kReleased:
MOZ_ASSERT(aHandle);
MOZ_ASSERT(!aNewConstraints);
MOZ_ASSERT(!mRegisteredHandles.Length());
if (camera::GetChildAndCall(&camera::CamerasChild::AllocateCaptureDevice,
mCapEngine, GetUUID().get(),
kMaxUniqueIdLength, mCaptureIndex,
@ -354,9 +304,6 @@ MediaEngineRemoteVideoSource::UpdateExisting(AllocationHandle* aHandle,
(aHandle? aHandle->mOrigin.get() : ""), mState));
break;
}
if (aHandle && aNewConstraints) {
aHandle->mConstraints = *aNewConstraints;
}
return NS_OK;
}

View File

@ -50,6 +50,7 @@ namespace mozilla {
class MediaEngineRemoteVideoSource : public MediaEngineCameraVideoSource,
public webrtc::ExternalRenderer
{
typedef MediaEngineCameraVideoSource Super;
public:
NS_DECL_THREADSAFE_ISUPPORTS
@ -71,36 +72,16 @@ public:
dom::MediaSourceEnum aMediaSource,
const char* aMonitorName = "RemoteVideo.Monitor");
class AllocationHandle : public BaseAllocationHandle
{
public:
AllocationHandle(const dom::MediaTrackConstraints& aConstraints,
const nsACString& aOrigin,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId)
: mConstraints(aConstraints),
mOrigin(aOrigin),
mPrefs(aPrefs),
mDeviceId(aDeviceId) {}
private:
~AllocationHandle() override {}
public:
NormalizedConstraints mConstraints;
nsCString mOrigin;
MediaEnginePrefs mPrefs;
nsString mDeviceId;
};
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint) override;
nsresult Deallocate(BaseAllocationHandle* aHandle) override;
nsresult Deallocate(AllocationHandle* aHandle) override;
nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
nsresult Stop(SourceMediaStream*, TrackID) override;
nsresult Restart(BaseAllocationHandle* aHandle,
nsresult Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
@ -123,7 +104,7 @@ public:
void Shutdown() override;
protected:
~MediaEngineRemoteVideoSource() { Shutdown(); }
~MediaEngineRemoteVideoSource() { }
private:
// Initialize the needed Video engine interfaces.
@ -132,46 +113,18 @@ private:
void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) const override;
void SetLastCapability(const webrtc::CaptureCapability& aCapability);
/* UpdateExisting - Centralized function to apply constraints and restart
* device as needed, considering all allocations and changes to one.
*
* aHandle - New or existing handle, or null to update after removal.
* aNewConstraints - Constraints to be applied to existing handle, or null.
* aPrefs - As passed in (in case of changes in about:config).
* aDeviceId - As passed in (origin dependent).
* aOutBadConstraint - Result: nonzero if failed to apply. Name of culprit.
*/
nsresult
UpdateExisting(AllocationHandle* aHandle,
NormalizedConstraints* aNewConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint);
nsresult
UpdateNew(AllocationHandle* aHandle,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) {
return UpdateExisting(aHandle, nullptr, aPrefs, aDeviceId, aOutBadConstraint);
}
nsresult
UpdateRemove(const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) {
return UpdateExisting(nullptr, nullptr, aPrefs, aDeviceId, aOutBadConstraint);
}
UpdateSingleSource(const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) override;
dom::MediaSourceEnum mMediaSource; // source of media (camera | application | screen)
mozilla::camera::CaptureEngine mCapEngine;
nsTArray<RefPtr<AllocationHandle>> mRegisteredHandles;
// To only restart camera when needed, we keep track previous settings.
webrtc::CaptureCapability mLastCapability;
bool mInShutdown;
};
}

View File

@ -140,7 +140,7 @@ MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstrain
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint)
{
// windowId is not a proper constraint, so just read it.
@ -153,7 +153,7 @@ MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstrain
}
nsresult
MediaEngineTabVideoSource::Restart(BaseAllocationHandle* aHandle,
MediaEngineTabVideoSource::Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const mozilla::MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
@ -184,7 +184,7 @@ MediaEngineTabVideoSource::Restart(BaseAllocationHandle* aHandle,
}
nsresult
MediaEngineTabVideoSource::Deallocate(BaseAllocationHandle* aHandle)
MediaEngineTabVideoSource::Deallocate(AllocationHandle* aHandle)
{
MOZ_ASSERT(!aHandle);
return NS_OK;

View File

@ -19,21 +19,20 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
NS_DECL_NSITIMERCALLBACK
MediaEngineTabVideoSource();
void Shutdown() override {};
void GetName(nsAString_internal&) const override;
void GetUUID(nsACString_internal&) const override;
nsresult Allocate(const dom::MediaTrackConstraints &,
const mozilla::MediaEnginePrefs&,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint) override;
nsresult Deallocate(BaseAllocationHandle* aHandle) override;
nsresult Deallocate(AllocationHandle* aHandle) override;
nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID, const mozilla::PrincipalHandle&) override;
void SetDirectListeners(bool aHasDirectListeners) override {};
void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime, const mozilla::PrincipalHandle& aPrincipalHandle) override;
nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID) override;
nsresult Restart(BaseAllocationHandle* aHandle,
nsresult Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const mozilla::MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,

View File

@ -78,28 +78,24 @@ public:
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
AllocationHandle** aOutHandle,
const char** aOutBadConstraint) override
{
// Nothing to do here, everything is managed in MediaManager.cpp
aOutHandle = nullptr;
return NS_OK;
}
nsresult Deallocate(BaseAllocationHandle* aHandle) override
nsresult Deallocate(AllocationHandle* aHandle) override
{
// Nothing to do here, everything is managed in MediaManager.cpp
MOZ_ASSERT(!aHandle);
return NS_OK;
}
void Shutdown() override
{
// Nothing to do here, everything is managed in MediaManager.cpp
}
nsresult Start(SourceMediaStream* aMediaStream,
TrackID aId,
const PrincipalHandle& aPrincipalHandle) override;
nsresult Stop(SourceMediaStream* aMediaStream, TrackID aId) override;
nsresult Restart(BaseAllocationHandle* aHandle,
nsresult Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
@ -139,7 +135,7 @@ public:
const nsString& aDeviceId) const override;
protected:
virtual ~MediaEngineWebRTCAudioCaptureSource() { Shutdown(); }
virtual ~MediaEngineWebRTCAudioCaptureSource() {}
nsCString mUUID;
};
@ -419,53 +415,26 @@ private:
};
class MediaEngineWebRTCMicrophoneSource : public MediaEngineAudioSource,
public webrtc::VoEMediaProcess,
private MediaConstraintsHelper
public webrtc::VoEMediaProcess
{
typedef MediaEngineAudioSource Super;
public:
MediaEngineWebRTCMicrophoneSource(nsIThread* aThread,
webrtc::VoiceEngine* aVoiceEnginePtr,
mozilla::AudioInput* aAudioInput,
int aIndex,
const char* name,
const char* uuid)
: MediaEngineAudioSource(kReleased)
, mVoiceEngine(aVoiceEnginePtr)
, mAudioInput(aAudioInput)
, mMonitor("WebRTCMic.Monitor")
, mThread(aThread)
, mCapIndex(aIndex)
, mChannel(-1)
, mNrAllocations(0)
, mStarted(false)
, mSampleFrequency(MediaEngine::DEFAULT_SAMPLE_RATE)
, mPlayoutDelay(0)
, mNullTransport(nullptr)
, mSkipProcessing(false)
{
MOZ_ASSERT(aVoiceEnginePtr);
MOZ_ASSERT(aAudioInput);
mDeviceName.Assign(NS_ConvertUTF8toUTF16(name));
mDeviceUUID.Assign(uuid);
mListener = new mozilla::WebRTCAudioDataListener(this);
// We'll init lazily as needed
}
const char* uuid);
void GetName(nsAString& aName) const override;
void GetUUID(nsACString& aUUID) const override;
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
const char** aOutBadConstraint) override;
nsresult Deallocate(BaseAllocationHandle* aHandle) override;
nsresult Deallocate(AllocationHandle* aHandle) override;
nsresult Start(SourceMediaStream* aStream,
TrackID aID,
const PrincipalHandle& aPrincipalHandle) override;
nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
nsresult Restart(BaseAllocationHandle* aHandle,
nsresult Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
@ -515,11 +484,18 @@ public:
NS_DECL_THREADSAFE_ISUPPORTS
protected:
~MediaEngineWebRTCMicrophoneSource() {
Shutdown();
}
~MediaEngineWebRTCMicrophoneSource() {}
private:
nsresult
UpdateSingleSource(const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) override;
void SetLastPrefs(const MediaEnginePrefs& aPrefs);
// These allocate/configure and release the channel
bool AllocChannel();
void FreeChannel();
@ -570,7 +546,6 @@ private:
nsCOMPtr<nsIThread> mThread;
int mCapIndex;
int mChannel;
int mNrAllocations; // Per-channel - When this becomes 0, we shut down HW for the channel
TrackID mTrackID;
bool mStarted;
@ -587,10 +562,14 @@ private:
// because of prefs or constraints. This allows simply copying the audio into
// the MSG, skipping resampling and the whole webrtc.org code.
bool mSkipProcessing;
// To only update microphone when needed, we keep track of previous settings.
MediaEnginePrefs mLastPrefs;
};
class MediaEngineWebRTC : public MediaEngine
{
typedef MediaEngine Super;
public:
explicit MediaEngineWebRTC(MediaEnginePrefs& aPrefs);
@ -607,7 +586,6 @@ public:
nsTArray<RefPtr<MediaEngineAudioSource>>*) override;
private:
~MediaEngineWebRTC() {
Shutdown();
#if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
AsyncLatencyLogger::Get()->Release();
#endif

View File

@ -183,6 +183,37 @@ AudioOutputObserver::InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aFrame
}
}
MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource(
nsIThread* aThread,
webrtc::VoiceEngine* aVoiceEnginePtr,
mozilla::AudioInput* aAudioInput,
int aIndex,
const char* name,
const char* uuid)
: MediaEngineAudioSource(kReleased)
, mVoiceEngine(aVoiceEnginePtr)
, mAudioInput(aAudioInput)
, mMonitor("WebRTCMic.Monitor")
, mThread(aThread)
, mCapIndex(aIndex)
, mChannel(-1)
, mStarted(false)
, mSampleFrequency(MediaEngine::DEFAULT_SAMPLE_RATE)
, mPlayoutDelay(0)
, mNullTransport(nullptr)
, mSkipProcessing(false)
{
MOZ_ASSERT(aVoiceEnginePtr);
MOZ_ASSERT(aAudioInput);
mDeviceName.Assign(NS_ConvertUTF8toUTF16(name));
mDeviceUUID.Assign(uuid);
mListener = new mozilla::WebRTCAudioDataListener(this);
mSettings.mEchoCancellation.Construct(0);
mSettings.mMozAutoGainControl.Construct(0);
mSettings.mMozNoiseSuppression.Construct(0);
// We'll init lazily as needed
}
void
MediaEngineWebRTCMicrophoneSource::GetName(nsAString& aName) const
{
@ -219,109 +250,152 @@ uint32_t MediaEngineWebRTCMicrophoneSource::GetBestFitnessDistance(
}
nsresult
MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const nsACString& aOrigin,
BaseAllocationHandle** aOutHandle,
const char** aOutBadConstraint)
{
AssertIsOnOwningThread();
if (mState == kReleased) {
if (sChannelsOpen == 0) {
if (!InitEngine()) {
LOG(("Audio engine is not initalized"));
return NS_ERROR_FAILURE;
}
}
if (!AllocChannel()) {
if (sChannelsOpen == 0) {
DeInitEngine();
}
LOG(("Audio device is not initalized"));
return NS_ERROR_FAILURE;
}
if (mAudioInput->SetRecordingDevice(mCapIndex)) {
FreeChannel();
if (sChannelsOpen == 0) {
DeInitEngine();
}
return NS_ERROR_FAILURE;
}
sChannelsOpen++;
mState = kAllocated;
LOG(("Audio device %d allocated", mCapIndex));
} else if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
MonitorAutoLock lock(mMonitor);
if (mSources.IsEmpty()) {
LOG(("Audio device %d reallocated", mCapIndex));
} else {
LOG(("Audio device %d allocated shared", mCapIndex));
}
}
++mNrAllocations;
aOutHandle = nullptr;
return Restart(nullptr, aConstraints, aPrefs, aDeviceId, aOutBadConstraint);
}
nsresult
MediaEngineWebRTCMicrophoneSource::Restart(BaseAllocationHandle* aHandle,
MediaEngineWebRTCMicrophoneSource::Restart(AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint)
{
MOZ_ASSERT(!aHandle);
FlattenedConstraints c(aConstraints);
AssertIsOnOwningThread();
MOZ_ASSERT(aHandle);
NormalizedConstraints constraints(aConstraints);
return ReevaluateAllocation(aHandle, &constraints, aPrefs, aDeviceId,
aOutBadConstraint);
}
bool aec_on = c.mEchoCancellation.Get(aPrefs.mAecOn);
bool agc_on = c.mMozAutoGainControl.Get(aPrefs.mAgcOn);
bool noise_on = c.mMozNoiseSuppression.Get(aPrefs.mNoiseOn);
bool operator == (const MediaEnginePrefs& a, const MediaEnginePrefs& b)
{
return !memcmp(&a, &b, sizeof(MediaEnginePrefs));
};
nsresult
MediaEngineWebRTCMicrophoneSource::UpdateSingleSource(
const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint)
{
FlattenedConstraints c(aNetConstraints);
MediaEnginePrefs prefs = aPrefs;
prefs.mAecOn = c.mEchoCancellation.Get(prefs.mAecOn);
prefs.mAgcOn = c.mMozAutoGainControl.Get(prefs.mAgcOn);
prefs.mNoiseOn = c.mMozNoiseSuppression.Get(prefs.mNoiseOn);
LOG(("Audio config: aec: %d, agc: %d, noise: %d, delay: %d",
aec_on ? aPrefs.mAec : -1,
agc_on ? aPrefs.mAgc : -1,
noise_on ? aPrefs.mNoise : -1,
aPrefs.mPlayoutDelay));
prefs.mAecOn ? prefs.mAec : -1,
prefs.mAgcOn ? prefs.mAgc : -1,
prefs.mNoiseOn ? prefs.mNoise : -1,
prefs.mPlayoutDelay));
mPlayoutDelay = aPrefs.mPlayoutDelay;
mPlayoutDelay = prefs.mPlayoutDelay;
switch (mState) {
case kReleased:
MOZ_ASSERT(aHandle);
if (sChannelsOpen == 0) {
if (!InitEngine()) {
LOG(("Audio engine is not initalized"));
return NS_ERROR_FAILURE;
}
}
if (!AllocChannel()) {
if (sChannelsOpen == 0) {
DeInitEngine();
}
LOG(("Audio device is not initalized"));
return NS_ERROR_FAILURE;
}
if (mAudioInput->SetRecordingDevice(mCapIndex)) {
FreeChannel();
if (sChannelsOpen == 0) {
DeInitEngine();
}
return NS_ERROR_FAILURE;
}
sChannelsOpen++;
mState = kAllocated;
LOG(("Audio device %d allocated", mCapIndex));
break;
case kStarted:
if (prefs == mLastPrefs) {
return NS_OK;
}
if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
MonitorAutoLock lock(mMonitor);
if (mSources.IsEmpty()) {
LOG(("Audio device %d reallocated", mCapIndex));
} else {
LOG(("Audio device %d allocated shared", mCapIndex));
}
}
break;
default:
LOG(("Audio device %d %s in ignored state %d", mCapIndex,
(aHandle? aHandle->mOrigin.get() : ""), mState));
break;
}
if (sChannelsOpen > 0) {
int error;
if (0 != (error = mVoEProcessing->SetEcStatus(aec_on, (webrtc::EcModes) aPrefs.mAec))) {
error = mVoEProcessing->SetEcStatus(prefs.mAecOn, (webrtc::EcModes)prefs.mAec);
if (error) {
LOG(("%s Error setting Echo Status: %d ",__FUNCTION__, error));
// Overhead of capturing all the time is very low (<0.1% of an audio only call)
if (aec_on) {
if (0 != (error = mVoEProcessing->SetEcMetricsStatus(true))) {
if (prefs.mAecOn) {
error = mVoEProcessing->SetEcMetricsStatus(true);
if (error) {
LOG(("%s Error setting Echo Metrics: %d ",__FUNCTION__, error));
}
}
}
if (0 != (error = mVoEProcessing->SetAgcStatus(agc_on, (webrtc::AgcModes) aPrefs.mAgc))) {
error = mVoEProcessing->SetAgcStatus(prefs.mAgcOn, (webrtc::AgcModes)prefs.mAgc);
if (error) {
LOG(("%s Error setting AGC Status: %d ",__FUNCTION__, error));
}
if (0 != (error = mVoEProcessing->SetNsStatus(noise_on, (webrtc::NsModes) aPrefs.mNoise))) {
error = mVoEProcessing->SetNsStatus(prefs.mNoiseOn, (webrtc::NsModes)prefs.mNoise);
if (error) {
LOG(("%s Error setting NoiseSuppression Status: %d ",__FUNCTION__, error));
}
}
mSkipProcessing = !(aec_on || agc_on || noise_on);
mSkipProcessing = !(prefs.mAecOn || prefs.mAgcOn || prefs.mNoiseOn);
if (mSkipProcessing) {
mSampleFrequency = MediaEngine::USE_GRAPH_RATE;
}
SetLastPrefs(prefs);
return NS_OK;
}
void
MediaEngineWebRTCMicrophoneSource::SetLastPrefs(
const MediaEnginePrefs& aPrefs)
{
mLastPrefs = aPrefs;
RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
NS_DispatchToMainThread(media::NewRunnableFrom([this, that, aPrefs]() mutable {
mSettings.mEchoCancellation.Value() = aPrefs.mAecOn;
mSettings.mMozAutoGainControl.Value() = aPrefs.mAgcOn;
mSettings.mMozNoiseSuppression.Value() = aPrefs.mNoiseOn;
return NS_OK;
}));
}
nsresult
MediaEngineWebRTCMicrophoneSource::Deallocate(BaseAllocationHandle* aHandle)
MediaEngineWebRTCMicrophoneSource::Deallocate(AllocationHandle* aHandle)
{
AssertIsOnOwningThread();
MOZ_ASSERT(!aHandle);
--mNrAllocations;
MOZ_ASSERT(mNrAllocations >= 0, "Double-deallocations are prohibited");
if (mNrAllocations == 0) {
Super::Deallocate(aHandle);
if (!mRegisteredHandles.Length()) {
// If empty, no callbacks to deliver data should be occuring
if (mState != kStopped && mState != kAllocated) {
return NS_ERROR_FAILURE;
@ -714,6 +788,7 @@ MediaEngineWebRTCMicrophoneSource::FreeChannel()
void
MediaEngineWebRTCMicrophoneSource::Shutdown()
{
Super::Shutdown();
if (mListener) {
// breaks a cycle, since the WebRTCAudioDataListener has a RefPtr to us
mListener->Shutdown();
@ -739,7 +814,7 @@ MediaEngineWebRTCMicrophoneSource::Shutdown()
MOZ_ASSERT(mState == kStopped);
}
while (mNrAllocations) {
while (mRegisteredHandles.Length()) {
MOZ_ASSERT(mState == kAllocated || mState == kStopped);
Deallocate(nullptr); // XXX Extend concurrent constraints code to mics.
}
@ -847,7 +922,7 @@ MediaEngineWebRTCAudioCaptureSource::Stop(SourceMediaStream *aMediaStream,
nsresult
MediaEngineWebRTCAudioCaptureSource::Restart(
BaseAllocationHandle* aHandle,
AllocationHandle* aHandle,
const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,

View File

@ -4,6 +4,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MediaTrackConstraints.h"
#include "mozilla/dom/MediaStreamTrackBinding.h"
#include <limits>
#include <algorithm>
@ -144,7 +145,7 @@ NormalizedConstraintSet::BooleanRange::BooleanRange(
mIdeal.emplace(aOther.GetAsBoolean());
}
} else {
const ConstrainBooleanParameters& r = aOther.GetAsConstrainBooleanParameters();
const dom::ConstrainBooleanParameters& r = aOther.GetAsConstrainBooleanParameters();
if (r.mIdeal.WasPassed()) {
mIdeal.emplace(r.mIdeal.Value());
}
@ -188,7 +189,7 @@ NormalizedConstraintSet::StringRange::StringRange(
void
NormalizedConstraintSet::StringRange::SetFrom(
const ConstrainDOMStringParameters& aOther)
const dom::ConstrainDOMStringParameters& aOther)
{
if (aOther.mIdeal.WasPassed()) {
mIdeal.clear();
@ -295,7 +296,7 @@ NormalizedConstraints::NormalizedConstraints(
{
// Create a list of member pointers.
nsTArray<MemberPtrType> list;
NormalizedConstraints dummy(MediaTrackConstraints(), &list);
NormalizedConstraints dummy(dom::MediaTrackConstraints(), &list);
// Do intersection of all required constraints, and average of ideals,

View File

@ -33,7 +33,6 @@ if CONFIG['MOZ_WEBRTC']:
'MediaEngineRemoteVideoSource.cpp',
'MediaEngineTabVideoSource.cpp',
'MediaEngineWebRTCAudio.cpp',
'MediaTrackConstraints.cpp',
'RTCCertificate.cpp',
'RTCIdentityProviderRegistrar.cpp',
]
@ -66,6 +65,7 @@ XPIDL_SOURCES += [
UNIFIED_SOURCES += [
'MediaEngineDefault.cpp',
'MediaTrackConstraints.cpp',
'PeerIdentity.cpp',
]

View File

@ -20,10 +20,6 @@ dictionary MediaStreamConstraints {
boolean fake; // For testing purpose. Generates frames of solid
// colors if video is enabled, and sound of 1Khz sine
// wave if audio is enabled.
boolean fakeTracks; // For testing purpose, works only if fake is
// enabled. Enable fakeTracks returns a stream
// with two extra empty video tracks and three
// extra empty audio tracks.
DOMString? peerIdentity = null;
};

View File

@ -13,6 +13,9 @@ dictionary MediaTrackSettings {
double frameRate;
DOMString facingMode;
DOMString deviceId;
boolean echoCancellation;
boolean mozNoiseSuppression;
boolean mozAutoGainControl;
// Mozilla-specific extensions:

View File

@ -21,7 +21,9 @@ addEventListener('fetch', function(evt) {
evt.respondWith(registration.unregister().then(function() {
return new Response('service worker generated download', {
headers: {
'Content-Disposition': 'attachment; filename="fake_download.bin"'
'Content-Disposition': 'attachment; filename="fake_download.bin"',
// fake encoding header that should have no effect
'Content-Encoding': 'gzip',
}
});
}));

View File

@ -34,9 +34,12 @@ namespace image {
static LazyLogModule sPNGLog("PNGDecoder");
static LazyLogModule sPNGDecoderAccountingLog("PNGDecoderAccounting");
// Limit image dimensions (bug #251381, #591822, and #967656)
#ifndef MOZ_PNG_MAX_DIMENSION
# define MOZ_PNG_MAX_DIMENSION 32767
// limit image dimensions (bug #251381, #591822, #967656, and #1283961)
#ifndef MOZ_PNG_MAX_WIDTH
# define MOZ_PNG_MAX_WIDTH 0x7fffffff // Unlimited
#endif
#ifndef MOZ_PNG_MAX_HEIGHT
# define MOZ_PNG_MAX_HEIGHT 0x7fffffff // Unlimited
#endif
nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
@ -323,6 +326,7 @@ nsPNGDecoder::InitInternal()
#endif
#ifdef PNG_SET_USER_LIMITS_SUPPORTED
png_set_user_limits(mPNG, MOZ_PNG_MAX_WIDTH, MOZ_PNG_MAX_HEIGHT);
if (mCMSMode != eCMSMode_Off) {
png_set_chunk_malloc_max(mPNG, 4000000L);
}
@ -557,11 +561,6 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
&interlace_type, &compression_type, &filter_type);
// Are we too big?
if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) {
png_longjmp(decoder->mPNG, 1);
}
const IntRect frameRect(0, 0, width, height);
// Post our size to the superclass

View File

@ -990,7 +990,7 @@ function ArrayConcat(arg1) {
if (n + len > MAX_NUMERIC_INDEX)
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
if (IsPackedArray(E)) {
if (IsPackedArray(A) && IsPackedArray(E)) {
// Step 5.c.i, 5.c.iv, and 5.c.iv.5.
for (k = 0; k < len; k++) {
// Steps 5.c.iv.1-3.

View File

@ -1792,8 +1792,11 @@ InitDateTimeFormatClass(JSContext* cx, HandleObject Intl, Handle<GlobalObject*>
// is enabled, also add it.
if (cx->compartment()->creationOptions().experimentalDateTimeFormatFormatToPartsEnabled()) {
RootedValue ftp(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(),
cx->names().DateTimeFormatFormatToParts, &ftp))
HandlePropertyName name = cx->names().formatToParts;
if (!GlobalObject::getSelfHostedFunction(cx, cx->global(),
cx->names().DateTimeFormatFormatToParts,
name,
0, &ftp))
{
return nullptr;
}

View File

@ -1559,6 +1559,15 @@ js::RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto, uint8_t* resul
return true;
}
JSNative unicodeGetter;
if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().unicode), &unicodeGetter))
return false;
if (unicodeGetter != regexp_unicode) {
*result = false;
return true;
}
// Check if @@match, @@search, and exec are own data properties,
// those values should be tested in selfhosted JS.
bool has = false;

View File

@ -579,6 +579,7 @@ function IsRegExpSplitOptimizable(rx, C) {
// If RegExpPrototypeOptimizable succeeds, `RegExpProto.exec` is guaranteed
// to be a data property.
return RegExpPrototypeOptimizable(RegExpProto) &&
RegExpInstanceOptimizable(rx, RegExpProto) &&
RegExpProto.exec === RegExp_prototype_Exec;
}
@ -603,7 +604,8 @@ function RegExpSplit(string, limit) {
// Steps 6-7.
var unicodeMatching = callFunction(std_String_includes, flags, "u");
var optimizable = IsRegExpSplitOptimizable(rx, C);
var optimizable = IsRegExpSplitOptimizable(rx, C) &&
(limit === undefined || typeof limit == "number");
var splitter;
if (optimizable) {
// Steps 8-9 (skipped).

View File

@ -864,23 +864,7 @@ GCState(JSContext* cx, unsigned argc, Value* vp)
return false;
}
const char* state;
gc::State globalState = cx->runtime()->gc.state();
if (globalState == gc::NO_INCREMENTAL)
state = "none";
else if (globalState == gc::MARK)
state = "mark";
else if (globalState == gc::SWEEP)
state = "sweep";
else if (globalState == gc::FINALIZE)
state = "finalize";
else if (globalState == gc::COMPACT)
state = "compact";
else if (globalState == gc::DECOMMIT)
state = "decommit";
else
MOZ_CRASH("Unobserveable global GC state");
const char* state = StateName(cx->runtime()->gc.state());
JSString* str = JS_NewStringCopyZ(cx, state);
if (!str)
return false;

View File

@ -689,8 +689,8 @@ class GCRuntime
public:
// Internal public interface
State state() const { return incrementalState; }
bool isHeapCompacting() const { return state() == COMPACT; }
bool isForegroundSweeping() const { return state() == SWEEP; }
bool isHeapCompacting() const { return state() == State::Compact; }
bool isForegroundSweeping() const { return state() == State::Sweep; }
bool isBackgroundSweeping() { return helperState.isBackgroundSweeping(); }
void waitBackgroundSweepEnd() { helperState.waitBackgroundSweepEnd(); }
void waitBackgroundSweepOrAllocEnd() {
@ -766,7 +766,7 @@ class GCRuntime
void disallowIncrementalGC() { incrementalAllowed = false; }
bool isIncrementalGCEnabled() const { return mode == JSGC_MODE_INCREMENTAL && incrementalAllowed; }
bool isIncrementalGCInProgress() const { return state() != NO_INCREMENTAL; }
bool isIncrementalGCInProgress() const { return state() != State::NotActive; }
bool isGenerationalGCEnabled() const { return generationalDisabled == 0; }
void disableGenerationalGC();

View File

@ -183,28 +183,28 @@ IsShapeAllocKind(AllocKind kind)
// Returns a sequence for use in a range-based for loop,
// to iterate over all alloc kinds.
inline decltype(mozilla::MakeEnumeratedRange<int>(AllocKind::FIRST, AllocKind::LIMIT))
inline decltype(mozilla::MakeEnumeratedRange(AllocKind::FIRST, AllocKind::LIMIT))
AllAllocKinds()
{
return mozilla::MakeEnumeratedRange<int>(AllocKind::FIRST, AllocKind::LIMIT);
return mozilla::MakeEnumeratedRange(AllocKind::FIRST, AllocKind::LIMIT);
}
// Returns a sequence for use in a range-based for loop,
// to iterate over all object alloc kinds.
inline decltype(mozilla::MakeEnumeratedRange<int>(AllocKind::OBJECT_FIRST, AllocKind::OBJECT_LIMIT))
inline decltype(mozilla::MakeEnumeratedRange(AllocKind::OBJECT_FIRST, AllocKind::OBJECT_LIMIT))
ObjectAllocKinds()
{
return mozilla::MakeEnumeratedRange<int>(AllocKind::OBJECT_FIRST, AllocKind::OBJECT_LIMIT);
return mozilla::MakeEnumeratedRange(AllocKind::OBJECT_FIRST, AllocKind::OBJECT_LIMIT);
}
// Returns a sequence for use in a range-based for loop,
// to iterate over alloc kinds from |first| to |limit|, exclusive.
inline decltype(mozilla::MakeEnumeratedRange<int>(AllocKind::FIRST, AllocKind::LIMIT))
inline decltype(mozilla::MakeEnumeratedRange(AllocKind::FIRST, AllocKind::LIMIT))
SomeAllocKinds(AllocKind first = AllocKind::FIRST, AllocKind limit = AllocKind::LIMIT)
{
MOZ_ASSERT(IsAllocKind(first), "|first| is not a valid AllocKind!");
MOZ_ASSERT(IsAllocKind(limit), "|limit| is not a valid AllocKind!");
return mozilla::MakeEnumeratedRange<int>(first, limit);
return mozilla::MakeEnumeratedRange(first, limit);
}
// AllAllocKindArray<ValueType> gives an enumerated array of ValueTypes,

View File

@ -346,8 +346,8 @@ static void
AssertRootMarkingPhase(JSTracer* trc)
{
MOZ_ASSERT_IF(trc->isMarkingTracer(),
trc->runtime()->gc.state() == NO_INCREMENTAL ||
trc->runtime()->gc.state() == MARK_ROOTS);
trc->runtime()->gc.state() == State::NotActive ||
trc->runtime()->gc.state() == State::MarkRoots);
}
@ -1945,7 +1945,7 @@ bool
GCMarker::markDelayedChildren(SliceBudget& budget)
{
GCRuntime& gc = runtime()->gc;
gcstats::AutoPhase ap(gc.stats, gc.state() == MARK, gcstats::PHASE_MARK_DELAYED);
gcstats::AutoPhase ap(gc.stats, gc.state() == State::Mark, gcstats::PHASE_MARK_DELAYED);
MOZ_ASSERT(unmarkedArenaStackTop);
do {
@ -2439,7 +2439,7 @@ CheckIsMarkedThing(T* thingp)
JSRuntime* rt = (*thingp)->runtimeFromAnyThread();
MOZ_ASSERT_IF(!ThingIsPermanentAtomOrWellKnownSymbol(*thingp),
CurrentThreadCanAccessRuntime(rt) ||
(rt->isHeapCollecting() && rt->gc.state() == SWEEP));
(rt->isHeapCollecting() && rt->gc.state() == State::Sweep));
#endif
}

View File

@ -253,7 +253,7 @@ struct Statistics
double startTimestamp, size_t startFaults, gc::State initialState)
: budget(budget), reason(reason),
initialState(initialState),
finalState(gc::NO_INCREMENTAL),
finalState(gc::State::NotActive),
resetReason(nullptr),
start(start), startTimestamp(startTimestamp),
startFaults(startFaults)

View File

@ -203,7 +203,7 @@ gc::GCRuntime::startVerifyPreBarriers()
/* Create the root node. */
trc->curnode = MakeNode(trc, nullptr, JS::TraceKind(0));
incrementalState = MARK_ROOTS;
incrementalState = State::MarkRoots;
/* Make all the roots be edges emanating from the root node. */
markRuntime(trc, TraceRuntime, prep.session().lock);
@ -230,7 +230,7 @@ gc::GCRuntime::startVerifyPreBarriers()
}
verifyPreData = trc;
incrementalState = MARK;
incrementalState = State::Mark;
marker.start();
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
@ -244,7 +244,7 @@ gc::GCRuntime::startVerifyPreBarriers()
return;
oom:
incrementalState = NO_INCREMENTAL;
incrementalState = State::NotActive;
js_delete(trc);
verifyPreData = nullptr;
}
@ -342,7 +342,7 @@ gc::GCRuntime::endVerifyPreBarriers()
number++;
verifyPreData = nullptr;
incrementalState = NO_INCREMENTAL;
incrementalState = State::NotActive;
if (!compartmentCreated && IsIncrementalGCSafe(rt)) {
CheckEdgeTracer cetrc(rt);

View File

@ -286,13 +286,6 @@ GetCaseIndependentLetters(char16_t character,
for (size_t i = 0; i < choices_length; i++) {
char16_t c = choices[i];
// The standard requires that non-ASCII characters cannot have ASCII
// character codes in their equivalence class, even though this
// situation occurs multiple times in the unicode tables.
static const unsigned kMaxAsciiCharCode = 127;
if (!unicode && character > kMaxAsciiCharCode && c <= kMaxAsciiCharCode)
continue;
// Skip characters that can't appear in one byte strings.
if (!unicode && ascii_subject && c > kMaxOneByteCharCode)
continue;
@ -332,10 +325,40 @@ GetCaseIndependentLetters(char16_t character,
choices, ArrayLength(choices), letters);
}
char16_t upper = unicode::ToUpperCase(character);
unicode::CodepointsWithSameUpperCase others(character);
char16_t other1 = others.other1();
char16_t other2 = others.other2();
char16_t other3 = others.other3();
// ES 2017 draft 996af87b7072b3c3dd2b1def856c66f456102215 21.2.4.2
// step 3.g.
// The standard requires that non-ASCII characters cannot have ASCII
// character codes in their equivalence class, even though this
// situation occurs multiple times in the unicode tables.
static const unsigned kMaxAsciiCharCode = 127;
if (upper <= kMaxAsciiCharCode) {
if (character > kMaxAsciiCharCode) {
// If Canonicalize(character) == character, all other characters
// should be ignored.
return GetCaseIndependentLetters(character, ascii_subject, unicode,
&character, 1, letters);
}
if (other1 > kMaxAsciiCharCode)
other1 = character;
if (other2 > kMaxAsciiCharCode)
other2 = character;
if (other3 > kMaxAsciiCharCode)
other3 = character;
}
const char16_t choices[] = {
character,
unicode::ToLowerCase(character),
unicode::ToUpperCase(character)
upper,
other1,
other2,
other3
};
return GetCaseIndependentLetters(character, ascii_subject, unicode,
choices, ArrayLength(choices), letters);

View File

@ -17,12 +17,12 @@ var g = newGlobal();
// Start an off thread compilation that will not run until GC has finished
if ("gcstate" in this)
assertEq(gcstate(), "mark");
assertEq(gcstate(), "Mark");
g.offThreadCompileScript('23;', {});
// Wait for the compilation to finish, which must finish the GC first
assertEq(23, g.runOffThreadScript());
if ("gcstate" in this)
assertEq(gcstate(), "none");
assertEq(gcstate(), "NotActive");
print("done");

View File

@ -27,7 +27,7 @@ function testAbort(zoneCount, objectCount, sliceCount, abortState)
var didAbort = false;
startgc(sliceCount, "shrinking");
while (gcstate() !== "none") {
while (gcstate() !== "NotActive") {
var state = gcstate();
if (state == abortState) {
abortgc();
@ -38,7 +38,7 @@ function testAbort(zoneCount, objectCount, sliceCount, abortState)
gcslice(sliceCount);
}
assertEq(gcstate(), "none");
assertEq(gcstate(), "NotActive");
if (abortState)
assertEq(didAbort, true);
@ -47,6 +47,8 @@ function testAbort(zoneCount, objectCount, sliceCount, abortState)
gczeal(0);
testAbort(10, 10000, 10000);
testAbort(10, 10000, 10000, "mark");
testAbort(10, 10000, 10000, "sweep");
testAbort(10, 10000, 10000, "compact");
testAbort(10, 10000, 10000, "Mark");
testAbort(10, 10000, 10000, "Sweep");
testAbort(10, 10000, 10000, "Compact");
// Note: we do not yield automatically before Finalize or Decommit, as they yield internally.
// Thus, we may not witness an incremental state in this phase and cannot test it explicitly.

View File

@ -27,11 +27,11 @@ function testCompacting(zoneCount, objectCount, sliceCount)
}
// Finish any alloc-triggered incremental GC
if (gcstate() !== "none")
if (gcstate() !== "NotActive")
gc();
startgc(sliceCount, "shrinking");
while (gcstate() !== "none") {
while (gcstate() !== "NotActive") {
gcslice(sliceCount);
}

View File

@ -6,26 +6,26 @@ gczeal(0);
// Non-incremental GC.
gc();
assertEq(gcstate(), "none");
assertEq(gcstate(), "NotActive");
// Incremental GC in minimal slice. Note that finalization always uses zero-
// sized slices while background finalization is on-going, so we need to loop.
gcslice(1000000);
while (gcstate() == "finalize") { gcslice(1); }
while (gcstate() == "decommit") { gcslice(1); }
assertEq(gcstate(), "none");
while (gcstate() == "Finalize") { gcslice(1); }
while (gcstate() == "Decommit") { gcslice(1); }
assertEq(gcstate(), "NotActive");
// Incremental GC in multiple slices: if marking takes more than one slice,
// we yield before we start sweeping.
gczeal(0);
gcslice(1);
assertEq(gcstate(), "mark");
assertEq(gcstate(), "Mark");
gcslice(1000000);
assertEq(gcstate(), "mark");
assertEq(gcstate(), "Mark");
gcslice(1000000);
while (gcstate() == "finalize") { gcslice(1); }
while (gcstate() == "decommit") { gcslice(1); }
assertEq(gcstate(), "none");
while (gcstate() == "Finalize") { gcslice(1); }
while (gcstate() == "Decommit") { gcslice(1); }
assertEq(gcstate(), "NotActive");
// Zeal mode 8: Incremental GC in two main slices:
// 1) mark roots
@ -33,11 +33,11 @@ assertEq(gcstate(), "none");
// *) finalize.
gczeal(8, 0);
gcslice(1);
assertEq(gcstate(), "mark");
assertEq(gcstate(), "Mark");
gcslice(1);
while (gcstate() == "finalize") { gcslice(1); }
while (gcstate() == "decommit") { gcslice(1); }
assertEq(gcstate(), "none");
while (gcstate() == "Finalize") { gcslice(1); }
while (gcstate() == "Decommit") { gcslice(1); }
assertEq(gcstate(), "NotActive");
// Zeal mode 9: Incremental GC in two main slices:
// 1) mark roots and marking
@ -45,19 +45,19 @@ assertEq(gcstate(), "none");
// *) finalize.
gczeal(9, 0);
gcslice(1);
assertEq(gcstate(), "mark");
assertEq(gcstate(), "Mark");
gcslice(1);
while (gcstate() == "finalize") { gcslice(1); }
while (gcstate() == "decommit") { gcslice(1); }
assertEq(gcstate(), "none");
while (gcstate() == "Finalize") { gcslice(1); }
while (gcstate() == "Decommit") { gcslice(1); }
assertEq(gcstate(), "NotActive");
// Zeal mode 10: Incremental GC in multiple slices (always yeilds before
// sweeping). This test uses long slices to prove that this zeal mode yields
// in sweeping, where normal IGC (above) does not.
gczeal(10, 0);
gcslice(1000000);
assertEq(gcstate(), "sweep");
assertEq(gcstate(), "Sweep");
gcslice(1000000);
while (gcstate() == "finalize") { gcslice(1); }
while (gcstate() == "decommit") { gcslice(1); }
assertEq(gcstate(), "none");
while (gcstate() == "Finalize") { gcslice(1); }
while (gcstate() == "Decommit") { gcslice(1); }
assertEq(gcstate(), "NotActive");

View File

@ -1502,11 +1502,9 @@ Simulator::exclusiveMonitorClear()
}
int
Simulator::readW(int32_t addr, SimInstruction* instr)
Simulator::readW(int32_t addr, SimInstruction* instr, UnalignedPolicy f)
{
// The regexp engine emits unaligned loads, so we don't check for them here
// like most of the other methods do.
if ((addr & 3) == 0 || !HasAlignmentFault()) {
if ((addr & 3) == 0 || (f == AllowUnaligned && !HasAlignmentFault())) {
intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
return *ptr;
}
@ -1527,9 +1525,9 @@ Simulator::readW(int32_t addr, SimInstruction* instr)
}
void
Simulator::writeW(int32_t addr, int value, SimInstruction* instr)
Simulator::writeW(int32_t addr, int value, SimInstruction* instr, UnalignedPolicy f)
{
if ((addr & 3) == 0 || !HasAlignmentFault()) {
if ((addr & 3) == 0 || (f == AllowUnaligned && !HasAlignmentFault())) {
intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
*ptr = value;
return;
@ -1592,10 +1590,10 @@ Simulator::writeExW(int32_t addr, int value, SimInstruction* instr)
return 1;
int32_t old = compareExchangeRelaxed(ptr, expected, int32_t(value));
return old != expected;
} else {
printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr);
MOZ_CRASH();
}
printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr);
MOZ_CRASH();
}
uint16_t
@ -1607,6 +1605,15 @@ Simulator::readHU(int32_t addr, SimInstruction* instr)
uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
return *ptr;
}
// See comments in readW.
if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
char* ptr = reinterpret_cast<char*>(addr);
uint16_t value;
memcpy(&value, ptr, sizeof(value));
return value;
}
printf("Unaligned unsigned halfword read at 0x%08x, pc=%p\n", addr, instr);
MOZ_CRASH();
return 0;
@ -1619,6 +1626,15 @@ Simulator::readH(int32_t addr, SimInstruction* instr)
int16_t* ptr = reinterpret_cast<int16_t*>(addr);
return *ptr;
}
// See comments in readW.
if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
char* ptr = reinterpret_cast<char*>(addr);
int16_t value;
memcpy(&value, ptr, sizeof(value));
return value;
}
printf("Unaligned signed halfword read at 0x%08x\n", addr);
MOZ_CRASH();
return 0;
@ -1630,10 +1646,18 @@ Simulator::writeH(int32_t addr, uint16_t value, SimInstruction* instr)
if ((addr & 1) == 0 || !HasAlignmentFault()) {
uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
*ptr = value;
} else {
printf("Unaligned unsigned halfword write at 0x%08x, pc=%p\n", addr, instr);
MOZ_CRASH();
return;
}
// See the comments above in readW.
if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
char* ptr = reinterpret_cast<char*>(addr);
memcpy(ptr, &value, sizeof(value));
return;
}
printf("Unaligned unsigned halfword write at 0x%08x, pc=%p\n", addr, instr);
MOZ_CRASH();
}
void
@ -1642,10 +1666,18 @@ Simulator::writeH(int32_t addr, int16_t value, SimInstruction* instr)
if ((addr & 1) == 0 || !HasAlignmentFault()) {
int16_t* ptr = reinterpret_cast<int16_t*>(addr);
*ptr = value;
} else {
printf("Unaligned halfword write at 0x%08x, pc=%p\n", addr, instr);
MOZ_CRASH();
return;
}
// See the comments above in readW.
if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
char* ptr = reinterpret_cast<char*>(addr);
memcpy(ptr, &value, sizeof(value));
return;
}
printf("Unaligned halfword write at 0x%08x, pc=%p\n", addr, instr);
MOZ_CRASH();
}
uint16_t
@ -3194,9 +3226,9 @@ Simulator::decodeType2(SimInstruction* instr)
}
} else {
if (instr->hasL())
set_register(rd, readW(addr, instr));
set_register(rd, readW(addr, instr, AllowUnaligned));
else
writeW(addr, get_register(rd), instr);
writeW(addr, get_register(rd), instr, AllowUnaligned);
}
}
@ -3461,9 +3493,9 @@ Simulator::decodeType3(SimInstruction* instr)
}
} else {
if (instr->hasL())
set_register(rd, readW(addr, instr));
set_register(rd, readW(addr, instr, AllowUnaligned));
else
writeW(addr, get_register(rd), instr);
writeW(addr, get_register(rd), instr, AllowUnaligned);
}
}
@ -4288,8 +4320,10 @@ Simulator::decodeSpecialCondition(SimInstruction* instr)
while (r < regs) {
uint32_t data[2];
get_d_register(Vd + r, data);
writeW(address, data[0], instr);
writeW(address + 4, data[1], instr);
// TODO: We should AllowUnaligned here only if the alignment attribute of
// the instruction calls for default alignment.
writeW(address, data[0], instr, AllowUnaligned);
writeW(address + 4, data[1], instr, AllowUnaligned);
address += 8;
r++;
}
@ -4327,8 +4361,10 @@ Simulator::decodeSpecialCondition(SimInstruction* instr)
int r = 0;
while (r < regs) {
uint32_t data[2];
data[0] = readW(address, instr);
data[1] = readW(address + 4, instr);
// TODO: We should AllowUnaligned here only if the alignment attribute of
// the instruction calls for default alignment.
data[0] = readW(address, instr, AllowUnaligned);
data[1] = readW(address + 4, instr, AllowUnaligned);
set_d_register(Vd + r, data);
address += 8;
r++;

View File

@ -204,6 +204,20 @@ class Simulator
end_sim_pc = -2
};
// ForbidUnaligned means "always fault on unaligned access".
//
// AllowUnaligned means "allow the unaligned access if other conditions are
// met". The "other conditions" vary with the instruction: For all
// instructions the base condition is !HasAlignmentFault(), ie, the chip is
// configured to allow unaligned accesses. For instructions like VLD1
// there is an additional constraint that the alignment attribute in the
// instruction must be set to "default alignment".
enum UnalignedPolicy {
ForbidUnaligned,
AllowUnaligned
};
bool init();
// Checks if the current instruction should be executed based on its
@ -261,8 +275,8 @@ class Simulator
inline uint16_t readExHU(int32_t addr, SimInstruction* instr);
inline int32_t writeExH(int32_t addr, uint16_t value, SimInstruction* instr);
inline int readW(int32_t addr, SimInstruction* instr);
inline void writeW(int32_t addr, int value, SimInstruction* instr);
inline int readW(int32_t addr, SimInstruction* instr, UnalignedPolicy f = ForbidUnaligned);
inline void writeW(int32_t addr, int value, SimInstruction* instr, UnalignedPolicy f = ForbidUnaligned);
inline int readExW(int32_t addr, SimInstruction* instr);
inline int writeExW(int32_t addr, int value, SimInstruction* instr);

View File

@ -85,6 +85,7 @@ static constexpr Register ABINonArgReturnReg1 = { Registers::invalid_reg };
static constexpr Register WasmTableCallPtrReg = { Registers::invalid_reg };
static constexpr Register WasmTableCallSigReg = { Registers::invalid_reg };
static constexpr Register WasmTlsReg = { Registers::invalid_reg };
static constexpr uint32_t ABIStackAlignment = 4;
static constexpr uint32_t CodeAlignment = 4;

View File

@ -44,6 +44,7 @@ UNIFIED_SOURCES += [
'testGCExactRooting.cpp',
'testGCFinalizeCallback.cpp',
'testGCHeapPostBarriers.cpp',
'testGCHooks.cpp',
'testGCMarking.cpp',
'testGCOutOfMemory.cpp',
'testGCStoreBufferRemoval.cpp',

View File

@ -103,7 +103,7 @@ BEGIN_TEST(testGCFinalizeCallback)
JS::PrepareForFullGC(cx);
js::SliceBudget budget(js::WorkBudget(1));
cx->gc.startDebugGC(GC_NORMAL, budget);
CHECK(cx->gc.state() == js::gc::MARK);
CHECK(cx->gc.state() == js::gc::State::Mark);
CHECK(cx->gc.isFullGc());
JS::RootedObject global4(cx, createTestGlobal());

View File

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/UniquePtr.h"
#include "js/GCAPI.h"
#include "jsapi-tests/tests.h"
static unsigned gSliceCallbackCount = 0;
static void
NonIncrementalGCSliceCallback(JSContext* cx, JS::GCProgress progress, const JS::GCDescription& desc)
{
++gSliceCallbackCount;
MOZ_RELEASE_ASSERT(progress == JS::GC_CYCLE_BEGIN || progress == JS::GC_CYCLE_END);
MOZ_RELEASE_ASSERT(desc.isCompartment_ == false);
MOZ_RELEASE_ASSERT(desc.invocationKind_ == GC_NORMAL);
MOZ_RELEASE_ASSERT(desc.reason_ == JS::gcreason::API);
if (progress == JS::GC_CYCLE_END) {
mozilla::UniquePtr<char16_t> summary(desc.formatSummaryMessage(cx));
mozilla::UniquePtr<char16_t> message(desc.formatSliceMessage(cx));
mozilla::UniquePtr<char16_t> json(desc.formatJSON(cx, 0));
}
}
BEGIN_TEST(testGCSliceCallback)
{
JS::SetGCSliceCallback(cx, NonIncrementalGCSliceCallback);
JS_GC(cx);
JS::SetGCSliceCallback(cx, nullptr);
CHECK(gSliceCallbackCount == 2);
return true;
}
END_TEST(testGCSliceCallback)

View File

@ -53,46 +53,49 @@
* The collector proceeds through the following states, the current state being
* held in JSRuntime::gcIncrementalState:
*
* - MARK_ROOTS - marks the stack and other roots
* - MARK - incrementally marks reachable things
* - SWEEP - sweeps zones in groups and continues marking unswept zones
* - MarkRoots - marks the stack and other roots
* - Mark - incrementally marks reachable things
* - Sweep - sweeps zones in groups and continues marking unswept zones
* - Finalize - performs background finalization, concurrent with mutator
* - Compact - incrementally compacts by zone
* - Decommit - performs background decommit and chunk removal
*
* The MARK_ROOTS activity always takes place in the first slice. The next two
* The MarkRoots activity always takes place in the first slice. The next two
* states can take place over one or more slices.
*
* In other words an incremental collection proceeds like this:
*
* Slice 1: MARK_ROOTS: Roots pushed onto the mark stack.
* MARK: The mark stack is processed by popping an element,
* Slice 1: MarkRoots: Roots pushed onto the mark stack.
* Mark: The mark stack is processed by popping an element,
* marking it, and pushing its children.
*
* ... JS code runs ...
*
* Slice 2: MARK: More mark stack processing.
* Slice 2: Mark: More mark stack processing.
*
* ... JS code runs ...
*
* Slice n-1: MARK: More mark stack processing.
* Slice n-1: Mark: More mark stack processing.
*
* ... JS code runs ...
*
* Slice n: MARK: Mark stack is completely drained.
* SWEEP: Select first group of zones to sweep and sweep them.
* Slice n: Mark: Mark stack is completely drained.
* Sweep: Select first group of zones to sweep and sweep them.
*
* ... JS code runs ...
*
* Slice n+1: SWEEP: Mark objects in unswept zones that were newly
* Slice n+1: Sweep: Mark objects in unswept zones that were newly
* identified as alive (see below). Then sweep more zone
* groups.
*
* ... JS code runs ...
*
* Slice n+2: SWEEP: Mark objects in unswept zones that were newly
* Slice n+2: Sweep: Mark objects in unswept zones that were newly
* identified as alive. Then sweep more zone groups.
*
* ... JS code runs ...
*
* Slice m: SWEEP: Sweeping is finished, and background sweeping
* Slice m: Sweep: Sweeping is finished, and background sweeping
* started on the helper thread.
*
* ... JS code runs, remaining sweeping done on background thread ...
@ -141,7 +144,7 @@
*
* The order of sweeping is restricted by cross compartment pointers - for
* example say that object |a| from zone A points to object |b| in zone B and
* neither object was marked when we transitioned to the SWEEP phase. Imagine we
* neither object was marked when we transitioned to the Sweep phase. Imagine we
* sweep B first and then return to the mutator. It's possible that the mutator
* could cause |a| to become alive through a read barrier (perhaps it was a
* shape that was accessed via a shape table). Then we would need to mark |b|,
@ -822,7 +825,7 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
#ifdef DEBUG
disableStrictProxyCheckingCount(0),
#endif
incrementalState(gc::NO_INCREMENTAL),
incrementalState(gc::State::NotActive),
lastMarkSlice(false),
sweepOnBackgroundThread(false),
foundBlackGrayEdges(false),
@ -4176,7 +4179,7 @@ js::gc::MarkingValidator::nonIncrementalMark(AutoLockForExclusiveAccess& lock)
/* Re-do all the marking, but non-incrementally. */
js::gc::State state = gc->incrementalState;
gc->incrementalState = MARK_ROOTS;
gc->incrementalState = State::MarkRoots;
{
gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_MARK);
@ -4196,12 +4199,12 @@ js::gc::MarkingValidator::nonIncrementalMark(AutoLockForExclusiveAccess& lock)
gc->markRuntime(gcmarker, GCRuntime::MarkRuntime, lock);
gc->incrementalState = MARK;
gc->incrementalState = State::Mark;
auto unlimited = SliceBudget::unlimited();
MOZ_RELEASE_ASSERT(gc->marker.drainMarkStack(unlimited));
}
gc->incrementalState = SWEEP;
gc->incrementalState = State::Sweep;
{
gcstats::AutoPhase ap1(gc->stats, gcstats::PHASE_SWEEP);
gcstats::AutoPhase ap2(gc->stats, gcstats::PHASE_SWEEP_MARK);
@ -5621,10 +5624,14 @@ void
GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lock)
{
switch (incrementalState) {
case NO_INCREMENTAL:
case State::NotActive:
return;
case MARK: {
case State::MarkRoots:
MOZ_CRASH("resetIncrementalGC did not expect MarkRoots state");
break;
case State::Mark: {
/* Cancel any ongoing marking. */
marker.reset();
marker.stop();
@ -5641,14 +5648,14 @@ GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lo
blocksToFreeAfterSweeping.freeAll();
incrementalState = NO_INCREMENTAL;
incrementalState = State::NotActive;
MOZ_ASSERT(!marker.shouldCheckCompartments());
break;
}
case SWEEP: {
case State::Sweep: {
marker.reset();
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
@ -5673,7 +5680,7 @@ GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lo
break;
}
case FINALIZE: {
case State::Finalize: {
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
rt->gc.waitBackgroundSweepOrAllocEnd();
@ -5690,7 +5697,7 @@ GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lo
break;
}
case COMPACT: {
case State::Compact: {
bool wasCompacting = isCompacting;
isCompacting = true;
@ -5704,14 +5711,11 @@ GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lo
break;
}
case DECOMMIT: {
case State::Decommit: {
auto unlimited = SliceBudget::unlimited();
incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
break;
}
default:
MOZ_CRASH("Invalid incremental GC state");
}
stats.reset(reason);
@ -5724,7 +5728,7 @@ GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lo
MOZ_ASSERT(!zone->isOnList());
}
MOZ_ASSERT(zonesToMaybeCompact.isEmpty());
MOZ_ASSERT(incrementalState == NO_INCREMENTAL);
MOZ_ASSERT(incrementalState == State::NotActive);
#endif
}
@ -5843,33 +5847,33 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
}
switch (incrementalState) {
case NO_INCREMENTAL:
case State::NotActive:
initialReason = reason;
cleanUpEverything = ShouldCleanUpEverything(reason, invocationKind);
isCompacting = shouldCompact();
lastMarkSlice = false;
incrementalState = MARK_ROOTS;
incrementalState = State::MarkRoots;
MOZ_FALLTHROUGH;
case MARK_ROOTS:
case State::MarkRoots:
if (!beginMarkPhase(reason, lock)) {
incrementalState = NO_INCREMENTAL;
incrementalState = State::NotActive;
return;
}
if (!destroyingRuntime)
pushZealSelectedObjects();
incrementalState = MARK;
incrementalState = State::Mark;
if (isIncremental && useZeal && hasZealMode(ZealMode::IncrementalRootsThenFinish))
break;
MOZ_FALLTHROUGH;
case MARK:
case State::Mark:
AutoGCRooter::traceAllWrappers(&marker);
/* If we needed delayed marking for gray roots, then collect until done. */
@ -5884,19 +5888,19 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
MOZ_ASSERT(marker.isDrained());
if (!lastMarkSlice && isIncremental && useZeal &&
((initialState == MARK && !hasZealMode(ZealMode::IncrementalRootsThenFinish)) ||
((initialState == State::Mark && !hasZealMode(ZealMode::IncrementalRootsThenFinish)) ||
hasZealMode(ZealMode::IncrementalMarkAllThenFinish)))
{
/*
* Yield with the aim of starting the sweep in the next
* slice. We will need to mark anything new on the stack
* when we resume, so we stay in MARK state.
* when we resume, so we stay in Mark state.
*/
lastMarkSlice = true;
break;
}
incrementalState = SWEEP;
incrementalState = State::Sweep;
/*
* This runs to completion, but we don't continue if the budget is
@ -5915,13 +5919,13 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
MOZ_FALLTHROUGH;
case SWEEP:
case State::Sweep:
if (sweepPhase(budget, lock) == NotFinished)
break;
endSweepPhase(destroyingRuntime, lock);
incrementalState = FINALIZE;
incrementalState = State::Finalize;
/* Yield before compacting since it is not incremental. */
if (isCompacting && isIncremental)
@ -5929,7 +5933,7 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
MOZ_FALLTHROUGH;
case FINALIZE:
case State::Finalize:
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
@ -5955,11 +5959,11 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
}
MOZ_ASSERT(!startedCompacting);
incrementalState = COMPACT;
incrementalState = State::Compact;
MOZ_FALLTHROUGH;
case COMPACT:
case State::Compact:
if (isCompacting) {
if (!startedCompacting)
beginCompactPhase();
@ -5971,11 +5975,11 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
}
startDecommit();
incrementalState = DECOMMIT;
incrementalState = State::Decommit;
MOZ_FALLTHROUGH;
case DECOMMIT:
case State::Decommit:
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
@ -5987,11 +5991,8 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
}
finishCollection(reason);
incrementalState = NO_INCREMENTAL;
incrementalState = State::NotActive;
break;
default:
MOZ_CRASH("unexpected GC incrementalState");
}
}
@ -6154,7 +6155,7 @@ GCRuntime::gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::
}
/* The GC was reset, so we need a do-over. */
if (prevState != NO_INCREMENTAL && !isIncrementalGCInProgress())
if (prevState != State::NotActive && !isIncrementalGCInProgress())
return true;
TraceMajorGCStart();
@ -6361,7 +6362,7 @@ GCRuntime::finishGC(JS::gcreason::Reason reason)
// compacting phase if we need to finish an ongoing incremental GC
// non-incrementally to avoid janking the browser.
if (!IsOOMReason(initialReason)) {
if (incrementalState == COMPACT) {
if (incrementalState == State::Compact) {
abortGC();
return;
}
@ -6799,8 +6800,8 @@ GCRuntime::runDebugGC()
* or compact phases.
*/
if (hasZealMode(ZealMode::IncrementalMultipleSlices)) {
if ((initialState == MARK && incrementalState == SWEEP) ||
(initialState == SWEEP && incrementalState == COMPACT))
if ((initialState == State::Mark && incrementalState == State::Sweep) ||
(initialState == State::Sweep && incrementalState == State::Compact))
{
incrementalLimit = zealFrequency / 2;
}
@ -7313,7 +7314,7 @@ JS::IsIncrementalGCInProgress(JSContext* cx)
JS_PUBLIC_API(bool)
JS::IsIncrementalBarrierNeeded(JSContext* cx)
{
return cx->gc.state() == gc::MARK && !cx->isHeapBusy();
return cx->gc.state() == gc::State::Mark && !cx->isHeapBusy();
}
struct IncrementalReferenceBarrierFunctor {
@ -7596,18 +7597,12 @@ NewMemoryInfoObject(JSContext* cx)
const char*
StateName(State state)
{
static const char* names[] = {
"None",
"MarkRoots",
"Mark",
"Sweep",
"Finalize",
"Compact",
"Decommit"
};
MOZ_ASSERT(ArrayLength(names) == NUM_STATES);
MOZ_ASSERT(state < NUM_STATES);
return names[state];
switch(state) {
#define MAKE_CASE(name) case State::name: return #name;
GCSTATES(MAKE_CASE)
#undef MAKE_CASE
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalide gc::State enum value");
}
void

View File

@ -43,16 +43,18 @@ namespace gc {
struct FinalizePhase;
enum State {
NO_INCREMENTAL,
MARK_ROOTS,
MARK,
SWEEP,
FINALIZE,
COMPACT,
DECOMMIT,
NUM_STATES
#define GCSTATES(D) \
D(NotActive) \
D(MarkRoots) \
D(Mark) \
D(Sweep) \
D(Finalize) \
D(Compact) \
D(Decommit)
enum class State {
#define MAKE_STATE(name) name,
GCSTATES(MAKE_STATE)
#undef MAKE_STATE
};
/*

View File

@ -528,6 +528,7 @@ dnl Android libstdc++, placed here so it can use MOZ_ARCH
dnl computed above.
dnl ========================================================
MOZ_ANDROID_CPU_ARCH
MOZ_ANDROID_STLPORT
dnl ========================================================

View File

@ -0,0 +1,25 @@
var BUGNUMBER = 1287520;
var summary = 'Array.prototype.concat should check HasProperty everytime for non-dense array';
print(BUGNUMBER + ": " + summary);
var a = [1, 2, 3];
a.constructor = {
[Symbol.species]: function(...args) {
var p = new Proxy(new Array(...args), {
defineProperty(target, propertyKey, receiver) {
if (propertyKey === "0") delete a[1];
return Reflect.defineProperty(target, propertyKey, receiver);
}
});
return p;
}
};
var p = a.concat();
assertEq(0 in p, true);
assertEq(1 in p, false);
assertEq(2 in p, true);
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,71 @@
var BUGNUMBER = 1280046;
var summary = "ignoreCase match should perform Canonicalize both on input and pattern.";
print(BUGNUMBER + ": " + summary);
// Each element [code1, upper, code2] satisfies the following condition:
// ToUpperCase(code1) == upper
// ToUpperCase(code2) == upper
var pairs =
[
// U+00B5: MICRO SIGN
// U+039C: GREEK CAPITAL LETTER MU
// U+03BC: GREEK SMALL LETTER MU
["\u00B5", "\u039C", "\u03BC"],
// U+0345: COMBINING GREEK YPOGEGRAMMENI
// U+0399: GREEK CAPITAL LETTER IOTA
// U+03B9: GREEK SMALL LETTER IOTA
["\u0345", "\u0399", "\u03B9"],
// U+03C2: GREEK SMALL LETTER FINAL SIGMA
// U+03A3: GREEK CAPITAL LETTER SIGMA
// U+03C3: GREEK SMALL LETTER SIGMA
["\u03C2", "\u03A3", "\u03C3"],
// U+03D0: GREEK BETA SYMBOL
// U+0392: GREEK CAPITAL LETTER BETA
// U+03B2: GREEK SMALL LETTER BETA
["\u03D0", "\u0392", "\u03B2"],
// U+03D1: GREEK THETA SYMBOL
// U+0398: GREEK CAPITAL LETTER THETA
// U+03B8: GREEK SMALL LETTER THETA
["\u03D1", "\u0398", "\u03B8"],
// U+03D5: GREEK PHI SYMBOL
// U+03A6: GREEK CAPITAL LETTER PHI
// U+03C6: GREEK SMALL LETTER PHI
["\u03D5", "\u03A6", "\u03C6"],
// U+03D6: GREEK PI SYMBOL
// U+03A0: GREEK CAPITAL LETTER PI
// U+03C0: GREEK SMALL LETTER PI
["\u03D6", "\u03A0", "\u03C0"],
// U+03F0: GREEK KAPPA SYMBOL
// U+039A: GREEK CAPITAL LETTER KAPPA
// U+03BA: GREEK SMALL LETTER KAPPA
["\u03F0", "\u039A", "\u03BA"],
// U+03F1: GREEK RHO SYMBOL
// U+03A1: GREEK CAPITAL LETTER RHO
// U+03C1: GREEK SMALL LETTER RHO
["\u03F1", "\u03A1", "\u03C1"],
// U+03F5: GREEK LUNATE EPSILON SYMBOL
// U+0395: GREEK CAPITAL LETTER EPSILON
// U+03B5: GREEK SMALL LETTER EPSILON
["\u03F5", "\u0395", "\u03B5"],
// U+1E9B: LATIN SMALL LETTER LONG S WITH DOT ABOVE
// U+1E60: LATIN CAPITAL LETTER S WITH DOT ABOVE
// U+1E61: LATIN SMALL LETTER S WITH DOT ABOVE
["\u1E9B", "\u1E60", "\u1E61"],
// U+1FBE: GREEK PROSGEGRAMMENI
// U+0399: GREEK CAPITAL LETTER IOTA
// U+03B9: GREEK SMALL LETTER IOTA
["\u1FBE", "\u0399", "\u03B9"],
];
for (var [code1, upper, code2] of pairs) {
assertEq(new RegExp(code1, "i").test(code2), true);
assertEq(new RegExp(code1, "i").test(upper), true);
assertEq(new RegExp(upper, "i").test(code1), true);
assertEq(new RegExp(upper, "i").test(code2), true);
assertEq(new RegExp(code2, "i").test(code1), true);
assertEq(new RegExp(code2, "i").test(upper), true);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,18 @@
var BUGNUMBER = 1287524;
var summary = 'RegExp.prototype[@@replace] should not use optimized path if RegExp.prototype.unicode is modified.';
print(BUGNUMBER + ": " + summary);
Object.defineProperty(RegExp.prototype, "unicode", {
get() {
RegExp.prototype.exec = () => null;
}
});
var rx = RegExp("a", "g");
var s = "abba";
var r = rx[Symbol.replace](s, "c");
assertEq(r, "abba");
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,14 @@
var BUGNUMBER = 1287525;
var summary = "RegExp.prototype[@@split] shouldn't use optimized path if limit is not number.";
print(BUGNUMBER + ": " + summary);
var rx = /a/;
var r = rx[Symbol.split]("abba", {valueOf() {
RegExp.prototype.exec = () => null;
return 100;
}});
assertEq(r.length, 1);
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,19 @@
var BUGNUMBER = 1287525;
var summary = 'String.prototype.split should call ToUint32(limit) before ToString(separator).';
print(BUGNUMBER + ": " + summary);
var accessed = false;
var rx = /a/;
Object.defineProperty(rx, Symbol.match, {
get() {
accessed = true;
}
});
rx[Symbol.split]("abba");
assertEq(accessed, true);
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -2829,12 +2829,12 @@ Debugger::markIncomingCrossCompartmentEdges(JSTracer* trc)
{
JSRuntime* rt = trc->runtime();
gc::State state = rt->gc.state();
MOZ_ASSERT(state == gc::MARK_ROOTS || state == gc::COMPACT);
MOZ_ASSERT(state == gc::State::MarkRoots || state == gc::State::Compact);
for (Debugger* dbg : rt->debuggerList) {
Zone* zone = MaybeForwarded(dbg->object.get())->zone();
if ((state == gc::MARK_ROOTS && !zone->isCollecting()) ||
(state == gc::COMPACT && !zone->isGCCompacting()))
if ((state == gc::State::MarkRoots && !zone->isCollecting()) ||
(state == gc::State::Compact && !zone->isGCCompacting()))
{
dbg->markCrossCompartmentEdges(trc);
}

View File

@ -326,6 +326,7 @@ class RegExpCompartment
* The shape of RegExp.prototype object that satisfies following:
* * RegExp.prototype.global getter is not modified
* * RegExp.prototype.sticky getter is not modified
* * RegExp.prototype.unicode getter is not modified
* * RegExp.prototype.exec is an own data property
* * RegExp.prototype[@@match] is an own data property
* * RegExp.prototype[@@search] is an own data property

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