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

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

@ -16,7 +16,7 @@
<project name="platform_system_libpdu" path="system/libpdu" remote="b2g" revision="f1a61fa8f97cc0a1ac4eca160acc222981b21d90"/> <project name="platform_system_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);
} }
} }

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