зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to f-t
This commit is contained in:
Коммит
3786ab03f1
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче