зеркало из 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_libpdu" path="system/libpdu" remote="b2g" revision="f1a61fa8f97cc0a1ac4eca160acc222981b21d90"/>
|
||||||
<project name="platform_system_sensorsd" path="system/sensorsd" remote="b2g" revision="3618678c472320de386f5ddc27897992d0e148a8"/>
|
<project name="platform_system_sensorsd" path="system/sensorsd" remote="b2g" revision="3618678c472320de386f5ddc27897992d0e148a8"/>
|
||||||
<!-- B2G specific things. -->
|
<!-- 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"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||||
|
|
|
@ -40,6 +40,7 @@ tags = webextensions
|
||||||
[browser_ext_getViews.js]
|
[browser_ext_getViews.js]
|
||||||
[browser_ext_incognito_popup.js]
|
[browser_ext_incognito_popup.js]
|
||||||
[browser_ext_lastError.js]
|
[browser_ext_lastError.js]
|
||||||
|
[browser_ext_legacy_extension_context_contentscript.js]
|
||||||
[browser_ext_optionsPage_privileges.js]
|
[browser_ext_optionsPage_privileges.js]
|
||||||
[browser_ext_pageAction_context.js]
|
[browser_ext_pageAction_context.js]
|
||||||
[browser_ext_pageAction_popup.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.
|
# Bug 1187421 - With e10s, NSS does not always free the error stack. m1.
|
||||||
leak:nss_ClearErrorStack
|
leak:nss_ClearErrorStack
|
||||||
|
|
||||||
# Bug 1189430 - DNS leaks in mochitest-chrome.
|
|
||||||
leak:nsDNSService::AsyncResolveExtended
|
|
||||||
leak:_GetAddrInfo_Portable
|
|
||||||
|
|
||||||
# Bug 1189568 - Indirect leaks of IMContextWrapper and nsIntRect.
|
# Bug 1189568 - Indirect leaks of IMContextWrapper and nsIntRect.
|
||||||
leak:nsWindow::Create
|
leak:nsWindow::Create
|
||||||
leak:nsBaseWidget::StoreWindowClipRegion
|
leak:nsBaseWidget::StoreWindowClipRegion
|
||||||
|
|
|
@ -46,6 +46,16 @@ const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
|
||||||
var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
|
var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
|
||||||
SpecialPowers.getBoolPref(OMTAPrefKey);
|
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) {
|
promise_test(function(t) {
|
||||||
// FIXME: When we implement Element.animate, use that here instead of CSS
|
// FIXME: When we implement Element.animate, use that here instead of CSS
|
||||||
// so that we remove any dependency on the CSS mapping.
|
// so that we remove any dependency on the CSS mapping.
|
||||||
|
@ -53,7 +63,7 @@ promise_test(function(t) {
|
||||||
var animation = div.getAnimations()[0];
|
var animation = div.getAnimations()[0];
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 reports that it is running on the compositor'
|
||||||
+ ' during playback');
|
+ ' during playback');
|
||||||
|
|
||||||
|
@ -61,7 +71,7 @@ promise_test(function(t) {
|
||||||
|
|
||||||
return animation.ready;
|
return animation.ready;
|
||||||
}).then(function() {
|
}).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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when paused');
|
+ ' when paused');
|
||||||
});
|
});
|
||||||
|
@ -72,7 +82,7 @@ promise_test(function(t) {
|
||||||
var animation = div.getAnimations()[0];
|
var animation = div.getAnimations()[0];
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' for animation of "background"');
|
+ ' for animation of "background"');
|
||||||
});
|
});
|
||||||
|
@ -83,7 +93,7 @@ promise_test(function(t) {
|
||||||
var animation = div.getAnimations()[0];
|
var animation = div.getAnimations()[0];
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 reports that it is running on the compositor'
|
||||||
+ ' when the animation has two properties, where one can run'
|
+ ' when the animation has two properties, where one can run'
|
||||||
+ ' on the compositor, the other cannot');
|
+ ' on the compositor, the other cannot');
|
||||||
|
@ -99,7 +109,7 @@ promise_test(function(t) {
|
||||||
animation.pause();
|
animation.pause();
|
||||||
return animation.ready;
|
return animation.ready;
|
||||||
}).then(function() {
|
}).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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when animation.pause() is called');
|
+ ' when animation.pause() is called');
|
||||||
});
|
});
|
||||||
|
@ -111,13 +121,13 @@ promise_test(function(t) {
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
return animation.ready.then(function() {
|
||||||
animation.finish();
|
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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' immediately after animation.finish() is called');
|
+ ' immediately after animation.finish() is called');
|
||||||
// Check that we don't set the flag back again on the next tick.
|
// Check that we don't set the flag back again on the next tick.
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' on the next tick after animation.finish() is called');
|
+ ' on the next tick after animation.finish() is called');
|
||||||
});
|
});
|
||||||
|
@ -129,13 +139,13 @@ promise_test(function(t) {
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
return animation.ready.then(function() {
|
||||||
animation.currentTime = 100 * MS_PER_SEC;
|
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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' immediately after manually seeking the animation to the end');
|
+ ' immediately after manually seeking the animation to the end');
|
||||||
// Check that we don't set the flag back again on the next tick.
|
// Check that we don't set the flag back again on the next tick.
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' on the next tick after manually seeking the animation to the end');
|
+ ' 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() {
|
return animation.ready.then(function() {
|
||||||
animation.cancel();
|
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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' immediately after animation.cancel() is called');
|
+ ' immediately after animation.cancel() is called');
|
||||||
// Check that we don't set the flag back again on the next tick.
|
// Check that we don't set the flag back again on the next tick.
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' on the next tick after animation.cancel() is called');
|
+ ' on the next tick after animation.cancel() is called');
|
||||||
});
|
});
|
||||||
|
@ -165,7 +175,7 @@ promise_test(function(t) {
|
||||||
var animation = div.getAnimations()[0];
|
var animation = div.getAnimations()[0];
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' while in the delay phase');
|
+ ' while in the delay phase');
|
||||||
});
|
});
|
||||||
|
@ -181,7 +191,7 @@ promise_test(function(t) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
window.requestAnimationFrame(function() {
|
window.requestAnimationFrame(function() {
|
||||||
t.step(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'
|
'Animation reports that it is running on the compositor'
|
||||||
+ ' in requestAnimationFrame callback');
|
+ ' in requestAnimationFrame callback');
|
||||||
});
|
});
|
||||||
|
@ -212,7 +222,7 @@ promise_test(function(t) {
|
||||||
assert_true(!!changedAnimation, 'The animation should be recorded '
|
assert_true(!!changedAnimation, 'The animation should be recorded '
|
||||||
+ 'as one of the changedAnimations');
|
+ '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'
|
'Animation reports that it is running on the compositor'
|
||||||
+ ' in MutationObserver callback');
|
+ ' in MutationObserver callback');
|
||||||
});
|
});
|
||||||
|
@ -244,7 +254,7 @@ promise_test(function(t) {
|
||||||
var timeAtStart = window.performance.now();
|
var timeAtStart = window.performance.now();
|
||||||
function handleFrame() {
|
function handleFrame() {
|
||||||
t.step(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'
|
'Animation reports that it is running on the compositor'
|
||||||
+ ' in requestAnimationFrame callback');
|
+ ' in requestAnimationFrame callback');
|
||||||
});
|
});
|
||||||
|
@ -274,7 +284,7 @@ promise_test(function(t) {
|
||||||
var animation = div.getAnimations()[0];
|
var animation = div.getAnimations()[0];
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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'
|
'Transition reports that it is running on the compositor'
|
||||||
+ ' during playback for opacity transition');
|
+ ' during playback for opacity transition');
|
||||||
});
|
});
|
||||||
|
@ -287,7 +297,7 @@ promise_test(function(t) {
|
||||||
var animation = div.getAnimations()[0];
|
var animation = div.getAnimations()[0];
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 '
|
'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 that cannot (due to Gecko limitations) but where the latter'
|
||||||
+ 'property is overridden in the CSS cascade, the animation should '
|
+ '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);
|
{ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 reports that it is running on the compositor');
|
||||||
|
|
||||||
animation.currentTime = 150 * MS_PER_SEC;
|
animation.currentTime = 150 * MS_PER_SEC;
|
||||||
animation.effect.timing.duration = 100 * 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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when the animation is set a shorter duration than current time');
|
+ ' 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);
|
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 reports that it is running on the compositor');
|
||||||
|
|
||||||
animation.currentTime = 500 * MS_PER_SEC;
|
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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when finished');
|
+ ' when finished');
|
||||||
|
|
||||||
animation.effect.timing.duration = 1000 * MS_PER_SEC;
|
animation.effect.timing.duration = 1000 * MS_PER_SEC;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(animation,
|
||||||
'Animation reports that it is running on the compositor'
|
'Animation reports that it is running on the compositor'
|
||||||
+ ' when restarted');
|
+ ' when restarted');
|
||||||
});
|
});
|
||||||
|
@ -346,19 +356,19 @@ promise_test(function(t) {
|
||||||
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 reports that it is running on the compositor');
|
||||||
|
|
||||||
animation.effect.timing.endDelay = 100 * MS_PER_SEC;
|
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'
|
'Animation reports that it is running on the compositor'
|
||||||
+ ' when endDelay is changed');
|
+ ' when endDelay is changed');
|
||||||
|
|
||||||
animation.currentTime = 110 * MS_PER_SEC;
|
animation.currentTime = 110 * MS_PER_SEC;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when currentTime is during endDelay');
|
+ ' when currentTime is during endDelay');
|
||||||
});
|
});
|
||||||
|
@ -371,13 +381,13 @@ promise_test(function(t) {
|
||||||
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 reports that it is running on the compositor');
|
||||||
|
|
||||||
animation.effect.timing.endDelay = -200 * MS_PER_SEC;
|
animation.effect.timing.endDelay = -200 * MS_PER_SEC;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when endTime is negative value');
|
+ ' when endTime is negative value');
|
||||||
});
|
});
|
||||||
|
@ -387,22 +397,22 @@ promise_test(function(t) {
|
||||||
promise_test(function(t) {
|
promise_test(function(t) {
|
||||||
var animation = addDivAndAnimate(t,
|
var animation = addDivAndAnimate(t,
|
||||||
{},
|
{},
|
||||||
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
{ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 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();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(animation,
|
||||||
'Animation reports that it is running on the compositor'
|
'Animation reports that it is running on the compositor'
|
||||||
+ ' when endTime is positive and endDelay is negative');
|
+ ' when endTime is positive and endDelay is negative');
|
||||||
animation.currentTime = 60 * MS_PER_SEC;
|
animation.currentTime = 110 * MS_PER_SEC;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).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'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when currentTime is after endTime');
|
+ ' when currentTime is after endTime');
|
||||||
});
|
});
|
||||||
|
@ -419,14 +429,14 @@ promise_test(function(t) {
|
||||||
var div = addDiv(t);
|
var div = addDiv(t);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 ' +
|
'Animation with null target reports that it is not running ' +
|
||||||
'on the compositor');
|
'on the compositor');
|
||||||
|
|
||||||
animation.effect.target = div;
|
animation.effect.target = div;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(animation,
|
||||||
'Animation reports that it is running on the compositor ' +
|
'Animation reports that it is running on the compositor ' +
|
||||||
'after setting a valid target');
|
'after setting a valid target');
|
||||||
});
|
});
|
||||||
|
@ -437,11 +447,11 @@ promise_test(function(t) {
|
||||||
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 reports that it is running on the compositor');
|
||||||
|
|
||||||
animation.effect.target = null;
|
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 ' +
|
'Animation reports that it is NOT running on the ' +
|
||||||
'compositor after setting null target');
|
'compositor after setting null target');
|
||||||
});
|
});
|
||||||
|
@ -456,14 +466,14 @@ promise_test(function(t) {
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
return animation.ready.then(function() {
|
||||||
// Will be fixed in bug 1223658.
|
// 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 ' +
|
'Animation with fill:backwards in delay phase reports ' +
|
||||||
'that it is NOT running on the compositor');
|
'that it is NOT running on the compositor');
|
||||||
|
|
||||||
animation.currentTime = 100 * MS_PER_SEC;
|
animation.currentTime = 100 * MS_PER_SEC;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(animation,
|
||||||
'Animation with fill:backwards in delay phase reports ' +
|
'Animation with fill:backwards in delay phase reports ' +
|
||||||
'that it is running on the compositor after delay phase');
|
'that it is running on the compositor after delay phase');
|
||||||
});
|
});
|
||||||
|
@ -479,14 +489,14 @@ promise_test(function(t) {
|
||||||
var another = addDiv(t);
|
var another = addDiv(t);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 ' +
|
'Opacity animation on a 100% opacity keyframe reports ' +
|
||||||
'that it is running on the compositor from the begining');
|
'that it is running on the compositor from the begining');
|
||||||
|
|
||||||
animation.effect.target = another;
|
animation.effect.target = another;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(animation,
|
||||||
'Opacity animation on a 100% opacity keyframe keeps ' +
|
'Opacity animation on a 100% opacity keyframe keeps ' +
|
||||||
'running on the compositor after changing the target ' +
|
'running on the compositor after changing the target ' +
|
||||||
'element');
|
'element');
|
||||||
|
@ -499,7 +509,7 @@ promise_test(function(t) {
|
||||||
var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
|
var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 ' +
|
'Color animation reports that it is not running on the ' +
|
||||||
'compositor');
|
'compositor');
|
||||||
|
|
||||||
|
@ -508,7 +518,7 @@ promise_test(function(t) {
|
||||||
{ opacity: 0, offset: 1 }]);
|
{ opacity: 0, offset: 1 }]);
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(animation,
|
||||||
'100% opacity animation set by using setKeyframes reports ' +
|
'100% opacity animation set by using setKeyframes reports ' +
|
||||||
'that it is running on the compositor');
|
'that it is running on the compositor');
|
||||||
});
|
});
|
||||||
|
@ -525,14 +535,14 @@ promise_test(function(t) {
|
||||||
100 * MS_PER_SEC);
|
100 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 ' +
|
'Color animation reports that it is not running on the ' +
|
||||||
'compositor');
|
'compositor');
|
||||||
|
|
||||||
animation.effect = effect;
|
animation.effect = effect;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(animation,
|
||||||
'100% opacity animation set up by changing effects reports ' +
|
'100% opacity animation set up by changing effects reports ' +
|
||||||
'that it is running on the compositor');
|
'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);
|
var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
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 ' +
|
'Opacity animation on an element which has 100% opacity style with ' +
|
||||||
'!important flag reports that it is not running on the compositor');
|
'!important flag reports that it is not running on the compositor');
|
||||||
// Clear important flag from the opacity style on the target element.
|
// Clear important flag from the opacity style on the target element.
|
||||||
div.style.setProperty("opacity", "1", "");
|
div.style.setProperty("opacity", "1", "");
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).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 '
|
'Opacity animation reports that it is running on the compositor after '
|
||||||
+ 'clearing the !important flag');
|
+ 'clearing the !important flag');
|
||||||
});
|
});
|
||||||
|
@ -568,21 +578,21 @@ promise_test(function(t) {
|
||||||
var another = addDiv(t);
|
var another = addDiv(t);
|
||||||
|
|
||||||
return Promise.all([firstAnimation.ready, secondAnimation.ready]).then(function() {
|
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 ' +
|
'The second opacity animation on an element reports that ' +
|
||||||
'it is running on the compositor');
|
'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 ' +
|
'The first opacity animation on the same element reports ' +
|
||||||
'that it is NOT running on the compositor');
|
'that it is NOT running on the compositor');
|
||||||
|
|
||||||
firstAnimation.effect.target = another;
|
firstAnimation.effect.target = another;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(secondAnimation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(secondAnimation,
|
||||||
'The second opacity animation on the element keeps ' +
|
'The second opacity animation on the element keeps ' +
|
||||||
'running on the compositor after the preiously overridden ' +
|
'running on the compositor after the preiously overridden ' +
|
||||||
'animation is applied to a different element');
|
'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 ' +
|
'The previously overridden opacity animation reports that ' +
|
||||||
'it it running on the compositor after being applied to a ' +
|
'it it running on the compositor after being applied to a ' +
|
||||||
'different element');
|
'different element');
|
||||||
|
@ -599,20 +609,20 @@ promise_test(function(t) {
|
||||||
var another = addDiv(t);
|
var another = addDiv(t);
|
||||||
|
|
||||||
return Promise.all([firstAnimation.ready, secondAnimation.ready]).then(function() {
|
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 ' +
|
'The second opacity animation on an element reports that ' +
|
||||||
'it is running on the compositor');
|
'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 ' +
|
'The first opacity animation on the same element reports ' +
|
||||||
'that it is NOT running on the compositor');
|
'that it is NOT running on the compositor');
|
||||||
|
|
||||||
secondAnimation.effect.target = another;
|
secondAnimation.effect.target = another;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(secondAnimation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(secondAnimation,
|
||||||
'The second opacity animation continues to run on the ' +
|
'The second opacity animation continues to run on the ' +
|
||||||
'compositor after being applied to a different element');
|
'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 ' +
|
'The previously overridden opacity animation now reports ' +
|
||||||
'that it is running on the compositor after the animation ' +
|
'that it is running on the compositor after the animation ' +
|
||||||
'that was overridding it is applied to a different element');
|
'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);
|
var anotherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return Promise.all([animation.ready, anotherAnimation.ready]).then(function() {
|
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 ' +
|
'An opacity animation on an element reports that ' +
|
||||||
'it is running on the compositor');
|
'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 ' +
|
'Opacity animation running on a different element reports ' +
|
||||||
'that it is running on the compositor');
|
'that it is running on the compositor');
|
||||||
|
|
||||||
anotherAnimation.effect.target = div;
|
anotherAnimation.effect.target = div;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(anotherAnimation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(anotherAnimation,
|
||||||
'Animation continues to run on the compositor after ' +
|
'Animation continues to run on the compositor after ' +
|
||||||
'being applied to a different element with a ' +
|
'being applied to a different element with a ' +
|
||||||
'lower-priority animation');
|
'lower-priority animation');
|
||||||
assert_equals(animation.isRunningOnCompositor, false,
|
assert_animation_is_not_running_on_compositor(animation,
|
||||||
'Animation stops running on the compositor after ' +
|
'Animation stops running on the compositor after ' +
|
||||||
'a higher-priority animation originally applied to ' +
|
'a higher-priority animation originally applied to ' +
|
||||||
'a different element is applied to the same element');
|
'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);
|
var animation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return animation.ready.then(function() {
|
return animation.ready.then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
assert_animation_is_running_on_compositor(animation,
|
||||||
'Opacity animation on an element reports ' +
|
'Opacity animation on an element reports ' +
|
||||||
'that it is running on the compositor');
|
'that it is running on the compositor');
|
||||||
|
|
||||||
animation.effect.target = null;
|
animation.effect.target = null;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, false,
|
assert_animation_is_not_running_on_compositor(animation,
|
||||||
'Animation is no longer running on the compositor after ' +
|
'Animation is no longer running on the compositor after ' +
|
||||||
'removing from the element');
|
'removing from the element');
|
||||||
animation.effect.target = importantOpacityElement;
|
animation.effect.target = importantOpacityElement;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(animation.isRunningOnCompositor, false,
|
assert_animation_is_not_running_on_compositor(animation,
|
||||||
'Animation is NOT running on the compositor even after ' +
|
'Animation is NOT running on the compositor even after ' +
|
||||||
'being applied to a different element which has an ' +
|
'being applied to a different element which has an ' +
|
||||||
'!important opacity declaration');
|
'!important opacity declaration');
|
||||||
|
@ -689,27 +699,27 @@ promise_test(function(t) {
|
||||||
var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return Promise.all([lowerAnimation.ready, higherAnimation.ready]).then(function() {
|
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 ' +
|
'An opacity animation on an element reports that ' +
|
||||||
'it is running on the compositor');
|
'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 ' +
|
'Opacity animation on a different element reports ' +
|
||||||
'that it is running on the compositor');
|
'that it is running on the compositor');
|
||||||
|
|
||||||
lowerAnimation.effect.target = null;
|
lowerAnimation.effect.target = null;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(lowerAnimation.isRunningOnCompositor, false,
|
assert_animation_is_not_running_on_compositor(lowerAnimation,
|
||||||
'Animation is no longer running on the compositor after ' +
|
'Animation is no longer running on the compositor after ' +
|
||||||
'being removed from the element');
|
'being removed from the element');
|
||||||
lowerAnimation.effect.target = another;
|
lowerAnimation.effect.target = another;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(lowerAnimation.isRunningOnCompositor, false,
|
assert_animation_is_not_running_on_compositor(lowerAnimation,
|
||||||
'A lower-priority animation does NOT begin running ' +
|
'A lower-priority animation does NOT begin running ' +
|
||||||
'on the compositor after being applied to an element ' +
|
'on the compositor after being applied to an element ' +
|
||||||
'which has a higher-priority animation');
|
'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 ' +
|
'A higher-priority animation continues to run on the ' +
|
||||||
'compositor even after a lower-priority animation is ' +
|
'compositor even after a lower-priority animation is ' +
|
||||||
'applied to the same element');
|
'applied to the same element');
|
||||||
|
@ -726,26 +736,26 @@ promise_test(function(t) {
|
||||||
var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
return Promise.all([lowerAnimation.ready, higherAnimation.ready]).then(function() {
|
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 ' +
|
'An opacity animation on an element reports that ' +
|
||||||
'it is running on the compositor');
|
'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 ' +
|
'Opacity animation on a different element reports ' +
|
||||||
'that it is running on the compositor');
|
'that it is running on the compositor');
|
||||||
|
|
||||||
higherAnimation.effect.target = null;
|
higherAnimation.effect.target = null;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(higherAnimation.isRunningOnCompositor, false,
|
assert_animation_is_not_running_on_compositor(higherAnimation,
|
||||||
'Animation is no longer running on the compositor after ' +
|
'Animation is no longer running on the compositor after ' +
|
||||||
'being removed from the element');
|
'being removed from the element');
|
||||||
higherAnimation.effect.target = div;
|
higherAnimation.effect.target = div;
|
||||||
return waitForFrame();
|
return waitForFrame();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
assert_equals(lowerAnimation.isRunningOnCompositor, false,
|
assert_animation_is_not_running_on_compositor(lowerAnimation,
|
||||||
'Animation stops running on the compositor after ' +
|
'Animation stops running on the compositor after ' +
|
||||||
'a higher-priority animation applied to the same element');
|
'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 ' +
|
'A higher-priority animation begins to running on the ' +
|
||||||
'compositor after being applied to an element which has ' +
|
'compositor after being applied to an element which has ' +
|
||||||
'a lower-priority-animation');
|
'a lower-priority-animation');
|
||||||
|
|
|
@ -1136,10 +1136,11 @@ public:
|
||||||
NS_LITERAL_CSTRING("explicit/dom/memory-file-data/small"),
|
NS_LITERAL_CSTRING("explicit/dom/memory-file-data/small"),
|
||||||
KIND_HEAP, UNITS_BYTES, smallObjectsTotal,
|
KIND_HEAP, UNITS_BYTES, smallObjectsTotal,
|
||||||
nsPrintfCString(
|
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 -- "
|
"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 "
|
"that is, an N-byte memory file may take up more than N bytes of "
|
||||||
"memory."),
|
"memory.", LARGE_OBJECT_MIN_SIZE),
|
||||||
aData);
|
aData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -461,7 +461,7 @@ nsTextFragment::UpdateBidiFlag(const char16_t* aBuffer, uint32_t aLength)
|
||||||
char16_t ch2 = *cp++;
|
char16_t ch2 = *cp++;
|
||||||
utf32Char = SURROGATE_TO_UCS4(ch1, ch2);
|
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;
|
mState.mIsBidi = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1567,7 +1567,11 @@ BrowserElementChild.prototype = {
|
||||||
location = Cc["@mozilla.org/docshell/urifixup;1"]
|
location = Cc["@mozilla.org/docshell/urifixup;1"]
|
||||||
.getService(Ci.nsIURIFixup).createExposableURI(location);
|
.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) {
|
onStateChange: function(webProgress, request, stateFlags, status) {
|
||||||
|
|
|
@ -70,7 +70,9 @@ function test3() {
|
||||||
|
|
||||||
function test4() {
|
function test4() {
|
||||||
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
|
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);
|
checkCanGoBackAndForward(true, false, test5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -81,7 +83,9 @@ function test4() {
|
||||||
|
|
||||||
function test5() {
|
function test5() {
|
||||||
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
|
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);
|
checkCanGoBackAndForward(true, true, test6);
|
||||||
});
|
});
|
||||||
iframe.goBack();
|
iframe.goBack();
|
||||||
|
@ -89,7 +93,9 @@ function test5() {
|
||||||
|
|
||||||
function test6() {
|
function test6() {
|
||||||
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
|
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);
|
checkCanGoBackAndForward(false, true, SimpleTest.finish);
|
||||||
});
|
});
|
||||||
iframe.goBack();
|
iframe.goBack();
|
||||||
|
|
|
@ -24,12 +24,12 @@ function runTest() {
|
||||||
document.body.appendChild(e.detail.frameElement);
|
document.body.appendChild(e.detail.frameElement);
|
||||||
|
|
||||||
e.detail.frameElement.addEventListener('mozbrowserlocationchange', function(e) {
|
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");
|
ok(true, "Got locationchange to http://example.com/#2");
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
else {
|
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(e.isTrusted, 'Event should be trusted.');
|
||||||
ok(!sawLocationChange, 'Just one locationchange event.');
|
ok(!sawLocationChange, 'Just one locationchange event.');
|
||||||
ok(!sawLoadEnd, 'locationchange before load.');
|
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;
|
sawLocationChange = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ var iframe;
|
||||||
|
|
||||||
function testPassword() {
|
function testPassword() {
|
||||||
function locationchange(e) {
|
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',
|
is(uri, 'http://mochi.test:8888/tests/dom/browser-element/mochitest/file_empty.html',
|
||||||
"Username and password shouldn't be exposed in uri.");
|
"Username and password shouldn't be exposed in uri.");
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
|
@ -33,7 +33,7 @@ function testWyciwyg() {
|
||||||
if (locationChangeCount == 0) {
|
if (locationChangeCount == 0) {
|
||||||
locationChangeCount ++;
|
locationChangeCount ++;
|
||||||
} else if (locationChangeCount == 1) {
|
} 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");
|
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);
|
iframe.removeEventListener('mozbrowserlocationchange', locationchange);
|
||||||
SimpleTest.executeSoon(testPassword);
|
SimpleTest.executeSoon(testPassword);
|
||||||
|
|
|
@ -24,13 +24,13 @@ function runTest() {
|
||||||
});
|
});
|
||||||
|
|
||||||
iframe.addEventListener('mozbrowserlocationchange', function(e) {
|
iframe.addEventListener('mozbrowserlocationchange', function(e) {
|
||||||
if (e.detail == browserElementTestHelpers.emptyPage1) {
|
if (e.detail.url == browserElementTestHelpers.emptyPage1) {
|
||||||
gotFirstLocationChange = true;
|
gotFirstLocationChange = true;
|
||||||
if (gotFirstPaint) {
|
if (gotFirstPaint) {
|
||||||
iframe.src = browserElementTestHelpers.emptyPage1 + '?2';
|
iframe.src = browserElementTestHelpers.emptyPage1 + '?2';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e.detail.endsWith('?2')) {
|
else if (e.detail.url.endsWith('?2')) {
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,8 +20,8 @@ function runTest() {
|
||||||
});
|
});
|
||||||
|
|
||||||
iframe.addEventListener('mozbrowserlocationchange', function(e) {
|
iframe.addEventListener('mozbrowserlocationchange', function(e) {
|
||||||
ok(true, "Got locationchange to " + e.detail);
|
ok(true, "Got locationchange to " + e.detail.url);
|
||||||
if (e.detail.endsWith("ForwardName.html#finish")) {
|
if (e.detail.url.endsWith("ForwardName.html#finish")) {
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,7 +38,7 @@ function runTest() {
|
||||||
seenLocationChange = true;
|
seenLocationChange = true;
|
||||||
ok(seenLoadStart, 'Location change after load start.');
|
ok(seenLoadStart, 'Location change after load start.');
|
||||||
ok(!seenLoadEnd, 'Location change before load end.');
|
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) {
|
function loadend(e) {
|
||||||
|
@ -91,7 +91,7 @@ function runTest2() {
|
||||||
seenLocationChange = true;
|
seenLocationChange = true;
|
||||||
ok(seenLoadStart, 'Location change after load start.');
|
ok(seenLoadStart, 'Location change after load start.');
|
||||||
ok(!seenLoadEnd, 'Location change before load end.');
|
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) {
|
iframe.addEventListener('mozbrowserloadend', function(e) {
|
||||||
|
|
|
@ -75,7 +75,7 @@ function test3() {
|
||||||
|
|
||||||
function test4() {
|
function test4() {
|
||||||
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
|
addOneShotIframeEventListener('mozbrowserlocationchange', function(e) {
|
||||||
is(e.detail, browserElementTestHelpers.emptyPage3);
|
is(e.detail.url, browserElementTestHelpers.emptyPage3);
|
||||||
purgeHistory(SimpleTest.finish);
|
purgeHistory(SimpleTest.finish);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ function runTest() {
|
||||||
|
|
||||||
iframe.addEventListener("mozbrowserlocationchange", function onlocchange(e) {
|
iframe.addEventListener("mozbrowserlocationchange", function onlocchange(e) {
|
||||||
var a = document.createElement("a");
|
var a = document.createElement("a");
|
||||||
a.href = e.detail;
|
a.href = e.detail.url;
|
||||||
|
|
||||||
switch (a.hash) {
|
switch (a.hash) {
|
||||||
case "#mousedown":
|
case "#mousedown":
|
||||||
|
|
|
@ -18,7 +18,7 @@ function runTest() {
|
||||||
});
|
});
|
||||||
|
|
||||||
iframe.addEventListener('mozbrowserlocationchange', function(e) {
|
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.');
|
ok(true, 'Got the locationchange we were looking for.');
|
||||||
SimpleTest.finish();
|
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);
|
nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
|
||||||
|
|
||||||
// We do this after calling the base class' PreHandleEvent so that
|
// 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.
|
// event to increase/decrease the value of the number control.
|
||||||
if (!aVisitor.mEvent->DefaultPreventedByContent() && IsMutable()) {
|
if (!aVisitor.mEvent->DefaultPreventedByContent() && IsMutable()) {
|
||||||
StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
|
StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
|
||||||
|
FireChangeEventIfNeeded();
|
||||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||||
}
|
}
|
||||||
} else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
|
} else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
|
||||||
|
@ -4480,6 +4468,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
SetValueOfRangeForUserEvent(newValue);
|
SetValueOfRangeForUserEvent(newValue);
|
||||||
|
FireChangeEventIfNeeded();
|
||||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4557,6 +4546,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||||
do_QueryFrame(GetPrimaryFrame());
|
do_QueryFrame(GetPrimaryFrame());
|
||||||
if (numberControlFrame && numberControlFrame->IsFocused()) {
|
if (numberControlFrame && numberControlFrame->IsFocused()) {
|
||||||
StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
|
StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
|
||||||
|
FireChangeEventIfNeeded();
|
||||||
aVisitor.mEvent->PreventDefault();
|
aVisitor.mEvent->PreventDefault();
|
||||||
}
|
}
|
||||||
} else if (mType == NS_FORM_INPUT_RANGE &&
|
} else if (mType == NS_FORM_INPUT_RANGE &&
|
||||||
|
@ -4570,6 +4560,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||||
MOZ_ASSERT(value.isFinite() && step.isFinite());
|
MOZ_ASSERT(value.isFinite() && step.isFinite());
|
||||||
SetValueOfRangeForUserEvent(wheelEvent->mDeltaY < 0 ?
|
SetValueOfRangeForUserEvent(wheelEvent->mDeltaY < 0 ?
|
||||||
value + step : value - step);
|
value + step : value - step);
|
||||||
|
FireChangeEventIfNeeded();
|
||||||
aVisitor.mEvent->PreventDefault();
|
aVisitor.mEvent->PreventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8344,18 +8335,6 @@ HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence)
|
||||||
aSequence.AppendElements(mEntries);
|
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 dom
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
||||||
|
|
|
@ -1515,12 +1515,6 @@ private:
|
||||||
aType == NS_FORM_INPUT_NUMBER;
|
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 {
|
struct nsFilePickerFilter {
|
||||||
nsFilePickerFilter()
|
nsFilePickerFilter()
|
||||||
: mFilterMask(0) {}
|
: mFilterMask(0) {}
|
||||||
|
|
|
@ -6541,7 +6541,7 @@ HTMLMediaElement::ShouldElementBePaused()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
HTMLMediaElement::SetMediaInfo(const MediaInfo aInfo)
|
HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo)
|
||||||
{
|
{
|
||||||
mMediaInfo = aInfo;
|
mMediaInfo = aInfo;
|
||||||
AudioCaptureStreamChangeIfNeeded();
|
AudioCaptureStreamChangeIfNeeded();
|
||||||
|
@ -6550,7 +6550,16 @@ HTMLMediaElement::SetMediaInfo(const MediaInfo aInfo)
|
||||||
void
|
void
|
||||||
HTMLMediaElement::AudioCaptureStreamChangeIfNeeded()
|
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) {
|
if (mAudioCapturedByWindow && !mCaptureStreamPort) {
|
||||||
nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
|
nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
|
||||||
if (!OwnerDoc()->GetInnerWindow()) {
|
if (!OwnerDoc()->GetInnerWindow()) {
|
||||||
|
|
|
@ -739,7 +739,7 @@ public:
|
||||||
bool ComputedMuted() const;
|
bool ComputedMuted() const;
|
||||||
nsSuspendedTypes ComputedSuspended() const;
|
nsSuspendedTypes ComputedSuspended() const;
|
||||||
|
|
||||||
void SetMediaInfo(const MediaInfo aInfo);
|
void SetMediaInfo(const MediaInfo& aInfo);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~HTMLMediaElement();
|
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");
|
is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes that don't change its value");
|
||||||
range.focus();
|
range.focus();
|
||||||
synthesizeKey("VK_HOME", {});
|
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();
|
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();
|
range.focus();
|
||||||
var bcr = range.getBoundingClientRect();
|
var bcr = range.getBoundingClientRect();
|
||||||
var centerOfRangeX = bcr.width / 2;
|
var centerOfRangeX = bcr.width / 2;
|
||||||
|
@ -233,6 +233,32 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=722599
|
||||||
synthesizeMouse(range, centerOfRangeX - 10, centerOfRangeY, {});
|
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");
|
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 type change test.
|
||||||
input = document.getElementById("input_checkbox");
|
input = document.getElementById("input_checkbox");
|
||||||
input.type = "text";
|
input.type = "text";
|
||||||
|
|
|
@ -631,3 +631,6 @@ skip-if = (os != 'win' && os != 'linux')
|
||||||
[test_bug1260704.html]
|
[test_bug1260704.html]
|
||||||
[test_allowMedia.html]
|
[test_allowMedia.html]
|
||||||
[test_bug1292522_same_domain_with_different_port_number.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 testIdx = 0;
|
||||||
let result = parseInt(input.value);
|
let result = parseInt(input.value);
|
||||||
|
let numberChange = 0;
|
||||||
|
let expectChange = 0;
|
||||||
|
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
++numberChange;
|
||||||
|
}, false);
|
||||||
|
|
||||||
function runNext() {
|
function runNext() {
|
||||||
let p = params[testIdx];
|
let p = params[testIdx];
|
||||||
(p["focus"]) ? input.focus() : input.blur();
|
(p["focus"]) ? input.focus() : input.blur();
|
||||||
|
expectChange = p["valueChanged"] == 0 ? expectChange : expectChange + 1;
|
||||||
result += parseInt(p["valueChanged"]);
|
result += parseInt(p["valueChanged"]);
|
||||||
synthesizeWheel(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] });
|
synthesizeWheel(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] });
|
||||||
window.postMessage("finished", "http://mochi.test:8888");
|
window.postMessage("finished", "http://mochi.test:8888");
|
||||||
|
@ -57,6 +64,8 @@ function runTests() {
|
||||||
ok(input.value == result,
|
ok(input.value == result,
|
||||||
"Handle wheel in number input test-" + testIdx + " expect " + result +
|
"Handle wheel in number input test-" + testIdx + " expect " + result +
|
||||||
" get " + input.value);
|
" 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();
|
(testIdx >= params.length) ? SimpleTest.finish() : runNext();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,14 +43,23 @@ function runTests() {
|
||||||
|
|
||||||
let testIdx = 0;
|
let testIdx = 0;
|
||||||
let result = parseInt(input.value);
|
let result = parseInt(input.value);
|
||||||
|
let rangeChange = 0;
|
||||||
|
let expectChange = 0;
|
||||||
|
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
++rangeChange;
|
||||||
|
}, false);
|
||||||
|
|
||||||
function runNext() {
|
function runNext() {
|
||||||
let p = params[testIdx];
|
let p = params[testIdx];
|
||||||
(p["focus"]) ? input.focus() : input.blur();
|
(p["focus"]) ? input.focus() : input.blur();
|
||||||
|
expectChange = p["valueChanged"] == 0 ? expectChange : expectChange + 1;
|
||||||
result += parseInt(p["valueChanged"]);
|
result += parseInt(p["valueChanged"]);
|
||||||
sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
|
sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
|
||||||
ok(input.value == result,
|
ok(input.value == result,
|
||||||
"Handle wheel in range input test-" + testIdx + " expect " + result + " get " + input.value);
|
"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();
|
(++testIdx >= params.length) ? SimpleTest.finish() : runNext();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,11 @@ function runTests() {
|
||||||
|
|
||||||
let testIdx = 0;
|
let testIdx = 0;
|
||||||
let result = parseInt(input.value);
|
let result = parseInt(input.value);
|
||||||
|
let rangeChange = 0;
|
||||||
|
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
++rangeChange;
|
||||||
|
}, false);
|
||||||
|
|
||||||
function runNext() {
|
function runNext() {
|
||||||
let p = params[testIdx];
|
let p = params[testIdx];
|
||||||
|
@ -48,6 +53,7 @@ function runTests() {
|
||||||
sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
|
sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
|
||||||
ok(input.value == result,
|
ok(input.value == result,
|
||||||
"Handle wheel in range input test-" + testIdx + " expect " + result + " get " + input.value);
|
"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++;
|
||||||
(testIdx >= params.length) ? SimpleTest.finish() : runNext();
|
(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>();
|
AudioSegment* inputSegment = tracks->Get<AudioSegment>();
|
||||||
StreamTime inputStart = s->GraphTimeToStreamTimeWithBlocking(aFrom);
|
StreamTime inputStart = s->GraphTimeToStreamTimeWithBlocking(aFrom);
|
||||||
StreamTime inputEnd = s->GraphTimeToStreamTimeWithBlocking(aTo);
|
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;
|
AudioSegment toMix;
|
||||||
toMix.AppendSlice(*inputSegment, inputStart, inputEnd);
|
toMix.AppendSlice(*inputSegment, inputStart, inputEnd);
|
||||||
// Care for streams blocked in the [aTo, aFrom] range.
|
// Care for streams blocked in the [aTo, aFrom] range.
|
||||||
|
|
|
@ -27,6 +27,12 @@ namespace {
|
||||||
|
|
||||||
// This mutex protects the variables below.
|
// This mutex protects the variables below.
|
||||||
StaticMutex sMutex;
|
StaticMutex sMutex;
|
||||||
|
enum class CubebState {
|
||||||
|
Uninitialized = 0,
|
||||||
|
Initialized,
|
||||||
|
Error,
|
||||||
|
Shutdown
|
||||||
|
} sCubebState = CubebState::Uninitialized;
|
||||||
cubeb* sCubebContext;
|
cubeb* sCubebContext;
|
||||||
double sVolumeScale;
|
double sVolumeScale;
|
||||||
uint32_t sCubebLatency;
|
uint32_t sCubebLatency;
|
||||||
|
@ -126,11 +132,15 @@ cubeb* GetCubebContext()
|
||||||
void InitPreferredSampleRate()
|
void InitPreferredSampleRate()
|
||||||
{
|
{
|
||||||
StaticMutexAutoLock lock(sMutex);
|
StaticMutexAutoLock lock(sMutex);
|
||||||
if (sPreferredSampleRate == 0 &&
|
if (sPreferredSampleRate == 0) {
|
||||||
cubeb_get_preferred_sample_rate(GetCubebContextUnlocked(),
|
cubeb* context = GetCubebContextUnlocked();
|
||||||
&sPreferredSampleRate) != CUBEB_OK) {
|
if (context) {
|
||||||
// Query failed, use a sensible default.
|
if (cubeb_get_preferred_sample_rate(context,
|
||||||
sPreferredSampleRate = 44100;
|
&sPreferredSampleRate) != CUBEB_OK) {
|
||||||
|
// Query failed, use a sensible default.
|
||||||
|
sPreferredSampleRate = 44100;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +173,9 @@ void InitBrandName()
|
||||||
cubeb* GetCubebContextUnlocked()
|
cubeb* GetCubebContextUnlocked()
|
||||||
{
|
{
|
||||||
sMutex.AssertCurrentThreadOwns();
|
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;
|
return sCubebContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,8 +186,9 @@ cubeb* GetCubebContextUnlocked()
|
||||||
sBrandName, "Did not initialize sbrandName, and not on the main thread?");
|
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.");
|
NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context.");
|
||||||
|
sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Error;
|
||||||
|
|
||||||
return sCubebContext;
|
return sCubebContext;
|
||||||
}
|
}
|
||||||
|
@ -247,6 +260,8 @@ void ShutdownLibrary()
|
||||||
sCubebContext = nullptr;
|
sCubebContext = nullptr;
|
||||||
}
|
}
|
||||||
sBrandName = nullptr;
|
sBrandName = nullptr;
|
||||||
|
// This will ensure we don't try to re-create a context.
|
||||||
|
sCubebState = CubebState::Shutdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t MaxNumberOfChannels()
|
uint32_t MaxNumberOfChannels()
|
||||||
|
@ -298,12 +313,15 @@ cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel)
|
||||||
|
|
||||||
void GetCurrentBackend(nsAString& aBackend)
|
void GetCurrentBackend(nsAString& aBackend)
|
||||||
{
|
{
|
||||||
const char* backend = cubeb_get_backend_id(GetCubebContext());
|
cubeb* cubebContext = GetCubebContext();
|
||||||
if (!backend) {
|
if (cubebContext) {
|
||||||
aBackend.AssignLiteral("unknown");
|
const char* backend = cubeb_get_backend_id(cubebContext);
|
||||||
return;
|
if (backend) {
|
||||||
|
aBackend.AssignASCII(backend);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
aBackend.AssignASCII(backend);
|
aBackend.AssignLiteral("unknown");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace CubebUtils
|
} // namespace CubebUtils
|
||||||
|
|
|
@ -588,6 +588,12 @@ AudioCallbackDriver::~AudioCallbackDriver()
|
||||||
void
|
void
|
||||||
AudioCallbackDriver::Init()
|
AudioCallbackDriver::Init()
|
||||||
{
|
{
|
||||||
|
cubeb* cubebContext = CubebUtils::GetCubebContext();
|
||||||
|
if (!cubebContext) {
|
||||||
|
NS_WARNING("Could not get cubeb context.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cubeb_stream_params output;
|
cubeb_stream_params output;
|
||||||
cubeb_stream_params input;
|
cubeb_stream_params input;
|
||||||
uint32_t latency_frames;
|
uint32_t latency_frames;
|
||||||
|
@ -619,7 +625,7 @@ AudioCallbackDriver::Init()
|
||||||
output.format = CUBEB_SAMPLE_FLOAT32NE;
|
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.");
|
NS_WARNING("Could not get minimal latency from cubeb.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -650,7 +656,7 @@ AudioCallbackDriver::Init()
|
||||||
// XXX Only pass input input if we have an input listener. Always
|
// 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.
|
// set up output because it's easier, and it will just get silence.
|
||||||
// XXX Add support for adding/removing an input listener later.
|
// XXX Add support for adding/removing an input listener later.
|
||||||
cubeb_stream_init(CubebUtils::GetCubebContext(), &stream,
|
cubeb_stream_init(cubebContext, &stream,
|
||||||
"AudioCallbackDriver",
|
"AudioCallbackDriver",
|
||||||
input_id,
|
input_id,
|
||||||
mGraphImpl->mInputWanted ? &input : nullptr,
|
mGraphImpl->mInputWanted ? &input : nullptr,
|
||||||
|
|
|
@ -584,11 +584,14 @@ MediaDecoderStateMachine::OnAudioDecoded(MediaData* aAudioSample)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case DECODER_STATE_DECODING_FIRSTFRAME: {
|
||||||
|
Push(audio, MediaData::AUDIO_DATA);
|
||||||
|
MaybeFinishDecodeFirstFrame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case DECODER_STATE_DECODING: {
|
case DECODER_STATE_DECODING: {
|
||||||
Push(audio, MediaData::AUDIO_DATA);
|
Push(audio, MediaData::AUDIO_DATA);
|
||||||
if (MaybeFinishDecodeFirstFrame()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mIsAudioPrerolling && DonePrerollingAudio()) {
|
if (mIsAudioPrerolling && DonePrerollingAudio()) {
|
||||||
StopPrerollingAudio();
|
StopPrerollingAudio();
|
||||||
}
|
}
|
||||||
|
@ -682,9 +685,6 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
|
||||||
} else {
|
} else {
|
||||||
StopPrerollingVideo();
|
StopPrerollingVideo();
|
||||||
}
|
}
|
||||||
if (mState == DECODER_STATE_BUFFERING || mState == DECODER_STATE_DECODING) {
|
|
||||||
MaybeFinishDecodeFirstFrame();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -708,11 +708,11 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
|
||||||
StopPrerollingVideo();
|
StopPrerollingVideo();
|
||||||
}
|
}
|
||||||
switch (mState) {
|
switch (mState) {
|
||||||
|
case DECODER_STATE_DECODING_FIRSTFRAME:
|
||||||
|
MaybeFinishDecodeFirstFrame();
|
||||||
|
return;
|
||||||
case DECODER_STATE_BUFFERING:
|
case DECODER_STATE_BUFFERING:
|
||||||
case DECODER_STATE_DECODING: {
|
case DECODER_STATE_DECODING: {
|
||||||
if (MaybeFinishDecodeFirstFrame()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (CheckIfDecodeComplete()) {
|
if (CheckIfDecodeComplete()) {
|
||||||
SetState(DECODER_STATE_COMPLETED);
|
SetState(DECODER_STATE_COMPLETED);
|
||||||
return;
|
return;
|
||||||
|
@ -729,23 +729,24 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
void
|
||||||
MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
|
MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
if (mSentFirstFrameLoadedEvent ||
|
MOZ_ASSERT(!mSentFirstFrameLoadedEvent);
|
||||||
(IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
|
|
||||||
|
if ((IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
|
||||||
(IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
|
(IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
|
||||||
return false;
|
return;
|
||||||
}
|
|
||||||
FinishDecodeFirstFrame();
|
|
||||||
if (!mQueuedSeek.Exists()) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can now complete the pending seek.
|
FinishDecodeFirstFrame();
|
||||||
InitiateSeek(Move(mQueuedSeek));
|
|
||||||
return true;
|
if (mQueuedSeek.Exists()) {
|
||||||
|
InitiateSeek(Move(mQueuedSeek));
|
||||||
|
} else {
|
||||||
|
SetState(DECODER_STATE_DECODING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -772,11 +773,14 @@ MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case DECODER_STATE_DECODING_FIRSTFRAME: {
|
||||||
|
Push(video, MediaData::VIDEO_DATA);
|
||||||
|
MaybeFinishDecodeFirstFrame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case DECODER_STATE_DECODING: {
|
case DECODER_STATE_DECODING: {
|
||||||
Push(video, MediaData::VIDEO_DATA);
|
Push(video, MediaData::VIDEO_DATA);
|
||||||
if (MaybeFinishDecodeFirstFrame()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mIsVideoPrerolling && DonePrerollingVideo()) {
|
if (mIsVideoPrerolling && DonePrerollingVideo()) {
|
||||||
StopPrerollingVideo();
|
StopPrerollingVideo();
|
||||||
}
|
}
|
||||||
|
@ -790,8 +794,7 @@ MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TimeDuration decodeTime = TimeStamp::Now() - aDecodeStartTime;
|
TimeDuration decodeTime = TimeStamp::Now() - aDecodeStartTime;
|
||||||
if (mSentFirstFrameLoadedEvent &&
|
if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
|
||||||
THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
|
|
||||||
!HasLowUndecodedData())
|
!HasLowUndecodedData())
|
||||||
{
|
{
|
||||||
mLowAudioThresholdUsecs =
|
mLowAudioThresholdUsecs =
|
||||||
|
@ -828,6 +831,8 @@ bool
|
||||||
MediaDecoderStateMachine::CheckIfDecodeComplete()
|
MediaDecoderStateMachine::CheckIfDecodeComplete()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
// DecodeComplete is possible only after decoding first frames.
|
||||||
|
MOZ_ASSERT(mSentFirstFrameLoadedEvent);
|
||||||
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
|
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
|
||||||
mState == DECODER_STATE_BUFFERING);
|
mState == DECODER_STATE_BUFFERING);
|
||||||
return !IsVideoDecoding() && !IsAudioDecoding();
|
return !IsVideoDecoding() && !IsAudioDecoding();
|
||||||
|
@ -941,6 +946,8 @@ void MediaDecoderStateMachine::StopPlayback()
|
||||||
void MediaDecoderStateMachine::MaybeStartPlayback()
|
void MediaDecoderStateMachine::MaybeStartPlayback()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
// Should try to start playback only after decoding first frames.
|
||||||
|
MOZ_ASSERT(mSentFirstFrameLoadedEvent);
|
||||||
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
|
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
|
||||||
mState == DECODER_STATE_COMPLETED);
|
mState == DECODER_STATE_COMPLETED);
|
||||||
|
|
||||||
|
@ -976,6 +983,8 @@ void
|
||||||
MediaDecoderStateMachine::MaybeStartBuffering()
|
MediaDecoderStateMachine::MaybeStartBuffering()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
// Buffering makes senses only after decoding first frames.
|
||||||
|
MOZ_ASSERT(mSentFirstFrameLoadedEvent);
|
||||||
MOZ_ASSERT(mState == DECODER_STATE_DECODING);
|
MOZ_ASSERT(mState == DECODER_STATE_DECODING);
|
||||||
|
|
||||||
if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
|
if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
|
||||||
|
@ -1023,14 +1032,15 @@ void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
|
||||||
MediaDecoderStateMachine::ToStateStr(State aState)
|
MediaDecoderStateMachine::ToStateStr(State aState)
|
||||||
{
|
{
|
||||||
switch (aState) {
|
switch (aState) {
|
||||||
case DECODER_STATE_DECODING_METADATA: return "DECODING_METADATA";
|
case DECODER_STATE_DECODING_METADATA: return "DECODING_METADATA";
|
||||||
case DECODER_STATE_WAIT_FOR_CDM: return "WAIT_FOR_CDM";
|
case DECODER_STATE_WAIT_FOR_CDM: return "WAIT_FOR_CDM";
|
||||||
case DECODER_STATE_DORMANT: return "DORMANT";
|
case DECODER_STATE_DORMANT: return "DORMANT";
|
||||||
case DECODER_STATE_DECODING: return "DECODING";
|
case DECODER_STATE_DECODING_FIRSTFRAME: return "DECODING_FIRSTFRAME";
|
||||||
case DECODER_STATE_SEEKING: return "SEEKING";
|
case DECODER_STATE_DECODING: return "DECODING";
|
||||||
case DECODER_STATE_BUFFERING: return "BUFFERING";
|
case DECODER_STATE_SEEKING: return "SEEKING";
|
||||||
case DECODER_STATE_COMPLETED: return "COMPLETED";
|
case DECODER_STATE_BUFFERING: return "BUFFERING";
|
||||||
case DECODER_STATE_SHUTDOWN: return "SHUTDOWN";
|
case DECODER_STATE_COMPLETED: return "COMPLETED";
|
||||||
|
case DECODER_STATE_SHUTDOWN: return "SHUTDOWN";
|
||||||
default: MOZ_ASSERT_UNREACHABLE("Invalid state.");
|
default: MOZ_ASSERT_UNREACHABLE("Invalid state.");
|
||||||
}
|
}
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
|
@ -1087,6 +1097,9 @@ MediaDecoderStateMachine::EnterState(State aState)
|
||||||
Reset();
|
Reset();
|
||||||
mReader->ReleaseResources();
|
mReader->ReleaseResources();
|
||||||
break;
|
break;
|
||||||
|
case DECODER_STATE_DECODING_FIRSTFRAME:
|
||||||
|
DecodeFirstFrame();
|
||||||
|
break;
|
||||||
case DECODER_STATE_DECODING:
|
case DECODER_STATE_DECODING:
|
||||||
StartDecoding();
|
StartDecoding();
|
||||||
break;
|
break;
|
||||||
|
@ -1254,18 +1267,39 @@ MediaDecoderStateMachine::Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaDecoderStateMachine::StartDecoding()
|
MediaDecoderStateMachine::DecodeFirstFrame()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
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
|
// Handle pending seek.
|
||||||
// will be handled after decoding first frames.
|
if (mQueuedSeek.Exists() &&
|
||||||
if (mSentFirstFrameLoadedEvent && mQueuedSeek.Exists()) {
|
(mSentFirstFrameLoadedEvent || mReader->ForceZeroStartTime())) {
|
||||||
InitiateSeek(Move(mQueuedSeek));
|
InitiateSeek(Move(mQueuedSeek));
|
||||||
return;
|
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()) {
|
if (CheckIfDecodeComplete()) {
|
||||||
SetState(DECODER_STATE_COMPLETED);
|
SetState(DECODER_STATE_COMPLETED);
|
||||||
return;
|
return;
|
||||||
|
@ -1307,7 +1341,9 @@ void MediaDecoderStateMachine::PlayStateChanged()
|
||||||
// all state transitions to the state machine task queue, but for now we just
|
// 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(),
|
// make sure that none of the possible main-thread state transitions (Seek(),
|
||||||
// SetDormant(), and Shutdown()) have not occurred.
|
// 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)
|
mState != DECODER_STATE_COMPLETED)
|
||||||
{
|
{
|
||||||
DECODER_LOG("Unexpected state - Bailing out of PlayInternal()");
|
DECODER_LOG("Unexpected state - Bailing out of PlayInternal()");
|
||||||
|
@ -1499,13 +1535,12 @@ MediaDecoderStateMachine::Seek(SeekTarget aTarget)
|
||||||
return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
|
return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA,
|
MOZ_ASSERT(mDuration.Ref().isSome(), "We should have got duration already");
|
||||||
"We should have got duration already");
|
|
||||||
|
|
||||||
// Can't seek until the start time is known.
|
// Can't seek until the start time is known.
|
||||||
bool hasStartTime = mSentFirstFrameLoadedEvent || mReader->ForceZeroStartTime();
|
bool hasStartTime = mSentFirstFrameLoadedEvent || mReader->ForceZeroStartTime();
|
||||||
// Can't seek when state is WAIT_FOR_CDM or DORMANT.
|
// 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) {
|
if (!stateAllowed || !hasStartTime) {
|
||||||
DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
|
DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
|
||||||
|
@ -1548,6 +1583,7 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
|
||||||
if (mState != DECODER_STATE_DECODING &&
|
if (mState != DECODER_STATE_DECODING &&
|
||||||
|
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
|
||||||
mState != DECODER_STATE_BUFFERING &&
|
mState != DECODER_STATE_BUFFERING &&
|
||||||
mState != DECODER_STATE_SEEKING) {
|
mState != DECODER_STATE_SEEKING) {
|
||||||
return;
|
return;
|
||||||
|
@ -1733,23 +1769,16 @@ MediaDecoderStateMachine::DiscardSeekTaskIfExist()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
void
|
||||||
MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
|
MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
if (!IsShutdown() && NeedToDecodeAudio()) {
|
||||||
if (IsShutdown()) {
|
EnsureAudioDecodeTaskQueued();
|
||||||
return NS_ERROR_FAILURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NeedToDecodeAudio()) {
|
|
||||||
return EnsureAudioDecodeTaskQueued();
|
|
||||||
}
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
void
|
||||||
MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
|
MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
@ -1759,17 +1788,18 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
|
||||||
IsAudioDecoding(), AudioRequestStatus());
|
IsAudioDecoding(), AudioRequestStatus());
|
||||||
|
|
||||||
if (mState != DECODER_STATE_DECODING &&
|
if (mState != DECODER_STATE_DECODING &&
|
||||||
|
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
|
||||||
mState != DECODER_STATE_BUFFERING) {
|
mState != DECODER_STATE_BUFFERING) {
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsAudioDecoding() || mReader->IsRequestingAudioData() ||
|
if (!IsAudioDecoding() ||
|
||||||
|
mReader->IsRequestingAudioData() ||
|
||||||
mReader->IsWaitingAudioData()) {
|
mReader->IsWaitingAudioData()) {
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestAudioData();
|
RequestAudioData();
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1784,23 +1814,16 @@ MediaDecoderStateMachine::RequestAudioData()
|
||||||
mReader->RequestAudioData();
|
mReader->RequestAudioData();
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
void
|
||||||
MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
|
MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
if (!IsShutdown() && NeedToDecodeVideo()) {
|
||||||
if (IsShutdown()) {
|
EnsureVideoDecodeTaskQueued();
|
||||||
return NS_ERROR_FAILURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NeedToDecodeVideo()) {
|
|
||||||
return EnsureVideoDecodeTaskQueued();
|
|
||||||
}
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
void
|
||||||
MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
|
MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
@ -1810,17 +1833,18 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
|
||||||
IsVideoDecoding(), VideoRequestStatus());
|
IsVideoDecoding(), VideoRequestStatus());
|
||||||
|
|
||||||
if (mState != DECODER_STATE_DECODING &&
|
if (mState != DECODER_STATE_DECODING &&
|
||||||
|
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
|
||||||
mState != DECODER_STATE_BUFFERING) {
|
mState != DECODER_STATE_BUFFERING) {
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsVideoDecoding() || mReader->IsRequestingVideoData() ||
|
if (!IsVideoDecoding() ||
|
||||||
|
mReader->IsRequestingVideoData() ||
|
||||||
mReader->IsWaitingVideoData()) {
|
mReader->IsWaitingVideoData()) {
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestVideoData();
|
RequestVideoData();
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1900,7 +1924,7 @@ bool MediaDecoderStateMachine::HasLowUndecodedData()
|
||||||
bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs)
|
bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
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");
|
"Must have loaded first frame for mBuffered to be valid");
|
||||||
|
|
||||||
// If we don't have a duration, mBuffered is probably not going to have
|
// If we don't have a duration, mBuffered is probably not going to have
|
||||||
|
@ -2017,7 +2041,7 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetState(DECODER_STATE_DECODING);
|
SetState(DECODER_STATE_DECODING_FIRSTFRAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -2243,7 +2267,8 @@ MediaDecoderStateMachine::FinishShutdown()
|
||||||
return OwnerThread()->BeginShutdown();
|
return OwnerThread()->BeginShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult MediaDecoderStateMachine::RunStateMachine()
|
void
|
||||||
|
MediaDecoderStateMachine::RunStateMachine()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(OnTaskQueue());
|
MOZ_ASSERT(OnTaskQueue());
|
||||||
|
|
||||||
|
@ -2251,20 +2276,17 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||||
mDispatchedStateMachine = false;
|
mDispatchedStateMachine = false;
|
||||||
|
|
||||||
MediaResource* resource = mResource;
|
MediaResource* resource = mResource;
|
||||||
NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
|
NS_ENSURE_TRUE_VOID(resource);
|
||||||
|
|
||||||
switch (mState) {
|
switch (mState) {
|
||||||
case DECODER_STATE_SHUTDOWN:
|
case DECODER_STATE_SHUTDOWN:
|
||||||
case DECODER_STATE_DORMANT:
|
case DECODER_STATE_DORMANT:
|
||||||
case DECODER_STATE_WAIT_FOR_CDM:
|
case DECODER_STATE_WAIT_FOR_CDM:
|
||||||
case DECODER_STATE_DECODING_METADATA:
|
case DECODER_STATE_DECODING_METADATA:
|
||||||
return NS_OK;
|
case DECODER_STATE_DECODING_FIRSTFRAME:
|
||||||
|
return;
|
||||||
|
|
||||||
case DECODER_STATE_DECODING: {
|
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())
|
if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying())
|
||||||
{
|
{
|
||||||
// We're playing, but the element/decoder is in paused state. Stop
|
// We're playing, but the element/decoder is in paused state. Stop
|
||||||
|
@ -2280,7 +2302,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||||
IsStateMachineScheduled(),
|
IsStateMachineScheduled(),
|
||||||
"Must have timer scheduled");
|
"Must have timer scheduled");
|
||||||
MaybeStartBuffering();
|
MaybeStartBuffering();
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case DECODER_STATE_BUFFERING: {
|
case DECODER_STATE_BUFFERING: {
|
||||||
|
@ -2303,7 +2325,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||||
mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
|
mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
|
||||||
(mQuickBuffering ? "(quick exit)" : ""));
|
(mQuickBuffering ? "(quick exit)" : ""));
|
||||||
ScheduleStateMachineIn(USECS_PER_S);
|
ScheduleStateMachineIn(USECS_PER_S);
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
} else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
|
} else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
|
||||||
MOZ_ASSERT(mReader->IsWaitForDataSupported(),
|
MOZ_ASSERT(mReader->IsWaitForDataSupported(),
|
||||||
|
@ -2315,7 +2337,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||||
"mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
|
"mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
|
||||||
OutOfDecodedAudio(), AudioRequestStatus(),
|
OutOfDecodedAudio(), AudioRequestStatus(),
|
||||||
OutOfDecodedVideo(), VideoRequestStatus());
|
OutOfDecodedVideo(), VideoRequestStatus());
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DECODER_LOG("Changed state from BUFFERING to DECODING");
|
DECODER_LOG("Changed state from BUFFERING to DECODING");
|
||||||
|
@ -2323,11 +2345,11 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||||
SetState(DECODER_STATE_DECODING);
|
SetState(DECODER_STATE_DECODING);
|
||||||
|
|
||||||
NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
|
NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case DECODER_STATE_SEEKING: {
|
case DECODER_STATE_SEEKING: {
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case DECODER_STATE_COMPLETED: {
|
case DECODER_STATE_COMPLETED: {
|
||||||
|
@ -2345,7 +2367,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||||
NS_ASSERTION(!IsPlaying() ||
|
NS_ASSERTION(!IsPlaying() ||
|
||||||
IsStateMachineScheduled(),
|
IsStateMachineScheduled(),
|
||||||
"Must have timer scheduled");
|
"Must have timer scheduled");
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopPlayback in order to reset the IsPlaying() state so audio
|
// StopPlayback in order to reset the IsPlaying() state so audio
|
||||||
|
@ -2370,11 +2392,9 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||||
StopMediaSink();
|
StopMediaSink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return NS_OK;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -2742,7 +2762,7 @@ MediaDecoderStateMachine::OnCDMProxyReady(RefPtr<CDMProxy> aProxy)
|
||||||
mCDMProxy = aProxy;
|
mCDMProxy = aProxy;
|
||||||
mReader->SetCDMProxy(aProxy);
|
mReader->SetCDMProxy(aProxy);
|
||||||
if (mState == DECODER_STATE_WAIT_FOR_CDM) {
|
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_DECODING_METADATA,
|
||||||
DECODER_STATE_WAIT_FOR_CDM,
|
DECODER_STATE_WAIT_FOR_CDM,
|
||||||
DECODER_STATE_DORMANT,
|
DECODER_STATE_DORMANT,
|
||||||
|
DECODER_STATE_DECODING_FIRSTFRAME,
|
||||||
DECODER_STATE_DECODING,
|
DECODER_STATE_DECODING,
|
||||||
DECODER_STATE_SEEKING,
|
DECODER_STATE_SEEKING,
|
||||||
DECODER_STATE_BUFFERING,
|
DECODER_STATE_BUFFERING,
|
||||||
|
@ -473,8 +474,10 @@ protected:
|
||||||
// If we don't, switch to buffering mode.
|
// If we don't, switch to buffering mode.
|
||||||
void MaybeStartBuffering();
|
void MaybeStartBuffering();
|
||||||
|
|
||||||
// Moves the decoder into decoding state. Called on the state machine
|
// The entry action of DECODER_STATE_DECODING_FIRSTFRAME.
|
||||||
// thread. The decoder monitor must be held.
|
void DecodeFirstFrame();
|
||||||
|
|
||||||
|
// The entry action of DECODER_STATE_DECODING.
|
||||||
void StartDecoding();
|
void StartDecoding();
|
||||||
|
|
||||||
// Moves the decoder into the shutdown state, and dispatches an error
|
// Moves the decoder into the shutdown state, and dispatches an error
|
||||||
|
@ -483,11 +486,6 @@ protected:
|
||||||
// decode thread.
|
// decode thread.
|
||||||
void DecodeError();
|
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.
|
// Dispatches a LoadedMetadataEvent.
|
||||||
// This is threadsafe and can be called on any thread.
|
// This is threadsafe and can be called on any thread.
|
||||||
// The decoder monitor must be held.
|
// The decoder monitor must be held.
|
||||||
|
@ -498,25 +496,19 @@ protected:
|
||||||
// Clears any previous seeking state and initiates a new seek on the decoder.
|
// Clears any previous seeking state and initiates a new seek on the decoder.
|
||||||
RefPtr<MediaDecoder::SeekPromise> InitiateSeek(SeekJob aSeekJob);
|
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.
|
// Start a task to decode audio.
|
||||||
// The decoder monitor must be held.
|
// The decoder monitor must be held.
|
||||||
void RequestAudioData();
|
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.
|
// Start a task to decode video.
|
||||||
// The decoder monitor must be held.
|
// The decoder monitor must be held.
|
||||||
void RequestVideoData();
|
void RequestVideoData();
|
||||||
|
@ -550,11 +542,9 @@ protected:
|
||||||
void OnMetadataRead(MetadataHolder* aMetadata);
|
void OnMetadataRead(MetadataHolder* aMetadata);
|
||||||
void OnMetadataNotRead(ReadMetadataFailureReason aReason);
|
void OnMetadataNotRead(ReadMetadataFailureReason aReason);
|
||||||
|
|
||||||
// Checks whether we're finished decoding first audio and/or video packets.
|
// Notify FirstFrameLoaded if having decoded first frames and
|
||||||
// If so will trigger firing loadeddata event.
|
// transition to SEEKING if there is any pending seek, or DECODING otherwise.
|
||||||
// If there are any queued seek, will change state to DECODER_STATE_SEEKING
|
void MaybeFinishDecodeFirstFrame();
|
||||||
// and return true.
|
|
||||||
bool MaybeFinishDecodeFirstFrame();
|
|
||||||
|
|
||||||
void FinishDecodeFirstFrame();
|
void FinishDecodeFirstFrame();
|
||||||
|
|
||||||
|
@ -564,10 +554,8 @@ protected:
|
||||||
// Queries our state to see whether the decode has finished for all streams.
|
// Queries our state to see whether the decode has finished for all streams.
|
||||||
bool CheckIfDecodeComplete();
|
bool CheckIfDecodeComplete();
|
||||||
|
|
||||||
// Performs one "cycle" of the state machine. Polls the state, and may send
|
// Performs one "cycle" of the state machine.
|
||||||
// a video frame to be displayed, and generally manages the decode. Called
|
void RunStateMachine();
|
||||||
// periodically via timer to ensure the video stays in sync.
|
|
||||||
nsresult RunStateMachine();
|
|
||||||
|
|
||||||
bool IsStateMachineScheduled() const;
|
bool IsStateMachineScheduled() const;
|
||||||
|
|
||||||
|
|
|
@ -294,6 +294,12 @@ void VideoFrameContainer::ClearFutureFrames()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VideoFrameContainer::ClearCachedResources()
|
||||||
|
{
|
||||||
|
mImageContainer->ClearCachedResources();
|
||||||
|
}
|
||||||
|
|
||||||
ImageContainer* VideoFrameContainer::GetImageContainer() {
|
ImageContainer* VideoFrameContainer::GetImageContainer() {
|
||||||
return mImageContainer;
|
return mImageContainer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,9 @@ public:
|
||||||
// but was actually painted at t+n, this returns n in seconds. Threadsafe.
|
// but was actually painted at t+n, this returns n in seconds. Threadsafe.
|
||||||
double GetFrameDelay();
|
double GetFrameDelay();
|
||||||
|
|
||||||
|
// Clear any resources that are not immediately necessary.
|
||||||
|
void ClearCachedResources();
|
||||||
|
|
||||||
// Returns a new frame ID for SetCurrentFrames(). The client must either
|
// Returns a new frame ID for SetCurrentFrames(). The client must either
|
||||||
// call this on only one thread or provide barriers. Do not use together
|
// call this on only one thread or provide barriers. Do not use together
|
||||||
// with SetCurrentFrame().
|
// with SetCurrentFrame().
|
||||||
|
|
|
@ -145,6 +145,9 @@ VideoSink::SetPlaying(bool aPlaying)
|
||||||
mUpdateScheduler.Reset();
|
mUpdateScheduler.Reset();
|
||||||
// Since playback is paused, tell compositor to render only current frame.
|
// Since playback is paused, tell compositor to render only current frame.
|
||||||
RenderVideoFrames(1);
|
RenderVideoFrames(1);
|
||||||
|
if (mContainer) {
|
||||||
|
mContainer->ClearCachedResources();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mAudioSink->SetPlaying(aPlaying);
|
mAudioSink->SetPlaying(aPlaying);
|
||||||
|
|
|
@ -54,9 +54,14 @@ StaticMutex AudioInputCubeb::sMutex;
|
||||||
|
|
||||||
void AudioInputCubeb::UpdateDeviceList()
|
void AudioInputCubeb::UpdateDeviceList()
|
||||||
{
|
{
|
||||||
|
cubeb* cubebContext = CubebUtils::GetCubebContext();
|
||||||
|
if (!cubebContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cubeb_device_collection *devices = nullptr;
|
cubeb_device_collection *devices = nullptr;
|
||||||
|
|
||||||
if (CUBEB_OK != cubeb_enumerate_devices(CubebUtils::GetCubebContext(),
|
if (CUBEB_OK != cubeb_enumerate_devices(cubebContext,
|
||||||
CUBEB_DEVICE_TYPE_INPUT,
|
CUBEB_DEVICE_TYPE_INPUT,
|
||||||
&devices)) {
|
&devices)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -72,7 +72,7 @@ AvailabilityCollection::Remove(PresentationAvailability* aAvailability)
|
||||||
}
|
}
|
||||||
|
|
||||||
already_AddRefed<PresentationAvailability>
|
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());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ AvailabilityCollection::Find(const uint64_t aWindowId, const nsAString& aUrl)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (availability->Equals(aWindowId, aUrl)) {
|
if (availability->Equals(aWindowId, aUrls)) {
|
||||||
RefPtr<PresentationAvailability> matchedAvailability = availability.get();
|
RefPtr<PresentationAvailability> matchedAvailability = availability.get();
|
||||||
return matchedAvailability.forget();
|
return matchedAvailability.forget();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
void Remove(PresentationAvailability* aAvailability);
|
void Remove(PresentationAvailability* aAvailability);
|
||||||
|
|
||||||
already_AddRefed<PresentationAvailability>
|
already_AddRefed<PresentationAvailability>
|
||||||
Find(const uint64_t aWindowId, const nsAString& aUrl);
|
Find(const uint64_t aWindowId, const nsTArray<nsString>& aUrls);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class StaticAutoPtr<AvailabilityCollection>;
|
friend class StaticAutoPtr<AvailabilityCollection>;
|
||||||
|
|
|
@ -37,20 +37,20 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||||
|
|
||||||
/* static */ already_AddRefed<PresentationAvailability>
|
/* static */ already_AddRefed<PresentationAvailability>
|
||||||
PresentationAvailability::Create(nsPIDOMWindowInner* aWindow,
|
PresentationAvailability::Create(nsPIDOMWindowInner* aWindow,
|
||||||
const nsAString& aUrl,
|
const nsTArray<nsString>& aUrls,
|
||||||
RefPtr<Promise>& aPromise)
|
RefPtr<Promise>& aPromise)
|
||||||
{
|
{
|
||||||
RefPtr<PresentationAvailability> availability =
|
RefPtr<PresentationAvailability> availability =
|
||||||
new PresentationAvailability(aWindow, aUrl);
|
new PresentationAvailability(aWindow, aUrls);
|
||||||
return NS_WARN_IF(!availability->Init(aPromise)) ? nullptr
|
return NS_WARN_IF(!availability->Init(aPromise)) ? nullptr
|
||||||
: availability.forget();
|
: availability.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow,
|
PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow,
|
||||||
const nsAString& aUrl)
|
const nsTArray<nsString>& aUrls)
|
||||||
: DOMEventTargetHelper(aWindow)
|
: DOMEventTargetHelper(aWindow)
|
||||||
, mIsAvailable(false)
|
, mIsAvailable(false)
|
||||||
, mUrl(aUrl)
|
, mUrls(aUrls)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,10 +120,15 @@ PresentationAvailability::WrapObject(JSContext* aCx,
|
||||||
|
|
||||||
bool
|
bool
|
||||||
PresentationAvailability::Equals(const uint64_t aWindowID,
|
PresentationAvailability::Equals(const uint64_t aWindowID,
|
||||||
const nsAString& aUrl) const
|
const nsTArray<nsString>& aUrls) const
|
||||||
{
|
{
|
||||||
if (GetOwner() && GetOwner()->WindowID() == aWindowID &&
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,8 +167,7 @@ PresentationAvailability::NotifyAvailableChange(bool aIsAvailable)
|
||||||
void
|
void
|
||||||
PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)
|
PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)
|
||||||
{
|
{
|
||||||
PRES_DEBUG("%s:id[%s]\n", __func__,
|
PRES_DEBUG("%s\n", __func__);
|
||||||
NS_ConvertUTF16toUTF8(mUrl).get());
|
|
||||||
bool isChanged = (aIsAvailable != mIsAvailable);
|
bool isChanged = (aIsAvailable != mIsAvailable);
|
||||||
|
|
||||||
mIsAvailable = aIsAvailable;
|
mIsAvailable = aIsAvailable;
|
||||||
|
|
|
@ -29,7 +29,7 @@ public:
|
||||||
|
|
||||||
static already_AddRefed<PresentationAvailability>
|
static already_AddRefed<PresentationAvailability>
|
||||||
Create(nsPIDOMWindowInner* aWindow,
|
Create(nsPIDOMWindowInner* aWindow,
|
||||||
const nsAString& aUrl,
|
const nsTArray<nsString>& aUrls,
|
||||||
RefPtr<Promise>& aPromise);
|
RefPtr<Promise>& aPromise);
|
||||||
|
|
||||||
virtual void DisconnectFromOwner() override;
|
virtual void DisconnectFromOwner() override;
|
||||||
|
@ -37,7 +37,7 @@ public:
|
||||||
virtual JSObject* WrapObject(JSContext* aCx,
|
virtual JSObject* WrapObject(JSContext* aCx,
|
||||||
JS::Handle<JSObject*> aGivenProto) override;
|
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();
|
bool IsCachedValueReady();
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit PresentationAvailability(nsPIDOMWindowInner* aWindow,
|
explicit PresentationAvailability(nsPIDOMWindowInner* aWindow,
|
||||||
const nsAString& aUrl);
|
const nsTArray<nsString>& aUrls);
|
||||||
|
|
||||||
virtual ~PresentationAvailability();
|
virtual ~PresentationAvailability();
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ private:
|
||||||
|
|
||||||
nsTArray<RefPtr<Promise>> mPromises;
|
nsTArray<RefPtr<Promise>> mPromises;
|
||||||
|
|
||||||
nsString mUrl;
|
nsTArray<nsString> mUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -25,11 +25,9 @@ using namespace mozilla::dom;
|
||||||
NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback)
|
NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback)
|
||||||
|
|
||||||
PresentationRequesterCallback::PresentationRequesterCallback(PresentationRequest* aRequest,
|
PresentationRequesterCallback::PresentationRequesterCallback(PresentationRequest* aRequest,
|
||||||
const nsAString& aUrl,
|
|
||||||
const nsAString& aSessionId,
|
const nsAString& aSessionId,
|
||||||
Promise* aPromise)
|
Promise* aPromise)
|
||||||
: mRequest(aRequest)
|
: mRequest(aRequest)
|
||||||
, mUrl(aUrl)
|
|
||||||
, mSessionId(aSessionId)
|
, mSessionId(aSessionId)
|
||||||
, mPromise(aPromise)
|
, mPromise(aPromise)
|
||||||
{
|
{
|
||||||
|
@ -44,12 +42,16 @@ PresentationRequesterCallback::~PresentationRequesterCallback()
|
||||||
|
|
||||||
// nsIPresentationServiceCallback
|
// nsIPresentationServiceCallback
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationRequesterCallback::NotifySuccess()
|
PresentationRequesterCallback::NotifySuccess(const nsAString& aUrl)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
if (aUrl.IsEmpty()) {
|
||||||
|
return NotifyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<PresentationConnection> connection =
|
RefPtr<PresentationConnection> connection =
|
||||||
PresentationConnection::Create(mRequest->GetOwner(), mSessionId, mUrl,
|
PresentationConnection::Create(mRequest->GetOwner(), mSessionId, aUrl,
|
||||||
nsIPresentationService::ROLE_CONTROLLER);
|
nsIPresentationService::ROLE_CONTROLLER);
|
||||||
if (NS_WARN_IF(!connection)) {
|
if (NS_WARN_IF(!connection)) {
|
||||||
mPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
|
mPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
|
||||||
|
@ -79,11 +81,10 @@ NS_IMPL_ISUPPORTS_INHERITED0(PresentationReconnectCallback,
|
||||||
|
|
||||||
PresentationReconnectCallback::PresentationReconnectCallback(
|
PresentationReconnectCallback::PresentationReconnectCallback(
|
||||||
PresentationRequest* aRequest,
|
PresentationRequest* aRequest,
|
||||||
const nsAString& aUrl,
|
|
||||||
const nsAString& aSessionId,
|
const nsAString& aSessionId,
|
||||||
Promise* aPromise,
|
Promise* aPromise,
|
||||||
PresentationConnection* aConnection)
|
PresentationConnection* aConnection)
|
||||||
: PresentationRequesterCallback(aRequest, aUrl, aSessionId, aPromise)
|
: PresentationRequesterCallback(aRequest, aSessionId, aPromise)
|
||||||
, mConnection(aConnection)
|
, mConnection(aConnection)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -93,7 +94,7 @@ PresentationReconnectCallback::~PresentationReconnectCallback()
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationReconnectCallback::NotifySuccess()
|
PresentationReconnectCallback::NotifySuccess(const nsAString& aUrl)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
@ -116,7 +117,7 @@ PresentationReconnectCallback::NotifySuccess()
|
||||||
} else {
|
} else {
|
||||||
// Use |PresentationRequesterCallback::NotifySuccess| to create a new
|
// Use |PresentationRequesterCallback::NotifySuccess| to create a new
|
||||||
// connection since we don't find one that can be reused.
|
// 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))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ public:
|
||||||
NS_DECL_NSIPRESENTATIONSERVICECALLBACK
|
NS_DECL_NSIPRESENTATIONSERVICECALLBACK
|
||||||
|
|
||||||
PresentationRequesterCallback(PresentationRequest* aRequest,
|
PresentationRequesterCallback(PresentationRequest* aRequest,
|
||||||
const nsAString& aUrl,
|
|
||||||
const nsAString& aSessionId,
|
const nsAString& aSessionId,
|
||||||
Promise* aPromise);
|
Promise* aPromise);
|
||||||
|
|
||||||
|
@ -39,7 +38,6 @@ protected:
|
||||||
virtual ~PresentationRequesterCallback();
|
virtual ~PresentationRequesterCallback();
|
||||||
|
|
||||||
RefPtr<PresentationRequest> mRequest;
|
RefPtr<PresentationRequest> mRequest;
|
||||||
nsString mUrl;
|
|
||||||
nsString mSessionId;
|
nsString mSessionId;
|
||||||
RefPtr<Promise> mPromise;
|
RefPtr<Promise> mPromise;
|
||||||
};
|
};
|
||||||
|
@ -51,7 +49,6 @@ public:
|
||||||
NS_DECL_NSIPRESENTATIONSERVICECALLBACK
|
NS_DECL_NSIPRESENTATIONSERVICECALLBACK
|
||||||
|
|
||||||
PresentationReconnectCallback(PresentationRequest* aRequest,
|
PresentationReconnectCallback(PresentationRequest* aRequest,
|
||||||
const nsAString& aUrl,
|
|
||||||
const nsAString& aSessionId,
|
const nsAString& aSessionId,
|
||||||
Promise* aPromise,
|
Promise* aPromise,
|
||||||
PresentationConnection* aConnection);
|
PresentationConnection* aConnection);
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "mozilla/dom/PresentationRequestBinding.h"
|
#include "mozilla/dom/PresentationRequestBinding.h"
|
||||||
#include "mozilla/dom/PresentationConnectionAvailableEvent.h"
|
#include "mozilla/dom/PresentationConnectionAvailableEvent.h"
|
||||||
#include "mozilla/dom/Promise.h"
|
#include "mozilla/dom/Promise.h"
|
||||||
|
#include "mozilla/Move.h"
|
||||||
#include "mozIThirdPartyUtil.h"
|
#include "mozIThirdPartyUtil.h"
|
||||||
#include "nsContentSecurityManager.h"
|
#include "nsContentSecurityManager.h"
|
||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
|
@ -64,6 +65,16 @@ GetAbsoluteURL(const nsAString& aUrl,
|
||||||
PresentationRequest::Constructor(const GlobalObject& aGlobal,
|
PresentationRequest::Constructor(const GlobalObject& aGlobal,
|
||||||
const nsAString& aUrl,
|
const nsAString& aUrl,
|
||||||
ErrorResult& aRv)
|
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());
|
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
|
||||||
if (!window) {
|
if (!window) {
|
||||||
|
@ -71,31 +82,35 @@ PresentationRequest::Constructor(const GlobalObject& aGlobal,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the URL is not empty.
|
if (aUrls.IsEmpty()) {
|
||||||
if (NS_WARN_IF(aUrl.IsEmpty())) {
|
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||||
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve relative URL to absolute URL
|
// Resolve relative URL to absolute URL
|
||||||
nsCOMPtr<nsIURI> baseUri = window->GetDocBaseURI();
|
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;
|
urls.AppendElement(absoluteUrl);
|
||||||
nsresult rv = GetAbsoluteURL(aUrl, baseUri, window->GetExtantDoc(), absoluteUrl);
|
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
||||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<PresentationRequest> request =
|
RefPtr<PresentationRequest> request =
|
||||||
new PresentationRequest(window, absoluteUrl);
|
new PresentationRequest(window, Move(urls));
|
||||||
return NS_WARN_IF(!request->Init()) ? nullptr : request.forget();
|
return NS_WARN_IF(!request->Init()) ? nullptr : request.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
PresentationRequest::PresentationRequest(nsPIDOMWindowInner* aWindow,
|
PresentationRequest::PresentationRequest(nsPIDOMWindowInner* aWindow,
|
||||||
const nsAString& aUrl)
|
nsTArray<nsString>&& aUrls)
|
||||||
: DOMEventTargetHelper(aWindow)
|
: DOMEventTargetHelper(aWindow)
|
||||||
, mUrl(aUrl)
|
, mUrls(Move(aUrls))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +167,7 @@ PresentationRequest::StartWithDevice(const nsAString& aDeviceId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsProhibitMixedSecurityContexts(doc) &&
|
if (IsProhibitMixedSecurityContexts(doc) &&
|
||||||
!IsPrioriAuthenticatedURL(mUrl)) {
|
!IsAllURLAuthenticated()) {
|
||||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
return promise.forget();
|
return promise.forget();
|
||||||
}
|
}
|
||||||
|
@ -185,8 +200,8 @@ PresentationRequest::StartWithDevice(const nsAString& aDeviceId,
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIPresentationServiceCallback> callback =
|
nsCOMPtr<nsIPresentationServiceCallback> callback =
|
||||||
new PresentationRequesterCallback(this, mUrl, id, promise);
|
new PresentationRequesterCallback(this, id, promise);
|
||||||
rv = service->StartSession(mUrl, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
|
rv = service->StartSession(mUrls, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
|
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
|
||||||
}
|
}
|
||||||
|
@ -216,7 +231,7 @@ PresentationRequest::Reconnect(const nsAString& aPresentationId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsProhibitMixedSecurityContexts(doc) &&
|
if (IsProhibitMixedSecurityContexts(doc) &&
|
||||||
!IsPrioriAuthenticatedURL(mUrl)) {
|
!IsAllURLAuthenticated()) {
|
||||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
return promise.forget();
|
return promise.forget();
|
||||||
}
|
}
|
||||||
|
@ -262,7 +277,7 @@ PresentationRequest::FindOrCreatePresentationConnection(
|
||||||
if (connection) {
|
if (connection) {
|
||||||
nsAutoString url;
|
nsAutoString url;
|
||||||
connection->GetUrl(url);
|
connection->GetUrl(url);
|
||||||
if (url.Equals(mUrl)) {
|
if (mUrls.Contains(url)) {
|
||||||
switch (connection->State()) {
|
switch (connection->State()) {
|
||||||
case PresentationConnectionState::Closed:
|
case PresentationConnectionState::Closed:
|
||||||
// We found the matched connection.
|
// We found the matched connection.
|
||||||
|
@ -293,13 +308,12 @@ PresentationRequest::FindOrCreatePresentationConnection(
|
||||||
|
|
||||||
nsCOMPtr<nsIPresentationServiceCallback> callback =
|
nsCOMPtr<nsIPresentationServiceCallback> callback =
|
||||||
new PresentationReconnectCallback(this,
|
new PresentationReconnectCallback(this,
|
||||||
mUrl,
|
|
||||||
aPresentationId,
|
aPresentationId,
|
||||||
aPromise,
|
aPromise,
|
||||||
connection);
|
connection);
|
||||||
|
|
||||||
nsresult rv =
|
nsresult rv =
|
||||||
service->ReconnectSession(mUrl,
|
service->ReconnectSession(mUrls,
|
||||||
aPresentationId,
|
aPresentationId,
|
||||||
nsIPresentationService::ROLE_CONTROLLER,
|
nsIPresentationService::ROLE_CONTROLLER,
|
||||||
callback);
|
callback);
|
||||||
|
@ -311,8 +325,7 @@ PresentationRequest::FindOrCreatePresentationConnection(
|
||||||
already_AddRefed<Promise>
|
already_AddRefed<Promise>
|
||||||
PresentationRequest::GetAvailability(ErrorResult& aRv)
|
PresentationRequest::GetAvailability(ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
PRES_DEBUG("%s:id[%s]\n", __func__,
|
PRES_DEBUG("%s\n", __func__);
|
||||||
NS_ConvertUTF16toUTF8(mUrl).get());
|
|
||||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
|
||||||
if (NS_WARN_IF(!global)) {
|
if (NS_WARN_IF(!global)) {
|
||||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||||
|
@ -331,7 +344,7 @@ PresentationRequest::GetAvailability(ErrorResult& aRv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsProhibitMixedSecurityContexts(doc) &&
|
if (IsProhibitMixedSecurityContexts(doc) &&
|
||||||
!IsPrioriAuthenticatedURL(mUrl)) {
|
!IsAllURLAuthenticated()) {
|
||||||
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
return promise.forget();
|
return promise.forget();
|
||||||
}
|
}
|
||||||
|
@ -363,13 +376,12 @@ PresentationRequest::FindOrCreatePresentationAvailability(RefPtr<Promise>& aProm
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<PresentationAvailability> availability =
|
RefPtr<PresentationAvailability> availability =
|
||||||
collection->Find(GetOwner()->WindowID(), mUrl);
|
collection->Find(GetOwner()->WindowID(), mUrls);
|
||||||
|
|
||||||
if (!availability) {
|
if (!availability) {
|
||||||
availability = PresentationAvailability::Create(GetOwner(), mUrl, aPromise);
|
availability = PresentationAvailability::Create(GetOwner(), mUrls, aPromise);
|
||||||
} else {
|
} else {
|
||||||
PRES_DEBUG(">resolve with same object:id[%s]\n",
|
PRES_DEBUG(">resolve with same object\n");
|
||||||
NS_ConvertUTF16toUTF8(mUrl).get());
|
|
||||||
|
|
||||||
// Fetching cached available devices is asynchronous in our implementation,
|
// Fetching cached available devices is asynchronous in our implementation,
|
||||||
// we need to ensure the promise is resolved in order.
|
// we need to ensure the promise is resolved in order.
|
||||||
|
@ -474,3 +486,15 @@ PresentationRequest::IsPrioriAuthenticatedURL(const nsAString& aUrl)
|
||||||
csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
|
csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
|
||||||
return 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
|
#ifndef mozilla_dom_PresentationRequest_h
|
||||||
#define mozilla_dom_PresentationRequest_h
|
#define mozilla_dom_PresentationRequest_h
|
||||||
|
|
||||||
|
#include "mozilla/dom/BindingDeclarations.h"
|
||||||
#include "mozilla/DOMEventTargetHelper.h"
|
#include "mozilla/DOMEventTargetHelper.h"
|
||||||
|
|
||||||
class nsIDocument;
|
class nsIDocument;
|
||||||
|
@ -23,9 +24,15 @@ class PresentationRequest final : public DOMEventTargetHelper
|
||||||
public:
|
public:
|
||||||
NS_DECL_ISUPPORTS_INHERITED
|
NS_DECL_ISUPPORTS_INHERITED
|
||||||
|
|
||||||
static already_AddRefed<PresentationRequest> Constructor(const GlobalObject& aGlobal,
|
static already_AddRefed<PresentationRequest> Constructor(
|
||||||
const nsAString& aUrl,
|
const GlobalObject& aGlobal,
|
||||||
ErrorResult& aRv);
|
const nsAString& aUrl,
|
||||||
|
ErrorResult& aRv);
|
||||||
|
|
||||||
|
static already_AddRefed<PresentationRequest> Constructor(
|
||||||
|
const GlobalObject& aGlobal,
|
||||||
|
const Sequence<nsString>& aUrls,
|
||||||
|
ErrorResult& aRv);
|
||||||
|
|
||||||
virtual JSObject* WrapObject(JSContext* aCx,
|
virtual JSObject* WrapObject(JSContext* aCx,
|
||||||
JS::Handle<JSObject*> aGivenProto) override;
|
JS::Handle<JSObject*> aGivenProto) override;
|
||||||
|
@ -47,7 +54,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PresentationRequest(nsPIDOMWindowInner* aWindow,
|
PresentationRequest(nsPIDOMWindowInner* aWindow,
|
||||||
const nsAString& aUrl);
|
nsTArray<nsString>&& aUrls);
|
||||||
|
|
||||||
~PresentationRequest();
|
~PresentationRequest();
|
||||||
|
|
||||||
|
@ -64,7 +71,9 @@ private:
|
||||||
// Implement https://w3c.github.io/webappsec-mixed-content/#a-priori-authenticated-url
|
// Implement https://w3c.github.io/webappsec-mixed-content/#a-priori-authenticated-url
|
||||||
bool IsPrioriAuthenticatedURL(const nsAString& aUrl);
|
bool IsPrioriAuthenticatedURL(const nsAString& aUrl);
|
||||||
|
|
||||||
nsString mUrl;
|
bool IsAllURLAuthenticated();
|
||||||
|
|
||||||
|
nsTArray<nsString> mUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -59,6 +59,44 @@ IsSameDevice(nsIPresentationDevice* aDevice, nsIPresentationDevice* aDeviceAnoth
|
||||||
return true;
|
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
|
* Implementation of PresentationDeviceRequest
|
||||||
*/
|
*/
|
||||||
|
@ -69,7 +107,7 @@ public:
|
||||||
NS_DECL_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
NS_DECL_NSIPRESENTATIONDEVICEREQUEST
|
NS_DECL_NSIPRESENTATIONDEVICEREQUEST
|
||||||
|
|
||||||
PresentationDeviceRequest(const nsAString& aRequestUrl,
|
PresentationDeviceRequest(const nsTArray<nsString>& aUrls,
|
||||||
const nsAString& aId,
|
const nsAString& aId,
|
||||||
const nsAString& aOrigin,
|
const nsAString& aOrigin,
|
||||||
uint64_t aWindowId,
|
uint64_t aWindowId,
|
||||||
|
@ -77,9 +115,10 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual ~PresentationDeviceRequest() = default;
|
virtual ~PresentationDeviceRequest() = default;
|
||||||
nsresult CreateSessionInfo(nsIPresentationDevice* aDevice);
|
nsresult CreateSessionInfo(nsIPresentationDevice* aDevice,
|
||||||
|
const nsAString& aSelectedRequestUrl);
|
||||||
|
|
||||||
nsString mRequestUrl;
|
nsTArray<nsString> mRequestUrls;
|
||||||
nsString mId;
|
nsString mId;
|
||||||
nsString mOrigin;
|
nsString mOrigin;
|
||||||
uint64_t mWindowId;
|
uint64_t mWindowId;
|
||||||
|
@ -94,18 +133,18 @@ LazyLogModule gPresentationLog("Presentation");
|
||||||
NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
|
NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
|
||||||
|
|
||||||
PresentationDeviceRequest::PresentationDeviceRequest(
|
PresentationDeviceRequest::PresentationDeviceRequest(
|
||||||
const nsAString& aRequestUrl,
|
const nsTArray<nsString>& aUrls,
|
||||||
const nsAString& aId,
|
const nsAString& aId,
|
||||||
const nsAString& aOrigin,
|
const nsAString& aOrigin,
|
||||||
uint64_t aWindowId,
|
uint64_t aWindowId,
|
||||||
nsIPresentationServiceCallback* aCallback)
|
nsIPresentationServiceCallback* aCallback)
|
||||||
: mRequestUrl(aRequestUrl)
|
: mRequestUrls(aUrls)
|
||||||
, mId(aId)
|
, mId(aId)
|
||||||
, mOrigin(aOrigin)
|
, mOrigin(aOrigin)
|
||||||
, mWindowId(aWindowId)
|
, mWindowId(aWindowId)
|
||||||
, mCallback(aCallback)
|
, mCallback(aCallback)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(!mRequestUrl.IsEmpty());
|
MOZ_ASSERT(!mRequestUrls.IsEmpty());
|
||||||
MOZ_ASSERT(!mId.IsEmpty());
|
MOZ_ASSERT(!mId.IsEmpty());
|
||||||
MOZ_ASSERT(!mOrigin.IsEmpty());
|
MOZ_ASSERT(!mOrigin.IsEmpty());
|
||||||
MOZ_ASSERT(mCallback);
|
MOZ_ASSERT(mCallback);
|
||||||
|
@ -119,29 +158,47 @@ PresentationDeviceRequest::GetOrigin(nsAString& aOrigin)
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationDeviceRequest::GetRequestURL(nsAString& aRequestUrl)
|
PresentationDeviceRequest::GetRequestURLs(nsIArray** aUrls)
|
||||||
{
|
{
|
||||||
aRequestUrl = mRequestUrl;
|
return ConvertURLArrayHelper(mRequestUrls, aUrls);
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice)
|
PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
MOZ_ASSERT(aDevice);
|
if (NS_WARN_IF(!aDevice)) {
|
||||||
|
MOZ_ASSERT(false, "|aDevice| should noe be null.");
|
||||||
nsresult rv = CreateSessionInfo(aDevice);
|
mCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
return NS_ERROR_INVALID_ARG;
|
||||||
mCallback->NotifyError(rv);
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
nsresult
|
||||||
PresentationDeviceRequest::CreateSessionInfo(nsIPresentationDevice* aDevice)
|
PresentationDeviceRequest::CreateSessionInfo(
|
||||||
|
nsIPresentationDevice* aDevice,
|
||||||
|
const nsAString& aSelectedRequestUrl)
|
||||||
{
|
{
|
||||||
nsCOMPtr<nsIPresentationService> service =
|
nsCOMPtr<nsIPresentationService> service =
|
||||||
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
|
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
|
||||||
|
@ -152,7 +209,7 @@ PresentationDeviceRequest::CreateSessionInfo(nsIPresentationDevice* aDevice)
|
||||||
// Create the controlling session info
|
// Create the controlling session info
|
||||||
RefPtr<PresentationSessionInfo> info =
|
RefPtr<PresentationSessionInfo> info =
|
||||||
static_cast<PresentationService*>(service.get())->
|
static_cast<PresentationService*>(service.get())->
|
||||||
CreateControllingSessionInfo(mRequestUrl, mId, mWindowId);
|
CreateControllingSessionInfo(aSelectedRequestUrl, mId, mWindowId);
|
||||||
if (NS_WARN_IF(!info)) {
|
if (NS_WARN_IF(!info)) {
|
||||||
return NS_ERROR_NOT_AVAILABLE;
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
@ -572,23 +629,22 @@ PresentationService::IsAppInstalled(nsIURI* aUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationService::StartSession(const nsAString& aUrl,
|
PresentationService::StartSession(const nsTArray<nsString>& aUrls,
|
||||||
const nsAString& aSessionId,
|
const nsAString& aSessionId,
|
||||||
const nsAString& aOrigin,
|
const nsAString& aOrigin,
|
||||||
const nsAString& aDeviceId,
|
const nsAString& aDeviceId,
|
||||||
uint64_t aWindowId,
|
uint64_t aWindowId,
|
||||||
nsIPresentationServiceCallback* aCallback)
|
nsIPresentationServiceCallback* aCallback)
|
||||||
{
|
{
|
||||||
PRES_DEBUG("%s:url[%s], id[%s]\n", __func__,
|
PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get());
|
||||||
NS_ConvertUTF16toUTF8(aUrl).get(),
|
|
||||||
NS_ConvertUTF16toUTF8(aSessionId).get());
|
|
||||||
|
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
MOZ_ASSERT(aCallback);
|
MOZ_ASSERT(aCallback);
|
||||||
MOZ_ASSERT(!aSessionId.IsEmpty());
|
MOZ_ASSERT(!aSessionId.IsEmpty());
|
||||||
|
MOZ_ASSERT(!aUrls.IsEmpty());
|
||||||
|
|
||||||
nsCOMPtr<nsIPresentationDeviceRequest> request =
|
nsCOMPtr<nsIPresentationDeviceRequest> request =
|
||||||
new PresentationDeviceRequest(aUrl,
|
new PresentationDeviceRequest(aUrls,
|
||||||
aSessionId,
|
aSessionId,
|
||||||
aOrigin,
|
aOrigin,
|
||||||
aWindowId,
|
aWindowId,
|
||||||
|
@ -617,15 +673,11 @@ PresentationService::StartSession(const nsAString& aUrl,
|
||||||
return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
|
return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIMutableArray> presentationUrls =
|
nsCOMPtr<nsIArray> presentationUrls;
|
||||||
do_CreateInstance(NS_ARRAY_CONTRACTID);
|
if (NS_WARN_IF(NS_FAILED(
|
||||||
if (!presentationUrls) {
|
ConvertURLArrayHelper(aUrls, getter_AddRefs(presentationUrls))))) {
|
||||||
return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
|
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;
|
nsCOMPtr<nsIArray> devices;
|
||||||
nsresult rv = deviceManager->GetAvailableDevices(presentationUrls, getter_AddRefs(devices));
|
nsresult rv = deviceManager->GetAvailableDevices(presentationUrls, getter_AddRefs(devices));
|
||||||
|
@ -747,18 +799,17 @@ PresentationService::TerminateSession(const nsAString& aSessionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationService::ReconnectSession(const nsAString& aUrl,
|
PresentationService::ReconnectSession(const nsTArray<nsString>& aUrls,
|
||||||
const nsAString& aSessionId,
|
const nsAString& aSessionId,
|
||||||
uint8_t aRole,
|
uint8_t aRole,
|
||||||
nsIPresentationServiceCallback* aCallback)
|
nsIPresentationServiceCallback* aCallback)
|
||||||
{
|
{
|
||||||
PRES_DEBUG("%s:url[%s], id[%s]\n", __func__,
|
PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get());
|
||||||
NS_ConvertUTF16toUTF8(aUrl).get(),
|
|
||||||
NS_ConvertUTF16toUTF8(aSessionId).get());
|
|
||||||
|
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
MOZ_ASSERT(!aSessionId.IsEmpty());
|
MOZ_ASSERT(!aSessionId.IsEmpty());
|
||||||
MOZ_ASSERT(aCallback);
|
MOZ_ASSERT(aCallback);
|
||||||
|
MOZ_ASSERT(!aUrls.IsEmpty());
|
||||||
|
|
||||||
if (aRole != nsIPresentationService::ROLE_CONTROLLER) {
|
if (aRole != nsIPresentationService::ROLE_CONTROLLER) {
|
||||||
MOZ_ASSERT(false, "Only controller can call ReconnectSession.");
|
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);
|
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);
|
return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -806,7 +806,7 @@ PresentationControllingInfo::NotifyReconnected()
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mReconnectCallback->NotifySuccess();
|
return mReconnectCallback->NotifySuccess(GetUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "nsISupports.idl"
|
#include "nsISupports.idl"
|
||||||
|
|
||||||
|
interface nsIArray;
|
||||||
interface nsIPresentationDevice;
|
interface nsIPresentationDevice;
|
||||||
|
|
||||||
%{C++
|
%{C++
|
||||||
|
@ -19,8 +20,8 @@ interface nsIPresentationDeviceRequest : nsISupports
|
||||||
// The origin which initiate the request.
|
// The origin which initiate the request.
|
||||||
readonly attribute DOMString origin;
|
readonly attribute DOMString origin;
|
||||||
|
|
||||||
// The URL to be opened after selection.
|
// The array of candidate URLs.
|
||||||
readonly attribute DOMString requestURL;
|
readonly attribute nsIArray requestURLs;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Callback after selecting a device
|
* Callback after selecting a device
|
||||||
|
|
|
@ -15,15 +15,23 @@ interface nsIPresentationSessionListener;
|
||||||
{ 0x9e, 0x4f, 0x40, 0x58, 0xb8, 0x51, 0x98, 0x32 } }
|
{ 0x9e, 0x4f, 0x40, 0x58, 0xb8, 0x51, 0x98, 0x32 } }
|
||||||
#define PRESENTATION_SERVICE_CONTRACTID \
|
#define PRESENTATION_SERVICE_CONTRACTID \
|
||||||
"@mozilla.org/presentation/presentationservice;1"
|
"@mozilla.org/presentation/presentationservice;1"
|
||||||
|
|
||||||
|
#include "nsTArray.h"
|
||||||
|
|
||||||
|
class nsString;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
[ref] native URLArrayRef(const nsTArray<nsString>);
|
||||||
|
|
||||||
[scriptable, uuid(12073206-0065-4b10-9488-a6eb9b23e65b)]
|
[scriptable, uuid(12073206-0065-4b10-9488-a6eb9b23e65b)]
|
||||||
interface nsIPresentationServiceCallback : nsISupports
|
interface nsIPresentationServiceCallback : nsISupports
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Called when the operation succeeds.
|
* 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.
|
* 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
|
* Start a new presentation session and display a prompt box which asks users
|
||||||
* to select a device.
|
* 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 sessionId: An ID to identify presentation session.
|
||||||
* @param origin: The url of requesting page.
|
* @param origin: The url of requesting page.
|
||||||
* @param deviceId: The specified device of handling this request, null string
|
* @param deviceId: The specified device of handling this request, null string
|
||||||
|
@ -61,12 +69,12 @@ interface nsIPresentationService : nsISupports
|
||||||
* established successfully with the selected device.
|
* established successfully with the selected device.
|
||||||
* Otherwise, NotifyError() is called with a error message.
|
* Otherwise, NotifyError() is called with a error message.
|
||||||
*/
|
*/
|
||||||
void startSession(in DOMString url,
|
[noscript] void startSession(in URLArrayRef urls,
|
||||||
in DOMString sessionId,
|
in DOMString sessionId,
|
||||||
in DOMString origin,
|
in DOMString origin,
|
||||||
in DOMString deviceId,
|
in DOMString deviceId,
|
||||||
in unsigned long long windowId,
|
in unsigned long long windowId,
|
||||||
in nsIPresentationServiceCallback callback);
|
in nsIPresentationServiceCallback callback);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send the message to the session.
|
* Send the message to the session.
|
||||||
|
@ -101,17 +109,17 @@ interface nsIPresentationService : nsISupports
|
||||||
/*
|
/*
|
||||||
* Reconnect the session.
|
* Reconnect the session.
|
||||||
*
|
*
|
||||||
* @param url: The url of presenting page.
|
* @param url: The request Urls.
|
||||||
* @param sessionId: An ID to identify presentation session.
|
* @param sessionId: An ID to identify presentation session.
|
||||||
* @param role: Identify the function called by controller or receiver.
|
* @param role: Identify the function called by controller or receiver.
|
||||||
* @param callback: NotifySuccess() is called when a control channel
|
* @param callback: NotifySuccess() is called when a control channel
|
||||||
* is opened successfully.
|
* is opened successfully.
|
||||||
* Otherwise, NotifyError() is called with a error message.
|
* Otherwise, NotifyError() is called with a error message.
|
||||||
*/
|
*/
|
||||||
void reconnectSession(in DOMString url,
|
[noscript] void reconnectSession(in URLArrayRef urls,
|
||||||
in DOMString sessionId,
|
in DOMString sessionId,
|
||||||
in uint8_t role,
|
in uint8_t role,
|
||||||
in nsIPresentationServiceCallback callback);
|
in nsIPresentationServiceCallback callback);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register an availability listener. Must be called from the main thread.
|
* Register an availability listener. Must be called from the main thread.
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace dom {
|
||||||
|
|
||||||
struct StartSessionRequest
|
struct StartSessionRequest
|
||||||
{
|
{
|
||||||
nsString url;
|
nsString[] urls;
|
||||||
nsString sessionId;
|
nsString sessionId;
|
||||||
nsString origin;
|
nsString origin;
|
||||||
nsString deviceId;
|
nsString deviceId;
|
||||||
|
@ -44,7 +44,7 @@ struct TerminateSessionRequest
|
||||||
|
|
||||||
struct ReconnectSessionRequest
|
struct ReconnectSessionRequest
|
||||||
{
|
{
|
||||||
nsString url;
|
nsString[] urls;
|
||||||
nsString sessionId;
|
nsString sessionId;
|
||||||
uint8_t role;
|
uint8_t role;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@ sync protocol PPresentationRequest
|
||||||
|
|
||||||
child:
|
child:
|
||||||
async __delete__(nsresult result);
|
async __delete__(nsresult result);
|
||||||
|
async NotifyRequestUrlSelected(nsString aUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -163,12 +163,17 @@ PresentationRequestChild::Recv__delete__(const nsresult& aResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCallback) {
|
if (mCallback) {
|
||||||
if (NS_SUCCEEDED(aResult)) {
|
if (NS_FAILED(aResult)) {
|
||||||
NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess()));
|
|
||||||
} else {
|
|
||||||
NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aResult)));
|
NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aResult)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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
|
virtual bool
|
||||||
Recv__delete__(const nsresult& aResult) override;
|
Recv__delete__(const nsresult& aResult) override;
|
||||||
|
|
||||||
|
virtual bool
|
||||||
|
RecvNotifyRequestUrlSelected(const nsString& aUrl) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual ~PresentationRequestChild();
|
virtual ~PresentationRequestChild();
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ PresentationIPCService::~PresentationIPCService()
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationIPCService::StartSession(const nsAString& aUrl,
|
PresentationIPCService::StartSession(const nsTArray<nsString>& aUrls,
|
||||||
const nsAString& aSessionId,
|
const nsAString& aSessionId,
|
||||||
const nsAString& aOrigin,
|
const nsAString& aOrigin,
|
||||||
const nsAString& aDeviceId,
|
const nsAString& aDeviceId,
|
||||||
|
@ -65,7 +65,7 @@ PresentationIPCService::StartSession(const nsAString& aUrl,
|
||||||
nsIPresentationService::ROLE_CONTROLLER);
|
nsIPresentationService::ROLE_CONTROLLER);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SendRequest(aCallback, StartSessionRequest(nsString(aUrl),
|
return SendRequest(aCallback, StartSessionRequest(aUrls,
|
||||||
nsString(aSessionId),
|
nsString(aSessionId),
|
||||||
nsString(aOrigin),
|
nsString(aOrigin),
|
||||||
nsString(aDeviceId),
|
nsString(aDeviceId),
|
||||||
|
@ -135,7 +135,7 @@ PresentationIPCService::TerminateSession(const nsAString& aSessionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationIPCService::ReconnectSession(const nsAString& aUrl,
|
PresentationIPCService::ReconnectSession(const nsTArray<nsString>& aUrls,
|
||||||
const nsAString& aSessionId,
|
const nsAString& aSessionId,
|
||||||
uint8_t aRole,
|
uint8_t aRole,
|
||||||
nsIPresentationServiceCallback* aCallback)
|
nsIPresentationServiceCallback* aCallback)
|
||||||
|
@ -147,7 +147,7 @@ PresentationIPCService::ReconnectSession(const nsAString& aUrl,
|
||||||
return NS_ERROR_INVALID_ARG;
|
return NS_ERROR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SendRequest(aCallback, ReconnectSessionRequest(nsString(aUrl),
|
return SendRequest(aCallback, ReconnectSessionRequest(aUrls,
|
||||||
nsString(aSessionId),
|
nsString(aSessionId),
|
||||||
aRole));
|
aRole));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "DCPresentationChannelDescription.h"
|
#include "DCPresentationChannelDescription.h"
|
||||||
#include "mozilla/ipc/InputStreamUtils.h"
|
#include "mozilla/ipc/InputStreamUtils.h"
|
||||||
|
#include "mozilla/Unused.h"
|
||||||
#include "nsIPresentationDeviceManager.h"
|
#include "nsIPresentationDeviceManager.h"
|
||||||
#include "nsServiceManagerUtils.h"
|
#include "nsServiceManagerUtils.h"
|
||||||
#include "PresentationBuilderParent.h"
|
#include "PresentationBuilderParent.h"
|
||||||
|
@ -333,7 +334,7 @@ PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest)
|
||||||
MOZ_ASSERT(mService);
|
MOZ_ASSERT(mService);
|
||||||
mNeedRegisterBuilder = true;
|
mNeedRegisterBuilder = true;
|
||||||
mSessionId = aRequest.sessionId();
|
mSessionId = aRequest.sessionId();
|
||||||
return mService->StartSession(aRequest.url(), aRequest.sessionId(),
|
return mService->StartSession(aRequest.urls(), aRequest.sessionId(),
|
||||||
aRequest.origin(), aRequest.deviceId(),
|
aRequest.origin(), aRequest.deviceId(),
|
||||||
aRequest.windowId(), this);
|
aRequest.windowId(), this);
|
||||||
}
|
}
|
||||||
|
@ -347,16 +348,16 @@ PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
|
||||||
// compromised child process can't fake the ID.
|
// compromised child process can't fake the ID.
|
||||||
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
|
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
|
||||||
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
|
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(),
|
nsresult rv = mService->SendSessionMessage(aRequest.sessionId(),
|
||||||
aRequest.role(),
|
aRequest.role(),
|
||||||
aRequest.data());
|
aRequest.data());
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return NotifyError(rv);
|
return SendResponse(rv);
|
||||||
}
|
}
|
||||||
return NotifySuccess();
|
return SendResponse(NS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
@ -368,16 +369,16 @@ PresentationRequestParent::DoRequest(const CloseSessionRequest& aRequest)
|
||||||
// compromised child process can't fake the ID.
|
// compromised child process can't fake the ID.
|
||||||
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
|
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
|
||||||
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
|
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(),
|
nsresult rv = mService->CloseSession(aRequest.sessionId(),
|
||||||
aRequest.role(),
|
aRequest.role(),
|
||||||
aRequest.closedReason());
|
aRequest.closedReason());
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return NotifyError(rv);
|
return SendResponse(rv);
|
||||||
}
|
}
|
||||||
return NotifySuccess();
|
return SendResponse(NS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
@ -389,14 +390,14 @@ PresentationRequestParent::DoRequest(const TerminateSessionRequest& aRequest)
|
||||||
// compromised child process can't fake the ID.
|
// compromised child process can't fake the ID.
|
||||||
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
|
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
|
||||||
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
|
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());
|
nsresult rv = mService->TerminateSession(aRequest.sessionId(), aRequest.role());
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return NotifyError(rv);
|
return SendResponse(rv);
|
||||||
}
|
}
|
||||||
return NotifySuccess();
|
return SendResponse(NS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
@ -411,12 +412,12 @@ PresentationRequestParent::DoRequest(const ReconnectSessionRequest& aRequest)
|
||||||
|
|
||||||
// NOTE: Return NS_ERROR_DOM_NOT_FOUND_ERR here to match the spec.
|
// NOTE: Return NS_ERROR_DOM_NOT_FOUND_ERR here to match the spec.
|
||||||
// https://w3c.github.io/presentation-api/#reconnecting-to-a-presentation
|
// 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;
|
mNeedRegisterBuilder = true;
|
||||||
mSessionId = aRequest.sessionId();
|
mSessionId = aRequest.sessionId();
|
||||||
return mService->ReconnectSession(aRequest.url(),
|
return mService->ReconnectSession(aRequest.urls(),
|
||||||
aRequest.sessionId(),
|
aRequest.sessionId(),
|
||||||
aRequest.role(),
|
aRequest.role(),
|
||||||
this);
|
this);
|
||||||
|
@ -431,18 +432,18 @@ PresentationRequestParent::DoRequest(const BuildTransportRequest& aRequest)
|
||||||
// compromised child process can't fake the ID.
|
// compromised child process can't fake the ID.
|
||||||
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
|
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
|
||||||
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
|
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());
|
nsresult rv = mService->BuildTransport(aRequest.sessionId(), aRequest.role());
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return NotifyError(rv);
|
return SendResponse(rv);
|
||||||
}
|
}
|
||||||
return NotifySuccess();
|
return SendResponse(NS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
PresentationRequestParent::NotifySuccess()
|
PresentationRequestParent::NotifySuccess(const nsAString& aUrl)
|
||||||
{
|
{
|
||||||
if (mNeedRegisterBuilder) {
|
if (mNeedRegisterBuilder) {
|
||||||
RefPtr<PresentationParent> parent = static_cast<PresentationParent*>(Manager());
|
RefPtr<PresentationParent> parent = static_cast<PresentationParent*>(Manager());
|
||||||
|
@ -451,6 +452,7 @@ PresentationRequestParent::NotifySuccess()
|
||||||
nsIPresentationService::ROLE_CONTROLLER));
|
nsIPresentationService::ROLE_CONTROLLER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Unused << SendNotifyRequestUrlSelected(nsString(aUrl));
|
||||||
return SendResponse(NS_OK);
|
return SendResponse(NS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,29 @@ function teardown() {
|
||||||
gScript.sendAsyncMessage('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() {
|
function runTests() {
|
||||||
ok(window.PresentationRequest, "PresentationRequest should be available.");
|
ok(window.PresentationRequest, "PresentationRequest should be available.");
|
||||||
|
|
||||||
|
@ -248,6 +271,7 @@ function runTests() {
|
||||||
then(testCloseConnection).
|
then(testCloseConnection).
|
||||||
then(testReconnect).
|
then(testReconnect).
|
||||||
then(testCloseConnection).
|
then(testCloseConnection).
|
||||||
|
then(testConstructRequestError).
|
||||||
then(teardown);
|
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() {
|
function testStartConnectionCancelPrompt() {
|
||||||
return new Promise(function(aResolve, aReject) {
|
return new Promise(function(aResolve, aReject) {
|
||||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||||
|
@ -380,8 +369,7 @@ function teardown() {
|
||||||
function runTests() {
|
function runTests() {
|
||||||
ok(window.PresentationRequest, "PresentationRequest should be available.");
|
ok(window.PresentationRequest, "PresentationRequest should be available.");
|
||||||
|
|
||||||
testCreateRequestWithEmptyURL().
|
setup().
|
||||||
then(setup).
|
|
||||||
then(testStartConnectionCancelPrompt).
|
then(testStartConnectionCancelPrompt).
|
||||||
then(testStartConnectionNoDevice).
|
then(testStartConnectionNoDevice).
|
||||||
then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit).
|
then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit).
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[Constructor(DOMString url),
|
[Constructor(DOMString url),
|
||||||
|
Constructor(sequence<DOMString> urls),
|
||||||
Pref="dom.presentation.controller.enabled"]
|
Pref="dom.presentation.controller.enabled"]
|
||||||
interface PresentationRequest : EventTarget {
|
interface PresentationRequest : EventTarget {
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -481,28 +481,28 @@ CreateGlobalDevModeAndInit(const nsXPIDLString& aPrintName,
|
||||||
nsAutoPrinter autoPrinter(hPrinter);
|
nsAutoPrinter autoPrinter(hPrinter);
|
||||||
|
|
||||||
// Get the buffer size
|
// Get the buffer size
|
||||||
DWORD dwNeeded = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
|
LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
|
||||||
nullptr, 0);
|
nullptr, 0);
|
||||||
if (dwNeeded == 0) {
|
if (needed < 0) {
|
||||||
return nsReturnRef<nsHGLOBAL>();
|
return nsReturnRef<nsHGLOBAL>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a buffer of the correct size.
|
// Allocate a buffer of the correct size.
|
||||||
nsAutoDevMode newDevMode((LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY,
|
nsAutoDevMode newDevMode((LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY,
|
||||||
dwNeeded));
|
needed));
|
||||||
if (!newDevMode) {
|
if (!newDevMode) {
|
||||||
return nsReturnRef<nsHGLOBAL>();
|
return nsReturnRef<nsHGLOBAL>();
|
||||||
}
|
}
|
||||||
|
|
||||||
nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, dwNeeded);
|
nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
|
||||||
nsAutoGlobalMem globalDevMode(hDevMode);
|
nsAutoGlobalMem globalDevMode(hDevMode);
|
||||||
if (!hDevMode) {
|
if (!hDevMode) {
|
||||||
return nsReturnRef<nsHGLOBAL>();
|
return nsReturnRef<nsHGLOBAL>();
|
||||||
}
|
}
|
||||||
|
|
||||||
DWORD dwRet = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
|
LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
|
||||||
nullptr, DM_OUT_BUFFER);
|
nullptr, DM_OUT_BUFFER);
|
||||||
if (dwRet != IDOK) {
|
if (ret != IDOK) {
|
||||||
return nsReturnRef<nsHGLOBAL>();
|
return nsReturnRef<nsHGLOBAL>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,16 +513,16 @@ CreateGlobalDevModeAndInit(const nsXPIDLString& aPrintName,
|
||||||
return nsReturnRef<nsHGLOBAL>();
|
return nsReturnRef<nsHGLOBAL>();
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(devMode, newDevMode.get(), dwNeeded);
|
memcpy(devMode, newDevMode.get(), needed);
|
||||||
// Initialize values from the PrintSettings
|
// Initialize values from the PrintSettings
|
||||||
nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
|
nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
|
||||||
MOZ_ASSERT(psWin);
|
MOZ_ASSERT(psWin);
|
||||||
psWin->CopyToNative(devMode);
|
psWin->CopyToNative(devMode);
|
||||||
|
|
||||||
// Sets back the changes we made to the DevMode into the Printer Driver
|
// Sets back the changes we made to the DevMode into the Printer Driver
|
||||||
dwRet = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
|
ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
|
||||||
DM_IN_BUFFER | DM_OUT_BUFFER);
|
DM_IN_BUFFER | DM_OUT_BUFFER);
|
||||||
if (dwRet != IDOK) {
|
if (ret != IDOK) {
|
||||||
::GlobalUnlock(hDevMode);
|
::GlobalUnlock(hDevMode);
|
||||||
return nsReturnRef<nsHGLOBAL>();
|
return nsReturnRef<nsHGLOBAL>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1016,7 +1016,12 @@ gfxFontUtils::RenameFont(const nsAString& aName, const uint8_t *aFontData,
|
||||||
uint16_t nameCount = ArrayLength(neededNameIDs);
|
uint16_t nameCount = ArrayLength(neededNameIDs);
|
||||||
|
|
||||||
// leave room for null-terminator
|
// 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
|
// round name table size up to 4-byte multiple
|
||||||
uint32_t nameTableSize = (sizeof(NameHeader) +
|
uint32_t nameTableSize = (sizeof(NameHeader) +
|
||||||
|
|
|
@ -2515,17 +2515,18 @@ gfxPlatform::InitOpenGLConfig()
|
||||||
openGLFeature.EnableByDefault();
|
openGLFeature.EnableByDefault();
|
||||||
#endif
|
#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 message;
|
||||||
nsCString failureId;
|
nsCString failureId;
|
||||||
if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &message, failureId)) {
|
if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &message, failureId)) {
|
||||||
openGLFeature.Disable(FeatureStatus::Blacklisted, message.get(), 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
|
bool
|
||||||
|
|
|
@ -111,10 +111,19 @@ typedef enum nsCharType nsCharType;
|
||||||
* Return false, otherwise
|
* Return false, otherwise
|
||||||
*/
|
*/
|
||||||
#define LRM_CHAR 0x200e
|
#define LRM_CHAR 0x200e
|
||||||
|
#define RLM_CHAR 0x200f
|
||||||
|
|
||||||
#define LRE_CHAR 0x202a
|
#define LRE_CHAR 0x202a
|
||||||
|
#define RLE_CHAR 0x202b
|
||||||
|
#define PDF_CHAR 0x202c
|
||||||
|
#define LRO_CHAR 0x202d
|
||||||
#define RLO_CHAR 0x202e
|
#define RLO_CHAR 0x202e
|
||||||
|
|
||||||
#define LRI_CHAR 0x2066
|
#define LRI_CHAR 0x2066
|
||||||
|
#define RLI_CHAR 0x2067
|
||||||
|
#define FSI_CHAR 0x2068
|
||||||
#define PDI_CHAR 0x2069
|
#define PDI_CHAR 0x2069
|
||||||
|
|
||||||
#define ALM_CHAR 0x061C
|
#define ALM_CHAR 0x061C
|
||||||
inline bool IsBidiControl(uint32_t aChar) {
|
inline bool IsBidiControl(uint32_t aChar) {
|
||||||
return ((LRE_CHAR <= aChar && aChar <= RLO_CHAR) ||
|
return ((LRE_CHAR <= aChar && aChar <= RLO_CHAR) ||
|
||||||
|
@ -123,6 +132,20 @@ typedef enum nsCharType nsCharType;
|
||||||
(aChar & 0xfffffe) == LRM_CHAR);
|
(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.
|
* Give an nsString.
|
||||||
* @return true if the string contains right-to-left characters
|
* @return true if the string contains right-to-left characters
|
||||||
|
|
|
@ -54,24 +54,6 @@ namespace {
|
||||||
|
|
||||||
static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
|
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*
|
inline XPCShellEnvironment*
|
||||||
Environment(Handle<JSObject*> global)
|
Environment(Handle<JSObject*> global)
|
||||||
{
|
{
|
||||||
|
@ -389,51 +371,6 @@ XPCShellEnvironment::ProcessFile(JSContext *cx,
|
||||||
fprintf(stdout, "\n");
|
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
|
// static
|
||||||
XPCShellEnvironment*
|
XPCShellEnvironment*
|
||||||
XPCShellEnvironment::CreateEnvironment()
|
XPCShellEnvironment::CreateEnvironment()
|
||||||
|
|
|
@ -169,9 +169,13 @@ FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue v
|
||||||
RootedValue value(cx, value_);
|
RootedValue value(cx, value_);
|
||||||
|
|
||||||
mozilla::Maybe<AutoCompartment> ac;
|
mozilla::Maybe<AutoCompartment> ac;
|
||||||
if (!IsWrapper(promiseObj)) {
|
if (!IsProxy(promiseObj)) {
|
||||||
promise = &promiseObj->as<PromiseObject>();
|
promise = &promiseObj->as<PromiseObject>();
|
||||||
} else {
|
} else {
|
||||||
|
if (JS_IsDeadWrapper(promiseObj)) {
|
||||||
|
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
|
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
|
||||||
ac.emplace(cx, promise);
|
ac.emplace(cx, promise);
|
||||||
if (!promise->compartment()->wrap(cx, &value))
|
if (!promise->compartment()->wrap(cx, &value))
|
||||||
|
@ -190,9 +194,13 @@ RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue re
|
||||||
RootedValue reason(cx, reason_);
|
RootedValue reason(cx, reason_);
|
||||||
|
|
||||||
mozilla::Maybe<AutoCompartment> ac;
|
mozilla::Maybe<AutoCompartment> ac;
|
||||||
if (!IsWrapper(promiseObj)) {
|
if (!IsProxy(promiseObj)) {
|
||||||
promise = &promiseObj->as<PromiseObject>();
|
promise = &promiseObj->as<PromiseObject>();
|
||||||
} else {
|
} else {
|
||||||
|
if (JS_IsDeadWrapper(promiseObj)) {
|
||||||
|
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
|
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
|
||||||
ac.emplace(cx, promise);
|
ac.emplace(cx, promise);
|
||||||
|
|
||||||
|
|
|
@ -4559,12 +4559,6 @@ Parser<SyntaxParseHandler>::importDeclaration()
|
||||||
return SyntaxParseHandler::NodeFailure;
|
return SyntaxParseHandler::NodeFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
|
||||||
ParseNode*
|
|
||||||
Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
|
|
||||||
ClassContext classContext,
|
|
||||||
DefaultHandling defaultHandling);
|
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
bool
|
bool
|
||||||
Parser<FullParseHandler>::checkExportedName(JSAtom* exportName)
|
Parser<FullParseHandler>::checkExportedName(JSAtom* exportName)
|
||||||
|
@ -6191,11 +6185,11 @@ GeneratorKindFromPropertyType(PropertyType propType)
|
||||||
return propType == PropertyType::GeneratorMethod ? StarGenerator : NotGenerator;
|
return propType == PropertyType::GeneratorMethod ? StarGenerator : NotGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <typename ParseHandler>
|
||||||
ParseNode*
|
typename ParseHandler::Node
|
||||||
Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
|
Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
|
||||||
ClassContext classContext,
|
ClassContext classContext,
|
||||||
DefaultHandling defaultHandling)
|
DefaultHandling defaultHandling)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS));
|
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.
|
// in order to provide it for the nodes created later.
|
||||||
TokenPos namePos = pos();
|
TokenPos namePos = pos();
|
||||||
|
|
||||||
ParseNode* classHeritage = null();
|
Node classHeritage = null();
|
||||||
bool hasHeritage;
|
bool hasHeritage;
|
||||||
if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS))
|
if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS))
|
||||||
return null();
|
return null();
|
||||||
|
@ -6264,7 +6258,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
|
||||||
|
|
||||||
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS);
|
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS);
|
||||||
|
|
||||||
ParseNode* classMethods = handler.newClassMethodList(pos().begin);
|
Node classMethods = handler.newClassMethodList(pos().begin);
|
||||||
if (!classMethods)
|
if (!classMethods)
|
||||||
return null();
|
return null();
|
||||||
|
|
||||||
|
@ -6304,7 +6298,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyType propType;
|
PropertyType propType;
|
||||||
ParseNode* propName = propertyName(yieldHandling, classMethods, &propType, &propAtom);
|
Node propName = propertyName(yieldHandling, classMethods, &propType, &propAtom);
|
||||||
if (!propName)
|
if (!propName)
|
||||||
return null();
|
return null();
|
||||||
|
|
||||||
|
@ -6356,7 +6350,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
|
||||||
if (!tokenStream.isCurrentTokenType(TOK_RB))
|
if (!tokenStream.isCurrentTokenType(TOK_RB))
|
||||||
funName = propAtom;
|
funName = propAtom;
|
||||||
}
|
}
|
||||||
ParseNode* fn = methodDefinition(yieldHandling, propType, funName);
|
Node fn = methodDefinition(yieldHandling, propType, funName);
|
||||||
if (!fn)
|
if (!fn)
|
||||||
return null();
|
return null();
|
||||||
|
|
||||||
|
@ -6365,18 +6359,18 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
|
||||||
return null();
|
return null();
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseNode* nameNode = null();
|
Node nameNode = null();
|
||||||
ParseNode* methodsOrBlock = classMethods;
|
Node methodsOrBlock = classMethods;
|
||||||
if (name) {
|
if (name) {
|
||||||
// The inner name is immutable.
|
// The inner name is immutable.
|
||||||
if (!noteDeclaredName(name, DeclarationKind::Const, namePos))
|
if (!noteDeclaredName(name, DeclarationKind::Const, namePos))
|
||||||
return null();
|
return null();
|
||||||
|
|
||||||
ParseNode* innerName = newName(name, namePos);
|
Node innerName = newName(name, namePos);
|
||||||
if (!innerName)
|
if (!innerName)
|
||||||
return null();
|
return null();
|
||||||
|
|
||||||
ParseNode* classBlock = finishLexicalScope(*classScope, classMethods);
|
Node classBlock = finishLexicalScope(*classScope, classMethods);
|
||||||
if (!classBlock)
|
if (!classBlock)
|
||||||
return null();
|
return null();
|
||||||
|
|
||||||
|
@ -6386,7 +6380,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
|
||||||
classScope.reset();
|
classScope.reset();
|
||||||
classStmt.reset();
|
classStmt.reset();
|
||||||
|
|
||||||
ParseNode* outerName = null();
|
Node outerName = null();
|
||||||
if (classContext == ClassStatement) {
|
if (classContext == ClassStatement) {
|
||||||
// The outer name is mutable.
|
// The outer name is mutable.
|
||||||
if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
|
if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
|
||||||
|
@ -6407,16 +6401,6 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
|
||||||
return handler.newClass(nameNode, classHeritage, methodsOrBlock);
|
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>
|
template <class ParseHandler>
|
||||||
bool
|
bool
|
||||||
Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
|
Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
|
||||||
|
@ -6851,8 +6835,6 @@ Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
|
||||||
|
|
||||||
// ClassDeclaration[?Yield, ~Default]
|
// ClassDeclaration[?Yield, ~Default]
|
||||||
case TOK_CLASS:
|
case TOK_CLASS:
|
||||||
if (!abortIfSyntaxParser())
|
|
||||||
return null();
|
|
||||||
return classDefinition(yieldHandling, ClassStatement, NameRequired);
|
return classDefinition(yieldHandling, ClassStatement, NameRequired);
|
||||||
|
|
||||||
// LexicalDeclaration[In, ?Yield]
|
// LexicalDeclaration[In, ?Yield]
|
||||||
|
|
|
@ -262,6 +262,8 @@ class SyntaxParseHandler
|
||||||
|
|
||||||
Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
|
Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
|
||||||
Node newClassMethodList(uint32_t begin) { return NodeGeneric; }
|
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 newNewTarget(Node newHolder, Node targetHolder) { return NodeGeneric; }
|
||||||
Node newPosHolder(const TokenPos& pos) { 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* call = MWasmCall::New(alloc, desc, callee, args, resultType, spIncrement,
|
||||||
MWasmCall::DontSaveTls, nullptr);
|
MWasmCall::DontSaveTls, nullptr);
|
||||||
|
|
||||||
|
if (!call)
|
||||||
|
return nullptr;
|
||||||
MOZ_ASSERT(instanceArg != ABIArg()); // instanceArg must be initialized.
|
MOZ_ASSERT(instanceArg != ABIArg()); // instanceArg must be initialized.
|
||||||
call->instanceArg_ = instanceArg;
|
call->instanceArg_ = instanceArg;
|
||||||
return call;
|
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_xrayed_iterator.js]
|
||||||
[test_xray_SavedFrame.js]
|
[test_xray_SavedFrame.js]
|
||||||
[test_xray_SavedFrame-02.js]
|
[test_xray_SavedFrame-02.js]
|
||||||
|
[test_resolve_dead_promise.js]
|
||||||
|
|
|
@ -222,10 +222,9 @@ GetBEndMarginClone(nsIFrame* aFrame,
|
||||||
// GetAvailableSpace has already been called.
|
// GetAvailableSpace has already been called.
|
||||||
void
|
void
|
||||||
BlockReflowInput::ComputeBlockAvailSpace(nsIFrame* aFrame,
|
BlockReflowInput::ComputeBlockAvailSpace(nsIFrame* aFrame,
|
||||||
const nsStyleDisplay* aDisplay,
|
const nsFlowAreaRect& aFloatAvailableSpace,
|
||||||
const nsFlowAreaRect& aFloatAvailableSpace,
|
bool aBlockAvoidsFloats,
|
||||||
bool aBlockAvoidsFloats,
|
LogicalRect& aResult)
|
||||||
LogicalRect& aResult)
|
|
||||||
{
|
{
|
||||||
#ifdef REALLY_NOISY_REFLOW
|
#ifdef REALLY_NOISY_REFLOW
|
||||||
printf("CBAS frame=%p has floats %d\n",
|
printf("CBAS frame=%p has floats %d\n",
|
||||||
|
|
|
@ -207,7 +207,6 @@ public:
|
||||||
|
|
||||||
// Caller must have called GetAvailableSpace for the current mBCoord
|
// Caller must have called GetAvailableSpace for the current mBCoord
|
||||||
void ComputeBlockAvailSpace(nsIFrame* aFrame,
|
void ComputeBlockAvailSpace(nsIFrame* aFrame,
|
||||||
const nsStyleDisplay* aDisplay,
|
|
||||||
const nsFlowAreaRect& aFloatAvailableSpace,
|
const nsFlowAreaRect& aFloatAvailableSpace,
|
||||||
bool aBlockAvoidsFloats,
|
bool aBlockAvoidsFloats,
|
||||||
mozilla::LogicalRect& aResult);
|
mozilla::LogicalRect& aResult);
|
||||||
|
|
|
@ -783,7 +783,9 @@ nsBlockFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
|
||||||
AutoNoisyIndenter lineindent(gNoisyIntrinsic);
|
AutoNoisyIndenter lineindent(gNoisyIntrinsic);
|
||||||
#endif
|
#endif
|
||||||
if (line->IsBlock()) {
|
if (line->IsBlock()) {
|
||||||
data.ForceBreak();
|
if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
|
||||||
|
data.ForceBreak();
|
||||||
|
}
|
||||||
data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
|
data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
|
||||||
line->mFirstChild, nsLayoutUtils::PREF_ISIZE);
|
line->mFirstChild, nsLayoutUtils::PREF_ISIZE);
|
||||||
data.ForceBreak();
|
data.ForceBreak();
|
||||||
|
@ -794,8 +796,13 @@ nsBlockFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
|
||||||
// percentage basis of 0 unconditionally would give strange
|
// percentage basis of 0 unconditionally would give strange
|
||||||
// behavior for calc(10%-3px).
|
// behavior for calc(10%-3px).
|
||||||
const nsStyleCoord &indent = StyleText()->mTextIndent;
|
const nsStyleCoord &indent = StyleText()->mTextIndent;
|
||||||
if (indent.ConvertsToLength())
|
if (indent.ConvertsToLength()) {
|
||||||
data.mCurrentLine += nsRuleNode::ComputeCoordPercentCalc(indent, 0);
|
nscoord length = indent.ToLength();
|
||||||
|
if (length != 0) {
|
||||||
|
data.mCurrentLine += length;
|
||||||
|
data.mLineIsEmpty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// XXX Bug NNNNNN Should probably handle percentage text-indent.
|
// XXX Bug NNNNNN Should probably handle percentage text-indent.
|
||||||
|
|
||||||
|
@ -3117,11 +3124,10 @@ nsBlockFrame::ReflowBlockFrame(BlockReflowInput& aState,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the block reflow engine
|
// Prepare the block reflow engine
|
||||||
const nsStyleDisplay* display = frame->StyleDisplay();
|
|
||||||
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
|
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
|
||||||
|
|
||||||
uint8_t breakType =
|
uint8_t breakType = frame->StyleDisplay()->
|
||||||
display->PhysicalBreakType(aState.mReflowInput.GetWritingMode());
|
PhysicalBreakType(aState.mReflowInput.GetWritingMode());
|
||||||
if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) {
|
if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) {
|
||||||
breakType = nsLayoutUtils::CombineBreakType(breakType,
|
breakType = nsLayoutUtils::CombineBreakType(breakType,
|
||||||
aState.mFloatBreakType);
|
aState.mFloatBreakType);
|
||||||
|
@ -3302,7 +3308,7 @@ nsBlockFrame::ReflowBlockFrame(BlockReflowInput& aState,
|
||||||
nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
|
nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
|
||||||
WritingMode wm = aState.mReflowInput.GetWritingMode();
|
WritingMode wm = aState.mReflowInput.GetWritingMode();
|
||||||
LogicalRect availSpace(wm);
|
LogicalRect availSpace(wm);
|
||||||
aState.ComputeBlockAvailSpace(frame, display, floatAvailableSpace,
|
aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
|
||||||
replacedBlock != nullptr, availSpace);
|
replacedBlock != nullptr, availSpace);
|
||||||
|
|
||||||
// The check for
|
// The check for
|
||||||
|
@ -3435,7 +3441,7 @@ nsBlockFrame::ReflowBlockFrame(BlockReflowInput& aState,
|
||||||
}
|
}
|
||||||
|
|
||||||
LogicalRect oldAvailSpace(availSpace);
|
LogicalRect oldAvailSpace(availSpace);
|
||||||
aState.ComputeBlockAvailSpace(frame, display, floatAvailableSpace,
|
aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
|
||||||
replacedBlock != nullptr, availSpace);
|
replacedBlock != nullptr, availSpace);
|
||||||
|
|
||||||
if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {
|
if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {
|
||||||
|
|
|
@ -125,6 +125,7 @@ nsFirstLetterFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext,
|
||||||
nsIFrame::InlinePrefISizeData *aData)
|
nsIFrame::InlinePrefISizeData *aData)
|
||||||
{
|
{
|
||||||
DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE);
|
DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE);
|
||||||
|
aData->mLineIsEmpty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needed for floating first-letter frames.
|
// Needed for floating first-letter frames.
|
||||||
|
|
|
@ -4443,6 +4443,7 @@ nsIFrame::InlinePrefISizeData::DefaultAddInlinePrefISize(nscoord aISize)
|
||||||
mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, aISize);
|
mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, aISize);
|
||||||
mTrailingWhitespace = 0;
|
mTrailingWhitespace = 0;
|
||||||
mSkipWhitespace = false;
|
mSkipWhitespace = false;
|
||||||
|
mLineIsEmpty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -4532,6 +4533,7 @@ nsIFrame::InlinePrefISizeData::ForceBreak()
|
||||||
mPrevLines = std::max(mPrevLines, mCurrentLine);
|
mPrevLines = std::max(mPrevLines, mCurrentLine);
|
||||||
mCurrentLine = mTrailingWhitespace = 0;
|
mCurrentLine = mTrailingWhitespace = 0;
|
||||||
mSkipWhitespace = true;
|
mSkipWhitespace = true;
|
||||||
|
mLineIsEmpty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
|
@ -1828,10 +1828,17 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InlinePrefISizeData : public InlineIntrinsicISizeData {
|
struct InlinePrefISizeData : public InlineIntrinsicISizeData {
|
||||||
|
InlinePrefISizeData()
|
||||||
|
: mLineIsEmpty(true)
|
||||||
|
{}
|
||||||
|
|
||||||
void ForceBreak();
|
void ForceBreak();
|
||||||
|
|
||||||
// The default implementation for nsIFrame::AddInlinePrefISize.
|
// The default implementation for nsIFrame::AddInlinePrefISize.
|
||||||
void DefaultAddInlinePrefISize(nscoord aISize);
|
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)
|
nsIFrame::InlinePrefISizeData *aData)
|
||||||
{
|
{
|
||||||
DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE);
|
DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE);
|
||||||
|
aData->mLineIsEmpty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* virtual */
|
/* virtual */
|
||||||
|
|
|
@ -87,6 +87,7 @@ nsRubyFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext,
|
||||||
e.GetBaseContainer()->AddInlinePrefISize(aRenderingContext, aData);
|
e.GetBaseContainer()->AddInlinePrefISize(aRenderingContext, aData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
aData->mLineIsEmpty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* virtual */ void
|
/* virtual */ void
|
||||||
|
|
|
@ -8347,6 +8347,7 @@ nsTextFrame::AddInlinePrefISizeForFlow(nsRenderingContext *aRenderingContext,
|
||||||
if (StyleContext()->IsTextCombined()) {
|
if (StyleContext()->IsTextCombined()) {
|
||||||
aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
|
aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
|
||||||
aData->mTrailingWhitespace = 0;
|
aData->mTrailingWhitespace = 0;
|
||||||
|
aData->mLineIsEmpty = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8384,6 +8385,7 @@ nsTextFrame::AddInlinePrefISizeForFlow(nsRenderingContext *aRenderingContext,
|
||||||
textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
|
textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
|
||||||
width = std::max(0, width);
|
width = std::max(0, width);
|
||||||
aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
|
aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
|
||||||
|
aData->mLineIsEmpty = false;
|
||||||
|
|
||||||
if (collapseWhitespace) {
|
if (collapseWhitespace) {
|
||||||
uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
|
uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
|
||||||
|
@ -8410,6 +8412,7 @@ nsTextFrame::AddInlinePrefISizeForFlow(nsRenderingContext *aRenderingContext,
|
||||||
AdvanceToNextTab(aData->mCurrentLine, this,
|
AdvanceToNextTab(aData->mCurrentLine, this,
|
||||||
textRun, &tabWidth);
|
textRun, &tabWidth);
|
||||||
aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
|
aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
|
||||||
|
aData->mLineIsEmpty = false;
|
||||||
lineStart = i + 1;
|
lineStart = i + 1;
|
||||||
} else if (preformattedNewline) {
|
} else if (preformattedNewline) {
|
||||||
aData->ForceBreak();
|
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
|
== 775350-1.html 775350-1-ref.html
|
||||||
== 1114329.html 1114329-ref.html
|
== 1114329.html 1114329-ref.html
|
||||||
== 1236745-1.html 1236745-1-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
|
== 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-1b.html float-in-rtl-1-ref.html
|
||||||
fuzzy-if(skiaContent,1,27000) == float-in-rtl-1c.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`.
|
/// 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_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
|
#ifdef __cplusplus
|
||||||
|
|
|
@ -1,29 +1,42 @@
|
||||||
--- a/media/libstagefright/binding/mp4parse/Cargo.toml
|
diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
|
||||||
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
|
index 5092cd7..ecbc8c0 100644
|
||||||
@@ -17,23 +17,9 @@ exclude = [
|
--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
|
||||||
|
+++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
|
||||||
|
@@ -17,14 +17,9 @@ exclude = [
|
||||||
"*.mp4",
|
"*.mp4",
|
||||||
]
|
]
|
||||||
|
|
||||||
-build = "build.rs"
|
-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"
|
-byteorder = "0.5.0"
|
||||||
-afl = { version = "0.1.1", optional = true }
|
-afl = { version = "0.1.1", optional = true }
|
||||||
-afl-plugin = { version = "0.1.1", optional = true }
|
-afl-plugin = { version = "0.1.1", optional = true }
|
||||||
-abort_on_panic = { version = "1.0.0", optional = true }
|
-abort_on_panic = { version = "1.0.0", optional = true }
|
||||||
-
|
+byteorder = { version = "0.5.0", path = "../byteorder" }
|
||||||
-[dev-dependencies]
|
|
||||||
-test-assembler = "0.1.2"
|
[dev-dependencies]
|
||||||
-
|
test-assembler = "0.1.2"
|
||||||
-[build-dependencies]
|
|
||||||
-rusty-cheddar = "0.3.2"
|
|
||||||
-
|
|
||||||
-[features]
|
-[features]
|
||||||
-fuzz = ["afl", "afl-plugin", "abort_on_panic"]
|
-fuzz = ["afl", "afl-plugin", "abort_on_panic"]
|
||||||
-
|
-
|
||||||
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
|
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug-assertions = true
|
debug-assertions = true
|
||||||
+
|
|
||||||
+[dependencies]
|
|
||||||
+byteorder = { path = "../byteorder" }
|
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mp4parse"
|
name = "mp4parse"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Ralph Giles <giles@mozilla.com>",
|
"Ralph Giles <giles@mozilla.com>",
|
||||||
"Matthew Gregan <kinetik@flim.org>",
|
"Matthew Gregan <kinetik@flim.org>",
|
||||||
]
|
]
|
||||||
|
|
||||||
description = "Parser for ISO base media file format (mp4)"
|
description = "Parser for ISO base media file format (mp4)"
|
||||||
|
documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
|
||||||
repository = "https://github.com/mozilla/mp4parse-rust"
|
repository = "https://github.com/mozilla/mp4parse-rust"
|
||||||
|
|
||||||
# Cargo includes random files from the working directory
|
# Avoid complaints about trying to package test files.
|
||||||
# by default! Avoid bloating the package with test files.
|
|
||||||
exclude = [
|
exclude = [
|
||||||
"*.mp4",
|
"*.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.
|
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug-assertions = true
|
debug-assertions = true
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
byteorder = { path = "../byteorder" }
|
|
||||||
|
|
|
@ -54,4 +54,5 @@ box_database!(
|
||||||
OpusSpecificBox 0x644f7073, // "dOps"
|
OpusSpecificBox 0x644f7073, // "dOps"
|
||||||
ProtectedVisualSampleEntry 0x656e6376, // "encv" - Need to check official name in spec.
|
ProtectedVisualSampleEntry 0x656e6376, // "encv" - Need to check official name in spec.
|
||||||
ProtectedAudioSampleEntry 0x656e6361, // "enca" - 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::io::{Read, Take};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
||||||
// Expose C api wrapper.
|
|
||||||
pub mod capi;
|
|
||||||
pub use capi::*;
|
|
||||||
|
|
||||||
mod boxes;
|
mod boxes;
|
||||||
use boxes::BoxType;
|
use boxes::BoxType;
|
||||||
|
|
||||||
|
@ -109,18 +105,18 @@ struct FileTypeBox {
|
||||||
/// Movie header box 'mvhd'.
|
/// Movie header box 'mvhd'.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MovieHeaderBox {
|
struct MovieHeaderBox {
|
||||||
timescale: u32,
|
pub timescale: u32,
|
||||||
duration: u64,
|
duration: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Track header box 'tkhd'
|
/// Track header box 'tkhd'
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct TrackHeaderBox {
|
pub struct TrackHeaderBox {
|
||||||
track_id: u32,
|
track_id: u32,
|
||||||
disabled: bool,
|
pub disabled: bool,
|
||||||
duration: u64,
|
pub duration: u64,
|
||||||
width: u32,
|
pub width: u32,
|
||||||
height: u32,
|
pub height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Edit list box 'elst'
|
/// Edit list box 'elst'
|
||||||
|
@ -201,7 +197,7 @@ struct SampleDescriptionBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum SampleEntry {
|
pub enum SampleEntry {
|
||||||
Audio(AudioSampleEntry),
|
Audio(AudioSampleEntry),
|
||||||
Video(VideoSampleEntry),
|
Video(VideoSampleEntry),
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -209,45 +205,45 @@ enum SampleEntry {
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum AudioCodecSpecific {
|
pub enum AudioCodecSpecific {
|
||||||
ES_Descriptor(Vec<u8>),
|
ES_Descriptor(Vec<u8>),
|
||||||
OpusSpecificBox(OpusSpecificBox),
|
OpusSpecificBox(OpusSpecificBox),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct AudioSampleEntry {
|
pub struct AudioSampleEntry {
|
||||||
data_reference_index: u16,
|
data_reference_index: u16,
|
||||||
channelcount: u16,
|
pub channelcount: u16,
|
||||||
samplesize: u16,
|
pub samplesize: u16,
|
||||||
samplerate: u32,
|
pub samplerate: u32,
|
||||||
codec_specific: AudioCodecSpecific,
|
pub codec_specific: AudioCodecSpecific,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum VideoCodecSpecific {
|
pub enum VideoCodecSpecific {
|
||||||
AVCConfig(Vec<u8>),
|
AVCConfig(Vec<u8>),
|
||||||
VPxConfig(VPxConfigBox),
|
VPxConfig(VPxConfigBox),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct VideoSampleEntry {
|
pub struct VideoSampleEntry {
|
||||||
data_reference_index: u16,
|
data_reference_index: u16,
|
||||||
width: u16,
|
pub width: u16,
|
||||||
height: u16,
|
pub height: u16,
|
||||||
codec_specific: VideoCodecSpecific,
|
pub codec_specific: VideoCodecSpecific,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9).
|
/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9).
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct VPxConfigBox {
|
pub struct VPxConfigBox {
|
||||||
profile: u8,
|
profile: u8,
|
||||||
level: u8,
|
level: u8,
|
||||||
bit_depth: u8,
|
pub bit_depth: u8,
|
||||||
color_space: u8, // Really an enum
|
pub color_space: u8, // Really an enum
|
||||||
chroma_subsampling: u8,
|
pub chroma_subsampling: u8,
|
||||||
transfer_function: u8,
|
transfer_function: u8,
|
||||||
video_full_range: bool,
|
video_full_range: bool,
|
||||||
codec_init: Vec<u8>, // Empty for vp8/vp9.
|
pub codec_init: Vec<u8>, // Empty for vp8/vp9.
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -259,8 +255,8 @@ struct ChannelMappingTable {
|
||||||
|
|
||||||
/// Represent an OpusSpecificBox 'dOps'
|
/// Represent an OpusSpecificBox 'dOps'
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct OpusSpecificBox {
|
pub struct OpusSpecificBox {
|
||||||
version: u8,
|
pub version: u8,
|
||||||
output_channel_count: u8,
|
output_channel_count: u8,
|
||||||
pre_skip: u16,
|
pre_skip: u16,
|
||||||
input_sample_rate: u32,
|
input_sample_rate: u32,
|
||||||
|
@ -270,73 +266,80 @@ struct OpusSpecificBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal data structures.
|
/// Internal data structures.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct MediaContext {
|
pub struct MediaContext {
|
||||||
timescale: Option<MediaTimeScale>,
|
pub timescale: Option<MediaTimeScale>,
|
||||||
|
pub has_mvex: bool,
|
||||||
/// Tracks found in the file.
|
/// Tracks found in the file.
|
||||||
tracks: Vec<Track>,
|
pub tracks: Vec<Track>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MediaContext {
|
impl MediaContext {
|
||||||
pub fn new() -> MediaContext {
|
pub fn new() -> MediaContext {
|
||||||
MediaContext {
|
Default::default()
|
||||||
timescale: None,
|
|
||||||
tracks: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum TrackType {
|
pub enum TrackType {
|
||||||
Audio,
|
Audio,
|
||||||
Video,
|
Video,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TrackType {
|
||||||
|
fn default() -> Self { TrackType::Unknown }
|
||||||
|
}
|
||||||
|
|
||||||
/// The media's global (mvhd) timescale.
|
/// The media's global (mvhd) timescale.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
struct MediaTimeScale(u64);
|
pub struct MediaTimeScale(pub u64);
|
||||||
|
|
||||||
/// A time scaled by the media's global (mvhd) timescale.
|
/// A time scaled by the media's global (mvhd) timescale.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
struct MediaScaledTime(u64);
|
pub struct MediaScaledTime(pub u64);
|
||||||
|
|
||||||
/// The track's local (mdhd) timescale.
|
/// The track's local (mdhd) timescale.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
struct TrackTimeScale(u64, usize);
|
pub struct TrackTimeScale(pub u64, pub usize);
|
||||||
|
|
||||||
/// A time scaled by the track's local (mdhd) timescale.
|
/// A time scaled by the track's local (mdhd) timescale.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
struct TrackScaledTime(u64, usize);
|
pub struct TrackScaledTime(pub u64, pub usize);
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// A fragmented file contains no sample data in stts, stsc, and stco.
|
||||||
struct Track {
|
#[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,
|
id: usize,
|
||||||
track_type: TrackType,
|
pub track_type: TrackType,
|
||||||
empty_duration: Option<MediaScaledTime>,
|
pub empty_duration: Option<MediaScaledTime>,
|
||||||
media_time: Option<TrackScaledTime>,
|
pub media_time: Option<TrackScaledTime>,
|
||||||
timescale: Option<TrackTimeScale>,
|
pub timescale: Option<TrackTimeScale>,
|
||||||
duration: Option<TrackScaledTime>,
|
pub duration: Option<TrackScaledTime>,
|
||||||
track_id: Option<u32>,
|
pub track_id: Option<u32>,
|
||||||
mime_type: String,
|
pub mime_type: String,
|
||||||
data: Option<SampleEntry>,
|
pub empty_sample_boxes: EmptySampleTableBoxes,
|
||||||
tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
|
pub data: Option<SampleEntry>,
|
||||||
|
pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Track {
|
impl Track {
|
||||||
fn new(id: usize) -> Track {
|
fn new(id: usize) -> Track {
|
||||||
Track {
|
Track { id: id, ..Default::default() }
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,7 +457,7 @@ macro_rules! check_parser_state {
|
||||||
|
|
||||||
/// Read the contents of a box, including sub boxes.
|
/// 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.
|
/// which can be examined later.
|
||||||
pub fn read_mp4<T: Read>(f: &mut T, context: &mut MediaContext) -> Result<()> {
|
pub fn read_mp4<T: Read>(f: &mut T, context: &mut MediaContext) -> Result<()> {
|
||||||
let mut found_ftyp = false;
|
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));
|
try!(read_trak(&mut b, &mut track));
|
||||||
context.tracks.push(track);
|
context.tracks.push(track);
|
||||||
}
|
}
|
||||||
|
BoxType::MovieExtendsBox => {
|
||||||
|
context.has_mvex = true;
|
||||||
|
try!(skip_box_content(&mut b));
|
||||||
|
}
|
||||||
_ => try!(skip_box_content(&mut b)),
|
_ => try!(skip_box_content(&mut b)),
|
||||||
};
|
};
|
||||||
check_parser_state!(b.content);
|
check_parser_state!(b.content);
|
||||||
|
@ -654,10 +661,12 @@ fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
|
||||||
}
|
}
|
||||||
BoxType::TimeToSampleBox => {
|
BoxType::TimeToSampleBox => {
|
||||||
let stts = try!(read_stts(&mut b));
|
let stts = try!(read_stts(&mut b));
|
||||||
|
track.empty_sample_boxes.empty_stts = stts.samples.is_empty();
|
||||||
log!("{:?}", stts);
|
log!("{:?}", stts);
|
||||||
}
|
}
|
||||||
BoxType::SampleToChunkBox => {
|
BoxType::SampleToChunkBox => {
|
||||||
let stsc = try!(read_stsc(&mut b));
|
let stsc = try!(read_stsc(&mut b));
|
||||||
|
track.empty_sample_boxes.empty_stsc = stsc.samples.is_empty();
|
||||||
log!("{:?}", stsc);
|
log!("{:?}", stsc);
|
||||||
}
|
}
|
||||||
BoxType::SampleSizeBox => {
|
BoxType::SampleSizeBox => {
|
||||||
|
@ -666,6 +675,7 @@ fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
|
||||||
}
|
}
|
||||||
BoxType::ChunkOffsetBox => {
|
BoxType::ChunkOffsetBox => {
|
||||||
let stco = try!(read_stco(&mut b));
|
let stco = try!(read_stco(&mut b));
|
||||||
|
track.empty_sample_boxes.empty_stco = stco.offsets.is_empty();
|
||||||
log!("{:?}", stco);
|
log!("{:?}", stco);
|
||||||
}
|
}
|
||||||
BoxType::ChunkLargeOffsetBox => {
|
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> {
|
fn read_dops<T: Read>(src: &mut BMFFBox<T>) -> Result<OpusSpecificBox> {
|
||||||
let version = try!(src.read_u8());
|
let version = try!(src.read_u8());
|
||||||
if version != 0 {
|
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
|
/// 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
|
/// tag and byte-swap the data from big- to little-endian relative to the
|
||||||
/// dOps box.
|
/// 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") {
|
match dst.write(b"OpusHead") {
|
||||||
Err(e) => return Err(Error::from(e)),
|
Err(e) => return Err(Error::from(e)),
|
||||||
Ok(bytes) => {
|
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);
|
check_parser_state!(b.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if codec_specific.is_none() {
|
codec_specific
|
||||||
return Err(Error::InvalidData("malformed video sample entry"));
|
.map(|codec_specific| SampleEntry::Video(VideoSampleEntry {
|
||||||
}
|
data_reference_index: data_reference_index,
|
||||||
|
width: width,
|
||||||
Ok(SampleEntry::Video(VideoSampleEntry {
|
height: height,
|
||||||
data_reference_index: data_reference_index,
|
codec_specific: codec_specific,
|
||||||
width: width,
|
}))
|
||||||
height: height,
|
.ok_or_else(|| Error::InvalidData("malformed video sample entry"))
|
||||||
codec_specific: codec_specific.unwrap(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an audio description inside an stsd box.
|
/// 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);
|
check_parser_state!(b.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if codec_specific.is_none() {
|
codec_specific
|
||||||
return Err(Error::InvalidData("malformed audio sample entry"));
|
.map(|codec_specific| SampleEntry::Audio(AudioSampleEntry {
|
||||||
}
|
data_reference_index: data_reference_index,
|
||||||
|
channelcount: channelcount,
|
||||||
Ok(SampleEntry::Audio(AudioSampleEntry {
|
samplesize: samplesize,
|
||||||
data_reference_index: data_reference_index,
|
samplerate: samplerate,
|
||||||
channelcount: channelcount,
|
codec_specific: codec_specific,
|
||||||
samplesize: samplesize,
|
}))
|
||||||
samplerate: samplerate,
|
.ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
|
||||||
codec_specific: codec_specific.unwrap(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a stsd box.
|
/// 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)
|
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> {
|
fn be_i16<T: ReadBytesExt>(src: &mut T) -> Result<i16> {
|
||||||
src.read_i16::<byteorder::BigEndian>().map_err(From::from)
|
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/.
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use super::*;
|
use super::read_mp4;
|
||||||
|
use super::MediaContext;
|
||||||
|
use super::Error;
|
||||||
extern crate test_assembler;
|
extern crate test_assembler;
|
||||||
use self::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() {
|
fn main() {
|
||||||
// Generate mp4parse.h.
|
// Generate mp4parse.h.
|
||||||
cheddar::Cheddar::new().expect("could not read manifest")
|
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 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("// 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")
|
.insert_code("// License, v. 2.0. If a copy of the MPL was not distributed with this\n")
|
|
@ -5,7 +5,7 @@
|
||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! extern crate mp4parse;
|
//! extern crate mp4parse_capi;
|
||||||
//! use std::io::Read;
|
//! use std::io::Read;
|
||||||
//!
|
//!
|
||||||
//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
|
//! 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 mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
|
||||||
//! let io = mp4parse::mp4parse_io { read: buf_read,
|
//! let io = mp4parse_capi::mp4parse_io {
|
||||||
//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
|
//! read: buf_read,
|
||||||
|
//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void
|
||||||
|
//! };
|
||||||
//! unsafe {
|
//! unsafe {
|
||||||
//! let parser = mp4parse::mp4parse_new(&io);
|
//! let parser = mp4parse_capi::mp4parse_new(&io);
|
||||||
//! let rv = mp4parse::mp4parse_read(parser);
|
//! let rv = mp4parse_capi::mp4parse_read(parser);
|
||||||
//! assert_eq!(rv, mp4parse::mp4parse_error::MP4PARSE_OK);
|
//! assert_eq!(rv, mp4parse_capi::mp4parse_error::MP4PARSE_OK);
|
||||||
//! mp4parse::mp4parse_free(parser);
|
//! mp4parse_capi::mp4parse_free(parser);
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
@ -32,21 +34,24 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// 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/.
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use std;
|
extern crate mp4parse;
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// Symbols we need from our rust api.
|
// Symbols we need from our rust api.
|
||||||
use MediaContext;
|
use mp4parse::MediaContext;
|
||||||
use TrackType;
|
use mp4parse::TrackType;
|
||||||
use read_mp4;
|
use mp4parse::read_mp4;
|
||||||
use Error;
|
use mp4parse::Error;
|
||||||
use media_time_to_ms;
|
use mp4parse::SampleEntry;
|
||||||
use track_time_to_ms;
|
use mp4parse::AudioCodecSpecific;
|
||||||
use SampleEntry;
|
use mp4parse::VideoCodecSpecific;
|
||||||
use AudioCodecSpecific;
|
use mp4parse::MediaTimeScale;
|
||||||
use VideoCodecSpecific;
|
use mp4parse::MediaScaledTime;
|
||||||
use serialize_opus_header;
|
use mp4parse::TrackTimeScale;
|
||||||
|
use mp4parse::TrackScaledTime;
|
||||||
|
use mp4parse::serialize_opus_header;
|
||||||
|
|
||||||
// rusty-cheddar's C enum generation doesn't namespace enum members by
|
// 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
|
// 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();
|
let context = (*parser).context();
|
||||||
|
|
||||||
// Make sure the track count fits in a u32.
|
// 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;
|
return MP4PARSE_ERROR_INVALID;
|
||||||
}
|
}
|
||||||
*count = context.tracks.len() as u32;
|
*count = context.tracks.len() as u32;
|
||||||
MP4PARSE_OK
|
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`.
|
/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_error {
|
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,
|
_ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Maybe context & track should just have a single simple is_valid() instead?
|
let track = &context.tracks[track_index];
|
||||||
if context.timescale.is_none() ||
|
|
||||||
context.tracks[track_index].timescale.is_none() ||
|
if let (Some(track_timescale),
|
||||||
context.tracks[track_index].duration.is_none() ||
|
Some(context_timescale),
|
||||||
context.tracks[track_index].track_id.is_none() {
|
Some(track_duration)) = (track.timescale,
|
||||||
return MP4PARSE_ERROR_INVALID;
|
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.track_id = match track.track_id {
|
||||||
info.media_time = track.media_time.map_or(0, |media_time| {
|
Some(track_id) => track_id,
|
||||||
track_time_to_ms(media_time, track.timescale.unwrap()) as i64
|
None => return MP4PARSE_ERROR_INVALID,
|
||||||
}) - 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();
|
|
||||||
|
|
||||||
MP4PARSE_OK
|
MP4PARSE_OK
|
||||||
}
|
}
|
||||||
|
@ -438,6 +460,32 @@ pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser,
|
||||||
MP4PARSE_OK
|
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)]
|
#[cfg(test)]
|
||||||
extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
|
extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
|
||||||
panic!("panic_read shouldn't be called in these tests");
|
panic!("panic_read shouldn't be called in these tests");
|
||||||
|
@ -614,7 +662,7 @@ fn get_track_count_poisoned_parser() {
|
||||||
#[test]
|
#[test]
|
||||||
fn arg_validation_with_data() {
|
fn arg_validation_with_data() {
|
||||||
unsafe {
|
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,
|
let io = mp4parse_io { read: valid_read,
|
||||||
userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
|
userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
|
||||||
let parser = mp4parse_new(&io);
|
let parser = mp4parse_new(&io);
|
|
@ -2,7 +2,7 @@
|
||||||
# Script to update mp4parse-rust sources to latest upstream
|
# Script to update mp4parse-rust sources to latest upstream
|
||||||
|
|
||||||
# Default version.
|
# Default version.
|
||||||
VER=v0.4.0
|
VER=v0.5.0
|
||||||
|
|
||||||
# Accept version or commit from the command line.
|
# Accept version or commit from the command line.
|
||||||
if test -n "$1"; then
|
if test -n "$1"; then
|
||||||
|
@ -14,15 +14,27 @@ rm -rf _upstream
|
||||||
git clone https://github.com/mozilla/mp4parse-rust _upstream/mp4parse
|
git clone https://github.com/mozilla/mp4parse-rust _upstream/mp4parse
|
||||||
pushd _upstream/mp4parse
|
pushd _upstream/mp4parse
|
||||||
git checkout ${VER}
|
git checkout ${VER}
|
||||||
|
echo "Verifying sources..."
|
||||||
|
pushd mp4parse
|
||||||
|
cargo test
|
||||||
|
popd
|
||||||
echo "Constructing C api header..."
|
echo "Constructing C api header..."
|
||||||
|
pushd mp4parse_capi
|
||||||
cargo build
|
cargo build
|
||||||
|
echo "Verifying sources..."
|
||||||
|
cargo test
|
||||||
|
popd
|
||||||
popd
|
popd
|
||||||
rm -rf mp4parse
|
rm -rf mp4parse
|
||||||
mkdir -p mp4parse/src
|
mkdir -p mp4parse/src
|
||||||
cp _upstream/mp4parse/Cargo.toml mp4parse/
|
cp _upstream/mp4parse/mp4parse/Cargo.toml mp4parse/
|
||||||
cp _upstream/mp4parse/build.rs mp4parse/
|
cp _upstream/mp4parse/mp4parse/src/*.rs mp4parse/src/
|
||||||
cp _upstream/mp4parse/src/*.rs mp4parse/src/
|
rm -rf mp4parse_capi
|
||||||
cp _upstream/mp4parse/include/mp4parse.h include/
|
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.
|
# 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 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
|
@RobocopTarget
|
||||||
public static final class SearchHistory implements CommonColumns, HistoryColumns {
|
public static final class SearchHistory implements CommonColumns, HistoryColumns {
|
||||||
private SearchHistory() {}
|
private SearchHistory() {}
|
||||||
|
|
|
@ -175,4 +175,12 @@ public interface BrowserDB {
|
||||||
public abstract boolean hasSuggestedImageUrl(String url);
|
public abstract boolean hasSuggestedImageUrl(String url);
|
||||||
public abstract String getSuggestedImageUrlForUrl(String url);
|
public abstract String getSuggestedImageUrlForUrl(String url);
|
||||||
public abstract int getSuggestedBackgroundColorForUrl(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.Combined;
|
||||||
import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
|
import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
|
||||||
import org.mozilla.gecko.db.BrowserContract.Favicons;
|
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.History;
|
||||||
import org.mozilla.gecko.db.BrowserContract.Visits;
|
import org.mozilla.gecko.db.BrowserContract.Visits;
|
||||||
import org.mozilla.gecko.db.BrowserContract.Schema;
|
import org.mozilla.gecko.db.BrowserContract.Schema;
|
||||||
|
@ -54,6 +55,8 @@ import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import static org.mozilla.gecko.db.DBUtils.qualifyColumn;
|
||||||
|
|
||||||
public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
||||||
public static final String ACTION_SHRINK_MEMORY = "org.mozilla.gecko.db.intent.action.SHRINK_MEMORY";
|
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 VISITS = 1100;
|
||||||
|
|
||||||
|
static final int METADATA = 1200;
|
||||||
|
|
||||||
|
static final int HIGHLIGHTS = 1300;
|
||||||
|
|
||||||
static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
|
static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
|
||||||
+ " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
|
+ " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
|
||||||
+ " ASC";
|
+ " ASC";
|
||||||
|
@ -288,6 +295,8 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
||||||
|
|
||||||
// Combined pinned sites, top visited sites, and suggested sites
|
// Combined pinned sites, top visited sites, and suggested sites
|
||||||
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "topsites", TOPSITES);
|
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "topsites", TOPSITES);
|
||||||
|
|
||||||
|
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "highlights", HIGHLIGHTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ShrinkMemoryReceiver extends BroadcastReceiver {
|
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
|
@Override
|
||||||
public Cursor query(Uri uri, String[] projection, String selection,
|
public Cursor query(Uri uri, String[] projection, String selection,
|
||||||
String[] selectionArgs, String sortOrder) {
|
String[] selectionArgs, String sortOrder) {
|
||||||
|
@ -1292,6 +1362,12 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case HIGHLIGHTS: {
|
||||||
|
debug("Highlights query: " + uri);
|
||||||
|
|
||||||
|
return getHighlights(db, limit);
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
Table table = findTableFor(match);
|
Table table = findTableFor(match);
|
||||||
if (table == null) {
|
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.SyncColumns;
|
||||||
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
||||||
import org.mozilla.gecko.db.BrowserContract.TopSites;
|
import org.mozilla.gecko.db.BrowserContract.TopSites;
|
||||||
|
import org.mozilla.gecko.db.BrowserContract.Highlights;
|
||||||
import org.mozilla.gecko.distribution.Distribution;
|
import org.mozilla.gecko.distribution.Distribution;
|
||||||
import org.mozilla.gecko.icons.decoders.FaviconDecoder;
|
import org.mozilla.gecko.icons.decoders.FaviconDecoder;
|
||||||
import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
|
import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
|
||||||
|
@ -115,6 +116,7 @@ public class LocalBrowserDB implements BrowserDB {
|
||||||
private final Uri mFaviconsUriWithProfile;
|
private final Uri mFaviconsUriWithProfile;
|
||||||
private final Uri mThumbnailsUriWithProfile;
|
private final Uri mThumbnailsUriWithProfile;
|
||||||
private final Uri mTopSitesUriWithProfile;
|
private final Uri mTopSitesUriWithProfile;
|
||||||
|
private final Uri mHighlightsUriWithProfile;
|
||||||
private final Uri mSearchHistoryUri;
|
private final Uri mSearchHistoryUri;
|
||||||
|
|
||||||
private LocalSearches searches;
|
private LocalSearches searches;
|
||||||
|
@ -141,6 +143,7 @@ public class LocalBrowserDB implements BrowserDB {
|
||||||
mCombinedUriWithProfile = DBUtils.appendProfile(profile, Combined.CONTENT_URI);
|
mCombinedUriWithProfile = DBUtils.appendProfile(profile, Combined.CONTENT_URI);
|
||||||
mFaviconsUriWithProfile = DBUtils.appendProfile(profile, Favicons.CONTENT_URI);
|
mFaviconsUriWithProfile = DBUtils.appendProfile(profile, Favicons.CONTENT_URI);
|
||||||
mTopSitesUriWithProfile = DBUtils.appendProfile(profile, TopSites.CONTENT_URI);
|
mTopSitesUriWithProfile = DBUtils.appendProfile(profile, TopSites.CONTENT_URI);
|
||||||
|
mHighlightsUriWithProfile = DBUtils.appendProfile(profile, Highlights.CONTENT_URI);
|
||||||
mThumbnailsUriWithProfile = DBUtils.appendProfile(profile, Thumbnails.CONTENT_URI);
|
mThumbnailsUriWithProfile = DBUtils.appendProfile(profile, Thumbnails.CONTENT_URI);
|
||||||
|
|
||||||
mSearchHistoryUri = BrowserContract.SearchHistory.CONTENT_URI;
|
mSearchHistoryUri = BrowserContract.SearchHistory.CONTENT_URI;
|
||||||
|
@ -1830,6 +1833,14 @@ public class LocalBrowserDB implements BrowserDB {
|
||||||
rb.add(TopSites.TYPE_BLANK);
|
rb.add(TopSites.TYPE_BLANK);
|
||||||
|
|
||||||
return new MergeCursor(new Cursor[] {topSitesCursor, blanksCursor});
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CursorLoader getHighlights(Context context, int limit) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Cursor getTopSites(ContentResolver cr, int suggestedRangeLimit, int limit) {
|
public Cursor getTopSites(ContentResolver cr, int suggestedRangeLimit, int limit) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ public class URLMetadataTable extends BaseTable {
|
||||||
private static final String LOGTAG = "GeckoURLMetadataTable";
|
private static final String LOGTAG = "GeckoURLMetadataTable";
|
||||||
|
|
||||||
private static final String TABLE = "metadata"; // Name of the table in the db
|
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
|
// Uri for querying this table
|
||||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(BrowserContract.AUTHORITY_URI, "metadata");
|
public static final Uri CONTENT_URI = Uri.withAppendedPath(BrowserContract.AUTHORITY_URI, "metadata");
|
||||||
|
|
|
@ -56,29 +56,14 @@ public class ActivityStream extends FrameLayout {
|
||||||
adapter.swapTopSitesCursor(null);
|
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> {
|
private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
if (id == LOADER_ID_HIGHLIGHTS) {
|
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) {
|
} else if (id == LOADER_ID_TOPSITES) {
|
||||||
return GeckoProfile.get(getContext()).getDB().getActivityStreamTopSites(getContext(),
|
return GeckoProfile.get(getContext()).getDB().getActivityStreamTopSites(getContext(),
|
||||||
TopSitesPagerAdapter.TOTAL_ITEMS);
|
TopSitesPagerAdapter.TOTAL_ITEMS);
|
||||||
|
|
|
@ -17,6 +17,12 @@ import org.mozilla.gecko.db.BrowserContract;
|
||||||
import org.mozilla.gecko.home.HomePager;
|
import org.mozilla.gecko.home.HomePager;
|
||||||
import org.mozilla.gecko.home.activitystream.topsites.CirclePageIndicator;
|
import org.mozilla.gecko.home.activitystream.topsites.CirclePageIndicator;
|
||||||
import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
|
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 abstract class StreamItem extends RecyclerView.ViewHolder {
|
||||||
public StreamItem(View itemView) {
|
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;
|
public static final int LAYOUT_ID = R.layout.activity_stream_card_history_item;
|
||||||
|
|
||||||
|
final FaviconView vIconView;
|
||||||
final TextView vLabel;
|
final TextView vLabel;
|
||||||
final TextView vTimeSince;
|
final TextView vTimeSince;
|
||||||
|
|
||||||
|
private Future<IconResponse> ongoingIconLoad;
|
||||||
|
|
||||||
public CompactItem(View itemView) {
|
public CompactItem(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
|
vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
|
||||||
vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
|
vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
|
||||||
|
vIconView = (FaviconView) itemView.findViewById(R.id.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bind(Cursor cursor) {
|
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));
|
vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
|
||||||
final String ago = DateUtils.getRelativeTimeSpanString(timeVisited, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
|
|
||||||
vTimeSince.setText(ago);
|
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) {
|
public void bind(Cursor cursor) {
|
||||||
vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
|
vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
|
||||||
|
|
||||||
final long timeVisited = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
|
final long time = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.DATE));
|
||||||
final String ago = DateUtils.getRelativeTimeSpanString(timeVisited, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
|
final String ago = DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
|
||||||
vTimeSince.setText(ago);
|
vTimeSince.setText(ago);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче