This commit is contained in:
Phil Ringnalda 2016-09-05 17:05:44 -07:00
Родитель 9e859c42fc 55c3c1efd5
Коммит 3786ab03f1
134 изменённых файлов: 2731 добавлений и 1026 удалений

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

@ -16,7 +16,7 @@
<project name="platform_system_libpdu" path="system/libpdu" remote="b2g" revision="f1a61fa8f97cc0a1ac4eca160acc222981b21d90"/>
<project name="platform_system_sensorsd" path="system/sensorsd" remote="b2g" revision="3618678c472320de386f5ddc27897992d0e148a8"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="aee7ff3dba262a037559d360b62af429b62cb876">
<project name="platform_build" path="build" remote="b2g" revision="76946dd709234cf4bdb860672ec66d6af8f2ea3c">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>

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

@ -40,6 +40,7 @@ tags = webextensions
[browser_ext_getViews.js]
[browser_ext_incognito_popup.js]
[browser_ext_lastError.js]
[browser_ext_legacy_extension_context_contentscript.js]
[browser_ext_optionsPage_privileges.js]
[browser_ext_pageAction_context.js]
[browser_ext_pageAction_popup.js]

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

@ -0,0 +1,171 @@
"use strict";
const {
LegacyExtensionContext,
} = Cu.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {});
function promiseAddonStartup(extension) {
const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
return new Promise((resolve) => {
let listener = (evt, extensionInstance) => {
Management.off("startup", listener);
resolve(extensionInstance);
};
Management.on("startup", listener);
});
}
/**
* This test case ensures that the LegacyExtensionContext can receive a connection
* from a content script and that the received port contains the expected sender
* tab info.
*/
add_task(function* test_legacy_extension_context_contentscript_connection() {
function backgroundScript() {
// Extract the assigned uuid from the background page url and send it
// in a test message.
let uuid = window.location.hostname;
browser.test.onMessage.addListener(msg => {
if (msg == "open-test-tab") {
browser.tabs.create({url: "http://example.com/"})
.then(tab => browser.test.sendMessage("get-expected-sender-info", {
uuid, tab,
}));
} else if (msg == "close-current-tab") {
browser.tabs.query({active: true})
.then(tabs => browser.tabs.remove(tabs[0].id))
.then(() => browser.test.sendMessage("current-tab-closed", true))
.catch(() => browser.test.sendMessage("current-tab-closed", false));
}
});
browser.test.sendMessage("ready");
}
function contentScript() {
browser.runtime.sendMessage("webextension -> legacy_extension message", (reply) => {
browser.test.assertEq("legacy_extension -> webextension reply", reply,
"Got the expected reply from the LegacyExtensionContext");
browser.test.sendMessage("got-reply-message");
});
let port = browser.runtime.connect();
port.onMessage.addListener(msg => {
browser.test.assertEq("legacy_extension -> webextension port message", msg,
"Got the expected message from the LegacyExtensionContext");
port.postMessage("webextension -> legacy_extension port message");
});
}
let extensionData = {
background: `new ${backgroundScript}`,
manifest: {
content_scripts: [
{
matches: ["http://example.com/*"],
js: ["content-script.js"],
},
],
},
files: {
"content-script.js": `new ${contentScript}`,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
let waitForExtensionReady = extension.awaitMessage("ready");
let waitForExtensionInstance = promiseAddonStartup(extension);
extension.startup();
let extensionInstance = yield waitForExtensionInstance;
// Connect to the target extension.id as an external context
// using the given custom sender info.
let legacyContext = new LegacyExtensionContext(extensionInstance);
let waitConnectPort = new Promise(resolve => {
let {browser} = legacyContext.api;
browser.runtime.onConnect.addListener(port => {
resolve(port);
});
});
let waitMessage = new Promise(resolve => {
let {browser} = legacyContext.api;
browser.runtime.onMessage.addListener((singleMsg, msgSender, sendReply) => {
sendReply("legacy_extension -> webextension reply");
resolve({singleMsg, msgSender});
});
});
is(legacyContext.type, "legacy_extension",
"LegacyExtensionContext instance has the expected type");
ok(legacyContext.api, "Got the API object");
yield waitForExtensionReady;
extension.sendMessage("open-test-tab");
let {uuid, tab} = yield extension.awaitMessage("get-expected-sender-info");
let {singleMsg, msgSender} = yield waitMessage;
is(singleMsg, "webextension -> legacy_extension message",
"Got the expected message");
ok(msgSender, "Got a message sender object");
is(msgSender.id, uuid, "The sender has the expected id property");
is(msgSender.url, "http://example.com/", "The sender has the expected url property");
ok(msgSender.tab, "The sender has a tab property");
is(msgSender.tab.id, tab.id, "The port sender has the expected tab.id");
// Wait confirmation that the reply has been received.
yield extension.awaitMessage("got-reply-message");
let port = yield waitConnectPort;
ok(port, "Got the Port API object");
ok(port.sender, "The port has a sender property");
is(port.sender.id, uuid, "The port sender has an id property");
is(port.sender.url, "http://example.com/", "The port sender has the expected url property");
ok(port.sender.tab, "The port sender has a tab property");
is(port.sender.tab.id, tab.id, "The port sender has the expected tab.id");
let waitPortMessage = new Promise(resolve => {
port.onMessage.addListener((msg) => {
resolve(msg);
});
});
port.postMessage("legacy_extension -> webextension port message");
let msg = yield waitPortMessage;
is(msg, "webextension -> legacy_extension port message",
"LegacyExtensionContext received the expected message from the webextension");
let waitForDisconnect = new Promise(resolve => {
port.onDisconnect.addListener(resolve);
});
let waitForTestDone = extension.awaitMessage("current-tab-closed");
extension.sendMessage("close-current-tab");
yield waitForDisconnect;
info("Got the disconnect event on tab closed");
let success = yield waitForTestDone;
ok(success, "Test completed successfully");
yield extension.unload();
});

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

@ -76,10 +76,6 @@ leak:processInternalEntity
# Bug 1187421 - With e10s, NSS does not always free the error stack. m1.
leak:nss_ClearErrorStack
# Bug 1189430 - DNS leaks in mochitest-chrome.
leak:nsDNSService::AsyncResolveExtended
leak:_GetAddrInfo_Portable
# Bug 1189568 - Indirect leaks of IMContextWrapper and nsIntRect.
leak:nsWindow::Create
leak:nsBaseWidget::StoreWindowClipRegion

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

@ -46,6 +46,16 @@ const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
SpecialPowers.getBoolPref(OMTAPrefKey);
function assert_animation_is_running_on_compositor(animation, desc) {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
desc + ' at ' + animation.currentTime + 'ms');
}
function assert_animation_is_not_running_on_compositor(animation, desc) {
assert_equals(animation.isRunningOnCompositor, false,
desc + ' at ' + animation.currentTime + 'ms');
}
promise_test(function(t) {
// FIXME: When we implement Element.animate, use that here instead of CSS
// so that we remove any dependency on the CSS mapping.
@ -53,7 +63,7 @@ promise_test(function(t) {
var animation = div.getAnimations()[0];
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor'
+ ' during playback');
@ -61,7 +71,7 @@ promise_test(function(t) {
return animation.ready;
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' when paused');
});
@ -72,7 +82,7 @@ promise_test(function(t) {
var animation = div.getAnimations()[0];
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' for animation of "background"');
});
@ -83,7 +93,7 @@ promise_test(function(t) {
var animation = div.getAnimations()[0];
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor'
+ ' when the animation has two properties, where one can run'
+ ' on the compositor, the other cannot');
@ -99,7 +109,7 @@ promise_test(function(t) {
animation.pause();
return animation.ready;
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' when animation.pause() is called');
});
@ -111,13 +121,13 @@ promise_test(function(t) {
return animation.ready.then(function() {
animation.finish();
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' immediately after animation.finish() is called');
// Check that we don't set the flag back again on the next tick.
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' on the next tick after animation.finish() is called');
});
@ -129,13 +139,13 @@ promise_test(function(t) {
return animation.ready.then(function() {
animation.currentTime = 100 * MS_PER_SEC;
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' immediately after manually seeking the animation to the end');
// Check that we don't set the flag back again on the next tick.
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' on the next tick after manually seeking the animation to the end');
});
@ -148,13 +158,13 @@ promise_test(function(t) {
return animation.ready.then(function() {
animation.cancel();
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' immediately after animation.cancel() is called');
// Check that we don't set the flag back again on the next tick.
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' on the next tick after animation.cancel() is called');
});
@ -165,7 +175,7 @@ promise_test(function(t) {
var animation = div.getAnimations()[0];
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' while in the delay phase');
});
@ -181,7 +191,7 @@ promise_test(function(t) {
return new Promise(function(resolve) {
window.requestAnimationFrame(function() {
t.step(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor'
+ ' in requestAnimationFrame callback');
});
@ -212,7 +222,7 @@ promise_test(function(t) {
assert_true(!!changedAnimation, 'The animation should be recorded '
+ 'as one of the changedAnimations');
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor'
+ ' in MutationObserver callback');
});
@ -244,7 +254,7 @@ promise_test(function(t) {
var timeAtStart = window.performance.now();
function handleFrame() {
t.step(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor'
+ ' in requestAnimationFrame callback');
});
@ -274,7 +284,7 @@ promise_test(function(t) {
var animation = div.getAnimations()[0];
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Transition reports that it is running on the compositor'
+ ' during playback for opacity transition');
});
@ -287,7 +297,7 @@ promise_test(function(t) {
var animation = div.getAnimations()[0];
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'If an animation has a property that can run on the compositor and a '
+ 'property that cannot (due to Gecko limitations) but where the latter'
+ 'property is overridden in the CSS cascade, the animation should '
@ -302,13 +312,13 @@ promise_test(function(t) {
{ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor');
animation.currentTime = 150 * MS_PER_SEC;
animation.effect.timing.duration = 100 * MS_PER_SEC;
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' when the animation is set a shorter duration than current time');
});
@ -321,19 +331,19 @@ promise_test(function(t) {
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor');
animation.currentTime = 500 * MS_PER_SEC;
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' when finished');
animation.effect.timing.duration = 1000 * MS_PER_SEC;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor'
+ ' when restarted');
});
@ -346,19 +356,19 @@ promise_test(function(t) {
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor');
animation.effect.timing.endDelay = 100 * MS_PER_SEC;
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor'
+ ' when endDelay is changed');
animation.currentTime = 110 * MS_PER_SEC;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' when currentTime is during endDelay');
});
@ -371,13 +381,13 @@ promise_test(function(t) {
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor');
animation.effect.timing.endDelay = -200 * MS_PER_SEC;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' when endTime is negative value');
});
@ -387,22 +397,22 @@ promise_test(function(t) {
promise_test(function(t) {
var animation = addDivAndAnimate(t,
{},
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
{ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor');
animation.effect.timing.endDelay = -50 * MS_PER_SEC;
animation.effect.timing.endDelay = -100 * MS_PER_SEC;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor'
+ ' when endTime is positive and endDelay is negative');
animation.currentTime = 60 * MS_PER_SEC;
animation.currentTime = 110 * MS_PER_SEC;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor'
+ ' when currentTime is after endTime');
});
@ -419,14 +429,14 @@ promise_test(function(t) {
var div = addDiv(t);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation with null target reports that it is not running ' +
'on the compositor');
animation.effect.target = div;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor ' +
'after setting a valid target');
});
@ -437,11 +447,11 @@ promise_test(function(t) {
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation reports that it is running on the compositor');
animation.effect.target = null;
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the ' +
'compositor after setting null target');
});
@ -456,14 +466,14 @@ promise_test(function(t) {
return animation.ready.then(function() {
// Will be fixed in bug 1223658.
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation with fill:backwards in delay phase reports ' +
'that it is NOT running on the compositor');
animation.currentTime = 100 * MS_PER_SEC;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Animation with fill:backwards in delay phase reports ' +
'that it is running on the compositor after delay phase');
});
@ -479,14 +489,14 @@ promise_test(function(t) {
var another = addDiv(t);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Opacity animation on a 100% opacity keyframe reports ' +
'that it is running on the compositor from the begining');
animation.effect.target = another;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Opacity animation on a 100% opacity keyframe keeps ' +
'running on the compositor after changing the target ' +
'element');
@ -499,7 +509,7 @@ promise_test(function(t) {
var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Color animation reports that it is not running on the ' +
'compositor');
@ -508,7 +518,7 @@ promise_test(function(t) {
{ opacity: 0, offset: 1 }]);
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'100% opacity animation set by using setKeyframes reports ' +
'that it is running on the compositor');
});
@ -525,14 +535,14 @@ promise_test(function(t) {
100 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Color animation reports that it is not running on the ' +
'compositor');
animation.effect = effect;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'100% opacity animation set up by changing effects reports ' +
'that it is running on the compositor');
});
@ -546,14 +556,14 @@ promise_test(function(t) {
var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Opacity animation on an element which has 100% opacity style with ' +
'!important flag reports that it is not running on the compositor');
// Clear important flag from the opacity style on the target element.
div.style.setProperty("opacity", "1", "");
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Opacity animation reports that it is running on the compositor after '
+ 'clearing the !important flag');
});
@ -568,21 +578,21 @@ promise_test(function(t) {
var another = addDiv(t);
return Promise.all([firstAnimation.ready, secondAnimation.ready]).then(function() {
assert_equals(secondAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(secondAnimation,
'The second opacity animation on an element reports that ' +
'it is running on the compositor');
assert_equals(firstAnimation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(firstAnimation,
'The first opacity animation on the same element reports ' +
'that it is NOT running on the compositor');
firstAnimation.effect.target = another;
return waitForFrame();
}).then(function() {
assert_equals(secondAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(secondAnimation,
'The second opacity animation on the element keeps ' +
'running on the compositor after the preiously overridden ' +
'animation is applied to a different element');
assert_equals(firstAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(firstAnimation,
'The previously overridden opacity animation reports that ' +
'it it running on the compositor after being applied to a ' +
'different element');
@ -599,20 +609,20 @@ promise_test(function(t) {
var another = addDiv(t);
return Promise.all([firstAnimation.ready, secondAnimation.ready]).then(function() {
assert_equals(secondAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(secondAnimation,
'The second opacity animation on an element reports that ' +
'it is running on the compositor');
assert_equals(firstAnimation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(firstAnimation,
'The first opacity animation on the same element reports ' +
'that it is NOT running on the compositor');
secondAnimation.effect.target = another;
return waitForFrame();
}).then(function() {
assert_equals(secondAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(secondAnimation,
'The second opacity animation continues to run on the ' +
'compositor after being applied to a different element');
assert_equals(firstAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(firstAnimation,
'The previously overridden opacity animation now reports ' +
'that it is running on the compositor after the animation ' +
'that was overridding it is applied to a different element');
@ -629,21 +639,21 @@ promise_test(function(t) {
var anotherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
return Promise.all([animation.ready, anotherAnimation.ready]).then(function() {
assert_equals(anotherAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(anotherAnimation,
'An opacity animation on an element reports that ' +
'it is running on the compositor');
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Opacity animation running on a different element reports ' +
'that it is running on the compositor');
anotherAnimation.effect.target = div;
return waitForFrame();
}).then(function() {
assert_equals(anotherAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(anotherAnimation,
'Animation continues to run on the compositor after ' +
'being applied to a different element with a ' +
'lower-priority animation');
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation stops running on the compositor after ' +
'a higher-priority animation originally applied to ' +
'a different element is applied to the same element');
@ -659,20 +669,20 @@ promise_test(function(t) {
var animation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
return animation.ready.then(function() {
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(animation,
'Opacity animation on an element reports ' +
'that it is running on the compositor');
animation.effect.target = null;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation is no longer running on the compositor after ' +
'removing from the element');
animation.effect.target = importantOpacityElement;
return waitForFrame();
}).then(function() {
assert_equals(animation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(animation,
'Animation is NOT running on the compositor even after ' +
'being applied to a different element which has an ' +
'!important opacity declaration');
@ -689,27 +699,27 @@ promise_test(function(t) {
var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
return Promise.all([lowerAnimation.ready, higherAnimation.ready]).then(function() {
assert_equals(lowerAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(lowerAnimation,
'An opacity animation on an element reports that ' +
'it is running on the compositor');
assert_equals(higherAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(higherAnimation,
'Opacity animation on a different element reports ' +
'that it is running on the compositor');
lowerAnimation.effect.target = null;
return waitForFrame();
}).then(function() {
assert_equals(lowerAnimation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(lowerAnimation,
'Animation is no longer running on the compositor after ' +
'being removed from the element');
lowerAnimation.effect.target = another;
return waitForFrame();
}).then(function() {
assert_equals(lowerAnimation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(lowerAnimation,
'A lower-priority animation does NOT begin running ' +
'on the compositor after being applied to an element ' +
'which has a higher-priority animation');
assert_equals(higherAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(higherAnimation,
'A higher-priority animation continues to run on the ' +
'compositor even after a lower-priority animation is ' +
'applied to the same element');
@ -726,26 +736,26 @@ promise_test(function(t) {
var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
return Promise.all([lowerAnimation.ready, higherAnimation.ready]).then(function() {
assert_equals(lowerAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(lowerAnimation,
'An opacity animation on an element reports that ' +
'it is running on the compositor');
assert_equals(higherAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(higherAnimation,
'Opacity animation on a different element reports ' +
'that it is running on the compositor');
higherAnimation.effect.target = null;
return waitForFrame();
}).then(function() {
assert_equals(higherAnimation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(higherAnimation,
'Animation is no longer running on the compositor after ' +
'being removed from the element');
higherAnimation.effect.target = div;
return waitForFrame();
}).then(function() {
assert_equals(lowerAnimation.isRunningOnCompositor, false,
assert_animation_is_not_running_on_compositor(lowerAnimation,
'Animation stops running on the compositor after ' +
'a higher-priority animation applied to the same element');
assert_equals(higherAnimation.isRunningOnCompositor, omtaEnabled,
assert_animation_is_running_on_compositor(higherAnimation,
'A higher-priority animation begins to running on the ' +
'compositor after being applied to an element which has ' +
'a lower-priority-animation');

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

@ -1136,10 +1136,11 @@ public:
NS_LITERAL_CSTRING("explicit/dom/memory-file-data/small"),
KIND_HEAP, UNITS_BYTES, smallObjectsTotal,
nsPrintfCString(
"Memory used to back small memory files (less than %d bytes each).\n\n"
"Memory used to back small memory files (i.e. those taking up less "
"than %zu bytes of memory each).\n\n"
"Note that the allocator may round up a memory file's length -- "
"that is, an N-byte memory file may take up more than N bytes of "
"memory."),
"memory.", LARGE_OBJECT_MIN_SIZE),
aData);
}

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

@ -461,7 +461,7 @@ nsTextFragment::UpdateBidiFlag(const char16_t* aBuffer, uint32_t aLength)
char16_t ch2 = *cp++;
utf32Char = SURROGATE_TO_UCS4(ch1, ch2);
}
if (UTF32_CHAR_IS_BIDI(utf32Char) || IsBidiControl(utf32Char)) {
if (UTF32_CHAR_IS_BIDI(utf32Char) || IsBidiControlRTL(utf32Char)) {
mState.mIsBidi = true;
break;
}

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

@ -1567,7 +1567,11 @@ BrowserElementChild.prototype = {
location = Cc["@mozilla.org/docshell/urifixup;1"]
.getService(Ci.nsIURIFixup).createExposableURI(location);
sendAsyncMsg('locationchange', { _payload_: location.spec });
var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
sendAsyncMsg('locationchange', { url: location.spec,
canGoBack: webNav.canGoBack,
canGoForward: webNav.canGoForward });
},
onStateChange: function(webProgress, request, stateFlags, status) {

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

@ -70,7 +70,9 @@ function test3() {
function test4() {
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
is(e.detail, browserElementTestHelpers.emptyPage3);
is(e.detail.url, browserElementTestHelpers.emptyPage3);
is(e.detail.canGoBack, true);
is(e.detail.canGoForward, false);
checkCanGoBackAndForward(true, false, test5);
});
@ -81,7 +83,9 @@ function test4() {
function test5() {
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
is(e.detail, browserElementTestHelpers.emptyPage2);
is(e.detail.url, browserElementTestHelpers.emptyPage2);
is(e.detail.canGoBack, true);
is(e.detail.canGoForward, true);
checkCanGoBackAndForward(true, true, test6);
});
iframe.goBack();
@ -89,7 +93,9 @@ function test5() {
function test6() {
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
is(e.detail, browserElementTestHelpers.emptyPage1);
is(e.detail.url, browserElementTestHelpers.emptyPage1);
is(e.detail.canGoBack, false);
is(e.detail.canGoForward, true);
checkCanGoBackAndForward(false, true, SimpleTest.finish);
});
iframe.goBack();

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

@ -24,12 +24,12 @@ function runTest() {
document.body.appendChild(e.detail.frameElement);
e.detail.frameElement.addEventListener('mozbrowserlocationchange', function(e) {
if (e.detail == "http://example.com/#2") {
if (e.detail.url == "http://example.com/#2") {
ok(true, "Got locationchange to http://example.com/#2");
SimpleTest.finish();
}
else {
ok(true, "Got locationchange to " + e.detail);
ok(true, "Got locationchange to " + e.detail.url);
}
});

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

@ -36,7 +36,7 @@ function runTest2() {
ok(e.isTrusted, 'Event should be trusted.');
ok(!sawLocationChange, 'Just one locationchange event.');
ok(!sawLoadEnd, 'locationchange before load.');
is(e.detail, 'data:text/html,1', "event's reported location");
is(e.detail.url, 'data:text/html,1', "event's reported location");
sawLocationChange = true;
});

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

@ -13,7 +13,7 @@ var iframe;
function testPassword() {
function locationchange(e) {
var uri = e.detail;
var uri = e.detail.url;
is(uri, 'http://mochi.test:8888/tests/dom/browser-element/mochitest/file_empty.html',
"Username and password shouldn't be exposed in uri.");
SimpleTest.finish();
@ -33,7 +33,7 @@ function testWyciwyg() {
if (locationChangeCount == 0) {
locationChangeCount ++;
} else if (locationChangeCount == 1) {
var uri = e.detail;
var uri = e.detail.url;
is(uri, 'http://mochi.test:8888/tests/dom/browser-element/mochitest/file_wyciwyg.html', "Scheme in string shouldn't be wyciwyg");
iframe.removeEventListener('mozbrowserlocationchange', locationchange);
SimpleTest.executeSoon(testPassword);

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

@ -24,13 +24,13 @@ function runTest() {
});
iframe.addEventListener('mozbrowserlocationchange', function(e) {
if (e.detail == browserElementTestHelpers.emptyPage1) {
if (e.detail.url == browserElementTestHelpers.emptyPage1) {
gotFirstLocationChange = true;
if (gotFirstPaint) {
iframe.src = browserElementTestHelpers.emptyPage1 + '?2';
}
}
else if (e.detail.endsWith('?2')) {
else if (e.detail.url.endsWith('?2')) {
SimpleTest.finish();
}
});

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

@ -20,8 +20,8 @@ function runTest() {
});
iframe.addEventListener('mozbrowserlocationchange', function(e) {
ok(true, "Got locationchange to " + e.detail);
if (e.detail.endsWith("ForwardName.html#finish")) {
ok(true, "Got locationchange to " + e.detail.url);
if (e.detail.url.endsWith("ForwardName.html#finish")) {
SimpleTest.finish();
}
});

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

@ -38,7 +38,7 @@ function runTest() {
seenLocationChange = true;
ok(seenLoadStart, 'Location change after load start.');
ok(!seenLoadEnd, 'Location change before load end.');
ok(e.detail, browserElementTestHelpers.emptyPage1, "event's reported location");
ok(e.detail.url, browserElementTestHelpers.emptyPage1, "event's reported location");
}
function loadend(e) {
@ -91,7 +91,7 @@ function runTest2() {
seenLocationChange = true;
ok(seenLoadStart, 'Location change after load start.');
ok(!seenLoadEnd, 'Location change before load end.');
ok(e.detail, browserElementTestHelpers.emptyPage2, "event's reported location");
ok(e.detail.url, browserElementTestHelpers.emptyPage2, "event's reported location");
});
iframe.addEventListener('mozbrowserloadend', function(e) {

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

@ -75,7 +75,7 @@ function test3() {
function test4() {
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
is(e.detail, browserElementTestHelpers.emptyPage3);
is(e.detail.url, browserElementTestHelpers.emptyPage3);
purgeHistory(SimpleTest.finish);
});

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

@ -35,7 +35,7 @@ function runTest() {
iframe.addEventListener("mozbrowserlocationchange", function onlocchange(e) {
var a = document.createElement("a");
a.href = e.detail;
a.href = e.detail.url;
switch (a.hash) {
case "#mousedown":

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

@ -18,7 +18,7 @@ function runTest() {
});
iframe.addEventListener('mozbrowserlocationchange', function(e) {
if (/file_browserElement_TargetTop.html\?2$/.test(e.detail)) {
if (/file_browserElement_TargetTop.html\?2$/.test(e.detail.url)) {
ok(true, 'Got the locationchange we were looking for.');
SimpleTest.finish();
}

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

@ -3805,19 +3805,6 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
}
}
if (aVisitor.mEvent->mMessage == eKeyUp && aVisitor.mEvent->IsTrusted()) {
WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
if (MayFireChangeOnKeyUp(keyEvent->mKeyCode) &&
!(keyEvent->IsShift() || keyEvent->IsControl() || keyEvent->IsAlt() ||
keyEvent->IsMeta() || keyEvent->IsAltGraph() || keyEvent->IsFn() ||
keyEvent->IsOS())) {
// The up/down arrow key events fire 'change' events when released
// so that at the end of a series of up/down arrow key repeat events
// the value is considered to be "commited" by the user.
FireChangeEventIfNeeded();
}
}
nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
// We do this after calling the base class' PreHandleEvent so that
@ -4303,6 +4290,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
// event to increase/decrease the value of the number control.
if (!aVisitor.mEvent->DefaultPreventedByContent() && IsMutable()) {
StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
FireChangeEventIfNeeded();
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
}
} else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
@ -4480,6 +4468,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
break;
}
SetValueOfRangeForUserEvent(newValue);
FireChangeEventIfNeeded();
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
}
}
@ -4557,6 +4546,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame && numberControlFrame->IsFocused()) {
StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
FireChangeEventIfNeeded();
aVisitor.mEvent->PreventDefault();
}
} else if (mType == NS_FORM_INPUT_RANGE &&
@ -4570,6 +4560,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
MOZ_ASSERT(value.isFinite() && step.isFinite());
SetValueOfRangeForUserEvent(wheelEvent->mDeltaY < 0 ?
value + step : value - step);
FireChangeEventIfNeeded();
aVisitor.mEvent->PreventDefault();
}
}
@ -8344,18 +8335,6 @@ HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence)
aSequence.AppendElements(mEntries);
}
bool HTMLInputElement::MayFireChangeOnKeyUp(uint32_t aKeyCode) const
{
switch (mType) {
case NS_FORM_INPUT_NUMBER:
return aKeyCode == NS_VK_UP || aKeyCode == NS_VK_DOWN;
case NS_FORM_INPUT_RANGE:
return aKeyCode == NS_VK_UP || aKeyCode == NS_VK_DOWN ||
aKeyCode == NS_VK_LEFT || aKeyCode == NS_VK_RIGHT;
}
return false;
}
} // namespace dom
} // namespace mozilla

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

@ -1515,12 +1515,6 @@ private:
aType == NS_FORM_INPUT_NUMBER;
}
/**
* Returns true if the key code may induce the input's value changed to fire
* a "change" event accordingly.
*/
bool MayFireChangeOnKeyUp(uint32_t aKeyCode) const;
struct nsFilePickerFilter {
nsFilePickerFilter()
: mFilterMask(0) {}

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

@ -6541,7 +6541,7 @@ HTMLMediaElement::ShouldElementBePaused()
}
void
HTMLMediaElement::SetMediaInfo(const MediaInfo aInfo)
HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo)
{
mMediaInfo = aInfo;
AudioCaptureStreamChangeIfNeeded();
@ -6550,7 +6550,16 @@ HTMLMediaElement::SetMediaInfo(const MediaInfo aInfo)
void
HTMLMediaElement::AudioCaptureStreamChangeIfNeeded()
{
// TODO : only capture media element with audio track, see bug1298777.
// Window audio capturing only happens after creating audio channel agent.
if (!mAudioChannelAgent) {
return;
}
// No need to capture a silence media element.
if (!HasAudio()) {
return;
}
if (mAudioCapturedByWindow && !mCaptureStreamPort) {
nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
if (!OwnerDoc()->GetInnerWindow()) {

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

@ -739,7 +739,7 @@ public:
bool ComputedMuted() const;
nsSuspendedTypes ComputedSuspended() const;
void SetMediaInfo(const MediaInfo aInfo);
void SetMediaInfo(const MediaInfo& aInfo);
protected:
virtual ~HTMLMediaElement();

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

@ -215,9 +215,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=722599
is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes that don't change its value");
range.focus();
synthesizeKey("VK_HOME", {});
is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes until it loses focus");
is(rangeChange, 1, "Change event should be dispatched on range input element for key changes");
range.blur();
is(rangeChange, 1, "Change event should be dispatched on range input element on blur");
is(rangeChange, 1, "Change event shouldn't be dispatched on range input element on blur");
range.focus();
var bcr = range.getBoundingClientRect();
var centerOfRangeX = bcr.width / 2;
@ -233,6 +233,32 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=722599
synthesizeMouse(range, centerOfRangeX - 10, centerOfRangeY, {});
is(rangeChange, 3, "Change event should be dispatched on range input element for a click that gives the range focus");
if (isDesktop) { // up/down arrow keys not supported on android/b2g
synthesizeKey("VK_UP", {});
is(rangeChange, 4, "Change event should be dispatched on range input element for key changes that change its value (VK_UP)");
synthesizeKey("VK_DOWN", {});
is(rangeChange, 5, "Change event should be dispatched on range input element for key changes that change its value (VK_DOWN)");
synthesizeKey("VK_RIGHT", {});
is(rangeChange, 6, "Change event should be dispatched on range input element for key changes that change its value (VK_RIGHT)");
synthesizeKey("VK_LEFT", {});
is(rangeChange, 7, "Change event should be dispatched on range input element for key changes that change its value (VK_LEFT)");
synthesizeKey("VK_UP", {shiftKey: true});
is(rangeChange, 8, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_UP)");
synthesizeKey("VK_DOWN", {shiftKey: true});
is(rangeChange, 9, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_DOWN)");
synthesizeKey("VK_RIGHT", {shiftKey: true});
is(rangeChange, 10, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_RIGHT)");
synthesizeKey("VK_LEFT", {shiftKey: true});
is(rangeChange, 11, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_LEFT)");
synthesizeKey("VK_PAGE_UP", {});
is(rangeChange, 12, "Change event should be dispatched on range input element for key changes that change its value (VK_PAGE_UP)");
synthesizeKey("VK_PAGE_DOWN", {});
is(rangeChange, 13, "Change event should be dispatched on range input element for key changes that change its value (VK_PAGE_DOWN");
synthesizeKey("VK_RIGHT", {shiftKey: true});
is(rangeChange, 14, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_PAGE_UP)");
synthesizeKey("VK_LEFT", {shiftKey: true});
is(rangeChange, 15, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_PAGE_DOWN)");
}
//Input type change test.
input = document.getElementById("input_checkbox");
input.type = "text";

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

@ -631,3 +631,6 @@ skip-if = (os != 'win' && os != 'linux')
[test_bug1260704.html]
[test_allowMedia.html]
[test_bug1292522_same_domain_with_different_port_number.html]
[test_bug1295719_event_sequence_for_arrow_keys.html]
skip-if = os == "android" || appname == "b2g" # up/down arrow keys not supported on android/b2g
[test_bug1295719_event_sequence_for_number_keys.html]

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

@ -42,10 +42,17 @@ function runTests() {
let testIdx = 0;
let result = parseInt(input.value);
let numberChange = 0;
let expectChange = 0;
input.addEventListener("change", () => {
++numberChange;
}, false);
function runNext() {
let p = params[testIdx];
(p["focus"]) ? input.focus() : input.blur();
expectChange = p["valueChanged"] == 0 ? expectChange : expectChange + 1;
result += parseInt(p["valueChanged"]);
synthesizeWheel(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] });
window.postMessage("finished", "http://mochi.test:8888");
@ -57,6 +64,8 @@ function runTests() {
ok(input.value == result,
"Handle wheel in number input test-" + testIdx + " expect " + result +
" get " + input.value);
ok(numberChange == expectChange,
"UA should fire change event when input's value changed, expect " + expectChange + " get " + numberChange);
(testIdx >= params.length) ? SimpleTest.finish() : runNext();
}
});

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

@ -43,14 +43,23 @@ function runTests() {
let testIdx = 0;
let result = parseInt(input.value);
let rangeChange = 0;
let expectChange = 0;
input.addEventListener("change", () => {
++rangeChange;
}, false);
function runNext() {
let p = params[testIdx];
(p["focus"]) ? input.focus() : input.blur();
expectChange = p["valueChanged"] == 0 ? expectChange : expectChange + 1;
result += parseInt(p["valueChanged"]);
sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
ok(input.value == result,
"Handle wheel in range input test-" + testIdx + " expect " + result + " get " + input.value);
ok(rangeChange == expectChange,
"UA should fire change event when input's value changed, expect " + expectChange + " get " + rangeChange);
(++testIdx >= params.length) ? SimpleTest.finish() : runNext();
});
}

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

@ -41,6 +41,11 @@ function runTests() {
let testIdx = 0;
let result = parseInt(input.value);
let rangeChange = 0;
input.addEventListener("change", () => {
++rangeChange;
}, false);
function runNext() {
let p = params[testIdx];
@ -48,6 +53,7 @@ function runTests() {
sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
ok(input.value == result,
"Handle wheel in range input test-" + testIdx + " expect " + result + " get " + input.value);
ok(rangeChange == 0, "Wheel event should not trigger change event when max < min");
testIdx++;
(testIdx >= params.length) ? SimpleTest.finish() : runNext();
});

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

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1295719
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1295719</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1295719">Mozilla Bug 1295719</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<input id="test_number" type="number" value=50>
<input id="test_range" type="range" value=50 max=100 min=0>
<script type="text/javascript">
/** Test for Bug 1295719 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTests);
function runTests() {
let number = window.document.getElementById("test_number");
let range = window.document.getElementById("test_range");
let waiting_event_sequence = ["keydown", "keypress", "input", "change"];
let waiting_event_idx = 0;
waiting_event_sequence.forEach((eventType) => {
number.addEventListener(eventType, (event) => {
let waiting_event = waiting_event_sequence[waiting_event_idx];
is(waiting_event, eventType, "Waiting " + waiting_event + " get " + eventType);
// Input element will fire input and change events when handling keypress
// with keycode=arrows. When user press and hold the keyboard, we expect
// that input element repeatedly fires "keydown", "keypress", "input", and
// "change" events until user release the keyboard. Using
// waiting_event_sequence as a circular buffer and reset waiting_event_idx
// when it point to the end of buffer.
waiting_event_idx = waiting_event_idx == waiting_event_sequence.length -1 ? 0 : waiting_event_idx + 1;
}, false);
range.addEventListener(eventType, (event) => {
let waiting_event = waiting_event_sequence[waiting_event_idx];
is(waiting_event, eventType, "Waiting " + waiting_event + " get " + eventType);
waiting_event_idx = waiting_event_idx == waiting_event_sequence.length - 1 ? 0 : waiting_event_idx + 1;
}, false);
});
number.focus();
synthesizeKey("VK_DOWN", {type: "keydown"});
synthesizeKey("VK_DOWN", {type: "keydown"});
synthesizeKey("VK_DOWN", {type: "keyup"});
number.blur();
range.focus();
waiting_event_idx = 0;
synthesizeKey("VK_DOWN", {type: "keydown"});
synthesizeKey("VK_DOWN", {type: "keydown"});
synthesizeKey("VK_DOWN", {type: "keyup"});
SimpleTest.finish();
}
</script>
</body>
</html>

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

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1295719
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1295719</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1295719">Mozilla Bug 1295719</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<input id="test_number" type="number" value=50>
<script type="text/javascript">
/** Test for Bug 1295719 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTests);
function runTests() {
let number = window.document.getElementById("test_number");
let waiting_event_sequence = ["keydown", "keypress", "input"];
let change_event_of_number = 0;
let keyup_event_of_number = 0;
let waiting_event_idx = 0;
waiting_event_sequence.forEach((eventType) => {
number.addEventListener(eventType, (event) => {
let waiting_event = waiting_event_sequence[waiting_event_idx];
is(eventType, waiting_event, "Waiting " + waiting_event + " get " + eventType);
// Input element will fire input event when handling keypress with
// keycode=numbers. When user press and hold the keyboard, we expect that
// input element repeatedly fires "keydown", "keypress", and "input" until
// user release the keyboard. Input element will fire change event when
// it's blurred. Using waiting_event_sequence as a circular buffer and
// reset waiting_event_idx when it point to the end of buffer.
waiting_event_idx = waiting_event_idx == waiting_event_sequence.length - 1 ? 0 : waiting_event_idx + 1;
}, false);
});
number.addEventListener("change", (event) => {
is(keyup_event_of_number, 1, "change event should be fired after blurred");
++change_event_of_number;
}, false);
number.addEventListener("keyup", (event) => {
is(keyup_event_of_number, 0, "keyup event should be fired once");
is(change_event_of_number, 0, "keyup event should be fired before change event");
++keyup_event_of_number;
}, false);
number.focus();
synthesizeKey("5", {type: "keydown"});
synthesizeKey("5", {type: "keydown"});
synthesizeKey("5", {type: "keyup"});
is(change_event_of_number, 0, "change event shouldn't be fired when input element is focused");
number.blur();
is(change_event_of_number, 1, "change event should be fired when input element is blurred");
SimpleTest.finish();
}
</script>
</body>
</html>

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

@ -107,6 +107,11 @@ AudioCaptureStream::ProcessInput(GraphTime aFrom, GraphTime aTo,
AudioSegment* inputSegment = tracks->Get<AudioSegment>();
StreamTime inputStart = s->GraphTimeToStreamTimeWithBlocking(aFrom);
StreamTime inputEnd = s->GraphTimeToStreamTimeWithBlocking(aTo);
if (tracks->IsEnded() && inputSegment->GetDuration() <= inputEnd) {
// If the input track has ended and we have consumed all its data it
// can be ignored.
continue;
}
AudioSegment toMix;
toMix.AppendSlice(*inputSegment, inputStart, inputEnd);
// Care for streams blocked in the [aTo, aFrom] range.

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

@ -27,6 +27,12 @@ namespace {
// This mutex protects the variables below.
StaticMutex sMutex;
enum class CubebState {
Uninitialized = 0,
Initialized,
Error,
Shutdown
} sCubebState = CubebState::Uninitialized;
cubeb* sCubebContext;
double sVolumeScale;
uint32_t sCubebLatency;
@ -126,11 +132,15 @@ cubeb* GetCubebContext()
void InitPreferredSampleRate()
{
StaticMutexAutoLock lock(sMutex);
if (sPreferredSampleRate == 0 &&
cubeb_get_preferred_sample_rate(GetCubebContextUnlocked(),
&sPreferredSampleRate) != CUBEB_OK) {
// Query failed, use a sensible default.
sPreferredSampleRate = 44100;
if (sPreferredSampleRate == 0) {
cubeb* context = GetCubebContextUnlocked();
if (context) {
if (cubeb_get_preferred_sample_rate(context,
&sPreferredSampleRate) != CUBEB_OK) {
// Query failed, use a sensible default.
sPreferredSampleRate = 44100;
}
}
}
}
@ -163,7 +173,9 @@ void InitBrandName()
cubeb* GetCubebContextUnlocked()
{
sMutex.AssertCurrentThreadOwns();
if (sCubebContext) {
if (sCubebState != CubebState::Uninitialized) {
// If we have already passed the initialization point (below), just return
// the current context, which may be null (e.g., after error or shutdown.)
return sCubebContext;
}
@ -174,8 +186,9 @@ cubeb* GetCubebContextUnlocked()
sBrandName, "Did not initialize sbrandName, and not on the main thread?");
}
DebugOnly<int> rv = cubeb_init(&sCubebContext, sBrandName);
int rv = cubeb_init(&sCubebContext, sBrandName);
NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context.");
sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Error;
return sCubebContext;
}
@ -247,6 +260,8 @@ void ShutdownLibrary()
sCubebContext = nullptr;
}
sBrandName = nullptr;
// This will ensure we don't try to re-create a context.
sCubebState = CubebState::Shutdown;
}
uint32_t MaxNumberOfChannels()
@ -298,12 +313,15 @@ cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel)
void GetCurrentBackend(nsAString& aBackend)
{
const char* backend = cubeb_get_backend_id(GetCubebContext());
if (!backend) {
aBackend.AssignLiteral("unknown");
return;
cubeb* cubebContext = GetCubebContext();
if (cubebContext) {
const char* backend = cubeb_get_backend_id(cubebContext);
if (backend) {
aBackend.AssignASCII(backend);
return;
}
}
aBackend.AssignASCII(backend);
aBackend.AssignLiteral("unknown");
}
} // namespace CubebUtils

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

@ -588,6 +588,12 @@ AudioCallbackDriver::~AudioCallbackDriver()
void
AudioCallbackDriver::Init()
{
cubeb* cubebContext = CubebUtils::GetCubebContext();
if (!cubebContext) {
NS_WARNING("Could not get cubeb context.");
return;
}
cubeb_stream_params output;
cubeb_stream_params input;
uint32_t latency_frames;
@ -619,7 +625,7 @@ AudioCallbackDriver::Init()
output.format = CUBEB_SAMPLE_FLOAT32NE;
}
if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), output, &latency_frames) != CUBEB_OK) {
if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) {
NS_WARNING("Could not get minimal latency from cubeb.");
return;
}
@ -650,7 +656,7 @@ AudioCallbackDriver::Init()
// XXX Only pass input input if we have an input listener. Always
// set up output because it's easier, and it will just get silence.
// XXX Add support for adding/removing an input listener later.
cubeb_stream_init(CubebUtils::GetCubebContext(), &stream,
cubeb_stream_init(cubebContext, &stream,
"AudioCallbackDriver",
input_id,
mGraphImpl->mInputWanted ? &input : nullptr,

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

@ -584,11 +584,14 @@ MediaDecoderStateMachine::OnAudioDecoded(MediaData* aAudioSample)
return;
}
case DECODER_STATE_DECODING_FIRSTFRAME: {
Push(audio, MediaData::AUDIO_DATA);
MaybeFinishDecodeFirstFrame();
return;
}
case DECODER_STATE_DECODING: {
Push(audio, MediaData::AUDIO_DATA);
if (MaybeFinishDecodeFirstFrame()) {
return;
}
if (mIsAudioPrerolling && DonePrerollingAudio()) {
StopPrerollingAudio();
}
@ -682,9 +685,6 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
} else {
StopPrerollingVideo();
}
if (mState == DECODER_STATE_BUFFERING || mState == DECODER_STATE_DECODING) {
MaybeFinishDecodeFirstFrame();
}
return;
}
@ -708,11 +708,11 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
StopPrerollingVideo();
}
switch (mState) {
case DECODER_STATE_DECODING_FIRSTFRAME:
MaybeFinishDecodeFirstFrame();
return;
case DECODER_STATE_BUFFERING:
case DECODER_STATE_DECODING: {
if (MaybeFinishDecodeFirstFrame()) {
return;
}
if (CheckIfDecodeComplete()) {
SetState(DECODER_STATE_COMPLETED);
return;
@ -729,23 +729,24 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
}
}
bool
void
MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
{
MOZ_ASSERT(OnTaskQueue());
if (mSentFirstFrameLoadedEvent ||
(IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
MOZ_ASSERT(!mSentFirstFrameLoadedEvent);
if ((IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
(IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
return false;
}
FinishDecodeFirstFrame();
if (!mQueuedSeek.Exists()) {
return false;
return;
}
// We can now complete the pending seek.
InitiateSeek(Move(mQueuedSeek));
return true;
FinishDecodeFirstFrame();
if (mQueuedSeek.Exists()) {
InitiateSeek(Move(mQueuedSeek));
} else {
SetState(DECODER_STATE_DECODING);
}
}
void
@ -772,11 +773,14 @@ MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample,
return;
}
case DECODER_STATE_DECODING_FIRSTFRAME: {
Push(video, MediaData::VIDEO_DATA);
MaybeFinishDecodeFirstFrame();
return;
}
case DECODER_STATE_DECODING: {
Push(video, MediaData::VIDEO_DATA);
if (MaybeFinishDecodeFirstFrame()) {
return;
}
if (mIsVideoPrerolling && DonePrerollingVideo()) {
StopPrerollingVideo();
}
@ -790,8 +794,7 @@ MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample,
return;
}
TimeDuration decodeTime = TimeStamp::Now() - aDecodeStartTime;
if (mSentFirstFrameLoadedEvent &&
THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
!HasLowUndecodedData())
{
mLowAudioThresholdUsecs =
@ -828,6 +831,8 @@ bool
MediaDecoderStateMachine::CheckIfDecodeComplete()
{
MOZ_ASSERT(OnTaskQueue());
// DecodeComplete is possible only after decoding first frames.
MOZ_ASSERT(mSentFirstFrameLoadedEvent);
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
mState == DECODER_STATE_BUFFERING);
return !IsVideoDecoding() && !IsAudioDecoding();
@ -941,6 +946,8 @@ void MediaDecoderStateMachine::StopPlayback()
void MediaDecoderStateMachine::MaybeStartPlayback()
{
MOZ_ASSERT(OnTaskQueue());
// Should try to start playback only after decoding first frames.
MOZ_ASSERT(mSentFirstFrameLoadedEvent);
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
mState == DECODER_STATE_COMPLETED);
@ -976,6 +983,8 @@ void
MediaDecoderStateMachine::MaybeStartBuffering()
{
MOZ_ASSERT(OnTaskQueue());
// Buffering makes senses only after decoding first frames.
MOZ_ASSERT(mSentFirstFrameLoadedEvent);
MOZ_ASSERT(mState == DECODER_STATE_DECODING);
if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
@ -1023,14 +1032,15 @@ void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
MediaDecoderStateMachine::ToStateStr(State aState)
{
switch (aState) {
case DECODER_STATE_DECODING_METADATA: return "DECODING_METADATA";
case DECODER_STATE_WAIT_FOR_CDM: return "WAIT_FOR_CDM";
case DECODER_STATE_DORMANT: return "DORMANT";
case DECODER_STATE_DECODING: return "DECODING";
case DECODER_STATE_SEEKING: return "SEEKING";
case DECODER_STATE_BUFFERING: return "BUFFERING";
case DECODER_STATE_COMPLETED: return "COMPLETED";
case DECODER_STATE_SHUTDOWN: return "SHUTDOWN";
case DECODER_STATE_DECODING_METADATA: return "DECODING_METADATA";
case DECODER_STATE_WAIT_FOR_CDM: return "WAIT_FOR_CDM";
case DECODER_STATE_DORMANT: return "DORMANT";
case DECODER_STATE_DECODING_FIRSTFRAME: return "DECODING_FIRSTFRAME";
case DECODER_STATE_DECODING: return "DECODING";
case DECODER_STATE_SEEKING: return "SEEKING";
case DECODER_STATE_BUFFERING: return "BUFFERING";
case DECODER_STATE_COMPLETED: return "COMPLETED";
case DECODER_STATE_SHUTDOWN: return "SHUTDOWN";
default: MOZ_ASSERT_UNREACHABLE("Invalid state.");
}
return "UNKNOWN";
@ -1087,6 +1097,9 @@ MediaDecoderStateMachine::EnterState(State aState)
Reset();
mReader->ReleaseResources();
break;
case DECODER_STATE_DECODING_FIRSTFRAME:
DecodeFirstFrame();
break;
case DECODER_STATE_DECODING:
StartDecoding();
break;
@ -1254,18 +1267,39 @@ MediaDecoderStateMachine::Shutdown()
}
void
MediaDecoderStateMachine::StartDecoding()
MediaDecoderStateMachine::DecodeFirstFrame()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mState == DECODER_STATE_DECODING);
MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
// Handle the pending seek now if we've decoded first frames. Otherwise it
// will be handled after decoding first frames.
if (mSentFirstFrameLoadedEvent && mQueuedSeek.Exists()) {
// Handle pending seek.
if (mQueuedSeek.Exists() &&
(mSentFirstFrameLoadedEvent || mReader->ForceZeroStartTime())) {
InitiateSeek(Move(mQueuedSeek));
return;
}
// Transition to DECODING if we've decoded first frames.
if (mSentFirstFrameLoadedEvent) {
SetState(DECODER_STATE_DECODING);
return;
}
// Dispatch tasks to decode first frames.
DispatchDecodeTasksIfNeeded();
}
void
MediaDecoderStateMachine::StartDecoding()
{
MOZ_ASSERT(OnTaskQueue());
// Should transition to DECODING only after decoding first frames.
MOZ_ASSERT(mSentFirstFrameLoadedEvent);
MOZ_ASSERT(mState == DECODER_STATE_DECODING);
// Pending seek should've been handled by DECODING_FIRSTFRAME before
// transitioning to DECODING.
MOZ_ASSERT(!mQueuedSeek.Exists());
if (CheckIfDecodeComplete()) {
SetState(DECODER_STATE_COMPLETED);
return;
@ -1307,7 +1341,9 @@ void MediaDecoderStateMachine::PlayStateChanged()
// all state transitions to the state machine task queue, but for now we just
// make sure that none of the possible main-thread state transitions (Seek(),
// SetDormant(), and Shutdown()) have not occurred.
if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING &&
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_COMPLETED)
{
DECODER_LOG("Unexpected state - Bailing out of PlayInternal()");
@ -1499,13 +1535,12 @@ MediaDecoderStateMachine::Seek(SeekTarget aTarget)
return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
}
MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA,
"We should have got duration already");
MOZ_ASSERT(mDuration.Ref().isSome(), "We should have got duration already");
// Can't seek until the start time is known.
bool hasStartTime = mSentFirstFrameLoadedEvent || mReader->ForceZeroStartTime();
// Can't seek when state is WAIT_FOR_CDM or DORMANT.
bool stateAllowed = mState >= DECODER_STATE_DECODING;
bool stateAllowed = mState >= DECODER_STATE_DECODING_FIRSTFRAME;
if (!stateAllowed || !hasStartTime) {
DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
@ -1548,6 +1583,7 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
MOZ_ASSERT(OnTaskQueue());
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_SEEKING) {
return;
@ -1733,23 +1769,16 @@ MediaDecoderStateMachine::DiscardSeekTaskIfExist()
}
}
nsresult
void
MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown()) {
return NS_ERROR_FAILURE;
if (!IsShutdown() && NeedToDecodeAudio()) {
EnsureAudioDecodeTaskQueued();
}
if (NeedToDecodeAudio()) {
return EnsureAudioDecodeTaskQueued();
}
return NS_OK;
}
nsresult
void
MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
{
MOZ_ASSERT(OnTaskQueue());
@ -1759,17 +1788,18 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
IsAudioDecoding(), AudioRequestStatus());
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
mState != DECODER_STATE_BUFFERING) {
return NS_OK;
return;
}
if (!IsAudioDecoding() || mReader->IsRequestingAudioData() ||
if (!IsAudioDecoding() ||
mReader->IsRequestingAudioData() ||
mReader->IsWaitingAudioData()) {
return NS_OK;
return;
}
RequestAudioData();
return NS_OK;
}
void
@ -1784,23 +1814,16 @@ MediaDecoderStateMachine::RequestAudioData()
mReader->RequestAudioData();
}
nsresult
void
MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown()) {
return NS_ERROR_FAILURE;
if (!IsShutdown() && NeedToDecodeVideo()) {
EnsureVideoDecodeTaskQueued();
}
if (NeedToDecodeVideo()) {
return EnsureVideoDecodeTaskQueued();
}
return NS_OK;
}
nsresult
void
MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
{
MOZ_ASSERT(OnTaskQueue());
@ -1810,17 +1833,18 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
IsVideoDecoding(), VideoRequestStatus());
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
mState != DECODER_STATE_BUFFERING) {
return NS_OK;
return;
}
if (!IsVideoDecoding() || mReader->IsRequestingVideoData() ||
if (!IsVideoDecoding() ||
mReader->IsRequestingVideoData() ||
mReader->IsWaitingVideoData()) {
return NS_OK;
return;
}
RequestVideoData();
return NS_OK;
}
void
@ -1900,7 +1924,7 @@ bool MediaDecoderStateMachine::HasLowUndecodedData()
bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs)
{
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(mState >= DECODER_STATE_DECODING && mSentFirstFrameLoadedEvent,
MOZ_ASSERT(mState >= DECODER_STATE_DECODING,
"Must have loaded first frame for mBuffered to be valid");
// If we don't have a duration, mBuffered is probably not going to have
@ -2017,7 +2041,7 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
return;
}
SetState(DECODER_STATE_DECODING);
SetState(DECODER_STATE_DECODING_FIRSTFRAME);
}
void
@ -2243,7 +2267,8 @@ MediaDecoderStateMachine::FinishShutdown()
return OwnerThread()->BeginShutdown();
}
nsresult MediaDecoderStateMachine::RunStateMachine()
void
MediaDecoderStateMachine::RunStateMachine()
{
MOZ_ASSERT(OnTaskQueue());
@ -2251,20 +2276,17 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
mDispatchedStateMachine = false;
MediaResource* resource = mResource;
NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
NS_ENSURE_TRUE_VOID(resource);
switch (mState) {
case DECODER_STATE_SHUTDOWN:
case DECODER_STATE_DORMANT:
case DECODER_STATE_WAIT_FOR_CDM:
case DECODER_STATE_DECODING_METADATA:
return NS_OK;
case DECODER_STATE_DECODING_FIRSTFRAME:
return;
case DECODER_STATE_DECODING: {
// Can't start playback until having decoded first frames.
if (!mSentFirstFrameLoadedEvent) {
return NS_OK;
}
if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying())
{
// We're playing, but the element/decoder is in paused state. Stop
@ -2280,7 +2302,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
IsStateMachineScheduled(),
"Must have timer scheduled");
MaybeStartBuffering();
return NS_OK;
return;
}
case DECODER_STATE_BUFFERING: {
@ -2303,7 +2325,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
(mQuickBuffering ? "(quick exit)" : ""));
ScheduleStateMachineIn(USECS_PER_S);
return NS_OK;
return;
}
} else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
MOZ_ASSERT(mReader->IsWaitForDataSupported(),
@ -2315,7 +2337,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
"mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
OutOfDecodedAudio(), AudioRequestStatus(),
OutOfDecodedVideo(), VideoRequestStatus());
return NS_OK;
return;
}
DECODER_LOG("Changed state from BUFFERING to DECODING");
@ -2323,11 +2345,11 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
SetState(DECODER_STATE_DECODING);
NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
return NS_OK;
return;
}
case DECODER_STATE_SEEKING: {
return NS_OK;
return;
}
case DECODER_STATE_COMPLETED: {
@ -2345,7 +2367,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
NS_ASSERTION(!IsPlaying() ||
IsStateMachineScheduled(),
"Must have timer scheduled");
return NS_OK;
return;
}
// StopPlayback in order to reset the IsPlaying() state so audio
@ -2370,11 +2392,9 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
StopMediaSink();
}
return NS_OK;
return;
}
}
return NS_OK;
}
void
@ -2742,7 +2762,7 @@ MediaDecoderStateMachine::OnCDMProxyReady(RefPtr<CDMProxy> aProxy)
mCDMProxy = aProxy;
mReader->SetCDMProxy(aProxy);
if (mState == DECODER_STATE_WAIT_FOR_CDM) {
SetState(DECODER_STATE_DECODING);
SetState(DECODER_STATE_DECODING_FIRSTFRAME);
}
}

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

@ -158,6 +158,7 @@ public:
DECODER_STATE_DECODING_METADATA,
DECODER_STATE_WAIT_FOR_CDM,
DECODER_STATE_DORMANT,
DECODER_STATE_DECODING_FIRSTFRAME,
DECODER_STATE_DECODING,
DECODER_STATE_SEEKING,
DECODER_STATE_BUFFERING,
@ -473,8 +474,10 @@ protected:
// If we don't, switch to buffering mode.
void MaybeStartBuffering();
// Moves the decoder into decoding state. Called on the state machine
// thread. The decoder monitor must be held.
// The entry action of DECODER_STATE_DECODING_FIRSTFRAME.
void DecodeFirstFrame();
// The entry action of DECODER_STATE_DECODING.
void StartDecoding();
// Moves the decoder into the shutdown state, and dispatches an error
@ -483,11 +486,6 @@ protected:
// decode thread.
void DecodeError();
// Dispatches a task to the decode task queue to begin decoding metadata.
// This is threadsafe and can be called on any thread.
// The decoder monitor must be held.
nsresult EnqueueDecodeMetadataTask();
// Dispatches a LoadedMetadataEvent.
// This is threadsafe and can be called on any thread.
// The decoder monitor must be held.
@ -498,25 +496,19 @@ protected:
// Clears any previous seeking state and initiates a new seek on the decoder.
RefPtr<MediaDecoder::SeekPromise> InitiateSeek(SeekJob aSeekJob);
nsresult DispatchAudioDecodeTaskIfNeeded();
void DispatchAudioDecodeTaskIfNeeded();
void DispatchVideoDecodeTaskIfNeeded();
// Dispatch a task to decode audio if there is not.
void EnsureAudioDecodeTaskQueued();
// Dispatch a task to decode video if there is not.
void EnsureVideoDecodeTaskQueued();
// Ensures a task to decode audio has been dispatched to the decode task queue.
// If a task to decode has already been dispatched, this does nothing,
// otherwise this dispatches a task to do the decode.
// This is called on the state machine or decode threads.
// The decoder monitor must be held.
nsresult EnsureAudioDecodeTaskQueued();
// Start a task to decode audio.
// The decoder monitor must be held.
void RequestAudioData();
nsresult DispatchVideoDecodeTaskIfNeeded();
// Ensures a task to decode video has been dispatched to the decode task queue.
// If a task to decode has already been dispatched, this does nothing,
// otherwise this dispatches a task to do the decode.
// The decoder monitor must be held.
nsresult EnsureVideoDecodeTaskQueued();
// Start a task to decode video.
// The decoder monitor must be held.
void RequestVideoData();
@ -550,11 +542,9 @@ protected:
void OnMetadataRead(MetadataHolder* aMetadata);
void OnMetadataNotRead(ReadMetadataFailureReason aReason);
// Checks whether we're finished decoding first audio and/or video packets.
// If so will trigger firing loadeddata event.
// If there are any queued seek, will change state to DECODER_STATE_SEEKING
// and return true.
bool MaybeFinishDecodeFirstFrame();
// Notify FirstFrameLoaded if having decoded first frames and
// transition to SEEKING if there is any pending seek, or DECODING otherwise.
void MaybeFinishDecodeFirstFrame();
void FinishDecodeFirstFrame();
@ -564,10 +554,8 @@ protected:
// Queries our state to see whether the decode has finished for all streams.
bool CheckIfDecodeComplete();
// Performs one "cycle" of the state machine. Polls the state, and may send
// a video frame to be displayed, and generally manages the decode. Called
// periodically via timer to ensure the video stays in sync.
nsresult RunStateMachine();
// Performs one "cycle" of the state machine.
void RunStateMachine();
bool IsStateMachineScheduled() const;

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

@ -294,6 +294,12 @@ void VideoFrameContainer::ClearFutureFrames()
}
}
void
VideoFrameContainer::ClearCachedResources()
{
mImageContainer->ClearCachedResources();
}
ImageContainer* VideoFrameContainer::GetImageContainer() {
return mImageContainer;
}

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

@ -73,6 +73,9 @@ public:
// but was actually painted at t+n, this returns n in seconds. Threadsafe.
double GetFrameDelay();
// Clear any resources that are not immediately necessary.
void ClearCachedResources();
// Returns a new frame ID for SetCurrentFrames(). The client must either
// call this on only one thread or provide barriers. Do not use together
// with SetCurrentFrame().

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

@ -145,6 +145,9 @@ VideoSink::SetPlaying(bool aPlaying)
mUpdateScheduler.Reset();
// Since playback is paused, tell compositor to render only current frame.
RenderVideoFrames(1);
if (mContainer) {
mContainer->ClearCachedResources();
}
}
mAudioSink->SetPlaying(aPlaying);

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

@ -54,9 +54,14 @@ StaticMutex AudioInputCubeb::sMutex;
void AudioInputCubeb::UpdateDeviceList()
{
cubeb* cubebContext = CubebUtils::GetCubebContext();
if (!cubebContext) {
return;
}
cubeb_device_collection *devices = nullptr;
if (CUBEB_OK != cubeb_enumerate_devices(CubebUtils::GetCubebContext(),
if (CUBEB_OK != cubeb_enumerate_devices(cubebContext,
CUBEB_DEVICE_TYPE_INPUT,
&devices)) {
return;

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

@ -72,7 +72,7 @@ AvailabilityCollection::Remove(PresentationAvailability* aAvailability)
}
already_AddRefed<PresentationAvailability>
AvailabilityCollection::Find(const uint64_t aWindowId, const nsAString& aUrl)
AvailabilityCollection::Find(const uint64_t aWindowId, const nsTArray<nsString>& aUrls)
{
MOZ_ASSERT(NS_IsMainThread());
@ -85,7 +85,7 @@ AvailabilityCollection::Find(const uint64_t aWindowId, const nsAString& aUrl)
continue;
}
if (availability->Equals(aWindowId, aUrl)) {
if (availability->Equals(aWindowId, aUrls)) {
RefPtr<PresentationAvailability> matchedAvailability = availability.get();
return matchedAvailability.forget();
}

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

@ -27,7 +27,7 @@ public:
void Remove(PresentationAvailability* aAvailability);
already_AddRefed<PresentationAvailability>
Find(const uint64_t aWindowId, const nsAString& aUrl);
Find(const uint64_t aWindowId, const nsTArray<nsString>& aUrls);
private:
friend class StaticAutoPtr<AvailabilityCollection>;

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

@ -37,20 +37,20 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
/* static */ already_AddRefed<PresentationAvailability>
PresentationAvailability::Create(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl,
const nsTArray<nsString>& aUrls,
RefPtr<Promise>& aPromise)
{
RefPtr<PresentationAvailability> availability =
new PresentationAvailability(aWindow, aUrl);
new PresentationAvailability(aWindow, aUrls);
return NS_WARN_IF(!availability->Init(aPromise)) ? nullptr
: availability.forget();
}
PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl)
const nsTArray<nsString>& aUrls)
: DOMEventTargetHelper(aWindow)
, mIsAvailable(false)
, mUrl(aUrl)
, mUrls(aUrls)
{
}
@ -120,10 +120,15 @@ PresentationAvailability::WrapObject(JSContext* aCx,
bool
PresentationAvailability::Equals(const uint64_t aWindowID,
const nsAString& aUrl) const
const nsTArray<nsString>& aUrls) const
{
if (GetOwner() && GetOwner()->WindowID() == aWindowID &&
mUrl.Equals(aUrl)) {
mUrls.Length() == aUrls.Length()) {
for (const auto& url : aUrls) {
if (!mUrls.Contains(url)) {
return false;
}
}
return true;
}
@ -162,8 +167,7 @@ PresentationAvailability::NotifyAvailableChange(bool aIsAvailable)
void
PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)
{
PRES_DEBUG("%s:id[%s]\n", __func__,
NS_ConvertUTF16toUTF8(mUrl).get());
PRES_DEBUG("%s\n", __func__);
bool isChanged = (aIsAvailable != mIsAvailable);
mIsAvailable = aIsAvailable;

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

@ -29,7 +29,7 @@ public:
static already_AddRefed<PresentationAvailability>
Create(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl,
const nsTArray<nsString>& aUrls,
RefPtr<Promise>& aPromise);
virtual void DisconnectFromOwner() override;
@ -37,7 +37,7 @@ public:
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
bool Equals(const uint64_t aWindowID, const nsAString& aUrl) const;
bool Equals(const uint64_t aWindowID, const nsTArray<nsString>& aUrls) const;
bool IsCachedValueReady();
@ -50,7 +50,7 @@ public:
private:
explicit PresentationAvailability(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl);
const nsTArray<nsString>& aUrls);
virtual ~PresentationAvailability();
@ -64,7 +64,7 @@ private:
nsTArray<RefPtr<Promise>> mPromises;
nsString mUrl;
nsTArray<nsString> mUrls;
};
} // namespace dom

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

@ -25,11 +25,9 @@ using namespace mozilla::dom;
NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback)
PresentationRequesterCallback::PresentationRequesterCallback(PresentationRequest* aRequest,
const nsAString& aUrl,
const nsAString& aSessionId,
Promise* aPromise)
: mRequest(aRequest)
, mUrl(aUrl)
, mSessionId(aSessionId)
, mPromise(aPromise)
{
@ -44,12 +42,16 @@ PresentationRequesterCallback::~PresentationRequesterCallback()
// nsIPresentationServiceCallback
NS_IMETHODIMP
PresentationRequesterCallback::NotifySuccess()
PresentationRequesterCallback::NotifySuccess(const nsAString& aUrl)
{
MOZ_ASSERT(NS_IsMainThread());
if (aUrl.IsEmpty()) {
return NotifyError(NS_ERROR_DOM_OPERATION_ERR);
}
RefPtr<PresentationConnection> connection =
PresentationConnection::Create(mRequest->GetOwner(), mSessionId, mUrl,
PresentationConnection::Create(mRequest->GetOwner(), mSessionId, aUrl,
nsIPresentationService::ROLE_CONTROLLER);
if (NS_WARN_IF(!connection)) {
mPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
@ -79,11 +81,10 @@ NS_IMPL_ISUPPORTS_INHERITED0(PresentationReconnectCallback,
PresentationReconnectCallback::PresentationReconnectCallback(
PresentationRequest* aRequest,
const nsAString& aUrl,
const nsAString& aSessionId,
Promise* aPromise,
PresentationConnection* aConnection)
: PresentationRequesterCallback(aRequest, aUrl, aSessionId, aPromise)
: PresentationRequesterCallback(aRequest, aSessionId, aPromise)
, mConnection(aConnection)
{
}
@ -93,7 +94,7 @@ PresentationReconnectCallback::~PresentationReconnectCallback()
}
NS_IMETHODIMP
PresentationReconnectCallback::NotifySuccess()
PresentationReconnectCallback::NotifySuccess(const nsAString& aUrl)
{
MOZ_ASSERT(NS_IsMainThread());
@ -116,7 +117,7 @@ PresentationReconnectCallback::NotifySuccess()
} else {
// Use |PresentationRequesterCallback::NotifySuccess| to create a new
// connection since we don't find one that can be reused.
rv = PresentationRequesterCallback::NotifySuccess();
rv = PresentationRequesterCallback::NotifySuccess(aUrl);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

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

@ -31,7 +31,6 @@ public:
NS_DECL_NSIPRESENTATIONSERVICECALLBACK
PresentationRequesterCallback(PresentationRequest* aRequest,
const nsAString& aUrl,
const nsAString& aSessionId,
Promise* aPromise);
@ -39,7 +38,6 @@ protected:
virtual ~PresentationRequesterCallback();
RefPtr<PresentationRequest> mRequest;
nsString mUrl;
nsString mSessionId;
RefPtr<Promise> mPromise;
};
@ -51,7 +49,6 @@ public:
NS_DECL_NSIPRESENTATIONSERVICECALLBACK
PresentationReconnectCallback(PresentationRequest* aRequest,
const nsAString& aUrl,
const nsAString& aSessionId,
Promise* aPromise,
PresentationConnection* aConnection);

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

@ -12,6 +12,7 @@
#include "mozilla/dom/PresentationRequestBinding.h"
#include "mozilla/dom/PresentationConnectionAvailableEvent.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Move.h"
#include "mozIThirdPartyUtil.h"
#include "nsContentSecurityManager.h"
#include "nsCycleCollectionParticipant.h"
@ -64,6 +65,16 @@ GetAbsoluteURL(const nsAString& aUrl,
PresentationRequest::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
ErrorResult& aRv)
{
Sequence<nsString> urls;
urls.AppendElement(aUrl, fallible);
return Constructor(aGlobal, urls, aRv);
}
/* static */ already_AddRefed<PresentationRequest>
PresentationRequest::Constructor(const GlobalObject& aGlobal,
const Sequence<nsString>& aUrls,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
@ -71,31 +82,35 @@ PresentationRequest::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
// Ensure the URL is not empty.
if (NS_WARN_IF(aUrl.IsEmpty())) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
if (aUrls.IsEmpty()) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
// Resolve relative URL to absolute URL
nsCOMPtr<nsIURI> baseUri = window->GetDocBaseURI();
nsTArray<nsString> urls;
for (const auto& url : aUrls) {
nsAutoString absoluteUrl;
nsresult rv =
GetAbsoluteURL(url, baseUri, window->GetExtantDoc(), absoluteUrl);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
nsAutoString absoluteUrl;
nsresult rv = GetAbsoluteURL(aUrl, baseUri, window->GetExtantDoc(), absoluteUrl);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
urls.AppendElement(absoluteUrl);
}
RefPtr<PresentationRequest> request =
new PresentationRequest(window, absoluteUrl);
new PresentationRequest(window, Move(urls));
return NS_WARN_IF(!request->Init()) ? nullptr : request.forget();
}
PresentationRequest::PresentationRequest(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl)
nsTArray<nsString>&& aUrls)
: DOMEventTargetHelper(aWindow)
, mUrl(aUrl)
, mUrls(Move(aUrls))
{
}
@ -152,7 +167,7 @@ PresentationRequest::StartWithDevice(const nsAString& aDeviceId,
}
if (IsProhibitMixedSecurityContexts(doc) &&
!IsPrioriAuthenticatedURL(mUrl)) {
!IsAllURLAuthenticated()) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
@ -185,8 +200,8 @@ PresentationRequest::StartWithDevice(const nsAString& aDeviceId,
}
nsCOMPtr<nsIPresentationServiceCallback> callback =
new PresentationRequesterCallback(this, mUrl, id, promise);
rv = service->StartSession(mUrl, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
new PresentationRequesterCallback(this, id, promise);
rv = service->StartSession(mUrls, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
}
@ -216,7 +231,7 @@ PresentationRequest::Reconnect(const nsAString& aPresentationId,
}
if (IsProhibitMixedSecurityContexts(doc) &&
!IsPrioriAuthenticatedURL(mUrl)) {
!IsAllURLAuthenticated()) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
@ -262,7 +277,7 @@ PresentationRequest::FindOrCreatePresentationConnection(
if (connection) {
nsAutoString url;
connection->GetUrl(url);
if (url.Equals(mUrl)) {
if (mUrls.Contains(url)) {
switch (connection->State()) {
case PresentationConnectionState::Closed:
// We found the matched connection.
@ -293,13 +308,12 @@ PresentationRequest::FindOrCreatePresentationConnection(
nsCOMPtr<nsIPresentationServiceCallback> callback =
new PresentationReconnectCallback(this,
mUrl,
aPresentationId,
aPromise,
connection);
nsresult rv =
service->ReconnectSession(mUrl,
service->ReconnectSession(mUrls,
aPresentationId,
nsIPresentationService::ROLE_CONTROLLER,
callback);
@ -311,8 +325,7 @@ PresentationRequest::FindOrCreatePresentationConnection(
already_AddRefed<Promise>
PresentationRequest::GetAvailability(ErrorResult& aRv)
{
PRES_DEBUG("%s:id[%s]\n", __func__,
NS_ConvertUTF16toUTF8(mUrl).get());
PRES_DEBUG("%s\n", __func__);
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
if (NS_WARN_IF(!global)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
@ -331,7 +344,7 @@ PresentationRequest::GetAvailability(ErrorResult& aRv)
}
if (IsProhibitMixedSecurityContexts(doc) &&
!IsPrioriAuthenticatedURL(mUrl)) {
!IsAllURLAuthenticated()) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
@ -363,13 +376,12 @@ PresentationRequest::FindOrCreatePresentationAvailability(RefPtr<Promise>& aProm
}
RefPtr<PresentationAvailability> availability =
collection->Find(GetOwner()->WindowID(), mUrl);
collection->Find(GetOwner()->WindowID(), mUrls);
if (!availability) {
availability = PresentationAvailability::Create(GetOwner(), mUrl, aPromise);
availability = PresentationAvailability::Create(GetOwner(), mUrls, aPromise);
} else {
PRES_DEBUG(">resolve with same object:id[%s]\n",
NS_ConvertUTF16toUTF8(mUrl).get());
PRES_DEBUG(">resolve with same object\n");
// Fetching cached available devices is asynchronous in our implementation,
// we need to ensure the promise is resolved in order.
@ -474,3 +486,15 @@ PresentationRequest::IsPrioriAuthenticatedURL(const nsAString& aUrl)
csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
return isTrustworthyOrigin;
}
bool
PresentationRequest::IsAllURLAuthenticated()
{
for (const auto& url : mUrls) {
if (!IsPrioriAuthenticatedURL(url)) {
return false;
}
}
return true;
}

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

@ -7,6 +7,7 @@
#ifndef mozilla_dom_PresentationRequest_h
#define mozilla_dom_PresentationRequest_h
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/DOMEventTargetHelper.h"
class nsIDocument;
@ -23,9 +24,15 @@ class PresentationRequest final : public DOMEventTargetHelper
public:
NS_DECL_ISUPPORTS_INHERITED
static already_AddRefed<PresentationRequest> Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
ErrorResult& aRv);
static already_AddRefed<PresentationRequest> Constructor(
const GlobalObject& aGlobal,
const nsAString& aUrl,
ErrorResult& aRv);
static already_AddRefed<PresentationRequest> Constructor(
const GlobalObject& aGlobal,
const Sequence<nsString>& aUrls,
ErrorResult& aRv);
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
@ -47,7 +54,7 @@ public:
private:
PresentationRequest(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl);
nsTArray<nsString>&& aUrls);
~PresentationRequest();
@ -64,7 +71,9 @@ private:
// Implement https://w3c.github.io/webappsec-mixed-content/#a-priori-authenticated-url
bool IsPrioriAuthenticatedURL(const nsAString& aUrl);
nsString mUrl;
bool IsAllURLAuthenticated();
nsTArray<nsString> mUrls;
};
} // namespace dom

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

@ -59,6 +59,44 @@ IsSameDevice(nsIPresentationDevice* aDevice, nsIPresentationDevice* aDeviceAnoth
return true;
}
static nsresult
ConvertURLArrayHelper(const nsTArray<nsString>& aUrls, nsIArray** aResult)
{
if (!aResult) {
return NS_ERROR_INVALID_POINTER;
}
*aResult = nullptr;
nsresult rv;
nsCOMPtr<nsIMutableArray> urls =
do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
for (const auto& url : aUrls) {
nsCOMPtr<nsISupportsString> isupportsString =
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = isupportsString->SetData(url);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = urls->AppendElement(isupportsString, false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
urls.forget(aResult);
return NS_OK;
}
/*
* Implementation of PresentationDeviceRequest
*/
@ -69,7 +107,7 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPRESENTATIONDEVICEREQUEST
PresentationDeviceRequest(const nsAString& aRequestUrl,
PresentationDeviceRequest(const nsTArray<nsString>& aUrls,
const nsAString& aId,
const nsAString& aOrigin,
uint64_t aWindowId,
@ -77,9 +115,10 @@ public:
private:
virtual ~PresentationDeviceRequest() = default;
nsresult CreateSessionInfo(nsIPresentationDevice* aDevice);
nsresult CreateSessionInfo(nsIPresentationDevice* aDevice,
const nsAString& aSelectedRequestUrl);
nsString mRequestUrl;
nsTArray<nsString> mRequestUrls;
nsString mId;
nsString mOrigin;
uint64_t mWindowId;
@ -94,18 +133,18 @@ LazyLogModule gPresentationLog("Presentation");
NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
PresentationDeviceRequest::PresentationDeviceRequest(
const nsAString& aRequestUrl,
const nsTArray<nsString>& aUrls,
const nsAString& aId,
const nsAString& aOrigin,
uint64_t aWindowId,
nsIPresentationServiceCallback* aCallback)
: mRequestUrl(aRequestUrl)
: mRequestUrls(aUrls)
, mId(aId)
, mOrigin(aOrigin)
, mWindowId(aWindowId)
, mCallback(aCallback)
{
MOZ_ASSERT(!mRequestUrl.IsEmpty());
MOZ_ASSERT(!mRequestUrls.IsEmpty());
MOZ_ASSERT(!mId.IsEmpty());
MOZ_ASSERT(!mOrigin.IsEmpty());
MOZ_ASSERT(mCallback);
@ -119,29 +158,47 @@ PresentationDeviceRequest::GetOrigin(nsAString& aOrigin)
}
NS_IMETHODIMP
PresentationDeviceRequest::GetRequestURL(nsAString& aRequestUrl)
PresentationDeviceRequest::GetRequestURLs(nsIArray** aUrls)
{
aRequestUrl = mRequestUrl;
return NS_OK;
return ConvertURLArrayHelper(mRequestUrls, aUrls);
}
NS_IMETHODIMP
PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aDevice);
nsresult rv = CreateSessionInfo(aDevice);
if (NS_WARN_IF(NS_FAILED(rv))) {
mCallback->NotifyError(rv);
return rv;
if (NS_WARN_IF(!aDevice)) {
MOZ_ASSERT(false, "|aDevice| should noe be null.");
mCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
return NS_ERROR_INVALID_ARG;
}
return mCallback->NotifySuccess();
// Select the most suitable URL for starting the presentation.
nsAutoString selectedRequestUrl;
for (const auto& url : mRequestUrls) {
bool isSupported;
if (NS_SUCCEEDED(aDevice->IsRequestedUrlSupported(url, &isSupported)) &&
isSupported) {
selectedRequestUrl.Assign(url);
break;
}
}
if (selectedRequestUrl.IsEmpty()) {
return mCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
}
if (NS_WARN_IF(NS_FAILED(CreateSessionInfo(aDevice, selectedRequestUrl)))) {
return mCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
}
return mCallback->NotifySuccess(selectedRequestUrl);
}
nsresult
PresentationDeviceRequest::CreateSessionInfo(nsIPresentationDevice* aDevice)
PresentationDeviceRequest::CreateSessionInfo(
nsIPresentationDevice* aDevice,
const nsAString& aSelectedRequestUrl)
{
nsCOMPtr<nsIPresentationService> service =
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
@ -152,7 +209,7 @@ PresentationDeviceRequest::CreateSessionInfo(nsIPresentationDevice* aDevice)
// Create the controlling session info
RefPtr<PresentationSessionInfo> info =
static_cast<PresentationService*>(service.get())->
CreateControllingSessionInfo(mRequestUrl, mId, mWindowId);
CreateControllingSessionInfo(aSelectedRequestUrl, mId, mWindowId);
if (NS_WARN_IF(!info)) {
return NS_ERROR_NOT_AVAILABLE;
}
@ -572,23 +629,22 @@ PresentationService::IsAppInstalled(nsIURI* aUri)
}
NS_IMETHODIMP
PresentationService::StartSession(const nsAString& aUrl,
PresentationService::StartSession(const nsTArray<nsString>& aUrls,
const nsAString& aSessionId,
const nsAString& aOrigin,
const nsAString& aDeviceId,
uint64_t aWindowId,
nsIPresentationServiceCallback* aCallback)
{
PRES_DEBUG("%s:url[%s], id[%s]\n", __func__,
NS_ConvertUTF16toUTF8(aUrl).get(),
NS_ConvertUTF16toUTF8(aSessionId).get());
PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCallback);
MOZ_ASSERT(!aSessionId.IsEmpty());
MOZ_ASSERT(!aUrls.IsEmpty());
nsCOMPtr<nsIPresentationDeviceRequest> request =
new PresentationDeviceRequest(aUrl,
new PresentationDeviceRequest(aUrls,
aSessionId,
aOrigin,
aWindowId,
@ -617,15 +673,11 @@ PresentationService::StartSession(const nsAString& aUrl,
return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
}
nsCOMPtr<nsIMutableArray> presentationUrls =
do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!presentationUrls) {
nsCOMPtr<nsIArray> presentationUrls;
if (NS_WARN_IF(NS_FAILED(
ConvertURLArrayHelper(aUrls, getter_AddRefs(presentationUrls))))) {
return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
}
nsCOMPtr<nsISupportsString> supportsStr =
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
supportsStr->SetData(aUrl);
presentationUrls->AppendElement(supportsStr, false);
nsCOMPtr<nsIArray> devices;
nsresult rv = deviceManager->GetAvailableDevices(presentationUrls, getter_AddRefs(devices));
@ -747,18 +799,17 @@ PresentationService::TerminateSession(const nsAString& aSessionId,
}
NS_IMETHODIMP
PresentationService::ReconnectSession(const nsAString& aUrl,
PresentationService::ReconnectSession(const nsTArray<nsString>& aUrls,
const nsAString& aSessionId,
uint8_t aRole,
nsIPresentationServiceCallback* aCallback)
{
PRES_DEBUG("%s:url[%s], id[%s]\n", __func__,
NS_ConvertUTF16toUTF8(aUrl).get(),
NS_ConvertUTF16toUTF8(aSessionId).get());
PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!aSessionId.IsEmpty());
MOZ_ASSERT(aCallback);
MOZ_ASSERT(!aUrls.IsEmpty());
if (aRole != nsIPresentationService::ROLE_CONTROLLER) {
MOZ_ASSERT(false, "Only controller can call ReconnectSession.");
@ -774,7 +825,7 @@ PresentationService::ReconnectSession(const nsAString& aUrl,
return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
}
if (NS_WARN_IF(!info->GetUrl().Equals(aUrl))) {
if (NS_WARN_IF(!aUrls.Contains(info->GetUrl()))) {
return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
}

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

@ -806,7 +806,7 @@ PresentationControllingInfo::NotifyReconnected()
return NS_ERROR_FAILURE;
}
return mReconnectCallback->NotifySuccess();
return mReconnectCallback->NotifySuccess(GetUrl());
}
nsresult

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

@ -4,6 +4,7 @@
#include "nsISupports.idl"
interface nsIArray;
interface nsIPresentationDevice;
%{C++
@ -19,8 +20,8 @@ interface nsIPresentationDeviceRequest : nsISupports
// The origin which initiate the request.
readonly attribute DOMString origin;
// The URL to be opened after selection.
readonly attribute DOMString requestURL;
// The array of candidate URLs.
readonly attribute nsIArray requestURLs;
/*
* Callback after selecting a device

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

@ -15,15 +15,23 @@ interface nsIPresentationSessionListener;
{ 0x9e, 0x4f, 0x40, 0x58, 0xb8, 0x51, 0x98, 0x32 } }
#define PRESENTATION_SERVICE_CONTRACTID \
"@mozilla.org/presentation/presentationservice;1"
#include "nsTArray.h"
class nsString;
%}
[ref] native URLArrayRef(const nsTArray<nsString>);
[scriptable, uuid(12073206-0065-4b10-9488-a6eb9b23e65b)]
interface nsIPresentationServiceCallback : nsISupports
{
/*
* Called when the operation succeeds.
*
* @param url: the selected request url used to start or reconnect a session.
*/
void notifySuccess();
void notifySuccess(in DOMString url);
/*
* Called when the operation fails.
@ -47,7 +55,7 @@ interface nsIPresentationService : nsISupports
* Start a new presentation session and display a prompt box which asks users
* to select a device.
*
* @param url: The url of presenting page.
* @param urls: The candidate Urls of presenting page. Only one url would be used.
* @param sessionId: An ID to identify presentation session.
* @param origin: The url of requesting page.
* @param deviceId: The specified device of handling this request, null string
@ -61,12 +69,12 @@ interface nsIPresentationService : nsISupports
* established successfully with the selected device.
* Otherwise, NotifyError() is called with a error message.
*/
void startSession(in DOMString url,
in DOMString sessionId,
in DOMString origin,
in DOMString deviceId,
in unsigned long long windowId,
in nsIPresentationServiceCallback callback);
[noscript] void startSession(in URLArrayRef urls,
in DOMString sessionId,
in DOMString origin,
in DOMString deviceId,
in unsigned long long windowId,
in nsIPresentationServiceCallback callback);
/*
* Send the message to the session.
@ -101,17 +109,17 @@ interface nsIPresentationService : nsISupports
/*
* Reconnect the session.
*
* @param url: The url of presenting page.
* @param url: The request Urls.
* @param sessionId: An ID to identify presentation session.
* @param role: Identify the function called by controller or receiver.
* @param callback: NotifySuccess() is called when a control channel
* is opened successfully.
* Otherwise, NotifyError() is called with a error message.
*/
void reconnectSession(in DOMString url,
in DOMString sessionId,
in uint8_t role,
in nsIPresentationServiceCallback callback);
[noscript] void reconnectSession(in URLArrayRef urls,
in DOMString sessionId,
in uint8_t role,
in nsIPresentationServiceCallback callback);
/*
* Register an availability listener. Must be called from the main thread.

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

@ -15,7 +15,7 @@ namespace dom {
struct StartSessionRequest
{
nsString url;
nsString[] urls;
nsString sessionId;
nsString origin;
nsString deviceId;
@ -44,7 +44,7 @@ struct TerminateSessionRequest
struct ReconnectSessionRequest
{
nsString url;
nsString[] urls;
nsString sessionId;
uint8_t role;
};

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

@ -15,6 +15,7 @@ sync protocol PPresentationRequest
child:
async __delete__(nsresult result);
async NotifyRequestUrlSelected(nsString aUrl);
};
} // namespace dom

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

@ -163,12 +163,17 @@ PresentationRequestChild::Recv__delete__(const nsresult& aResult)
}
if (mCallback) {
if (NS_SUCCEEDED(aResult)) {
NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess()));
} else {
if (NS_FAILED(aResult)) {
NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aResult)));
}
}
return true;
}
bool
PresentationRequestChild::RecvNotifyRequestUrlSelected(const nsString& aUrl)
{
NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess(aUrl)));
return true;
}

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

@ -78,6 +78,9 @@ public:
virtual bool
Recv__delete__(const nsresult& aResult) override;
virtual bool
RecvNotifyRequestUrlSelected(const nsString& aUrl) override;
private:
virtual ~PresentationRequestChild();

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

@ -52,7 +52,7 @@ PresentationIPCService::~PresentationIPCService()
}
NS_IMETHODIMP
PresentationIPCService::StartSession(const nsAString& aUrl,
PresentationIPCService::StartSession(const nsTArray<nsString>& aUrls,
const nsAString& aSessionId,
const nsAString& aOrigin,
const nsAString& aDeviceId,
@ -65,7 +65,7 @@ PresentationIPCService::StartSession(const nsAString& aUrl,
nsIPresentationService::ROLE_CONTROLLER);
}
return SendRequest(aCallback, StartSessionRequest(nsString(aUrl),
return SendRequest(aCallback, StartSessionRequest(aUrls,
nsString(aSessionId),
nsString(aOrigin),
nsString(aDeviceId),
@ -135,7 +135,7 @@ PresentationIPCService::TerminateSession(const nsAString& aSessionId,
}
NS_IMETHODIMP
PresentationIPCService::ReconnectSession(const nsAString& aUrl,
PresentationIPCService::ReconnectSession(const nsTArray<nsString>& aUrls,
const nsAString& aSessionId,
uint8_t aRole,
nsIPresentationServiceCallback* aCallback)
@ -147,7 +147,7 @@ PresentationIPCService::ReconnectSession(const nsAString& aUrl,
return NS_ERROR_INVALID_ARG;
}
return SendRequest(aCallback, ReconnectSessionRequest(nsString(aUrl),
return SendRequest(aCallback, ReconnectSessionRequest(aUrls,
nsString(aSessionId),
aRole));
}

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

@ -6,6 +6,7 @@
#include "DCPresentationChannelDescription.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/Unused.h"
#include "nsIPresentationDeviceManager.h"
#include "nsServiceManagerUtils.h"
#include "PresentationBuilderParent.h"
@ -333,7 +334,7 @@ PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest)
MOZ_ASSERT(mService);
mNeedRegisterBuilder = true;
mSessionId = aRequest.sessionId();
return mService->StartSession(aRequest.url(), aRequest.sessionId(),
return mService->StartSession(aRequest.urls(), aRequest.sessionId(),
aRequest.origin(), aRequest.deviceId(),
aRequest.windowId(), this);
}
@ -347,16 +348,16 @@ PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
// compromised child process can't fake the ID.
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
return SendResponse(NS_ERROR_DOM_SECURITY_ERR);
}
nsresult rv = mService->SendSessionMessage(aRequest.sessionId(),
aRequest.role(),
aRequest.data());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyError(rv);
return SendResponse(rv);
}
return NotifySuccess();
return SendResponse(NS_OK);
}
nsresult
@ -368,16 +369,16 @@ PresentationRequestParent::DoRequest(const CloseSessionRequest& aRequest)
// compromised child process can't fake the ID.
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
return SendResponse(NS_ERROR_DOM_SECURITY_ERR);
}
nsresult rv = mService->CloseSession(aRequest.sessionId(),
aRequest.role(),
aRequest.closedReason());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyError(rv);
return SendResponse(rv);
}
return NotifySuccess();
return SendResponse(NS_OK);
}
nsresult
@ -389,14 +390,14 @@ PresentationRequestParent::DoRequest(const TerminateSessionRequest& aRequest)
// compromised child process can't fake the ID.
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
return SendResponse(NS_ERROR_DOM_SECURITY_ERR);
}
nsresult rv = mService->TerminateSession(aRequest.sessionId(), aRequest.role());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyError(rv);
return SendResponse(rv);
}
return NotifySuccess();
return SendResponse(NS_OK);
}
nsresult
@ -411,12 +412,12 @@ PresentationRequestParent::DoRequest(const ReconnectSessionRequest& aRequest)
// NOTE: Return NS_ERROR_DOM_NOT_FOUND_ERR here to match the spec.
// https://w3c.github.io/presentation-api/#reconnecting-to-a-presentation
return NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
return SendResponse(NS_ERROR_DOM_NOT_FOUND_ERR);
}
mNeedRegisterBuilder = true;
mSessionId = aRequest.sessionId();
return mService->ReconnectSession(aRequest.url(),
return mService->ReconnectSession(aRequest.urls(),
aRequest.sessionId(),
aRequest.role(),
this);
@ -431,18 +432,18 @@ PresentationRequestParent::DoRequest(const BuildTransportRequest& aRequest)
// compromised child process can't fake the ID.
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
return SendResponse(NS_ERROR_DOM_SECURITY_ERR);
}
nsresult rv = mService->BuildTransport(aRequest.sessionId(), aRequest.role());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyError(rv);
return SendResponse(rv);
}
return NotifySuccess();
return SendResponse(NS_OK);
}
NS_IMETHODIMP
PresentationRequestParent::NotifySuccess()
PresentationRequestParent::NotifySuccess(const nsAString& aUrl)
{
if (mNeedRegisterBuilder) {
RefPtr<PresentationParent> parent = static_cast<PresentationParent*>(Manager());
@ -451,6 +452,7 @@ PresentationRequestParent::NotifySuccess()
nsIPresentationService::ROLE_CONTROLLER));
}
Unused << SendNotifyRequestUrlSelected(nsString(aUrl));
return SendResponse(NS_OK);
}

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

@ -238,6 +238,29 @@ function teardown() {
gScript.sendAsyncMessage('teardown');
}
function testConstructRequestError() {
return Promise.all([
new Promise(function(aResolve, aReject) {
try {
request = new PresentationRequest("\\\\\\");
}
catch(e) {
is(e.name, "SyntaxError", "Expect to get SyntaxError.");
aResolve();
}
}),
new Promise(function(aResolve, aReject) {
try {
request = new PresentationRequest([]);
}
catch(e) {
is(e.name, "NotSupportedError", "Expect to get NotSupportedError.");
aResolve();
}
}),
]);
}
function runTests() {
ok(window.PresentationRequest, "PresentationRequest should be available.");
@ -248,6 +271,7 @@ function runTests() {
then(testCloseConnection).
then(testReconnect).
then(testCloseConnection).
then(testConstructRequestError).
then(teardown);
}

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

@ -41,17 +41,6 @@ function setup() {
});
}
function testCreateRequestWithEmptyURL() {
return new Promise(function(aResolve, aReject) {
try {
request = new PresentationRequest("");
} catch (aError) {
is(aError.name, "SyntaxError", "SyntaxError is expected when using an empty URL.");
aResolve();
}
});
}
function testStartConnectionCancelPrompt() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
@ -380,8 +369,7 @@ function teardown() {
function runTests() {
ok(window.PresentationRequest, "PresentationRequest should be available.");
testCreateRequestWithEmptyURL().
then(setup).
setup().
then(testStartConnectionCancelPrompt).
then(testStartConnectionNoDevice).
then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit).

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

@ -5,6 +5,7 @@
*/
[Constructor(DOMString url),
Constructor(sequence<DOMString> urls),
Pref="dom.presentation.controller.enabled"]
interface PresentationRequest : EventTarget {
/*

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

@ -481,28 +481,28 @@ CreateGlobalDevModeAndInit(const nsXPIDLString& aPrintName,
nsAutoPrinter autoPrinter(hPrinter);
// Get the buffer size
DWORD dwNeeded = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
nullptr, 0);
if (dwNeeded == 0) {
LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
nullptr, 0);
if (needed < 0) {
return nsReturnRef<nsHGLOBAL>();
}
// Allocate a buffer of the correct size.
nsAutoDevMode newDevMode((LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY,
dwNeeded));
needed));
if (!newDevMode) {
return nsReturnRef<nsHGLOBAL>();
}
nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, dwNeeded);
nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
nsAutoGlobalMem globalDevMode(hDevMode);
if (!hDevMode) {
return nsReturnRef<nsHGLOBAL>();
}
DWORD dwRet = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
nullptr, DM_OUT_BUFFER);
if (dwRet != IDOK) {
LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
nullptr, DM_OUT_BUFFER);
if (ret != IDOK) {
return nsReturnRef<nsHGLOBAL>();
}
@ -513,16 +513,16 @@ CreateGlobalDevModeAndInit(const nsXPIDLString& aPrintName,
return nsReturnRef<nsHGLOBAL>();
}
memcpy(devMode, newDevMode.get(), dwNeeded);
memcpy(devMode, newDevMode.get(), needed);
// Initialize values from the PrintSettings
nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
MOZ_ASSERT(psWin);
psWin->CopyToNative(devMode);
// Sets back the changes we made to the DevMode into the Printer Driver
dwRet = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
DM_IN_BUFFER | DM_OUT_BUFFER);
if (dwRet != IDOK) {
ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
DM_IN_BUFFER | DM_OUT_BUFFER);
if (ret != IDOK) {
::GlobalUnlock(hDevMode);
return nsReturnRef<nsHGLOBAL>();
}

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

@ -1016,7 +1016,12 @@ gfxFontUtils::RenameFont(const nsAString& aName, const uint8_t *aFontData,
uint16_t nameCount = ArrayLength(neededNameIDs);
// leave room for null-terminator
uint16_t nameStrLength = (aName.Length() + 1) * sizeof(char16_t);
uint32_t nameStrLength = (aName.Length() + 1) * sizeof(char16_t);
if (nameStrLength > 65535) {
// The name length _in bytes_ must fit in an unsigned short field;
// therefore, a name longer than this cannot be used.
return NS_ERROR_FAILURE;
}
// round name table size up to 4-byte multiple
uint32_t nameTableSize = (sizeof(NameHeader) +

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

@ -2515,17 +2515,18 @@ gfxPlatform::InitOpenGLConfig()
openGLFeature.EnableByDefault();
#endif
// When layers acceleration is force-enabled, enable it even for blacklisted
// devices.
if (gfxPrefs::LayersAccelerationForceEnabledDoNotUseDirectly()) {
openGLFeature.UserForceEnable("Force-enabled by pref");
return;
}
nsCString message;
nsCString failureId;
if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &message, failureId)) {
openGLFeature.Disable(FeatureStatus::Blacklisted, message.get(), failureId);
}
// Ensure that an accelerated compositor backend is available when layers
// acceleration is force-enabled.
if (gfxPrefs::LayersAccelerationForceEnabledDoNotUseDirectly()) {
openGLFeature.UserForceEnable("Force-enabled by pref");
}
}
bool

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

@ -111,10 +111,19 @@ typedef enum nsCharType nsCharType;
* Return false, otherwise
*/
#define LRM_CHAR 0x200e
#define RLM_CHAR 0x200f
#define LRE_CHAR 0x202a
#define RLE_CHAR 0x202b
#define PDF_CHAR 0x202c
#define LRO_CHAR 0x202d
#define RLO_CHAR 0x202e
#define LRI_CHAR 0x2066
#define RLI_CHAR 0x2067
#define FSI_CHAR 0x2068
#define PDI_CHAR 0x2069
#define ALM_CHAR 0x061C
inline bool IsBidiControl(uint32_t aChar) {
return ((LRE_CHAR <= aChar && aChar <= RLO_CHAR) ||
@ -123,6 +132,20 @@ typedef enum nsCharType nsCharType;
(aChar & 0xfffffe) == LRM_CHAR);
}
/**
* Give a UTF-32 codepoint
* Return true if the codepoint is a Bidi control character that may result
* in RTL directionality and therefore needs to trigger bidi resolution;
* return false otherwise.
*/
inline bool IsBidiControlRTL(uint32_t aChar) {
return aChar == RLM_CHAR ||
aChar == RLE_CHAR ||
aChar == RLO_CHAR ||
aChar == RLI_CHAR ||
aChar == ALM_CHAR;
}
/**
* Give an nsString.
* @return true if the string contains right-to-left characters

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

@ -54,24 +54,6 @@ namespace {
static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
class XPCShellDirProvider : public nsIDirectoryServiceProvider
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDIRECTORYSERVICEPROVIDER
XPCShellDirProvider() { }
~XPCShellDirProvider() { }
bool SetGREDirs(const char *dir);
void ClearGREDirs() { mGREDir = nullptr;
mGREBinDir = nullptr; }
private:
nsCOMPtr<nsIFile> mGREDir;
nsCOMPtr<nsIFile> mGREBinDir;
};
inline XPCShellEnvironment*
Environment(Handle<JSObject*> global)
{
@ -389,51 +371,6 @@ XPCShellEnvironment::ProcessFile(JSContext *cx,
fprintf(stdout, "\n");
}
NS_IMETHODIMP_(MozExternalRefCountType)
XPCShellDirProvider::AddRef()
{
return 2;
}
NS_IMETHODIMP_(MozExternalRefCountType)
XPCShellDirProvider::Release()
{
return 1;
}
NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider, nsIDirectoryServiceProvider)
bool
XPCShellDirProvider::SetGREDirs(const char *dir)
{
nsresult rv = XRE_GetFileFromPath(dir, getter_AddRefs(mGREDir));
if (NS_SUCCEEDED(rv)) {
mGREDir->Clone(getter_AddRefs(mGREBinDir));
#ifdef XP_MACOSX
mGREBinDir->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
#endif
}
return NS_SUCCEEDED(rv);
}
NS_IMETHODIMP
XPCShellDirProvider::GetFile(const char *prop,
bool *persistent,
nsIFile* *result)
{
if (mGREDir && !strcmp(prop, NS_GRE_DIR)) {
*persistent = true;
NS_ADDREF(*result = mGREDir);
return NS_OK;
} else if (mGREBinDir && !strcmp(prop, NS_GRE_BIN_DIR)) {
*persistent = true;
NS_ADDREF(*result = mGREBinDir);
return NS_OK;
}
return NS_ERROR_FAILURE;
}
// static
XPCShellEnvironment*
XPCShellEnvironment::CreateEnvironment()

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

@ -169,9 +169,13 @@ FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue v
RootedValue value(cx, value_);
mozilla::Maybe<AutoCompartment> ac;
if (!IsWrapper(promiseObj)) {
if (!IsProxy(promiseObj)) {
promise = &promiseObj->as<PromiseObject>();
} else {
if (JS_IsDeadWrapper(promiseObj)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return false;
}
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
ac.emplace(cx, promise);
if (!promise->compartment()->wrap(cx, &value))
@ -190,9 +194,13 @@ RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue re
RootedValue reason(cx, reason_);
mozilla::Maybe<AutoCompartment> ac;
if (!IsWrapper(promiseObj)) {
if (!IsProxy(promiseObj)) {
promise = &promiseObj->as<PromiseObject>();
} else {
if (JS_IsDeadWrapper(promiseObj)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return false;
}
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
ac.emplace(cx, promise);

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

@ -4559,12 +4559,6 @@ Parser<SyntaxParseHandler>::importDeclaration()
return SyntaxParseHandler::NodeFailure;
}
template <>
ParseNode*
Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
ClassContext classContext,
DefaultHandling defaultHandling);
template<>
bool
Parser<FullParseHandler>::checkExportedName(JSAtom* exportName)
@ -6191,11 +6185,11 @@ GeneratorKindFromPropertyType(PropertyType propType)
return propType == PropertyType::GeneratorMethod ? StarGenerator : NotGenerator;
}
template <>
ParseNode*
Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
ClassContext classContext,
DefaultHandling defaultHandling)
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
ClassContext classContext,
DefaultHandling defaultHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS));
@ -6250,7 +6244,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
// in order to provide it for the nodes created later.
TokenPos namePos = pos();
ParseNode* classHeritage = null();
Node classHeritage = null();
bool hasHeritage;
if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS))
return null();
@ -6264,7 +6258,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS);
ParseNode* classMethods = handler.newClassMethodList(pos().begin);
Node classMethods = handler.newClassMethodList(pos().begin);
if (!classMethods)
return null();
@ -6304,7 +6298,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
}
PropertyType propType;
ParseNode* propName = propertyName(yieldHandling, classMethods, &propType, &propAtom);
Node propName = propertyName(yieldHandling, classMethods, &propType, &propAtom);
if (!propName)
return null();
@ -6356,7 +6350,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
if (!tokenStream.isCurrentTokenType(TOK_RB))
funName = propAtom;
}
ParseNode* fn = methodDefinition(yieldHandling, propType, funName);
Node fn = methodDefinition(yieldHandling, propType, funName);
if (!fn)
return null();
@ -6365,18 +6359,18 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
return null();
}
ParseNode* nameNode = null();
ParseNode* methodsOrBlock = classMethods;
Node nameNode = null();
Node methodsOrBlock = classMethods;
if (name) {
// The inner name is immutable.
if (!noteDeclaredName(name, DeclarationKind::Const, namePos))
return null();
ParseNode* innerName = newName(name, namePos);
Node innerName = newName(name, namePos);
if (!innerName)
return null();
ParseNode* classBlock = finishLexicalScope(*classScope, classMethods);
Node classBlock = finishLexicalScope(*classScope, classMethods);
if (!classBlock)
return null();
@ -6386,7 +6380,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
classScope.reset();
classStmt.reset();
ParseNode* outerName = null();
Node outerName = null();
if (classContext == ClassStatement) {
// The outer name is mutable.
if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
@ -6407,16 +6401,6 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
return handler.newClass(nameNode, classHeritage, methodsOrBlock);
}
template <>
SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::classDefinition(YieldHandling yieldHandling,
ClassContext classContext,
DefaultHandling defaultHandling)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return SyntaxParseHandler::NodeFailure;
}
template <class ParseHandler>
bool
Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
@ -6851,8 +6835,6 @@ Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
// ClassDeclaration[?Yield, ~Default]
case TOK_CLASS:
if (!abortIfSyntaxParser())
return null();
return classDefinition(yieldHandling, ClassStatement, NameRequired);
// LexicalDeclaration[In, ?Yield]

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

@ -262,6 +262,8 @@ class SyntaxParseHandler
Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
Node newClassMethodList(uint32_t begin) { return NodeGeneric; }
Node newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; }
Node newClass(Node name, Node heritage, Node methodBlock) { return NodeGeneric; }
Node newNewTarget(Node newHolder, Node targetHolder) { return NodeGeneric; }
Node newPosHolder(const TokenPos& pos) { return NodeGeneric; }

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

@ -5431,6 +5431,8 @@ MWasmCall::NewBuiltinInstanceMethodCall(TempAllocator& alloc,
MWasmCall* call = MWasmCall::New(alloc, desc, callee, args, resultType, spIncrement,
MWasmCall::DontSaveTls, nullptr);
if (!call)
return nullptr;
MOZ_ASSERT(instanceArg != ABIArg()); // instanceArg must be initialized.
call->instanceArg_ = instanceArg;
return call;

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

@ -0,0 +1,39 @@
/* 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/. */
/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1298597 */
function run_test()
{
var sb = Components.utils.Sandbox("http://www.blah.com");
var resolveFun;
var p1 = new sb.Promise((res, rej) => {resolveFun = res});
var rejectFun;
var p2 = new sb.Promise((res, rej) => {rejectFun = rej});
Components.utils.nukeSandbox(sb);
do_check_true(Components.utils.isDeadWrapper(sb), "sb should be dead");
do_check_true(Components.utils.isDeadWrapper(p1), "p1 should be dead");
do_check_true(Components.utils.isDeadWrapper(p2), "p2 should be dead");
var exception;
try{
resolveFun(1);
do_check_true(false);
} catch (e) {
exception = e;
}
do_check_true(exception.toString().includes("can't access dead object"),
"Resolving dead wrapped promise should throw");
exception = undefined;
try{
rejectFun(1);
do_check_true(false);
} catch (e) {
exception = e;
}
do_check_true(exception.toString().includes("can't access dead object"),
"Rejecting dead wrapped promise should throw");
}

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

@ -133,3 +133,4 @@ head = head_watchdog.js
[test_xrayed_iterator.js]
[test_xray_SavedFrame.js]
[test_xray_SavedFrame-02.js]
[test_resolve_dead_promise.js]

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

@ -222,10 +222,9 @@ GetBEndMarginClone(nsIFrame* aFrame,
// GetAvailableSpace has already been called.
void
BlockReflowInput::ComputeBlockAvailSpace(nsIFrame* aFrame,
const nsStyleDisplay* aDisplay,
const nsFlowAreaRect& aFloatAvailableSpace,
bool aBlockAvoidsFloats,
LogicalRect& aResult)
const nsFlowAreaRect& aFloatAvailableSpace,
bool aBlockAvoidsFloats,
LogicalRect& aResult)
{
#ifdef REALLY_NOISY_REFLOW
printf("CBAS frame=%p has floats %d\n",

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

@ -207,7 +207,6 @@ public:
// Caller must have called GetAvailableSpace for the current mBCoord
void ComputeBlockAvailSpace(nsIFrame* aFrame,
const nsStyleDisplay* aDisplay,
const nsFlowAreaRect& aFloatAvailableSpace,
bool aBlockAvoidsFloats,
mozilla::LogicalRect& aResult);

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

@ -783,7 +783,9 @@ nsBlockFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
AutoNoisyIndenter lineindent(gNoisyIntrinsic);
#endif
if (line->IsBlock()) {
data.ForceBreak();
if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
data.ForceBreak();
}
data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
line->mFirstChild, nsLayoutUtils::PREF_ISIZE);
data.ForceBreak();
@ -794,8 +796,13 @@ nsBlockFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
// percentage basis of 0 unconditionally would give strange
// behavior for calc(10%-3px).
const nsStyleCoord &indent = StyleText()->mTextIndent;
if (indent.ConvertsToLength())
data.mCurrentLine += nsRuleNode::ComputeCoordPercentCalc(indent, 0);
if (indent.ConvertsToLength()) {
nscoord length = indent.ToLength();
if (length != 0) {
data.mCurrentLine += length;
data.mLineIsEmpty = false;
}
}
}
// XXX Bug NNNNNN Should probably handle percentage text-indent.
@ -3117,11 +3124,10 @@ nsBlockFrame::ReflowBlockFrame(BlockReflowInput& aState,
}
// Prepare the block reflow engine
const nsStyleDisplay* display = frame->StyleDisplay();
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
uint8_t breakType =
display->PhysicalBreakType(aState.mReflowInput.GetWritingMode());
uint8_t breakType = frame->StyleDisplay()->
PhysicalBreakType(aState.mReflowInput.GetWritingMode());
if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) {
breakType = nsLayoutUtils::CombineBreakType(breakType,
aState.mFloatBreakType);
@ -3302,7 +3308,7 @@ nsBlockFrame::ReflowBlockFrame(BlockReflowInput& aState,
nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
WritingMode wm = aState.mReflowInput.GetWritingMode();
LogicalRect availSpace(wm);
aState.ComputeBlockAvailSpace(frame, display, floatAvailableSpace,
aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
replacedBlock != nullptr, availSpace);
// The check for
@ -3435,7 +3441,7 @@ nsBlockFrame::ReflowBlockFrame(BlockReflowInput& aState,
}
LogicalRect oldAvailSpace(availSpace);
aState.ComputeBlockAvailSpace(frame, display, floatAvailableSpace,
aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
replacedBlock != nullptr, availSpace);
if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {

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

@ -125,6 +125,7 @@ nsFirstLetterFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext,
nsIFrame::InlinePrefISizeData *aData)
{
DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE);
aData->mLineIsEmpty = false;
}
// Needed for floating first-letter frames.

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

@ -4443,6 +4443,7 @@ nsIFrame::InlinePrefISizeData::DefaultAddInlinePrefISize(nscoord aISize)
mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, aISize);
mTrailingWhitespace = 0;
mSkipWhitespace = false;
mLineIsEmpty = false;
}
void
@ -4532,6 +4533,7 @@ nsIFrame::InlinePrefISizeData::ForceBreak()
mPrevLines = std::max(mPrevLines, mCurrentLine);
mCurrentLine = mTrailingWhitespace = 0;
mSkipWhitespace = true;
mLineIsEmpty = true;
}
static void

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

@ -1828,10 +1828,17 @@ public:
};
struct InlinePrefISizeData : public InlineIntrinsicISizeData {
InlinePrefISizeData()
: mLineIsEmpty(true)
{}
void ForceBreak();
// The default implementation for nsIFrame::AddInlinePrefISize.
void DefaultAddInlinePrefISize(nscoord aISize);
// True if the current line contains nothing other than placeholders.
bool mLineIsEmpty;
};
/**

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

@ -273,6 +273,7 @@ nsInlineFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext,
nsIFrame::InlinePrefISizeData *aData)
{
DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE);
aData->mLineIsEmpty = false;
}
/* virtual */

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

@ -87,6 +87,7 @@ nsRubyFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext,
e.GetBaseContainer()->AddInlinePrefISize(aRenderingContext, aData);
}
}
aData->mLineIsEmpty = false;
}
/* virtual */ void

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

@ -8347,6 +8347,7 @@ nsTextFrame::AddInlinePrefISizeForFlow(nsRenderingContext *aRenderingContext,
if (StyleContext()->IsTextCombined()) {
aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
aData->mTrailingWhitespace = 0;
aData->mLineIsEmpty = false;
return;
}
@ -8384,6 +8385,7 @@ nsTextFrame::AddInlinePrefISizeForFlow(nsRenderingContext *aRenderingContext,
textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
width = std::max(0, width);
aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
aData->mLineIsEmpty = false;
if (collapseWhitespace) {
uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
@ -8410,6 +8412,7 @@ nsTextFrame::AddInlinePrefISizeForFlow(nsRenderingContext *aRenderingContext,
AdvanceToNextTab(aData->mCurrentLine, this,
textRun, &tabWidth);
aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
aData->mLineIsEmpty = false;
lineStart = i + 1;
} else if (preformattedNewline) {
aData->ForceBreak();

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

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bug 1260031 - Intrinsic width with float</title>
<style>
#left {
display: inline-block;
width: 50px;
height: 50px;
background: green;
}
#right {
display: inline-block;
width: 50px;
height: 50px;
background: blue;
}
</style>
</head>
<body>
<div id="test">
<div id="wrapper">
<div id="left"></div><div id="right"></div>
</div>
</div>
</body>
</html>

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

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bug 1260031 - Intrinsic width with float</title>
<style>
#wrapper {
background: red;
width: -moz-fit-content;
width: fit-content;
}
#left {
float: left;
width: 50px;
height: 50px;
background: green;
}
#right {
width: 50px;
height: 50px;
background: blue;
}
</style>
</head>
<body>
<div id="test">
<div id="wrapper">
<div id="left"></div>
<div id="right"></div>
</div>
</div>
<script>
document.getElementById("right").style = location.search.slice(1);
</script>
</body>
</html>

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

@ -21,6 +21,9 @@ fails == 345369-2.html 345369-2-ref.html
== 775350-1.html 775350-1-ref.html
== 1114329.html 1114329-ref.html
== 1236745-1.html 1236745-1-ref.html
== 1260031-1.html?display:table 1260031-1-ref.html
== 1260031-1.html?display:table-cell 1260031-1-ref.html
== 1260031-1.html?overflow:hidden 1260031-1-ref.html
== float-in-rtl-1a.html float-in-rtl-1-ref.html
fuzzy-if(skiaContent,1,27000) == float-in-rtl-1b.html float-in-rtl-1-ref.html
fuzzy-if(skiaContent,1,27000) == float-in-rtl-1c.html float-in-rtl-1-ref.html

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

@ -93,6 +93,8 @@ mp4parse_error mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t t
/// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
mp4parse_error mp4parse_get_track_video_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_video_info* info);
mp4parse_error mp4parse_is_fragmented(mp4parse_parser* parser, uint32_t track_id, uint8_t* fragmented);
#ifdef __cplusplus

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

@ -1,29 +1,42 @@
--- a/media/libstagefright/binding/mp4parse/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
@@ -17,23 +17,9 @@ exclude = [
diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
index 5092cd7..ecbc8c0 100644
--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
@@ -17,14 +17,9 @@ exclude = [
"*.mp4",
]
-build = "build.rs"
-
-[dependencies]
[dependencies]
"mp4parse" = {version = "0.5.0", path = "../mp4parse"}
-[build-dependencies]
-rusty-cheddar = "0.3.2"
-
[features]
fuzz = ["mp4parse/fuzz"]
diff --git a/media/libstagefright/binding/mp4parse/Cargo.toml b/media/libstagefright/binding/mp4parse/Cargo.toml
index ff9422c..814c4c6 100644
--- a/media/libstagefright/binding/mp4parse/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
@@ -18,17 +18,11 @@ exclude = [
]
[dependencies]
-byteorder = "0.5.0"
-afl = { version = "0.1.1", optional = true }
-afl-plugin = { version = "0.1.1", optional = true }
-abort_on_panic = { version = "1.0.0", optional = true }
-
-[dev-dependencies]
-test-assembler = "0.1.2"
-
-[build-dependencies]
-rusty-cheddar = "0.3.2"
-
+byteorder = { version = "0.5.0", path = "../byteorder" }
[dev-dependencies]
test-assembler = "0.1.2"
-[features]
-fuzz = ["afl", "afl-plugin", "abort_on_panic"]
-
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
[profile.release]
debug-assertions = true
+
+[dependencies]
+byteorder = { path = "../byteorder" }

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

@ -1,25 +1,28 @@
[package]
name = "mp4parse"
version = "0.4.0"
version = "0.5.0"
authors = [
"Ralph Giles <giles@mozilla.com>",
"Matthew Gregan <kinetik@flim.org>",
]
description = "Parser for ISO base media file format (mp4)"
documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
license = "MPL-2.0"
repository = "https://github.com/mozilla/mp4parse-rust"
# Cargo includes random files from the working directory
# by default! Avoid bloating the package with test files.
# Avoid complaints about trying to package test files.
exclude = [
"*.mp4",
]
[dependencies]
byteorder = { version = "0.5.0", path = "../byteorder" }
[dev-dependencies]
test-assembler = "0.1.2"
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
[profile.release]
debug-assertions = true
[dependencies]
byteorder = { path = "../byteorder" }

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

@ -54,4 +54,5 @@ box_database!(
OpusSpecificBox 0x644f7073, // "dOps"
ProtectedVisualSampleEntry 0x656e6376, // "encv" - Need to check official name in spec.
ProtectedAudioSampleEntry 0x656e6361, // "enca" - Need to check official name in spec.
MovieExtendsBox 0x6d766578, // "mvex"
);

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

@ -13,10 +13,6 @@ use byteorder::ReadBytesExt;
use std::io::{Read, Take};
use std::cmp;
// Expose C api wrapper.
pub mod capi;
pub use capi::*;
mod boxes;
use boxes::BoxType;
@ -109,18 +105,18 @@ struct FileTypeBox {
/// Movie header box 'mvhd'.
#[derive(Debug)]
struct MovieHeaderBox {
timescale: u32,
pub timescale: u32,
duration: u64,
}
/// Track header box 'tkhd'
#[derive(Debug, Clone)]
struct TrackHeaderBox {
pub struct TrackHeaderBox {
track_id: u32,
disabled: bool,
duration: u64,
width: u32,
height: u32,
pub disabled: bool,
pub duration: u64,
pub width: u32,
pub height: u32,
}
/// Edit list box 'elst'
@ -201,7 +197,7 @@ struct SampleDescriptionBox {
}
#[derive(Debug, Clone)]
enum SampleEntry {
pub enum SampleEntry {
Audio(AudioSampleEntry),
Video(VideoSampleEntry),
Unknown,
@ -209,45 +205,45 @@ enum SampleEntry {
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
enum AudioCodecSpecific {
pub enum AudioCodecSpecific {
ES_Descriptor(Vec<u8>),
OpusSpecificBox(OpusSpecificBox),
}
#[derive(Debug, Clone)]
struct AudioSampleEntry {
pub struct AudioSampleEntry {
data_reference_index: u16,
channelcount: u16,
samplesize: u16,
samplerate: u32,
codec_specific: AudioCodecSpecific,
pub channelcount: u16,
pub samplesize: u16,
pub samplerate: u32,
pub codec_specific: AudioCodecSpecific,
}
#[derive(Debug, Clone)]
enum VideoCodecSpecific {
pub enum VideoCodecSpecific {
AVCConfig(Vec<u8>),
VPxConfig(VPxConfigBox),
}
#[derive(Debug, Clone)]
struct VideoSampleEntry {
pub struct VideoSampleEntry {
data_reference_index: u16,
width: u16,
height: u16,
codec_specific: VideoCodecSpecific,
pub width: u16,
pub height: u16,
pub codec_specific: VideoCodecSpecific,
}
/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9).
#[derive(Debug, Clone)]
struct VPxConfigBox {
pub struct VPxConfigBox {
profile: u8,
level: u8,
bit_depth: u8,
color_space: u8, // Really an enum
chroma_subsampling: u8,
pub bit_depth: u8,
pub color_space: u8, // Really an enum
pub chroma_subsampling: u8,
transfer_function: u8,
video_full_range: bool,
codec_init: Vec<u8>, // Empty for vp8/vp9.
pub codec_init: Vec<u8>, // Empty for vp8/vp9.
}
#[derive(Debug, Clone)]
@ -259,8 +255,8 @@ struct ChannelMappingTable {
/// Represent an OpusSpecificBox 'dOps'
#[derive(Debug, Clone)]
struct OpusSpecificBox {
version: u8,
pub struct OpusSpecificBox {
pub version: u8,
output_channel_count: u8,
pre_skip: u16,
input_sample_rate: u32,
@ -270,73 +266,80 @@ struct OpusSpecificBox {
}
/// Internal data structures.
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct MediaContext {
timescale: Option<MediaTimeScale>,
pub timescale: Option<MediaTimeScale>,
pub has_mvex: bool,
/// Tracks found in the file.
tracks: Vec<Track>,
pub tracks: Vec<Track>,
}
impl MediaContext {
pub fn new() -> MediaContext {
MediaContext {
timescale: None,
tracks: Vec::new(),
}
Default::default()
}
}
#[derive(Debug)]
enum TrackType {
pub enum TrackType {
Audio,
Video,
Unknown,
}
impl Default for TrackType {
fn default() -> Self { TrackType::Unknown }
}
/// The media's global (mvhd) timescale.
#[derive(Debug, Copy, Clone)]
struct MediaTimeScale(u64);
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct MediaTimeScale(pub u64);
/// A time scaled by the media's global (mvhd) timescale.
#[derive(Debug, Copy, Clone)]
struct MediaScaledTime(u64);
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct MediaScaledTime(pub u64);
/// The track's local (mdhd) timescale.
#[derive(Debug, Copy, Clone)]
struct TrackTimeScale(u64, usize);
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TrackTimeScale(pub u64, pub usize);
/// A time scaled by the track's local (mdhd) timescale.
#[derive(Debug, Copy, Clone)]
struct TrackScaledTime(u64, usize);
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TrackScaledTime(pub u64, pub usize);
#[derive(Debug)]
struct Track {
/// A fragmented file contains no sample data in stts, stsc, and stco.
#[derive(Debug, Default)]
pub struct EmptySampleTableBoxes {
pub empty_stts : bool,
pub empty_stsc : bool,
pub empty_stco : bool,
}
/// Check boxes contain data.
impl EmptySampleTableBoxes {
pub fn all_empty(&self) -> bool {
self.empty_stts & self.empty_stsc & self.empty_stco
}
}
#[derive(Debug, Default)]
pub struct Track {
id: usize,
track_type: TrackType,
empty_duration: Option<MediaScaledTime>,
media_time: Option<TrackScaledTime>,
timescale: Option<TrackTimeScale>,
duration: Option<TrackScaledTime>,
track_id: Option<u32>,
mime_type: String,
data: Option<SampleEntry>,
tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
pub track_type: TrackType,
pub empty_duration: Option<MediaScaledTime>,
pub media_time: Option<TrackScaledTime>,
pub timescale: Option<TrackTimeScale>,
pub duration: Option<TrackScaledTime>,
pub track_id: Option<u32>,
pub mime_type: String,
pub empty_sample_boxes: EmptySampleTableBoxes,
pub data: Option<SampleEntry>,
pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
}
impl Track {
fn new(id: usize) -> Track {
Track {
id: id,
track_type: TrackType::Unknown,
empty_duration: None,
media_time: None,
timescale: None,
duration: None,
track_id: None,
mime_type: String::new(),
data: None,
tkhd: None,
}
Track { id: id, ..Default::default() }
}
}
@ -454,7 +457,7 @@ macro_rules! check_parser_state {
/// Read the contents of a box, including sub boxes.
///
/// Metadata is accumulated in the passed-through MediaContext struct,
/// Metadata is accumulated in the passed-through `MediaContext` struct,
/// which can be examined later.
pub fn read_mp4<T: Read>(f: &mut T, context: &mut MediaContext) -> Result<()> {
let mut found_ftyp = false;
@ -533,6 +536,10 @@ fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: &mut MediaContext) -> Result<
try!(read_trak(&mut b, &mut track));
context.tracks.push(track);
}
BoxType::MovieExtendsBox => {
context.has_mvex = true;
try!(skip_box_content(&mut b));
}
_ => try!(skip_box_content(&mut b)),
};
check_parser_state!(b.content);
@ -654,10 +661,12 @@ fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
}
BoxType::TimeToSampleBox => {
let stts = try!(read_stts(&mut b));
track.empty_sample_boxes.empty_stts = stts.samples.is_empty();
log!("{:?}", stts);
}
BoxType::SampleToChunkBox => {
let stsc = try!(read_stsc(&mut b));
track.empty_sample_boxes.empty_stsc = stsc.samples.is_empty();
log!("{:?}", stsc);
}
BoxType::SampleSizeBox => {
@ -666,6 +675,7 @@ fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
}
BoxType::ChunkOffsetBox => {
let stco = try!(read_stco(&mut b));
track.empty_sample_boxes.empty_stco = stco.offsets.is_empty();
log!("{:?}", stco);
}
BoxType::ChunkLargeOffsetBox => {
@ -985,7 +995,7 @@ fn read_vpcc<T: Read>(src: &mut BMFFBox<T>) -> Result<VPxConfigBox> {
})
}
/// Parse OpusSpecificBox.
/// Parse `OpusSpecificBox`.
fn read_dops<T: Read>(src: &mut BMFFBox<T>) -> Result<OpusSpecificBox> {
let version = try!(src.read_u8());
if version != 0 {
@ -1024,13 +1034,13 @@ fn read_dops<T: Read>(src: &mut BMFFBox<T>) -> Result<OpusSpecificBox> {
})
}
/// Re-serialize the Opus codec-specific config data as an OpusHead packet.
/// Re-serialize the Opus codec-specific config data as an `OpusHead` packet.
///
/// Some decoders expect the initialization data in the format used by the
/// Ogg and WebM encapsulations. To support this we prepend the 'OpusHead'
/// Ogg and WebM encapsulations. To support this we prepend the `OpusHead`
/// tag and byte-swap the data from big- to little-endian relative to the
/// dOps box.
fn serialize_opus_header<W: byteorder::WriteBytesExt + std::io::Write>(opus: &OpusSpecificBox, dst: &mut W) -> Result<()> {
pub fn serialize_opus_header<W: byteorder::WriteBytesExt + std::io::Write>(opus: &OpusSpecificBox, dst: &mut W) -> Result<()> {
match dst.write(b"OpusHead") {
Err(e) => return Err(Error::from(e)),
Ok(bytes) => {
@ -1151,16 +1161,14 @@ fn read_video_desc<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<S
check_parser_state!(b.content);
}
if codec_specific.is_none() {
return Err(Error::InvalidData("malformed video sample entry"));
}
Ok(SampleEntry::Video(VideoSampleEntry {
data_reference_index: data_reference_index,
width: width,
height: height,
codec_specific: codec_specific.unwrap(),
}))
codec_specific
.map(|codec_specific| SampleEntry::Video(VideoSampleEntry {
data_reference_index: data_reference_index,
width: width,
height: height,
codec_specific: codec_specific,
}))
.ok_or_else(|| Error::InvalidData("malformed video sample entry"))
}
/// Parse an audio description inside an stsd box.
@ -1235,17 +1243,15 @@ fn read_audio_desc<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<S
check_parser_state!(b.content);
}
if codec_specific.is_none() {
return Err(Error::InvalidData("malformed audio sample entry"));
}
Ok(SampleEntry::Audio(AudioSampleEntry {
data_reference_index: data_reference_index,
channelcount: channelcount,
samplesize: samplesize,
samplerate: samplerate,
codec_specific: codec_specific.unwrap(),
}))
codec_specific
.map(|codec_specific| SampleEntry::Audio(AudioSampleEntry {
data_reference_index: data_reference_index,
channelcount: channelcount,
samplesize: samplesize,
samplerate: samplerate,
codec_specific: codec_specific,
}))
.ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
}
/// Parse a stsd box.
@ -1352,17 +1358,6 @@ fn read_fixed_length_pascal_string<T: Read>(src: &mut T, size: usize) -> Result<
String::from_utf8(buf).map_err(From::from)
}
fn media_time_to_ms(time: MediaScaledTime, scale: MediaTimeScale) -> u64 {
assert!(scale.0 != 0);
time.0 * 1000000 / scale.0
}
fn track_time_to_ms(time: TrackScaledTime, scale: TrackTimeScale) -> u64 {
assert!(time.1 == scale.1);
assert!(scale.0 != 0);
time.0 * 1000000 / scale.0
}
fn be_i16<T: ReadBytesExt>(src: &mut T) -> Result<i16> {
src.read_i16::<byteorder::BigEndian>().map_err(From::from)
}

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

@ -6,7 +6,9 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use std::io::Cursor;
use super::*;
use super::read_mp4;
use super::MediaContext;
use super::Error;
extern crate test_assembler;
use self::test_assembler::*;

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

@ -0,0 +1,28 @@
[package]
name = "mp4parse_capi"
version = "0.5.0"
authors = [
"Ralph Giles <giles@mozilla.com>",
"Matthew Gregan <kinetik@flim.org>",
]
description = "Parser for ISO base media file format (mp4)"
documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
license = "MPL-2.0"
repository = "https://github.com/mozilla/mp4parse-rust"
# Avoid complaints about trying to package test files.
exclude = [
"*.mp4",
]
[dependencies]
"mp4parse" = {version = "0.5.0", path = "../mp4parse"}
[features]
fuzz = ["mp4parse/fuzz"]
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
[profile.release]
debug-assertions = true

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

@ -3,7 +3,6 @@ extern crate cheddar;
fn main() {
// Generate mp4parse.h.
cheddar::Cheddar::new().expect("could not read manifest")
.module("capi").expect("invalid module path")
.insert_code("// THIS FILE IS AUTOGENERATED BY mp4parse-rust/build.rs - DO NOT EDIT\n\n")
.insert_code("// This Source Code Form is subject to the terms of the Mozilla Public\n")
.insert_code("// License, v. 2.0. If a copy of the MPL was not distributed with this\n")

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

@ -5,7 +5,7 @@
//! # Examples
//!
//! ```rust
//! extern crate mp4parse;
//! extern crate mp4parse_capi;
//! use std::io::Read;
//!
//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
@ -17,14 +17,16 @@
//! }
//! }
//!
//! let mut file = std::fs::File::open("examples/minimal.mp4").unwrap();
//! let io = mp4parse::mp4parse_io { read: buf_read,
//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
//! let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
//! let io = mp4parse_capi::mp4parse_io {
//! read: buf_read,
//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void
//! };
//! unsafe {
//! let parser = mp4parse::mp4parse_new(&io);
//! let rv = mp4parse::mp4parse_read(parser);
//! assert_eq!(rv, mp4parse::mp4parse_error::MP4PARSE_OK);
//! mp4parse::mp4parse_free(parser);
//! let parser = mp4parse_capi::mp4parse_new(&io);
//! let rv = mp4parse_capi::mp4parse_read(parser);
//! assert_eq!(rv, mp4parse_capi::mp4parse_error::MP4PARSE_OK);
//! mp4parse_capi::mp4parse_free(parser);
//! }
//! ```
@ -32,21 +34,24 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use std;
extern crate mp4parse;
use std::io::Read;
use std::collections::HashMap;
// Symbols we need from our rust api.
use MediaContext;
use TrackType;
use read_mp4;
use Error;
use media_time_to_ms;
use track_time_to_ms;
use SampleEntry;
use AudioCodecSpecific;
use VideoCodecSpecific;
use serialize_opus_header;
use mp4parse::MediaContext;
use mp4parse::TrackType;
use mp4parse::read_mp4;
use mp4parse::Error;
use mp4parse::SampleEntry;
use mp4parse::AudioCodecSpecific;
use mp4parse::VideoCodecSpecific;
use mp4parse::MediaTimeScale;
use mp4parse::MediaScaledTime;
use mp4parse::TrackTimeScale;
use mp4parse::TrackScaledTime;
use mp4parse::serialize_opus_header;
// rusty-cheddar's C enum generation doesn't namespace enum members by
// prefixing them, so we're forced to do it in our member names until
@ -266,13 +271,24 @@ pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, co
let context = (*parser).context();
// Make sure the track count fits in a u32.
if context.tracks.len() >= u32::max_value() as usize {
if context.tracks.len() > u32::max_value() as usize {
return MP4PARSE_ERROR_INVALID;
}
*count = context.tracks.len() as u32;
MP4PARSE_OK
}
fn media_time_to_ms(time: MediaScaledTime, scale: MediaTimeScale) -> u64 {
assert!(scale.0 != 0);
time.0 * 1000000 / scale.0
}
fn track_time_to_ms(time: TrackScaledTime, scale: TrackTimeScale) -> u64 {
assert!(time.1 == scale.1);
assert!(scale.0 != 0);
time.0 * 1000000 / scale.0
}
/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
#[no_mangle]
pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_error {
@ -310,22 +326,28 @@ pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track
_ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
};
// Maybe context & track should just have a single simple is_valid() instead?
if context.timescale.is_none() ||
context.tracks[track_index].timescale.is_none() ||
context.tracks[track_index].duration.is_none() ||
context.tracks[track_index].track_id.is_none() {
return MP4PARSE_ERROR_INVALID;
let track = &context.tracks[track_index];
if let (Some(track_timescale),
Some(context_timescale),
Some(track_duration)) = (track.timescale,
context.timescale,
track.duration) {
info.media_time = track.media_time.map_or(0, |media_time| {
track_time_to_ms(media_time, track_timescale) as i64
}) - track.empty_duration.map_or(0, |empty_duration| {
media_time_to_ms(empty_duration, context_timescale) as i64
});
info.duration = track_time_to_ms(track_duration, track_timescale);
} else {
return MP4PARSE_ERROR_INVALID
}
let track = &context.tracks[track_index];
info.media_time = track.media_time.map_or(0, |media_time| {
track_time_to_ms(media_time, track.timescale.unwrap()) as i64
}) - track.empty_duration.map_or(0, |empty_duration| {
media_time_to_ms(empty_duration, context.timescale.unwrap()) as i64
});
info.duration = track_time_to_ms(track.duration.unwrap(), track.timescale.unwrap());
info.track_id = track.track_id.unwrap();
info.track_id = match track.track_id {
Some(track_id) => track_id,
None => return MP4PARSE_ERROR_INVALID,
};
MP4PARSE_OK
}
@ -438,6 +460,32 @@ pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser,
MP4PARSE_OK
}
// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
#[no_mangle]
pub unsafe extern fn mp4parse_is_fragmented(parser: *mut mp4parse_parser, track_id: u32, fragmented: *mut u8) -> mp4parse_error {
if parser.is_null() || (*parser).poisoned() {
return MP4PARSE_ERROR_BADARG;
}
let context = (*parser).context_mut();
let tracks = &context.tracks;
(*fragmented) = false as u8;
if !context.has_mvex {
return MP4PARSE_OK;
}
// check sample tables.
let mut iter = tracks.iter();
match iter.find(|track| track.track_id == Some(track_id)) {
Some(track) if track.empty_sample_boxes.all_empty() => (*fragmented) = true as u8,
Some(_) => {},
None => return MP4PARSE_ERROR_BADARG,
}
MP4PARSE_OK
}
#[cfg(test)]
extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
panic!("panic_read shouldn't be called in these tests");
@ -614,7 +662,7 @@ fn get_track_count_poisoned_parser() {
#[test]
fn arg_validation_with_data() {
unsafe {
let mut file = std::fs::File::open("examples/minimal.mp4").unwrap();
let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
let io = mp4parse_io { read: valid_read,
userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
let parser = mp4parse_new(&io);

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

@ -2,7 +2,7 @@
# Script to update mp4parse-rust sources to latest upstream
# Default version.
VER=v0.4.0
VER=v0.5.0
# Accept version or commit from the command line.
if test -n "$1"; then
@ -14,15 +14,27 @@ rm -rf _upstream
git clone https://github.com/mozilla/mp4parse-rust _upstream/mp4parse
pushd _upstream/mp4parse
git checkout ${VER}
echo "Verifying sources..."
pushd mp4parse
cargo test
popd
echo "Constructing C api header..."
pushd mp4parse_capi
cargo build
echo "Verifying sources..."
cargo test
popd
popd
rm -rf mp4parse
mkdir -p mp4parse/src
cp _upstream/mp4parse/Cargo.toml mp4parse/
cp _upstream/mp4parse/build.rs mp4parse/
cp _upstream/mp4parse/src/*.rs mp4parse/src/
cp _upstream/mp4parse/include/mp4parse.h include/
cp _upstream/mp4parse/mp4parse/Cargo.toml mp4parse/
cp _upstream/mp4parse/mp4parse/src/*.rs mp4parse/src/
rm -rf mp4parse_capi
mkdir -p mp4parse_capi/src
cp _upstream/mp4parse/mp4parse_capi/Cargo.toml mp4parse_capi/
cp _upstream/mp4parse/mp4parse_capi/build.rs mp4parse_capi/
cp _upstream/mp4parse/mp4parse_capi/include/mp4parse.h include/
cp _upstream/mp4parse/mp4parse_capi/src/*.rs mp4parse_capi/src/
# TODO: download deps from crates.io.

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

@ -590,6 +590,12 @@ public class BrowserContract {
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "topsites");
}
public static final class Highlights {
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "highlights");
public static final String DATE = "date";
}
@RobocopTarget
public static final class SearchHistory implements CommonColumns, HistoryColumns {
private SearchHistory() {}

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

@ -175,4 +175,12 @@ public interface BrowserDB {
public abstract boolean hasSuggestedImageUrl(String url);
public abstract String getSuggestedImageUrlForUrl(String url);
public abstract int getSuggestedBackgroundColorForUrl(String url);
/**
* Obtain a set of links for highlights from bookmarks and history.
*
* @param context The context to load the cursor.
* @param limit Maximum number of results to return.
*/
CursorLoader getHighlights(Context context, int limit);
}

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

@ -19,6 +19,7 @@ import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
import org.mozilla.gecko.db.BrowserContract.Favicons;
import org.mozilla.gecko.db.BrowserContract.Highlights;
import org.mozilla.gecko.db.BrowserContract.History;
import org.mozilla.gecko.db.BrowserContract.Visits;
import org.mozilla.gecko.db.BrowserContract.Schema;
@ -54,6 +55,8 @@ import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import static org.mozilla.gecko.db.DBUtils.qualifyColumn;
public class BrowserProvider extends SharedBrowserDatabaseProvider {
public static final String ACTION_SHRINK_MEMORY = "org.mozilla.gecko.db.intent.action.SHRINK_MEMORY";
@ -126,6 +129,10 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
static final int VISITS = 1100;
static final int METADATA = 1200;
static final int HIGHLIGHTS = 1300;
static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
+ " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
+ " ASC";
@ -288,6 +295,8 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
// Combined pinned sites, top visited sites, and suggested sites
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "topsites", TOPSITES);
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "highlights", HIGHLIGHTS);
}
private static class ShrinkMemoryReceiver extends BroadcastReceiver {
@ -1136,6 +1145,67 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
}
}
/**
* Obtain a set of links for highlights (from bookmarks and history).
*
* Based on the query for Activity^ Stream (desktop):
* https://github.com/mozilla/activity-stream/blob/9eb9f451b553bb62ae9b8d6b41a8ef94a2e020ea/addon/PlacesProvider.js#L578
*/
public Cursor getHighlights(final SQLiteDatabase db, String limit) {
final int totalLimit = limit == null ? 20 : Integer.parseInt(limit);
final long threeDaysAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24 * 3);
final long bookmarkLimit = 1;
// Select recent bookmarks that have not been visited much
final String bookmarksQuery = "SELECT * FROM (SELECT " +
DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + ", " +
DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.TITLE) + ", " +
DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " AS " + Highlights.DATE + " " +
"FROM " + Bookmarks.TABLE_NAME + " " +
"LEFT JOIN " + History.TABLE_NAME + " ON " +
DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + " = " +
DBUtils.qualifyColumn(History.TABLE_NAME, History.URL) + " " +
"WHERE " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " > " + threeDaysAgo + " " +
"AND (" + DBUtils.qualifyColumn(History.TABLE_NAME, History.VISITS) + " <= 3 " +
"OR " + DBUtils.qualifyColumn(History.TABLE_NAME, History.VISITS) + " IS NULL) " +
"AND " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.IS_DELETED) + " = 0 " +
"AND " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " " +
// TODO: Implement block list (bug 1298783)
"ORDER BY " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " DESC " +
"LIMIT " + bookmarkLimit + ")";
final long last30Minutes = System.currentTimeMillis() - (1000 * 60 * 30);
final long historyLimit = totalLimit - bookmarkLimit;
// Select recent history that has not been visited much.
final String historyQuery = "SELECT * FROM (SELECT " +
History._ID + " AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.BOOKMARK_ID + ", " +
History.URL + ", " +
History.TITLE + ", " +
History.DATE_LAST_VISITED + " AS " + Highlights.DATE + " " +
"FROM " + History.TABLE_NAME + " " +
"WHERE " + History.DATE_LAST_VISITED + " < " + last30Minutes + " " +
"AND " + History.VISITS + " <= 3 " +
"AND " + History.TITLE + " NOT NULL AND " + History.TITLE + " != '' " +
"AND " + History.IS_DELETED + " = 0 " +
// TODO: Implement block list (bug 1298783)
// TODO: Implement domain black list (bug 1298786)
// TODO: Group by host (bug 1298785)
"ORDER BY " + History.DATE_LAST_VISITED + " DESC " +
"LIMIT " + historyLimit + ")";
final String query = "SELECT DISTINCT * " +
"FROM (" + bookmarksQuery + " " +
"UNION ALL " + historyQuery + ") " +
"GROUP BY " + Combined.URL + ";";
return db.rawQuery(query, null);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
@ -1292,6 +1362,12 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
break;
}
case HIGHLIGHTS: {
debug("Highlights query: " + uri);
return getHighlights(db, limit);
}
default: {
Table table = findTableFor(match);
if (table == null) {

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

@ -35,6 +35,7 @@ import org.mozilla.gecko.db.BrowserContract.History;
import org.mozilla.gecko.db.BrowserContract.SyncColumns;
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
import org.mozilla.gecko.db.BrowserContract.TopSites;
import org.mozilla.gecko.db.BrowserContract.Highlights;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.icons.decoders.FaviconDecoder;
import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
@ -115,6 +116,7 @@ public class LocalBrowserDB implements BrowserDB {
private final Uri mFaviconsUriWithProfile;
private final Uri mThumbnailsUriWithProfile;
private final Uri mTopSitesUriWithProfile;
private final Uri mHighlightsUriWithProfile;
private final Uri mSearchHistoryUri;
private LocalSearches searches;
@ -141,6 +143,7 @@ public class LocalBrowserDB implements BrowserDB {
mCombinedUriWithProfile = DBUtils.appendProfile(profile, Combined.CONTENT_URI);
mFaviconsUriWithProfile = DBUtils.appendProfile(profile, Favicons.CONTENT_URI);
mTopSitesUriWithProfile = DBUtils.appendProfile(profile, TopSites.CONTENT_URI);
mHighlightsUriWithProfile = DBUtils.appendProfile(profile, Highlights.CONTENT_URI);
mThumbnailsUriWithProfile = DBUtils.appendProfile(profile, Thumbnails.CONTENT_URI);
mSearchHistoryUri = BrowserContract.SearchHistory.CONTENT_URI;
@ -1830,6 +1833,14 @@ public class LocalBrowserDB implements BrowserDB {
rb.add(TopSites.TYPE_BLANK);
return new MergeCursor(new Cursor[] {topSitesCursor, blanksCursor});
}
@Override
public CursorLoader getHighlights(Context context, int limit) {
final Uri uri = mHighlightsUriWithProfile.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit))
.build();
return new CursorLoader(context, uri, null, null, null, null);
}
}

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

@ -361,6 +361,11 @@ public class StubBrowserDB implements BrowserDB {
return 0;
}
@Override
public CursorLoader getHighlights(Context context, int limit) {
return null;
}
public Cursor getTopSites(ContentResolver cr, int suggestedRangeLimit, int limit) {
return null;
}

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

@ -17,7 +17,7 @@ public class URLMetadataTable extends BaseTable {
private static final String LOGTAG = "GeckoURLMetadataTable";
private static final String TABLE = "metadata"; // Name of the table in the db
private static final int TABLE_ID_NUMBER = 1200;
private static final int TABLE_ID_NUMBER = BrowserProvider.METADATA;
// Uri for querying this table
public static final Uri CONTENT_URI = Uri.withAppendedPath(BrowserContract.AUTHORITY_URI, "metadata");

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

@ -56,29 +56,14 @@ public class ActivityStream extends FrameLayout {
adapter.swapTopSitesCursor(null);
}
/**
* This is a temporary cursor loader. We'll probably need a completely new query for AS,
* at that time we can switch to the new CursorLoader, as opposed to using our outdated
* SimpleCursorLoader.
*/
private static class HistoryLoader extends SimpleCursorLoader {
public HistoryLoader(Context context) {
super(context);
}
@Override
protected Cursor loadCursor() {
final Context context = getContext();
return GeckoProfile.get(context).getDB()
.getRecentHistory(context.getContentResolver(), 10);
}
}
private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_ID_HIGHLIGHTS) {
return new HistoryLoader(getContext());
final Context context = getContext();
return GeckoProfile.get(context)
.getDB()
.getHighlights(context, 10);
} else if (id == LOADER_ID_TOPSITES) {
return GeckoProfile.get(getContext()).getDB().getActivityStreamTopSites(getContext(),
TopSitesPagerAdapter.TOTAL_ITEMS);

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

@ -17,6 +17,12 @@ import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.activitystream.topsites.CirclePageIndicator;
import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
import org.mozilla.gecko.icons.IconCallback;
import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.icons.Icons;
import org.mozilla.gecko.widget.FaviconView;
import java.util.concurrent.Future;
public abstract class StreamItem extends RecyclerView.ViewHolder {
public StreamItem(View itemView) {
@ -55,25 +61,45 @@ public abstract class StreamItem extends RecyclerView.ViewHolder {
}
}
public static class CompactItem extends StreamItem {
public static class CompactItem extends StreamItem implements IconCallback {
public static final int LAYOUT_ID = R.layout.activity_stream_card_history_item;
final FaviconView vIconView;
final TextView vLabel;
final TextView vTimeSince;
private Future<IconResponse> ongoingIconLoad;
public CompactItem(View itemView) {
super(itemView);
vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
vIconView = (FaviconView) itemView.findViewById(R.id.icon);
}
@Override
public void bind(Cursor cursor) {
vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
final long time = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.DATE));
final String ago = DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
final String url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
final long timeVisited = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
final String ago = DateUtils.getRelativeTimeSpanString(timeVisited, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
vTimeSince.setText(ago);
if (ongoingIconLoad != null) {
ongoingIconLoad.cancel(true);
}
ongoingIconLoad = Icons.with(itemView.getContext())
.pageUrl(url)
.skipNetwork()
.build()
.execute(this);
}
@Override
public void onIconResponse(IconResponse response) {
vIconView.updateImage(response);
}
}
@ -95,8 +121,8 @@ public abstract class StreamItem extends RecyclerView.ViewHolder {
public void bind(Cursor cursor) {
vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
final long timeVisited = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
final String ago = DateUtils.getRelativeTimeSpanString(timeVisited, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
final long time = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.DATE));
final String ago = DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
vTimeSince.setText(ago);
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше