Merge inbound to central, a=merge

MozReview-Commit-ID: Q3SnSRpznI
This commit is contained in:
Wes Kocher 2016-03-28 13:04:14 -07:00
commit 21d0ee2e6c
262 changed files with 6798 additions and 3235 deletions

View File

@ -121,6 +121,9 @@ AccShowEvent::
AccShowEvent(Accessible* aTarget) :
AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget)
{
int32_t idx = aTarget->IndexInParent();
MOZ_ASSERT(idx >= 0);
mInsertionIndex = idx;
}

View File

@ -287,6 +287,11 @@ public:
{
return AccMutationEvent::GetEventGroups() | (1U << eShowEvent);
}
uint32_t InsertionIndex() const { return mInsertionIndex; }
private:
uint32_t mInsertionIndex;
};

View File

@ -142,7 +142,7 @@ nsCoreUtils::DispatchMouseEvent(EventMessage aMessage, int32_t aX, int32_t aY,
event.clickCount = 1;
event.button = WidgetMouseEvent::eLeftButton;
event.time = PR_IntervalNow();
event.mTime = PR_IntervalNow();
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
nsEventStatus status = nsEventStatus_eIgnore;
@ -159,7 +159,7 @@ nsCoreUtils::DispatchTouchEvent(EventMessage aMessage, int32_t aX, int32_t aY,
WidgetTouchEvent event(true, aMessage, aRootWidget);
event.time = PR_IntervalNow();
event.mTime = PR_IntervalNow();
// XXX: Touch has an identifier of -1 to hint that it is synthesized.
RefPtr<dom::Touch> t = new dom::Touch(-1, LayoutDeviceIntPoint(aX, aY),

View File

@ -143,7 +143,7 @@ DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent)
{
Accessible* parent = aShowEvent->Parent();
uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent();
uint32_t idxInParent = aShowEvent->InsertionIndex();
nsTArray<AccessibleData> shownTree;
ShowEventData data(parentID, idxInParent, shownTree);
SerializeTree(aShowEvent->GetAccessible(), data.NewTree());

View File

@ -1385,6 +1385,9 @@ pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3
// activates the remote-hosted newtab page
pref("browser.newtabpage.remote", false);
// remote newtab version targeted
pref("browser.newtabpage.remote.version", "1");
// Toggles endpoints allowed for remote newtab communications
pref("browser.newtabpage.remote.mode", "production");

View File

@ -19,6 +19,7 @@ XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
const gPrefsMap = new Map([
["browser.newtabpage.remote", "bool"],
["browser.newtabpage.remote.mode", "str"],
["browser.newtabpage.remote.version", "str"],
["browser.newtabpage.enabled", "bool"],
["browser.newtabpage.enhanced", "bool"],
["browser.newtabpage.introShown", "bool"],

View File

@ -7,7 +7,7 @@ this.EXPORTED_SYMBOLS = ["NewTabRemoteResources"];
const NewTabRemoteResources = {
MODE_CHANNEL_MAP: {
production: {origin: "https://content.cdn.mozilla.net"},
staging: {origin: "https://content-cdn.stage.mozaws.net"},
staging: {origin: "https://s3_proxy_tiles.stage.mozaws.net"},
test: {origin: "https://example.com"},
test2: {origin: "http://mochi.test:8888"},
dev: {origin: "http://localhost:8888"}

View File

@ -25,7 +25,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml";
const REMOTE_NEWTAB_PATH = "/v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
const REMOTE_NEWTAB_PATH = "/newtab/v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
const ABOUT_URL = "about:newtab";
@ -44,9 +44,10 @@ const PREF_SELECTED_LOCALE = "general.useragent.locale";
// The preference that tells what remote mode is enabled.
const PREF_REMOTE_MODE = "browser.newtabpage.remote.mode";
const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
// The preference that tells which remote version is expected.
const PREF_REMOTE_VERSION = "browser.newtabpage.remote.version";
const REMOTE_NEWTAB_VERSION = "0";
const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
function AboutNewTabService() {
NewTabPrefsProvider.prefs.on(PREF_REMOTE_ENABLED, this._handleToggleEvent.bind(this));
@ -145,11 +146,15 @@ AboutNewTabService.prototype = {
NewTabPrefsProvider.prefs.on(
PREF_REMOTE_MODE,
this._updateRemoteMaybe);
NewTabPrefsProvider.prefs.on(
PREF_REMOTE_VERSION,
this._updateRemoteMaybe);
this._remoteEnabled = true;
} else {
NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateRemoteMaybe);
NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateRemoteMaybe);
NewTabPrefsProvider.prefs.off(PREF_REMOTE_MODE, this._updateRemoteMaybe);
NewTabPrefsProvider.prefs.off(PREF_REMOTE_VERSION, this._updateRemoteMaybe);
this._remoteEnabled = false;
}
if (!csTest) {
@ -164,7 +169,7 @@ AboutNewTabService.prototype = {
generateRemoteURL() {
let releaseName = this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
let path = REMOTE_NEWTAB_PATH
.replace("%VERSION%", REMOTE_NEWTAB_VERSION)
.replace("%VERSION%", this.remoteVersion)
.replace("%LOCALE%", Locale.getLocale())
.replace("%CHANNEL%", releaseName);
let mode = Services.prefs.getCharPref(PREF_REMOTE_MODE, "production");
@ -224,7 +229,7 @@ AboutNewTabService.prototype = {
},
get remoteVersion() {
return REMOTE_NEWTAB_VERSION;
return Services.prefs.getCharPref(PREF_REMOTE_VERSION, "1");
},
get remoteReleaseName() {

View File

@ -18,12 +18,17 @@ XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
"resource://gre/modules/Locale.jsm");
const DEFAULT_HREF = aboutNewTabService.generateRemoteURL();
const DEFAULT_CHROME_URL = "chrome://browser/content/newtab/newTab.xhtml";
const DOWNLOADS_URL = "chrome://browser/content/downloads/contentAreaDownloadsView.xul";
const DEFAULT_VERSION = aboutNewTabService.remoteVersion;
function cleanup() {
Services.prefs.setBoolPref("browser.newtabpage.remote", false);
Services.prefs.setCharPref("browser.newtabpage.remote.version", DEFAULT_VERSION);
aboutNewTabService.resetNewTabURL();
NewTabPrefsProvider.prefs.uninit();
}
@ -107,7 +112,8 @@ add_task(function* test_updates() {
let notificationPromise;
let productionModeBaseUrl = "https://content.cdn.mozilla.net";
let testModeBaseUrl = "https://example.com";
let expectedPath = `/v${aboutNewTabService.remoteVersion}` +
let expectedPath = `/newtab` +
`/v${aboutNewTabService.remoteVersion}` +
`/${aboutNewTabService.remoteReleaseName}` +
"/en-GB" +
"/index.html";
@ -193,6 +199,32 @@ add_task(function* test_release_names() {
}
});
/**
* Verifies that remote version updates changes the remote newtab url
*/
add_task(function* test_version_update() {
NewTabPrefsProvider.prefs.init();
Services.prefs.setBoolPref("browser.newtabpage.remote", true);
Assert.ok(aboutNewTabService.remoteEnabled, "remote mode enabled");
let productionModeBaseUrl = "https://content.cdn.mozilla.net";
let version_incr = String(parseInt(DEFAULT_VERSION) + 1);
let expectedPath = `/newtab` +
`/v${version_incr}` +
`/${aboutNewTabService.remoteReleaseName}` +
`/${Locale.getLocale()}` +
`/index.html`;
let expectedHref = productionModeBaseUrl + expectedPath;
let notificationPromise;
notificationPromise = nextChangeNotificationPromise(expectedHref);
Preferences.set("browser.newtabpage.remote.version", version_incr);
yield notificationPromise;
cleanup();
});
function nextChangeNotificationPromise(aNewURL, testMessage) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint unused:false

View File

@ -107,9 +107,34 @@ this.ContentLinkHandler = {
}
sizeHistogramTypes.add(sizesType);
chromeGlobal.sendAsyncMessage(
"Link:SetIcon",
{url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
if (uri.scheme == 'blob') {
// Blob URLs don't work cross process, work around this by sending as a data uri
let channel = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService).newChannelFromURI2(uri, null, Services.scriptSecurityManager.getSystemPrincipal(), null, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsIContentPolicy.TYPE_OTHER);
let listener = {
encoded: "",
bis: null,
onStartRequest: function(aRequest, aContext) {
this.bis = Components.classes["@mozilla.org/binaryinputstream;1"]
.createInstance(Components.interfaces.nsIBinaryInputStream);
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
let spec = "data:" + channel.contentType + ";base64," + this.encoded;
chromeGlobal.sendAsyncMessage(
"Link:SetIcon",
{url: spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
},
onDataAvailable: function(request, context, inputStream, offset, count) {
this.bis.setInputStream(inputStream);
this.encoded += btoa(this.bis.readBytes(this.bis.available()));
}
}
channel.asyncOpen(listener, null);
} else {
chromeGlobal.sendAsyncMessage(
"Link:SetIcon",
{url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
}
iconAdded = true;
break;
case "search":

View File

@ -3282,13 +3282,6 @@ menulist.translate-infobar-element > .menulist-dropmarker {
color: hsl(210,11%,16%);
}
/* Developer Toolbar */
#developer-toolbar-closebutton {
margin-left: 8px;
margin-right: 8px;
}
/* Error counter */
#developer-toolbar-toolbox-button[error-count]:before {

View File

@ -444,11 +444,13 @@ nsresult nsChromeRegistry::RefreshWindow(nsPIDOMWindowOuter* aWindow)
}
// Iterate over our old sheets and kick off a sync load of the new
// sheet if and only if it's a chrome URL.
// sheet if and only if it's a non-inline sheet with a chrome URL.
for (StyleSheetHandle sheet : oldSheets) {
nsIURI* uri = sheet ? sheet->GetOriginalURI() : nullptr;
MOZ_ASSERT(sheet, "GetStyleSheetAt shouldn't return nullptr for "
"in-range sheet indexes");
nsIURI* uri = sheet->GetSheetURI();
if (uri && IsChromeURI(uri)) {
if (!sheet->IsInline() && IsChromeURI(uri)) {
// Reload the sheet.
StyleSheetHandle::RefPtr newSheet;
// XXX what about chrome sheets that have a title or are disabled? This

View File

@ -54,7 +54,7 @@ AnimationTimeBlock.prototype = {
// Create a container element to hold the delay and iterations.
// It is positioned according to its delay (divided by the playbackrate),
// and its width is according to its duration (divided by the playbackrate).
let {x, iterationW, delayX, delayW, negativeDelayW} =
let {x, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW} =
TimeScale.getAnimationDimensions(animation);
// background properties for .iterations element
@ -106,6 +106,18 @@ AnimationTimeBlock.prototype = {
}
});
}
// endDelay
if (state.endDelay) {
createNode({
parent: this.containerEl,
attributes: {
"class": "end-delay" + (state.endDelay < 0 ? " negative" : ""),
"style": `left:${endDelayX}%;
width:${endDelayW}%;`
}
});
}
},
getTooltipText: function(state) {
@ -119,15 +131,24 @@ AnimationTimeBlock.prototype = {
text += "\n";
// Adding the delay.
text += L10N.getStr("player.animationDelayLabel") + " ";
text += getTime(state.delay);
text += "\n";
if (state.delay) {
text += L10N.getStr("player.animationDelayLabel") + " ";
text += getTime(state.delay);
text += "\n";
}
// Adding the duration.
text += L10N.getStr("player.animationDurationLabel") + " ";
text += getTime(state.duration);
text += "\n";
// Adding the endDelay.
if (state.endDelay) {
text += L10N.getStr("player.animationEndDelayLabel") + " ";
text += getTime(state.endDelay);
text += "\n";
}
// Adding the iteration count (the infinite symbol, or an integer).
if (state.iterationCount !== 1) {
text += L10N.getStr("player.animationIterationCountLabel") + " ";

View File

@ -4,6 +4,7 @@ subsuite = devtools
skip-if = e10s && debug # bug 1252283
support-files =
doc_body_animation.html
doc_end_delay.html
doc_frame_script.js
doc_keyframes.html
doc_modify_playbackRate.html
@ -48,6 +49,7 @@ skip-if = os == "linux" && bits == 32 # Bug 1220974
[browser_animation_timeline_scrubber_movable.js]
[browser_animation_timeline_scrubber_moves.js]
[browser_animation_timeline_shows_delay.js]
[browser_animation_timeline_shows_endDelay.js]
[browser_animation_timeline_shows_iterations.js]
[browser_animation_timeline_shows_time_info.js]
[browser_animation_timeline_takes_rate_into_account.js]

View File

@ -0,0 +1,44 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
requestLongerTimeout(2);
// Check that animation endDelay is visualized in the timeline when the
// animation is delayed.
// Also check that negative endDelays do not overflow the UI, and are shown
// like positive endDelays.
add_task(function*() {
yield addTab(URL_ROOT + "doc_end_delay.html");
let {inspector, panel} = yield openAnimationInspector();
let selectors = ["#target1", "#target2", "#target3", "#target4"];
for (let i = 0; i < selectors.length; i++) {
let selector = selectors[i];
yield selectNode(selector, inspector);
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
let animationEl = timelineEl.querySelectorAll(".animation")[0];
checkEndDelayAndName(animationEl);
}
});
function checkEndDelayAndName(animationEl) {
let endDelay = animationEl.querySelector(".end-delay");
let name = animationEl.querySelector(".name");
let targetNode = animationEl.querySelector(".target");
// Check that the endDelay element does not cause the timeline to overflow.
let endDelayLeft = Math.round(endDelay.getBoundingClientRect().x);
let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width);
ok(endDelayLeft >= sidebarWidth,
"The endDelay element isn't displayed over the sidebar");
// Check that the endDelay is not displayed on top of the name.
let endDelayRight = Math.round(endDelay.getBoundingClientRect().right);
let nameLeft = Math.round(name.getBoundingClientRect().left);
ok(endDelayRight >= nameLeft,
"The endDelay element does not span over the name element");
}

View File

@ -23,8 +23,13 @@ add_task(function*() {
ok(el.hasAttribute("title"), "The tooltip is defined for animation " + i);
let title = el.getAttribute("title");
ok(title.match(/Delay: [\d.-]+s/), "The tooltip shows the delay");
if (controller.animationPlayers[i].state.delay) {
ok(title.match(/Delay: [\d.-]+s/), "The tooltip shows the delay");
}
ok(title.match(/Duration: [\d.]+s/), "The tooltip shows the duration");
if (controller.animationPlayers[i].state.endDelay) {
ok(title.match(/End delay: [\d.-]+s/), "The tooltip shows the endDelay");
}
if (controller.animationPlayers[i].state.iterationCount !== 1) {
ok(title.match(/Repeats: /), "The tooltip shows the iterations");
} else {

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.target {
width: 50px;
height: 50px;
background: blue;
}
</style>
</head>
<body>
<div id="target1" class="target"></div>
<div id="target2" class="target"></div>
<div id="target3" class="target"></div>
<div id="target4" class="target"></div>
<script>
"use strict";
var el = document.getElementById("target1");
el.animate(
{ opacity: [ 0, 1 ] },
{ id: "endDelay_animation1",
duration: 1000000,
endDelay: 500000,
fill: "none" }
);
el = document.getElementById("target2");
el.animate(
{ opacity: [ 0, 1 ] },
{ id: "endDelay_animation2",
duration: 1000000,
endDelay: -500000,
fill: "none" }
);
el = document.getElementById("target3");
el.animate(
{ opacity: [ 0, 1 ] },
{ id: "endDelay_animation3",
duration: 1000000,
endDelay: -1500000,
fill: "forwards" }
);
el = document.getElementById("target4");
el.animate(
{ opacity: [ 0, 1 ] },
{ id: "endDelay_animation4",
duration: 100000,
delay: 100000,
endDelay: -1500000,
fill: "forwards" }
);
</script>
</body>
</html>

View File

@ -128,5 +128,17 @@
<div class="ball negative-delay"></div>
<div class="ball no-compositor"></div>
<div class="ball pseudo"></div>
<div class="ball" id="endDelayed"></div>
<script>
"use strict";
var el = document.getElementById("endDelayed");
el.animate(
{ opacity: [ 0, 1 ] },
{ duration: 1000000,
endDelay: 500000,
fill: "none" }
);
</script>
</body>
</html>

View File

@ -199,20 +199,32 @@ var TimeScale = {
* @param {Object} state A PlayerFront.state object.
*/
addAnimation: function(state) {
let {previousStartTime, delay, duration,
let {previousStartTime, delay, duration, endDelay,
iterationCount, playbackRate} = state;
endDelay = typeof endDelay === "undefined" ? 0 : endDelay;
let toRate = v => v / playbackRate;
let minZero = v => Math.max(v, 0);
let rateRelativeDuration =
toRate(duration * (!iterationCount ? 1 : iterationCount));
// Negative-delayed animations have their startTimes set such that we would
// be displaying the delay outside the time window if we didn't take it into
// account here.
let relevantDelay = delay < 0 ? delay / playbackRate : 0;
let relevantDelay = delay < 0 ? toRate(delay) : 0;
previousStartTime = previousStartTime || 0;
this.minStartTime = Math.min(this.minStartTime,
previousStartTime + relevantDelay);
let length = (delay / playbackRate) +
((duration / playbackRate) *
(!iterationCount ? 1 : iterationCount));
let startTime = toRate(minZero(delay)) +
rateRelativeDuration +
endDelay;
this.minStartTime = Math.min(
this.minStartTime,
previousStartTime +
relevantDelay +
Math.min(startTime, 0)
);
let length = toRate(delay) +
rateRelativeDuration +
toRate(minZero(endDelay));
let endTime = previousStartTime + length;
this.maxEndTime = Math.max(this.maxEndTime, endTime);
},
@ -294,6 +306,7 @@ var TimeScale = {
let rate = state.playbackRate;
let count = state.iterationCount;
let delay = state.delay || 0;
let endDelay = state.endDelay || 0;
// The start position.
let x = this.startTimeToDistance(start + (delay / rate));
@ -307,8 +320,13 @@ var TimeScale = {
let delayW = this.durationToDistance(Math.abs(delay) / rate);
// The width of the delay if it is negative, 0 otherwise.
let negativeDelayW = delay < 0 ? delayW : 0;
// The width of the endDelay.
let endDelayW = this.durationToDistance(Math.abs(endDelay) / rate);
// The start position of the endDelay.
let endDelayX = endDelay < 0 ? x + w - endDelayW : x + w;
return {x, w, iterationW, delayX, delayW, negativeDelayW};
return {x, w, iterationW, delayX, delayW, negativeDelayW,
endDelayX, endDelayW};
},
/**

View File

@ -43,6 +43,11 @@ player.animationDurationLabel=Duration:
# displayed before the animation delay.
player.animationDelayLabel=Delay:
# LOCALIZATION NOTE (player.animationEndDelayLabel):
# This string is displayed in each animation player widget. It is the label
# displayed before the animation endDelay.
player.animationEndDelayLabel=End delay:
# LOCALIZATION NOTE (player.animationRateLabel):
# This string is displayed in each animation player widget. It is the label
# displayed before the animation playback rate.

View File

@ -227,7 +227,7 @@ body {
}
.animation-timeline .scrubber-wrapper {
z-index: 1;
z-index: 2;
pointer-events: none;
height: 100%;
}
@ -374,6 +374,7 @@ body {
padding: 0 2px;
box-sizing: border-box;
--fast-track-icon-width: 12px;
z-index: 1;
}
.animation-timeline .animation .name div {
@ -396,20 +397,23 @@ body {
right: 0;
height: 100%;
width: var(--fast-track-icon-width);
z-index: 1;
background-image: url("images/animation-fast-track.svg");
background-repeat: no-repeat;
background-position: center;
}
.animation-timeline .animation .delay {
.animation-timeline .animation .delay,
.animation-timeline .animation .end-delay {
position: absolute;
height: 100%;
border: 1px solid var(--timeline-border-color);
box-sizing: border-box;
border-width: 1px 0 1px 1px;
}
.animation-timeline .animation .delay {
border-width: 1px 0 1px 1px;
background-image: repeating-linear-gradient(45deg,
transparent,
transparent 1px,
@ -418,7 +422,18 @@ body {
background-color: var(--timeline-border-color);
}
.animation-timeline .animation .delay.negative {
.animation-timeline .animation .end-delay {
border-width: 1px 1px 1px 0;
background-image: repeating-linear-gradient(
-45deg,
transparent,
transparent 3px,
var(--timeline-border-color) 3px,
var(--timeline-border-color) 4px);
}
.animation-timeline .animation .delay.negative,
.animation-timeline .animation .end-delay.negative {
/* Negative delays are displayed on top of the animation, so they need a
right border. Whereas normal delays are displayed just before the
animation, so there's already the animation's left border that serves as

View File

@ -50,17 +50,21 @@
margin: auto 10px;
}
:root[devtoolstheme="light"] #developer-toolbar > toolbarbutton:not([checked=true]) > image,
:root[devtoolstheme="light"] #developer-toolbar > .developer-toolbar-button:not([checked=true]) > image,
:root[devtoolstheme="light"] .gclitoolbar-input-node:not([focused=true])::before {
filter: invert(1);
}
.developer-toolbar-button > .toolbarbutton-icon,
#developer-toolbar-closebutton > .toolbarbutton-icon {
.developer-toolbar-button > .toolbarbutton-icon {
width: 16px;
height: 16px;
}
/* The toolkit close button is low contrast in the dark theme so invert it. */
:root[devtoolstheme="dark"] #developer-toolbar > .close-icon:not(:hover) > image {
filter: invert(1);
}
#developer-toolbar-toolbox-button {
list-style-image: url("chrome://devtools/skin/images/toggle-tools.png");
-moz-image-region: rect(0px, 16px, 16px, 0px);
@ -101,40 +105,6 @@
}
}
#developer-toolbar-closebutton {
list-style-image: url("chrome://devtools/skin/images/close.png");
-moz-appearance: none;
border: none;
margin: 0 4px;
min-width: 16px;
width: 16px;
opacity: 0.6;
}
@media (min-resolution: 1.1dppx) {
#developer-toolbar-closebutton {
list-style-image: url("chrome://devtools/skin/images/close@2x.png");
}
}
#developer-toolbar-closebutton > .toolbarbutton-icon {
/* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must
use evil CSS to give the impression of smaller content */
margin: -4px;
}
#developer-toolbar-closebutton > .toolbarbutton-text {
display: none;
}
#developer-toolbar-closebutton:hover {
opacity: 0.8;
}
#developer-toolbar-closebutton:hover:active {
opacity: 1;
}
/* GCLI */
html|*#gcli-tooltip-frame,

View File

@ -181,6 +181,14 @@ var AnimationPlayerActor = ActorClass({
return this.player.effect.getComputedTiming().delay;
},
/**
* Get the animation endDelay from this player, in milliseconds.
* @return {Number}
*/
getEndDelay: function() {
return this.player.effect.getComputedTiming().endDelay;
},
/**
* Get the animation iteration count for this player. That is, how many times
* is the animation scheduled to run.
@ -229,6 +237,7 @@ var AnimationPlayerActor = ActorClass({
name: this.getName(),
duration: this.getDuration(),
delay: this.getDelay(),
endDelay: this.getEndDelay(),
iterationCount: this.getIterationCount(),
iterationStart: this.getIterationStart(),
// animation is hitting the fast path or not. Returns false whenever the
@ -303,7 +312,8 @@ var AnimationPlayerActor = ActorClass({
hasChanged = newState.delay !== oldState.delay ||
newState.iterationCount !== oldState.iterationCount ||
newState.iterationStart !== oldState.iterationStart ||
newState.duration !== oldState.duration;
newState.duration !== oldState.duration ||
newState.endDelay !== oldState.endDelay;
break;
}
}
@ -456,6 +466,7 @@ var AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
name: this._form.name,
duration: this._form.duration,
delay: this._form.delay,
endDelay: this._form.endDelay,
iterationCount: this._form.iterationCount,
iterationStart: this._form.iterationStart,
isRunningOnCompositor: this._form.isRunningOnCompositor,

View File

@ -293,9 +293,6 @@
<h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
<h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
<h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
<h1 id="et_forbiddenBlocked">&forbiddenBlocked.title;</h1>
<h1 id="et_cspBlocked">&cspBlocked.title;</h1>
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
<h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
@ -321,9 +318,6 @@
<div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
<div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
<div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
<div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
<div id="ed_forbiddenBlocked">&forbiddenBlocked.longDesc;</div>
<div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
<div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>

View File

@ -61,8 +61,7 @@ public:
void AddPropertiesToSet(nsCSSPropertySet& aSet) const
{
for (size_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) {
const PropertyValuePair &cv = mPropertyValuePairs[i];
for (const PropertyValuePair& cv : mPropertyValuePairs) {
aSet.AddProperty(cv.mProperty);
}
}

View File

@ -73,7 +73,18 @@ AnimationEffectTiming::SetIterationStart(double aIterationStart,
void
AnimationEffectTiming::SetIterations(double aIterations, ErrorResult& aRv)
{
// TODO: Bug 1244640 - implement AnimationEffectTiming iterations
if (mTiming.mIterations == aIterations) {
return;
}
TimingParams::ValidateIterations(aIterations, aRv);
if (aRv.Failed()) {
return;
}
mTiming.mIterations = aIterations;
PostSpecifiedTimingUpdated(mEffect);
}
void
@ -98,7 +109,13 @@ AnimationEffectTiming::SetDuration(const UnrestrictedDoubleOrString& aDuration,
void
AnimationEffectTiming::SetDirection(const PlaybackDirection& aDirection)
{
// TODO: Bug 1244642 - implement AnimationEffectTiming direction
if (mTiming.mDirection == aDirection) {
return;
}
mTiming.mDirection = aDirection;
PostSpecifiedTimingUpdated(mEffect);
}
void

View File

@ -40,7 +40,7 @@ AnimationEffectTimingReadOnly::GetDuration(
void
AnimationEffectTimingReadOnly::GetEasing(nsString& aRetVal) const
{
if (mTiming.mFunction.isSome()) {
if (mTiming.mFunction) {
mTiming.mFunction->AppendToString(aRetVal);
} else {
aRetVal.AssignLiteral("linear");

View File

@ -44,7 +44,7 @@ public:
static double GetPortion(const Maybe<ComputedTimingFunction>& aFunction,
double aPortion)
{
return aFunction.isSome() ? aFunction->GetValue(aPortion) : aPortion;
return aFunction ? aFunction->GetValue(aPortion) : aPortion;
}
static int32_t Compare(const Maybe<ComputedTimingFunction>& aLhs,
const Maybe<ComputedTimingFunction>& aRhs);

View File

@ -1601,6 +1601,39 @@ addAsyncAnimTest("change_enddelay_and_currenttime",
assert_records([], "records after animation is added");
});
addAsyncAnimTest("change_iterations",
{ observe: div, subtree: true }, function*() {
var anim = div.animate({ opacity: [ 0, 1 ] }, 100000);
yield await_frame();
assert_records([{ added: [anim], changed: [], removed: [] }],
"records after animation is added");
anim.effect.timing.iterations = 2;
yield await_frame();
assert_records([{ added: [], changed: [anim], removed: [] }],
"records after iterations is changed");
anim.effect.timing.iterations = 2;
yield await_frame();
assert_records([], "records after assigning same value");
anim.effect.timing.iterations = 0;
yield await_frame();
assert_records([{ added: [], changed: [], removed: [anim] }],
"records after animation end");
anim.effect.timing.iterations = Infinity;
yield await_frame();
assert_records([{ added: [anim], changed: [], removed: [] }],
"records after animation restarted");
anim.cancel();
yield await_frame();
assert_records([{ added: [], changed: [], removed: [anim] }],
"records after animation end");
});
addAsyncAnimTest("exclude_animations_targeting_pseudo_elements",
{ observe: div, subtree: false }, function*() {
var anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 100000 });

View File

@ -217,24 +217,28 @@ async_test(function(t)
}));
}, 'startTime while pause-pending and play-pending');
async_test(function(t)
{
async_test(function(t) {
var div = addDiv(t, { 'style': 'animation: anim 100s' });
var animation = div.getAnimations()[0];
// Seek to end to put us in the finished state
// FIXME: Once we implement finish(), use that here.
animation.currentTime = 100 * 1000;
animation.ready.then(t.step_func(function() {
// Call play() which puts us back in the running state
animation.play();
// FIXME: Enable this once we implement finishing behavior (bug 1074630)
/*
assert_equals(animation.startTime, null, 'startTime is unresolved');
*/
t.done();
}));
}, 'startTime while play-pending from finished state');
test(function(t) {
var div = addDiv(t, { 'style': 'animation: anim 100s' });
var animation = div.getAnimations()[0];
animation.finish();
// Call play() which puts us back in the running state
animation.play();
assert_equals(animation.startTime, null, 'startTime is unresolved');
}, 'startTime while play-pending from finished state using finish()');
async_test(function(t) {
var div = addDiv(t, { style: 'animation: anim 1000s' });
var animation = div.getAnimations()[0];

View File

@ -2923,7 +2923,7 @@ Element::PreHandleEventForLinks(EventChainPreVisitor& aVisitor)
MOZ_FALLTHROUGH;
case eFocus: {
InternalFocusEvent* focusEvent = aVisitor.mEvent->AsFocusEvent();
if (!focusEvent || !focusEvent->isRefocus) {
if (!focusEvent || !focusEvent->mIsRefocus) {
nsAutoString target;
GetLinkTarget(target);
nsContentUtils::TriggerLink(this, aVisitor.mPresContext, absURI, target,
@ -3015,7 +3015,7 @@ Element::PostHandleEventForLinks(EventChainPostVisitor& aVisitor)
// DOMActive event should be trusted since the activation is actually
// occurred even if the cause is an untrusted click event.
InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
actEvent.detail = 1;
actEvent.mDetail = 1;
rv = shell->HandleDOMEventWithTarget(this, &actEvent, &status);
if (NS_SUCCEEDED(rv)) {

View File

@ -7699,7 +7699,7 @@ nsContentUtils::SendKeyEvent(nsIWidget* aWidget,
}
event.refPoint.x = event.refPoint.y = 0;
event.time = PR_IntervalNow();
event.mTime = PR_IntervalNow();
if (!(aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_NOT_SYNTHESIZED_FOR_TESTS)) {
event.mFlags.mIsSynthesizedForTests = true;
}
@ -7774,7 +7774,7 @@ nsContentUtils::SendMouseEvent(nsCOMPtr<nsIPresShell> aPresShell,
event.pressure = aPressure;
event.inputSource = aInputSourceArg;
event.clickCount = aClickCount;
event.time = PR_IntervalNow();
event.mTime = PR_IntervalNow();
event.mFlags.mIsSynthesizedForTests = aIsSynthesized;
nsPresContext* presContext = aPresShell->GetPresContext();

View File

@ -693,7 +693,7 @@ nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
nsEventStatus status = nsEventStatus_eIgnore;
InternalClipboardEvent evt(true, aEventMessage);
evt.clipboardData = clipboardData;
evt.mClipboardData = clipboardData;
EventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt,
nullptr, &status);
// If the event was cancelled, don't do the clipboard operation

View File

@ -682,7 +682,7 @@ nsDOMWindowUtils::SendPointerEventCommon(const nsAString& aType,
event.tiltY = aTiltY;
event.isPrimary = (nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == aInputSourceArg) ? true : aIsPrimary;
event.clickCount = aClickCount;
event.time = PR_IntervalNow();
event.mTime = PR_IntervalNow();
event.mFlags.mIsSynthesizedForTests = aOptionalArgCount >= 10 ? aIsSynthesized : true;
nsPresContext* presContext = GetPresContext();
@ -807,7 +807,7 @@ nsDOMWindowUtils::SendWheelEvent(float aX,
wheelEvent.lineOrPageDeltaY = aLineOrPageDeltaY;
wheelEvent.widget = widget;
wheelEvent.time = PR_Now() / 1000;
wheelEvent.mTime = PR_Now() / 1000;
nsPresContext* presContext = GetPresContext();
NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
@ -939,7 +939,7 @@ nsDOMWindowUtils::SendTouchEventCommon(const nsAString& aType,
WidgetTouchEvent event(true, msg, widget);
event.modifiers = nsContentUtils::GetWidgetModifiers(aModifiers);
event.widget = widget;
event.time = PR_Now();
event.mTime = PR_Now();
nsPresContext* presContext = GetPresContext();
if (!presContext) {
@ -1296,7 +1296,7 @@ nsDOMWindowUtils::SendSimpleGestureEvent(const nsAString& aType,
event.direction = aDirection;
event.delta = aDelta;
event.clickCount = aClickCount;
event.time = PR_IntervalNow();
event.mTime = PR_IntervalNow();
nsPresContext* presContext = GetPresContext();
if (!presContext)
@ -1802,7 +1802,7 @@ InitEvent(WidgetGUIEvent& aEvent, LayoutDeviceIntPoint* aPt = nullptr)
if (aPt) {
aEvent.refPoint = *aPt;
}
aEvent.time = PR_IntervalNow();
aEvent.mTime = PR_IntervalNow();
}
NS_IMETHODIMP

View File

@ -1965,8 +1965,8 @@ public:
InternalFocusEvent event(true, mEventMessage);
event.mFlags.mBubbles = false;
event.mFlags.mCancelable = false;
event.fromRaise = mWindowRaised;
event.isRefocus = mIsRefocus;
event.mFromRaise = mWindowRaised;
event.mIsRefocus = mIsRefocus;
return EventDispatcher::Dispatch(mTarget, mContext, &event);
}

View File

@ -3116,7 +3116,7 @@ nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor)
myCoord[0] = aVisitor.mEvent->refPoint.x;
myCoord[1] = aVisitor.mEvent->refPoint.y;
gEntropyCollector->RandomUpdate((void*)myCoord, sizeof(myCoord));
gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->time),
gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->mTime),
sizeof(uint32_t));
}
} else if (msg == eResize && aVisitor.mEvent->IsTrusted()) {

View File

@ -219,7 +219,7 @@ nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver* aObserver,
nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ?
thisContent->OwnerDoc() : thisContent->GetUncomposedDoc();
if (doc && doc->CSSLoader()->GetEnabled() &&
mStyleSheet && mStyleSheet->GetOriginalURI()) {
mStyleSheet && !mStyleSheet->IsInline()) {
doc->CSSLoader()->ObsoleteSheet(mStyleSheet->GetOriginalURI());
}
}

View File

@ -22,7 +22,7 @@ AnimationEvent::AnimationEvent(EventTarget* aOwner,
}
else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
}
@ -47,9 +47,9 @@ AnimationEvent::Constructor(const GlobalObject& aGlobal,
e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
InternalAnimationEvent* internalEvent = e->mEvent->AsAnimationEvent();
internalEvent->animationName = aParam.mAnimationName;
internalEvent->elapsedTime = aParam.mElapsedTime;
internalEvent->pseudoElement = aParam.mPseudoElement;
internalEvent->mAnimationName = aParam.mAnimationName;
internalEvent->mElapsedTime = aParam.mElapsedTime;
internalEvent->mPseudoElement = aParam.mPseudoElement;
e->SetTrusted(trusted);
return e.forget();
@ -58,7 +58,7 @@ AnimationEvent::Constructor(const GlobalObject& aGlobal,
NS_IMETHODIMP
AnimationEvent::GetAnimationName(nsAString& aAnimationName)
{
aAnimationName = mEvent->AsAnimationEvent()->animationName;
aAnimationName = mEvent->AsAnimationEvent()->mAnimationName;
return NS_OK;
}
@ -72,13 +72,13 @@ AnimationEvent::GetElapsedTime(float* aElapsedTime)
float
AnimationEvent::ElapsedTime()
{
return mEvent->AsAnimationEvent()->elapsedTime;
return mEvent->AsAnimationEvent()->mElapsedTime;
}
NS_IMETHODIMP
AnimationEvent::GetPseudoElement(nsAString& aPseudoElement)
{
aPseudoElement = mEvent->AsAnimationEvent()->pseudoElement;
aPseudoElement = mEvent->AsAnimationEvent()->mPseudoElement;
return NS_OK;
}

View File

@ -26,7 +26,7 @@ BeforeAfterKeyboardEvent::BeforeAfterKeyboardEvent(
if (!aEvent) {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
}

View File

@ -22,7 +22,7 @@ ClipboardEvent::ClipboardEvent(EventTarget* aOwner,
mEventIsInternal = false;
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
}
@ -54,7 +54,7 @@ ClipboardEvent::InitClipboardEvent(const nsAString& aType, bool aCanBubble,
DataTransfer* aClipboardData)
{
Event::InitEvent(aType, aCanBubble, aCancelable);
mEvent->AsClipboardEvent()->clipboardData = aClipboardData;
mEvent->AsClipboardEvent()->mClipboardData = aClipboardData;
}
already_AddRefed<ClipboardEvent>
@ -98,19 +98,19 @@ ClipboardEvent::GetClipboardData()
{
InternalClipboardEvent* event = mEvent->AsClipboardEvent();
if (!event->clipboardData) {
if (!event->mClipboardData) {
if (mEventIsInternal) {
event->clipboardData =
event->mClipboardData =
new DataTransfer(ToSupports(this), eCopy, false, -1);
} else {
event->clipboardData =
event->mClipboardData =
new DataTransfer(ToSupports(this), event->mMessage,
event->mMessage == ePaste,
nsIClipboard::kGlobalClipboard);
}
}
return event->clipboardData;
return event->mClipboardData;
}
} // namespace dom

View File

@ -18,7 +18,7 @@ CommandEvent::CommandEvent(EventTarget* aOwner,
aEvent ? aEvent :
new WidgetCommandEvent(false, nullptr, nullptr, nullptr))
{
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
if (aEvent) {
mEventIsInternal = false;
} else {

View File

@ -25,7 +25,7 @@ CompositionEvent::CompositionEvent(EventTarget* aOwner,
mEventIsInternal = false;
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
// XXX compositionstart is cancelable in draft of DOM3 Events.
// However, it doesn't make sence for us, we cannot cancel composition

View File

@ -24,7 +24,7 @@ DragEvent::DragEvent(EventTarget* aOwner,
}
else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
mEvent->refPoint.x = mEvent->refPoint.y = 0;
mEvent->AsMouseEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
}

View File

@ -107,7 +107,7 @@ Event::ConstructorInit(EventTarget* aOwner,
}
*/
mEvent = new WidgetEvent(false, eVoidEvent);
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
InitPresContextData(aPresContext);
@ -171,13 +171,13 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Event)
break;
}
case eClipboardEventClass:
tmp->mEvent->AsClipboardEvent()->clipboardData = nullptr;
tmp->mEvent->AsClipboardEvent()->mClipboardData = nullptr;
break;
case eMutationEventClass:
tmp->mEvent->AsMutationEvent()->mRelatedNode = nullptr;
break;
case eFocusEventClass:
tmp->mEvent->AsFocusEvent()->relatedTarget = nullptr;
tmp->mEvent->AsFocusEvent()->mRelatedTarget = nullptr;
break;
default:
break;
@ -212,16 +212,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event)
break;
}
case eClipboardEventClass:
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->clipboardData");
cb.NoteXPCOMChild(tmp->mEvent->AsClipboardEvent()->clipboardData);
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mClipboardData");
cb.NoteXPCOMChild(tmp->mEvent->AsClipboardEvent()->mClipboardData);
break;
case eMutationEventClass:
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedNode");
cb.NoteXPCOMChild(tmp->mEvent->AsMutationEvent()->mRelatedNode);
break;
case eFocusEventClass:
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
cb.NoteXPCOMChild(tmp->mEvent->AsFocusEvent()->relatedTarget);
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedTarget");
cb.NoteXPCOMChild(tmp->mEvent->AsFocusEvent()->mRelatedTarget);
break;
default:
break;
@ -459,7 +459,7 @@ Event::GetCancelable(bool* aCancelable)
NS_IMETHODIMP
Event::GetTimeStamp(uint64_t* aTimeStamp)
{
*aTimeStamp = mEvent->time;
*aTimeStamp = mEvent->mTime;
return NS_OK;
}
@ -1064,10 +1064,10 @@ double
Event::TimeStamp() const
{
if (!sReturnHighResTimeStamp) {
return static_cast<double>(mEvent->time);
return static_cast<double>(mEvent->mTime);
}
if (mEvent->timeStamp.IsNull()) {
if (mEvent->mTimeStamp.IsNull()) {
return 0.0;
}
@ -1085,7 +1085,7 @@ Event::TimeStamp() const
return 0.0;
}
return perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->timeStamp);
return perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->mTimeStamp);
}
// For dedicated workers, we should make times relative to the navigation
@ -1096,7 +1096,7 @@ Event::TimeStamp() const
MOZ_ASSERT(workerPrivate);
TimeDuration duration =
mEvent->timeStamp - workerPrivate->NowBaseTimeStamp();
mEvent->mTimeStamp - workerPrivate->NowBaseTimeStamp();
return duration.ToMilliseconds();
}

View File

@ -51,6 +51,7 @@
#include "nsFrameManager.h"
#include "nsITabChild.h"
#include "nsPluginFrame.h"
#include "nsMenuPopupFrame.h"
#include "nsIDOMXULElement.h"
#include "nsIDOMKeyEvent.h"
@ -2298,8 +2299,8 @@ EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame,
event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
event.refPoint = aEvent->refPoint;
event.widget = aEvent->widget;
event.time = aEvent->time;
event.timeStamp = aEvent->timeStamp;
event.mTime = aEvent->mTime;
event.mTimeStamp = aEvent->mTimeStamp;
event.modifiers = aEvent->modifiers;
event.buttons = aEvent->buttons;
event.isHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
@ -2338,8 +2339,8 @@ EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame,
event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
event.refPoint = aEvent->refPoint;
event.widget = aEvent->widget;
event.time = aEvent->time;
event.timeStamp = aEvent->timeStamp;
event.mTime = aEvent->mTime;
event.mTimeStamp = aEvent->mTimeStamp;
event.modifiers = aEvent->modifiers;
event.buttons = aEvent->buttons;
event.isHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
@ -2438,6 +2439,10 @@ EventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame,
return scrollFrame;
}
}
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(scrollFrame);
if (menuPopupFrame) {
return nullptr;
}
continue;
}
@ -4638,8 +4643,8 @@ EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent,
event.clickCount = aEvent->clickCount;
event.modifiers = aEvent->modifiers;
event.buttons = aEvent->buttons;
event.time = aEvent->time;
event.timeStamp = aEvent->timeStamp;
event.mTime = aEvent->mTime;
event.mTimeStamp = aEvent->mTimeStamp;
event.mFlags.mNoContentDispatch = notDispatchToContents;
event.button = aEvent->button;
event.inputSource = aEvent->inputSource;

View File

@ -23,7 +23,7 @@ FocusEvent::FocusEvent(EventTarget* aOwner,
mEventIsInternal = false;
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
}
@ -38,7 +38,7 @@ FocusEvent::GetRelatedTarget(nsIDOMEventTarget** aRelatedTarget)
EventTarget*
FocusEvent::GetRelatedTarget()
{
return mEvent->AsFocusEvent()->relatedTarget;
return mEvent->AsFocusEvent()->mRelatedTarget;
}
void
@ -50,7 +50,7 @@ FocusEvent::InitFocusEvent(const nsAString& aType,
EventTarget* aRelatedTarget)
{
UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail);
mEvent->AsFocusEvent()->relatedTarget = aRelatedTarget;
mEvent->AsFocusEvent()->mRelatedTarget = aRelatedTarget;
}
already_AddRefed<FocusEvent>

View File

@ -25,7 +25,7 @@ InputEvent::InputEvent(EventTarget* aOwner,
mEventIsInternal = false;
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
}

View File

@ -25,7 +25,7 @@ KeyboardEvent::KeyboardEvent(EventTarget* aOwner,
}
else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
mEvent->AsKeyboardEvent()->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
}
}

View File

@ -31,7 +31,7 @@ MouseEvent::MouseEvent(EventTarget* aOwner,
}
else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
mEvent->refPoint.x = mEvent->refPoint.y = 0;
mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
}

View File

@ -23,7 +23,7 @@ MouseScrollEvent::MouseScrollEvent(EventTarget* aOwner,
mEventIsInternal = false;
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
mEvent->refPoint.x = mEvent->refPoint.y = 0;
static_cast<WidgetMouseEventBase*>(mEvent)->inputSource =
nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;

View File

@ -28,7 +28,7 @@ PointerEvent::PointerEvent(EventTarget* aOwner,
mEventIsInternal = false;
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
mEvent->refPoint.x = mEvent->refPoint.y = 0;
mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
}

View File

@ -26,7 +26,7 @@ SimpleGestureEvent::SimpleGestureEvent(EventTarget* aOwner,
mEventIsInternal = false;
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
mEvent->refPoint.x = mEvent->refPoint.y = 0;
static_cast<WidgetMouseEventBase*>(mEvent)->inputSource =
nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;

View File

@ -120,8 +120,8 @@ TextComposition::CloneAndDispatchAs(
WidgetCompositionEvent compositionEvent(aCompositionEvent->IsTrusted(),
aMessage, aCompositionEvent->widget);
compositionEvent.time = aCompositionEvent->time;
compositionEvent.timeStamp = aCompositionEvent->timeStamp;
compositionEvent.mTime = aCompositionEvent->mTime;
compositionEvent.mTimeStamp = aCompositionEvent->mTimeStamp;
compositionEvent.mData = aCompositionEvent->mData;
compositionEvent.mNativeIMEContext = aCompositionEvent->mNativeIMEContext;
compositionEvent.mOriginalMessage = aCompositionEvent->mMessage;

View File

@ -75,7 +75,7 @@ TouchEvent::TouchEvent(EventTarget* aOwner,
}
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
}

View File

@ -22,7 +22,7 @@ TransitionEvent::TransitionEvent(EventTarget* aOwner,
}
else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
}
@ -47,9 +47,9 @@ TransitionEvent::Constructor(const GlobalObject& aGlobal,
e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
InternalTransitionEvent* internalEvent = e->mEvent->AsTransitionEvent();
internalEvent->propertyName = aParam.mPropertyName;
internalEvent->elapsedTime = aParam.mElapsedTime;
internalEvent->pseudoElement = aParam.mPseudoElement;
internalEvent->mPropertyName = aParam.mPropertyName;
internalEvent->mElapsedTime = aParam.mElapsedTime;
internalEvent->mPseudoElement = aParam.mPseudoElement;
e->SetTrusted(trusted);
return e.forget();
@ -58,7 +58,7 @@ TransitionEvent::Constructor(const GlobalObject& aGlobal,
NS_IMETHODIMP
TransitionEvent::GetPropertyName(nsAString& aPropertyName)
{
aPropertyName = mEvent->AsTransitionEvent()->propertyName;
aPropertyName = mEvent->AsTransitionEvent()->mPropertyName;
return NS_OK;
}
@ -72,13 +72,13 @@ TransitionEvent::GetElapsedTime(float* aElapsedTime)
float
TransitionEvent::ElapsedTime()
{
return mEvent->AsTransitionEvent()->elapsedTime;
return mEvent->AsTransitionEvent()->mElapsedTime;
}
NS_IMETHODIMP
TransitionEvent::GetPseudoElement(nsAString& aPseudoElement)
{
aPseudoElement = mEvent->AsTransitionEvent()->pseudoElement;
aPseudoElement = mEvent->AsTransitionEvent()->mPseudoElement;
return NS_OK;
}

View File

@ -42,7 +42,7 @@ UIEvent::UIEvent(EventTarget* aOwner,
}
else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
// Fill mDetail and mView according to the mEvent (widget-generated
@ -50,7 +50,7 @@ UIEvent::UIEvent(EventTarget* aOwner,
switch(mEvent->mClass) {
case eUIEventClass:
{
mDetail = mEvent->AsUIEvent()->detail;
mDetail = mEvent->AsUIEvent()->mDetail;
break;
}

View File

@ -30,7 +30,7 @@ WheelEvent::WheelEvent(EventTarget* aOwner,
}
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
mEvent->refPoint.x = mEvent->refPoint.y = 0;
mEvent->AsWheelEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
}

View File

@ -22,7 +22,7 @@ XULCommandEvent::XULCommandEvent(EventTarget* aOwner,
}
else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
}

View File

@ -253,7 +253,7 @@ HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
// DOMActive event should be trusted since the activation is actually
// occurred even if the cause is an untrusted click event.
InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
actEvent.detail = 1;
actEvent.mDetail = 1;
nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
if (shell) {

View File

@ -3781,7 +3781,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
// DOMActive event should be trusted since the activation is actually
// occurred even if the cause is an untrusted click event.
InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
actEvent.detail = 1;
actEvent.mDetail = 1;
nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
if (shell) {
@ -3902,7 +3902,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
// just because we raised a window.
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm && IsSingleLineTextControl(false) &&
!aVisitor.mEvent->AsFocusEvent()->fromRaise &&
!aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
SelectTextFieldOnFocus()) {
nsIDocument* document = GetComposedDoc();
if (document) {

View File

@ -2759,7 +2759,7 @@ TabParent::InjectTouchEvent(const nsAString& aType,
WidgetTouchEvent event(true, msg, widget);
event.modifiers = aModifiers;
event.time = PR_IntervalNow();
event.mTime = PR_IntervalNow();
nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
if (!content || !content->OwnerDoc()) {

View File

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef MOZILLA_AUTOTASKQUEUE_H_
#define MOZILLA_AUTOTASKQUEUE_H_
#include "mozilla/RefPtr.h"
#include "mozilla/TaskQueue.h"
namespace mozilla {
// A convenience TaskQueue not requiring explicit shutdown.
class AutoTaskQueue : public AbstractThread
{
public:
explicit AutoTaskQueue(already_AddRefed<SharedThreadPool> aPool, bool aSupportsTailDispatch = false)
: AbstractThread(aSupportsTailDispatch)
, mTaskQueue(new TaskQueue(Move(aPool), aSupportsTailDispatch))
{}
TaskDispatcher& TailDispatcher() override
{
return mTaskQueue->TailDispatcher();
}
void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
DispatchReason aReason = NormalDispatch) override
{
mTaskQueue->Dispatch(Move(aRunnable), aFailureHandling, aReason);
}
// Blocks until all tasks finish executing.
void AwaitIdle() { mTaskQueue->AwaitIdle(); }
bool IsEmpty() { return mTaskQueue->IsEmpty(); }
// Returns true if the current thread is currently running a Runnable in
// the task queue.
bool IsCurrentThreadIn() override { return mTaskQueue->IsCurrentThreadIn(); }
private:
~AutoTaskQueue()
{
RefPtr<TaskQueue> taskqueue = mTaskQueue;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([taskqueue]() { taskqueue->BeginShutdown(); });
AbstractThread::MainThread()->Dispatch(task.forget());
}
RefPtr<TaskQueue> mTaskQueue;
};
} // namespace mozilla
#endif

View File

@ -480,16 +480,6 @@ MediaSource::DurationChange(double aOldDuration, double aNewDuration)
// TODO: If partial audio frames/text cues exist, clamp duration based on mSourceBuffers.
}
void
MediaSource::NotifyEvicted(double aStart, double aEnd)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("NotifyEvicted(aStart=%f, aEnd=%f)", aStart, aEnd);
// Cycle through all SourceBuffers and tell them to evict data in
// the given range.
mSourceBuffers->Evict(aStart, aEnd);
}
void
MediaSource::GetMozDebugReaderData(nsAString& aString)
{

View File

@ -95,11 +95,6 @@ public:
return mPrincipal;
}
// Called by SourceBuffers to notify this MediaSource that data has
// been evicted from the buffered data. The start and end times
// that were evicted are provided.
void NotifyEvicted(double aStart, double aEnd);
// Returns a string describing the state of the MediaSource internal
// buffered data. Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString);

View File

@ -21,8 +21,8 @@ using media::TimeUnit;
using media::TimeIntervals;
MediaSourceDemuxer::MediaSourceDemuxer()
: mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
/* aSupportsTailDispatch = */ false))
: mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
/* aSupportsTailDispatch = */ false))
, mMonitor("MediaSourceDemuxer")
{
MOZ_ASSERT(NS_IsMainThread());
@ -250,8 +250,6 @@ MediaSourceDemuxer::GetManager(TrackType aTrack)
MediaSourceDemuxer::~MediaSourceDemuxer()
{
mInitPromise.RejectIfExists(DemuxerFailureReason::SHUTDOWN, __func__);
mTaskQueue->BeginShutdown();
mTaskQueue = nullptr;
}
void

View File

@ -10,7 +10,7 @@
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/Monitor.h"
#include "mozilla/TaskQueue.h"
#include "AutoTaskQueue.h"
#include "MediaDataDemuxer.h"
#include "MediaDecoderReader.h"
@ -47,7 +47,7 @@ public:
/* interface for TrackBuffersManager */
void AttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
void DetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
TaskQueue* GetTaskQueue() { return mTaskQueue; }
AutoTaskQueue* GetTaskQueue() { return mTaskQueue; }
// Returns a string describing the state of the MediaSource internal
// buffered data. Used for debugging purposes.
@ -73,7 +73,7 @@ private:
return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
}
RefPtr<TaskQueue> mTaskQueue;
RefPtr<AutoTaskQueue> mTaskQueue;
nsTArray<RefPtr<MediaSourceTrackDemuxer>> mDemuxers;
nsTArray<RefPtr<TrackBuffersManager>> mSourceBuffers;

View File

@ -33,24 +33,29 @@ extern mozilla::LogModule* GetMediaSourceAPILog();
#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
#define MSE_API(arg, ...) MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
#if defined(MOZ_GONK_MEDIACODEC) || defined(XP_WIN) || defined(MOZ_APPLEMEDIA) || defined(MOZ_FFMPEG)
#define MP4_READER_DORMANT_HEURISTIC
#else
#undef MP4_READER_DORMANT_HEURISTIC
#endif
namespace mozilla {
using media::TimeUnit;
typedef SourceBufferAttributes::AppendState AppendState;
namespace dom {
void
SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
{
typedef mozilla::SourceBufferContentManager::AppendState AppendState;
MOZ_ASSERT(NS_IsMainThread());
MSE_API("SetMode(aMode=%d)", aMode);
if (!IsAttached() || mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mAttributes->mGenerateTimestamps &&
if (mCurrentAttributes.mGenerateTimestamps &&
aMode == SourceBufferAppendMode::Segments) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return;
@ -59,24 +64,22 @@ SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
mMediaSource->SetReadyState(MediaSourceReadyState::Open);
}
if (mContentManager->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (aMode == SourceBufferAppendMode::Sequence) {
// Will set GroupStartTimestamp to GroupEndTimestamp.
mContentManager->RestartGroupStartTimestamp();
mCurrentAttributes.RestartGroupStartTimestamp();
}
mAttributes->SetAppendMode(aMode);
mCurrentAttributes.SetAppendMode(aMode);
}
void
SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv)
{
typedef mozilla::SourceBufferContentManager::AppendState AppendState;
MOZ_ASSERT(NS_IsMainThread());
MSE_API("SetTimestampOffset(aTimestampOffset=%f)", aTimestampOffset);
if (!IsAttached() || mUpdating) {
@ -87,13 +90,13 @@ SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv)
if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
mMediaSource->SetReadyState(MediaSourceReadyState::Open);
}
if (mContentManager->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mAttributes->SetApparentTimestampOffset(aTimestampOffset);
if (mAttributes->GetAppendMode() == SourceBufferAppendMode::Sequence) {
mContentManager->SetGroupStartTimestamp(mAttributes->GetTimestampOffset());
mCurrentAttributes.SetApparentTimestampOffset(aTimestampOffset);
if (mCurrentAttributes.GetAppendMode() == SourceBufferAppendMode::Sequence) {
mCurrentAttributes.SetGroupStartTimestamp(mCurrentAttributes.GetTimestampOffset());
}
}
@ -108,7 +111,7 @@ SourceBuffer::GetBuffered(ErrorResult& aRv)
return nullptr;
}
bool rangeChanged = true;
media::TimeIntervals intersection = mContentManager->Buffered();
media::TimeIntervals intersection = mTrackBuffersManager->Buffered();
MSE_DEBUGV("intersection=%s", DumpTimeRanges(intersection).get());
if (mBuffered) {
media::TimeIntervals currentValue(mBuffered);
@ -127,7 +130,7 @@ SourceBuffer::GetBuffered(ErrorResult& aRv)
media::TimeIntervals
SourceBuffer::GetTimeIntervals()
{
return mContentManager->Buffered();
return mTrackBuffersManager->Buffered();
}
void
@ -140,11 +143,11 @@ SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv)
return;
}
if (aAppendWindowStart < 0 ||
aAppendWindowStart >= mAttributes->GetAppendWindowEnd()) {
aAppendWindowStart >= mCurrentAttributes.GetAppendWindowEnd()) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return;
}
mAttributes->SetAppendWindowStart(aAppendWindowStart);
mCurrentAttributes.SetAppendWindowStart(aAppendWindowStart);
}
void
@ -157,11 +160,11 @@ SourceBuffer::SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv)
return;
}
if (IsNaN(aAppendWindowEnd) ||
aAppendWindowEnd <= mAttributes->GetAppendWindowStart()) {
aAppendWindowEnd <= mCurrentAttributes.GetAppendWindowStart()) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return;
}
mAttributes->SetAppendWindowEnd(aAppendWindowEnd);
mCurrentAttributes.SetAppendWindowEnd(aAppendWindowEnd);
}
void
@ -196,9 +199,9 @@ SourceBuffer::Abort(ErrorResult& aRv)
return;
}
AbortBufferAppend();
mContentManager->ResetParserState();
mAttributes->SetAppendWindowStart(0);
mAttributes->SetAppendWindowEnd(PositiveInfinity<double>());
ResetParserState();
mCurrentAttributes.SetAppendWindowStart(0);
mCurrentAttributes.SetAppendWindowEnd(PositiveInfinity<double>());
}
void
@ -207,15 +210,18 @@ SourceBuffer::AbortBufferAppend()
if (mUpdating) {
if (mPendingAppend.Exists()) {
mPendingAppend.Disconnect();
mContentManager->AbortAppendData();
// Some data may have been added by the Segment Parser Loop.
// Check if we need to update the duration.
CheckEndTime();
mTrackBuffersManager->AbortAppendData();
}
AbortUpdating();
}
}
void
SourceBuffer::ResetParserState()
{
mTrackBuffersManager->ResetParserState(mCurrentAttributes);
}
void
SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
{
@ -248,8 +254,8 @@ SourceBuffer::RangeRemoval(double aStart, double aEnd)
StartUpdating();
RefPtr<SourceBuffer> self = this;
mContentManager->RangeRemoval(TimeUnit::FromSeconds(aStart),
TimeUnit::FromSeconds(aEnd))
mTrackBuffersManager->RangeRemoval(TimeUnit::FromSeconds(aStart),
TimeUnit::FromSeconds(aEnd))
->Then(AbstractThread::MainThread(), __func__,
[self] (bool) { self->StopUpdating(); },
[]() { MOZ_ASSERT(false); });
@ -265,12 +271,12 @@ SourceBuffer::Detach()
return;
}
AbortBufferAppend();
if (mContentManager) {
mContentManager->Detach();
if (mTrackBuffersManager) {
mTrackBuffersManager->Detach();
mMediaSource->GetDecoder()->GetDemuxer()->DetachSourceBuffer(
static_cast<mozilla::TrackBuffersManager*>(mContentManager.get()));
mTrackBuffersManager.get());
}
mContentManager = nullptr;
mTrackBuffersManager = nullptr;
mMediaSource = nullptr;
}
@ -280,7 +286,7 @@ SourceBuffer::Ended()
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsAttached());
MSE_DEBUG("Ended");
mContentManager->Ended();
mTrackBuffersManager->Ended();
// We want the MediaSourceReader to refresh its buffered range as it may
// have been modified (end lined up).
mMediaSource->GetDecoder()->NotifyDataArrived();
@ -289,34 +295,34 @@ SourceBuffer::Ended()
SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
: DOMEventTargetHelper(aMediaSource->GetParentObject())
, mMediaSource(aMediaSource)
, mCurrentAttributes(aType.LowerCaseEqualsLiteral("audio/mpeg") ||
aType.LowerCaseEqualsLiteral("audio/aac"))
, mUpdating(false)
, mActive(false)
, mType(aType)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aMediaSource);
bool generateTimestamps = false;
if (aType.LowerCaseEqualsLiteral("audio/mpeg") ||
aType.LowerCaseEqualsLiteral("audio/aac")) {
generateTimestamps = true;
}
mAttributes = new SourceBufferAttributes(generateTimestamps);
mContentManager =
SourceBufferContentManager::CreateManager(mAttributes,
aMediaSource->GetDecoder(),
aType);
MSE_DEBUG("Create mContentManager=%p",
mContentManager.get());
mTrackBuffersManager =
new TrackBuffersManager(aMediaSource->GetDecoder(), aType);
// Now that we know what type we're dealing with, enable dormant as needed.
#if defined(MP4_READER_DORMANT_HEURISTIC)
aMediaSource->GetDecoder()->NotifyDormantSupported(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false));
#endif
MSE_DEBUG("Create mTrackBuffersManager=%p",
mTrackBuffersManager.get());
ErrorResult dummy;
if (mAttributes->mGenerateTimestamps) {
if (mCurrentAttributes.mGenerateTimestamps) {
SetMode(SourceBufferAppendMode::Sequence, dummy);
} else {
SetMode(SourceBufferAppendMode::Segments, dummy);
}
mMediaSource->GetDecoder()->GetDemuxer()->AttachSourceBuffer(
static_cast<mozilla::TrackBuffersManager*>(mContentManager.get()));
mTrackBuffersManager.get());
}
SourceBuffer::~SourceBuffer()
@ -391,7 +397,7 @@ SourceBuffer::CheckEndTime()
{
MOZ_ASSERT(NS_IsMainThread());
// Check if we need to update mMediaSource duration
double endTime = mContentManager->GroupEndTimestamp().ToSeconds();
double endTime = mCurrentAttributes.GetGroupEndTimestamp().ToSeconds();
double duration = mMediaSource->Duration();
if (endTime > duration) {
mMediaSource->SetDuration(endTime, MSRangeRemovalAction::SKIP);
@ -407,33 +413,21 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
if (!data) {
return;
}
mContentManager->AppendData(data, mAttributes->GetTimestampOffset());
StartUpdating();
BufferAppend();
}
void
SourceBuffer::BufferAppend()
{
MOZ_ASSERT(mUpdating);
MOZ_ASSERT(mMediaSource);
MOZ_ASSERT(!mPendingAppend.Exists());
mPendingAppend.Begin(mContentManager->BufferAppend()
mPendingAppend.Begin(mTrackBuffersManager->AppendData(data, mCurrentAttributes)
->Then(AbstractThread::MainThread(), __func__, this,
&SourceBuffer::AppendDataCompletedWithSuccess,
&SourceBuffer::AppendDataErrored));
}
void
SourceBuffer::AppendDataCompletedWithSuccess(bool aHasActiveTracks)
SourceBuffer::AppendDataCompletedWithSuccess(SourceBufferTask::AppendBufferResult aResult)
{
MOZ_ASSERT(mUpdating);
mPendingAppend.Complete();
if (aHasActiveTracks) {
if (aResult.first()) {
if (!mActive) {
mActive = true;
mMediaSource->SourceBufferIsActive(this);
@ -446,6 +440,8 @@ SourceBuffer::AppendDataCompletedWithSuccess(bool aHasActiveTracks)
mMediaSource->GetDecoder()->NotifyBytesDownloaded();
}
mCurrentAttributes = aResult.second();
CheckEndTime();
StopUpdating();
@ -473,7 +469,7 @@ SourceBuffer::AppendError(bool aDecoderError)
{
MOZ_ASSERT(NS_IsMainThread());
mContentManager->ResetParserState();
ResetParserState();
mUpdating = false;
@ -491,7 +487,7 @@ SourceBuffer::AppendError(bool aDecoderError)
already_AddRefed<MediaByteBuffer>
SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
{
typedef SourceBufferContentManager::EvictDataResult Result;
typedef TrackBuffersManager::EvictDataResult Result;
if (!IsAttached() || mUpdating) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
@ -512,33 +508,16 @@ SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult&
}
// Eviction uses a byte threshold. If the buffer is greater than the
// number of bytes then data is evicted. The time range for this
// eviction is reported back to the media source. It will then
// evict data before that range across all SourceBuffers it knows
// about.
// TODO: Make the eviction threshold smaller for audio-only streams.
// number of bytes then data is evicted.
// TODO: Drive evictions off memory pressure notifications.
// TODO: Consider a global eviction threshold rather than per TrackBuffer.
TimeUnit newBufferStartTime;
// Attempt to evict the amount of data we are about to add by lowering the
// threshold.
// Give a chance to the TrackBuffersManager to evict some data if needed.
Result evicted =
mContentManager->EvictData(TimeUnit::FromSeconds(mMediaSource->GetDecoder()->GetCurrentTime()),
aLength, &newBufferStartTime);
if (evicted == Result::DATA_EVICTED) {
MSE_DEBUG("AppendData Evict; current buffered start=%f",
GetBufferedStart());
// We notify that we've evicted from the time range 0 through to
// the current start point.
mMediaSource->NotifyEvicted(0.0, newBufferStartTime.ToSeconds());
}
mTrackBuffersManager->EvictData(TimeUnit::FromSeconds(mMediaSource->GetDecoder()->GetCurrentTime()),
aLength);
// See if we have enough free space to append our new data.
// As we can only evict once we have playable data, we must give a chance
// to the DASH player to provide a complete media segment.
if (aLength > mContentManager->EvictionThreshold() ||
evicted == Result::BUFFER_FULL) {
if (evicted == Result::BUFFER_FULL) {
aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
return nullptr;
}
@ -569,25 +548,11 @@ SourceBuffer::GetBufferedEnd()
return ranges->Length() > 0 ? ranges->GetEndTime() : 0;
}
void
SourceBuffer::Evict(double aStart, double aEnd)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("Evict(aStart=%f, aEnd=%f)", aStart, aEnd);
double currentTime = mMediaSource->GetDecoder()->GetCurrentTime();
double evictTime = aEnd;
const double safety_threshold = 5;
if (currentTime + safety_threshold >= evictTime) {
evictTime -= safety_threshold;
}
mContentManager->EvictBefore(TimeUnit::FromSeconds(evictTime));
}
NS_IMPL_CYCLE_COLLECTION_CLASS(SourceBuffer)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SourceBuffer)
// Tell the TrackBuffer to end its current SourceBufferResource.
SourceBufferContentManager* manager = tmp->mContentManager;
TrackBuffersManager* manager = tmp->mTrackBuffersManager;
if (manager) {
manager->Detach();
}

View File

@ -14,6 +14,7 @@
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/SourceBufferBinding.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/mozalloc.h"
@ -24,8 +25,8 @@
#include "nsISupports.h"
#include "nsString.h"
#include "nscore.h"
#include "SourceBufferContentManager.h"
#include "mozilla/Monitor.h"
#include "TrackBuffersManager.h"
#include "SourceBufferTask.h"
class JSObject;
struct JSContext;
@ -35,109 +36,18 @@ namespace mozilla {
class ErrorResult;
class MediaByteBuffer;
template <typename T> class AsyncEventRunner;
class TrackBuffersManager;
namespace dom {
class TimeRanges;
class SourceBufferAttributes {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SourceBufferAttributes);
explicit SourceBufferAttributes(bool aGenerateTimestamp)
: mGenerateTimestamps(aGenerateTimestamp)
, mMonitor("SourceBufferAttributes")
, mAppendWindowStart(0)
, mAppendWindowEnd(PositiveInfinity<double>())
, mAppendMode(SourceBufferAppendMode::Segments)
, mApparentTimestampOffset(0)
{}
double GetAppendWindowStart()
{
MonitorAutoLock mon(mMonitor);
return mAppendWindowStart;
}
double GetAppendWindowEnd()
{
MonitorAutoLock mon(mMonitor);
return mAppendWindowEnd;
}
void SetAppendWindowStart(double aWindowStart)
{
MonitorAutoLock mon(mMonitor);
mAppendWindowStart = aWindowStart;
}
void SetAppendWindowEnd(double aWindowEnd)
{
MonitorAutoLock mon(mMonitor);
mAppendWindowEnd = aWindowEnd;
}
double GetApparentTimestampOffset()
{
MonitorAutoLock mon(mMonitor);
return mApparentTimestampOffset;
}
void SetApparentTimestampOffset(double aTimestampOffset)
{
MonitorAutoLock mon(mMonitor);
mApparentTimestampOffset = aTimestampOffset;
mTimestampOffset = media::TimeUnit::FromSeconds(aTimestampOffset);
}
media::TimeUnit GetTimestampOffset()
{
MonitorAutoLock mon(mMonitor);
return mTimestampOffset;
}
void SetTimestampOffset(media::TimeUnit& aTimestampOffset)
{
MonitorAutoLock mon(mMonitor);
mTimestampOffset = aTimestampOffset;
mApparentTimestampOffset = aTimestampOffset.ToSeconds();
}
SourceBufferAppendMode GetAppendMode()
{
MonitorAutoLock mon(mMonitor);
return mAppendMode;
}
void SetAppendMode(SourceBufferAppendMode aAppendMode)
{
MonitorAutoLock mon(mMonitor);
mAppendMode = aAppendMode;
}
// mGenerateTimestamp isn't mutable once the source buffer has been constructed
// We don't need a monitor to protect it across threads.
const bool mGenerateTimestamps;
private:
~SourceBufferAttributes() {};
// Monitor protecting all members below.
Monitor mMonitor;
double mAppendWindowStart;
double mAppendWindowEnd;
SourceBufferAppendMode mAppendMode;
double mApparentTimestampOffset;
media::TimeUnit mTimestampOffset;
};
class SourceBuffer final : public DOMEventTargetHelper
{
public:
/** WebIDL Methods. */
SourceBufferAppendMode Mode() const
{
return mAttributes->GetAppendMode();
return mCurrentAttributes.GetAppendMode();
}
void SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv);
@ -152,21 +62,21 @@ public:
double TimestampOffset() const
{
return mAttributes->GetApparentTimestampOffset();
return mCurrentAttributes.GetApparentTimestampOffset();
}
void SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv);
double AppendWindowStart() const
{
return mAttributes->GetAppendWindowStart();
return mCurrentAttributes.GetAppendWindowStart();
}
void SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv);
double AppendWindowEnd() const
{
return mAttributes->GetAppendWindowEnd();
return mCurrentAttributes.GetAppendWindowEnd();
}
void SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv);
@ -199,9 +109,6 @@ public:
void Ended();
// Evict data in the source buffer in the given time range.
void Evict(double aStart, double aEnd);
double GetBufferedStart();
double GetBufferedEnd();
@ -226,6 +133,7 @@ private:
void StartUpdating();
void StopUpdating();
void AbortUpdating();
void ResetParserState();
// If the media segment contains data beyond the current duration,
// then run the duration change algorithm with new duration set to the
@ -234,7 +142,6 @@ private:
// Shared implementation of AppendBuffer overloads.
void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
void BufferAppend();
// Implement the "Append Error Algorithm".
// Will call endOfStream() with "decode" error if aDecodeError is true.
@ -248,19 +155,19 @@ private:
uint32_t aLength,
ErrorResult& aRv);
void AppendDataCompletedWithSuccess(bool aHasActiveTracks);
void AppendDataCompletedWithSuccess(SourceBufferTask::AppendBufferResult aResult);
void AppendDataErrored(nsresult aError);
RefPtr<MediaSource> mMediaSource;
RefPtr<SourceBufferContentManager> mContentManager;
RefPtr<SourceBufferAttributes> mAttributes;
RefPtr<TrackBuffersManager> mTrackBuffersManager;
SourceBufferAttributes mCurrentAttributes;
bool mUpdating;
mozilla::Atomic<bool> mActive;
MozPromiseRequestHolder<SourceBufferContentManager::AppendPromise> mPendingAppend;
MozPromiseRequestHolder<SourceBufferTask::AppendPromise> mPendingAppend;
const nsCString mType;
RefPtr<TimeRanges> mBuffered;

View File

@ -0,0 +1,157 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_SourceBufferAttributes_h_
#define mozilla_SourceBufferAttributes_h_
#include "TimeUnits.h"
#include "mozilla/dom/SourceBufferBinding.h"
#include "mozilla/Maybe.h"
namespace mozilla {
class SourceBufferAttributes {
public:
// Current state as per Segment Parser Loop Algorithm
// http://w3c.github.io/media-source/index.html#sourcebuffer-segment-parser-loop
enum class AppendState
{
WAITING_FOR_SEGMENT,
PARSING_INIT_SEGMENT,
PARSING_MEDIA_SEGMENT,
};
explicit SourceBufferAttributes(bool aGenerateTimestamp)
: mGenerateTimestamps(aGenerateTimestamp)
, mAppendWindowStart(0)
, mAppendWindowEnd(PositiveInfinity<double>())
, mAppendMode(dom::SourceBufferAppendMode::Segments)
, mApparentTimestampOffset(0)
, mAppendState(AppendState::WAITING_FOR_SEGMENT)
{}
SourceBufferAttributes(const SourceBufferAttributes& aOther) = default;
double GetAppendWindowStart() const
{
return mAppendWindowStart;
}
double GetAppendWindowEnd() const
{
return mAppendWindowEnd;
}
void SetAppendWindowStart(double aWindowStart)
{
mAppendWindowStart = aWindowStart;
}
void SetAppendWindowEnd(double aWindowEnd)
{
mAppendWindowEnd = aWindowEnd;
}
double GetApparentTimestampOffset() const
{
return mApparentTimestampOffset;
}
void SetApparentTimestampOffset(double aTimestampOffset)
{
mApparentTimestampOffset = aTimestampOffset;
mTimestampOffset = media::TimeUnit::FromSeconds(aTimestampOffset);
}
media::TimeUnit GetTimestampOffset() const
{
return mTimestampOffset;
}
void SetTimestampOffset(const media::TimeUnit& aTimestampOffset)
{
mTimestampOffset = aTimestampOffset;
mApparentTimestampOffset = aTimestampOffset.ToSeconds();
}
dom::SourceBufferAppendMode GetAppendMode() const
{
return mAppendMode;
}
void SetAppendMode(dom::SourceBufferAppendMode aAppendMode)
{
mAppendMode = aAppendMode;
}
void SetGroupStartTimestamp(const media::TimeUnit& aGroupStartTimestamp)
{
mGroupStartTimestamp = Some(aGroupStartTimestamp);
}
media::TimeUnit GetGroupStartTimestamp() const
{
return mGroupStartTimestamp.ref();
}
bool HaveGroupStartTimestamp() const
{
return mGroupStartTimestamp.isSome();
}
void ResetGroupStartTimestamp()
{
mGroupStartTimestamp.reset();
}
void RestartGroupStartTimestamp()
{
mGroupStartTimestamp = Some(mGroupEndTimestamp);
}
media::TimeUnit GetGroupEndTimestamp() const
{
return mGroupEndTimestamp;
}
void SetGroupEndTimestamp(const media::TimeUnit& aGroupEndTimestamp)
{
mGroupEndTimestamp = aGroupEndTimestamp;
}
AppendState GetAppendState() const
{
return mAppendState;
}
void SetAppendState(AppendState aState)
{
mAppendState = aState;
}
// mGenerateTimestamp isn't mutable once the source buffer has been constructed
bool mGenerateTimestamps;
SourceBufferAttributes& operator=(const SourceBufferAttributes& aOther) = default;
private:
SourceBufferAttributes() = delete;
double mAppendWindowStart;
double mAppendWindowEnd;
dom::SourceBufferAppendMode mAppendMode;
double mApparentTimestampOffset;
media::TimeUnit mTimestampOffset;
Maybe<media::TimeUnit> mGroupStartTimestamp;
media::TimeUnit mGroupEndTimestamp;
// The current append state as per https://w3c.github.io/media-source/#sourcebuffer-append-state
AppendState mAppendState;
};
} // end namespace mozilla
#endif /* mozilla_SourceBufferAttributes_h_ */

View File

@ -1,35 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SourceBufferContentManager.h"
#include "mozilla/Preferences.h"
#include "TrackBuffersManager.h"
namespace mozilla {
#if defined(MOZ_GONK_MEDIACODEC) || defined(XP_WIN) || defined(MOZ_APPLEMEDIA) || defined(MOZ_FFMPEG)
#define MP4_READER_DORMANT_HEURISTIC
#else
#undef MP4_READER_DORMANT_HEURISTIC
#endif
already_AddRefed<SourceBufferContentManager>
SourceBufferContentManager::CreateManager(dom::SourceBufferAttributes* aAttributes,
MediaSourceDecoder* aParentDecoder,
const nsACString &aType)
{
RefPtr<SourceBufferContentManager> manager;
manager = new TrackBuffersManager(aAttributes, aParentDecoder, aType);
// Now that we know what type we're dealing with, enable dormant as needed.
#if defined(MP4_READER_DORMANT_HEURISTIC)
aParentDecoder->NotifyDormantSupported(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false));
#endif
return manager.forget();
}
} // namespace mozilla

View File

@ -1,118 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef MOZILLA_SOURCEBUFFERCONTENTMANAGER_H_
#define MOZILLA_SOURCEBUFFERCONTENTMANAGER_H_
#include "mozilla/MozPromise.h"
#include "MediaData.h"
#include "MediaSourceDecoder.h"
#include "TimeUnits.h"
#include "nsString.h"
namespace mozilla {
namespace dom {
class SourceBuffer;
class SourceBufferAttributes;
}
class SourceBufferContentManager {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SourceBufferContentManager);
typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> AppendPromise;
typedef AppendPromise RangeRemovalPromise;
static already_AddRefed<SourceBufferContentManager>
CreateManager(dom::SourceBufferAttributes* aAttributes,
MediaSourceDecoder* aParentDecoder,
const nsACString& aType);
// Add data to the end of the input buffer.
// Returns false if the append failed.
virtual bool
AppendData(MediaByteBuffer* aData, media::TimeUnit aTimestampOffset) = 0;
// Run MSE Buffer Append Algorithm
// 3.5.5 Buffer Append Algorithm.
// http://w3c.github.io/media-source/index.html#sourcebuffer-buffer-append
virtual RefPtr<AppendPromise> BufferAppend() = 0;
// Abort any pending AppendData.
virtual void AbortAppendData() = 0;
// Run MSE Reset Parser State Algorithm.
// 3.5.2 Reset Parser State
// http://w3c.github.io/media-source/#sourcebuffer-reset-parser-state
virtual void ResetParserState() = 0;
// Runs MSE range removal algorithm.
// http://w3c.github.io/media-source/#sourcebuffer-coded-frame-removal
virtual RefPtr<RangeRemovalPromise> RangeRemoval(media::TimeUnit aStart,
media::TimeUnit aEnd) = 0;
enum class EvictDataResult : int8_t
{
NO_DATA_EVICTED,
DATA_EVICTED,
CANT_EVICT,
BUFFER_FULL,
};
// Evicts data up to aPlaybackTime. aThreshold is used to
// bound the data being evicted. It will not evict more than aThreshold
// bytes. aBufferStartTime contains the new start time of the data after the
// eviction.
virtual EvictDataResult
EvictData(media::TimeUnit aPlaybackTime,
int64_t aThreshold,
media::TimeUnit* aBufferStartTime) = 0;
// Evicts data up to aTime.
virtual void EvictBefore(media::TimeUnit aTime) = 0;
// Returns the buffered range currently managed.
// This may be called on any thread.
// Buffered must conform to http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
virtual media::TimeIntervals Buffered() = 0;
// Return the size of the data managed by this SourceBufferContentManager.
virtual int64_t GetSize() = 0;
// Indicate that the MediaSource parent object got into "ended" state.
virtual void Ended() = 0;
// The parent SourceBuffer is about to be destroyed.
virtual void Detach() = 0;
// Current state as per Segment Parser Loop Algorithm
// http://w3c.github.io/media-source/index.html#sourcebuffer-segment-parser-loop
enum class AppendState : int32_t
{
WAITING_FOR_SEGMENT,
PARSING_INIT_SEGMENT,
PARSING_MEDIA_SEGMENT,
};
virtual AppendState GetAppendState()
{
return AppendState::WAITING_FOR_SEGMENT;
}
virtual void SetGroupStartTimestamp(const media::TimeUnit& aGroupStartTimestamp) {}
virtual void RestartGroupStartTimestamp() {}
virtual media::TimeUnit GroupEndTimestamp() = 0;
virtual int64_t EvictionThreshold() const = 0;
protected:
virtual ~SourceBufferContentManager() { }
};
} // namespace mozilla
#endif /* MOZILLA_SOURCEBUFFERCONTENTMANAGER_H_ */

View File

@ -130,16 +130,6 @@ SourceBufferList::RangeRemoval(double aStart, double aEnd)
}
}
void
SourceBufferList::Evict(double aStart, double aEnd)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("Evict(aStart=%f, aEnd=%f)", aStart, aEnd);
for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
mSourceBuffers[i]->Evict(aStart, aEnd);
}
}
void
SourceBufferList::Ended()
{

View File

@ -71,9 +71,6 @@ public:
// Mark all SourceBuffers input buffers as ended.
void Ended();
// Evicts data for the given time range from each SourceBuffer in the list.
void Evict(double aStart, double aEnd);
// Returns the highest end time of any of the Sourcebuffers.
double GetHighestBufferedEndTime();

View File

@ -0,0 +1,103 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef MOZILLA_SOURCEBUFFERTASK_H_
#define MOZILLA_SOURCEBUFFERTASK_H_
#include "mozilla/MozPromise.h"
#include "mozilla/Pair.h"
#include "mozilla/RefPtr.h"
#include "SourceBufferAttributes.h"
#include "TimeUnits.h"
namespace mozilla {
class SourceBufferTask {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SourceBufferTask);
enum class Type {
AppendBuffer,
Abort,
Reset,
RangeRemoval,
EvictData
};
typedef Pair<bool, SourceBufferAttributes> AppendBufferResult;
typedef MozPromise<AppendBufferResult, nsresult, /* IsExclusive = */ true> AppendPromise;
typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> RangeRemovalPromise;
virtual Type GetType() const = 0;
template<typename ReturnType>
ReturnType* As()
{
MOZ_ASSERT(this->GetType() == ReturnType::sType);
return static_cast<ReturnType*>(this);
}
protected:
virtual ~SourceBufferTask() {}
};
class AppendBufferTask : public SourceBufferTask {
public:
AppendBufferTask(MediaByteBuffer* aData,
SourceBufferAttributes aAttributes)
: mBuffer(aData)
, mAttributes(aAttributes)
{}
static const Type sType = Type::AppendBuffer;
Type GetType() const override { return Type::AppendBuffer; }
RefPtr<MediaByteBuffer> mBuffer;
SourceBufferAttributes mAttributes;
MozPromiseHolder<AppendPromise> mPromise;
};
class AbortTask : public SourceBufferTask {
public:
static const Type sType = Type::Abort;
Type GetType() const override { return Type::Abort; }
};
class ResetTask : public SourceBufferTask {
public:
static const Type sType = Type::Reset;
Type GetType() const override { return Type::Reset; }
};
class RangeRemovalTask : public SourceBufferTask {
public:
explicit RangeRemovalTask(const media::TimeInterval& aRange)
: mRange(aRange)
{}
static const Type sType = Type::RangeRemoval;
Type GetType() const override { return Type::RangeRemoval; }
media::TimeInterval mRange;
MozPromiseHolder<RangeRemovalPromise> mPromise;
};
class EvictDataTask : public SourceBufferTask {
public:
EvictDataTask(const media::TimeUnit& aPlaybackTime, int64_t aSizetoEvict)
: mPlaybackTime(aPlaybackTime)
, mSizeToEvict(aSizetoEvict)
{}
static const Type sType = Type::EvictData;
Type GetType() const override { return Type::EvictData; }
media::TimeUnit mPlaybackTime;
int64_t mSizeToEvict;
};
} // end mozilla namespace
#endif

View File

@ -13,6 +13,7 @@
#include "SourceBufferResource.h"
#include "SourceBuffer.h"
#include "WebMDemuxer.h"
#include "SourceBufferTask.h"
#ifdef MOZ_FMP4
#include "MP4Demuxer.h"
@ -38,16 +39,17 @@ using dom::SourceBufferAppendMode;
using media::TimeUnit;
using media::TimeInterval;
using media::TimeIntervals;
typedef SourceBufferTask::AppendBufferResult AppendBufferResult;
static const char*
AppendStateToStr(TrackBuffersManager::AppendState aState)
AppendStateToStr(SourceBufferAttributes::AppendState aState)
{
switch (aState) {
case TrackBuffersManager::AppendState::WAITING_FOR_SEGMENT:
case SourceBufferAttributes::AppendState::WAITING_FOR_SEGMENT:
return "WAITING_FOR_SEGMENT";
case TrackBuffersManager::AppendState::PARSING_INIT_SEGMENT:
case SourceBufferAttributes::AppendState::PARSING_INIT_SEGMENT:
return "PARSING_INIT_SEGMENT";
case TrackBuffersManager::AppendState::PARSING_MEDIA_SEGMENT:
case SourceBufferAttributes::AppendState::PARSING_MEDIA_SEGMENT:
return "PARSING_MEDIA_SEGMENT";
default:
return "IMPOSSIBLE";
@ -84,11 +86,9 @@ private:
};
#endif // MOZ_EME
TrackBuffersManager::TrackBuffersManager(dom::SourceBufferAttributes* aAttributes,
MediaSourceDecoder* aParentDecoder,
TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
const nsACString& aType)
: mInputBuffer(new MediaByteBuffer)
, mAppendState(AppendState::WAITING_FOR_SEGMENT)
, mBufferFull(false)
, mFirstInitializationSegmentReceived(false)
, mNewMediaSegmentStarted(false)
@ -97,98 +97,198 @@ TrackBuffersManager::TrackBuffersManager(dom::SourceBufferAttributes* aAttribute
, mParser(ContainerParser::CreateForMIMEType(aType))
, mProcessedInput(0)
, mTaskQueue(aParentDecoder->GetDemuxer()->GetTaskQueue())
, mSourceBufferAttributes(aAttributes)
, mParentDecoder(new nsMainThreadPtrHolder<MediaSourceDecoder>(aParentDecoder, false /* strict */))
, mEnded(false)
, mDetached(false)
, mVideoEvictionThreshold(Preferences::GetUint("media.mediasource.eviction_threshold.video",
100 * 1024 * 1024))
, mAudioEvictionThreshold(Preferences::GetUint("media.mediasource.eviction_threshold.audio",
15 * 1024 * 1024))
30 * 1024 * 1024))
, mEvictionOccurred(false)
, mMonitor("TrackBuffersManager")
, mAppendRunning(false)
{
MOZ_ASSERT(NS_IsMainThread(), "Must be instanciated on the main thread");
}
TrackBuffersManager::~TrackBuffersManager()
{
CancelAllTasks();
ShutdownDemuxers();
}
bool
RefPtr<TrackBuffersManager::AppendPromise>
TrackBuffersManager::AppendData(MediaByteBuffer* aData,
TimeUnit aTimestampOffset)
const SourceBufferAttributes& aAttributes)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("Appending %lld bytes", aData->Length());
mEnded = false;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArg<IncomingBuffer>(
this, &TrackBuffersManager::AppendIncomingBuffer,
IncomingBuffer(aData, aTimestampOffset));
GetTaskQueue()->Dispatch(task.forget());
return true;
}
void
TrackBuffersManager::AppendIncomingBuffer(IncomingBuffer aData)
{
MOZ_ASSERT(OnTaskQueue());
mIncomingBuffers.AppendElement(aData);
RefPtr<MediaByteBuffer> buffer = aData;
return InvokeAsync(GetTaskQueue(), this,
__func__, &TrackBuffersManager::DoAppendData,
buffer, aAttributes);
}
RefPtr<TrackBuffersManager::AppendPromise>
TrackBuffersManager::BufferAppend()
TrackBuffersManager::DoAppendData(RefPtr<MediaByteBuffer> aData,
SourceBufferAttributes aAttributes)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("");
RefPtr<AppendBufferTask> task = new AppendBufferTask(aData, aAttributes);
RefPtr<AppendPromise> p = task->mPromise.Ensure(__func__);
mQueue.Push(task);
mAppendRunning = true;
return InvokeAsync(GetTaskQueue(), this,
__func__, &TrackBuffersManager::InitSegmentParserLoop);
ProcessTasks();
return p;
}
void
TrackBuffersManager::ProcessTasks()
{
typedef SourceBufferTask::Type Type;
if (mDetached) {
return;
}
if (OnTaskQueue()) {
if (mCurrentTask) {
// Already have a task pending. ProcessTask will be scheduled once the
// current task complete.
return;
}
RefPtr<SourceBufferTask> task = mQueue.Pop();
if (!task) {
// nothing to do.
return;
}
switch (task->GetType()) {
case Type::AppendBuffer:
mCurrentTask = task;
if (!mInputBuffer) {
mInputBuffer = task->As<AppendBufferTask>()->mBuffer;
} else if (!mInputBuffer->AppendElements(*task->As<AppendBufferTask>()->mBuffer, fallible)) {
RejectAppend(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
mSourceBufferAttributes =
MakeUnique<SourceBufferAttributes>(task->As<AppendBufferTask>()->mAttributes);
mAppendWindow =
TimeInterval(TimeUnit::FromSeconds(mSourceBufferAttributes->GetAppendWindowStart()),
TimeUnit::FromSeconds(mSourceBufferAttributes->GetAppendWindowEnd()));
ScheduleSegmentParserLoop();
break;
case Type::RangeRemoval:
{
bool rv = CodedFrameRemoval(task->As<RangeRemovalTask>()->mRange);
task->As<RangeRemovalTask>()->mPromise.Resolve(rv, __func__);
break;
}
case Type::EvictData:
DoEvictData(task->As<EvictDataTask>()->mPlaybackTime,
task->As<EvictDataTask>()->mSizeToEvict);
break;
case Type::Abort:
// not handled yet, and probably never.
break;
case Type::Reset:
CompleteResetParserState();
break;
default:
NS_WARNING("Invalid Task");
}
}
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethod(this, &TrackBuffersManager::ProcessTasks);
GetTaskQueue()->Dispatch(task.forget());
}
// A PromiseHolder will assert upon destruction if it has a pending promise
// that hasn't been completed. It is possible that a task didn't get processed
// due to the owning SourceBuffer having shutdown.
// We resolve/reject all pending promises and remove all pending tasks from the
// queue.
void
TrackBuffersManager::CancelAllTasks()
{
typedef SourceBufferTask::Type Type;
MOZ_DIAGNOSTIC_ASSERT(mDetached);
if (mCurrentTask) {
mQueue.Push(mCurrentTask);
mCurrentTask = nullptr;
}
RefPtr<SourceBufferTask> task;
while ((task = mQueue.Pop())) {
switch (task->GetType()) {
case Type::AppendBuffer:
task->As<AppendBufferTask>()->mPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
break;
case Type::RangeRemoval:
task->As<RangeRemovalTask>()->mPromise.ResolveIfExists(false, __func__);
break;
case Type::EvictData:
break;
case Type::Abort:
// not handled yet, and probably never.
break;
case Type::Reset:
break;
default:
NS_WARNING("Invalid Task");
}
}
}
// The MSE spec requires that we abort the current SegmentParserLoop
// which is then followed by a call to ResetParserState.
// However due to our asynchronous design this causes inherent difficulities.
// As the spec behaviour is non deterministic anyway, we instead wait until the
// current AppendData has completed its run.
// However due to our asynchronous design this causes inherent difficulties.
// As the spec behaviour is non deterministic anyway, we instead process all
// pending frames found in the input buffer.
void
TrackBuffersManager::AbortAppendData()
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("");
MonitorAutoLock mon(mMonitor);
while (mAppendRunning) {
mon.Wait();
}
RefPtr<AbortTask> task = new AbortTask();
mQueue.Push(task);
ProcessTasks();
}
void
TrackBuffersManager::ResetParserState()
TrackBuffersManager::ResetParserState(SourceBufferAttributes& aAttributes)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(!mAppendRunning, "Append is running, abort must have been called");
MSE_DEBUG("");
// Spec states:
// 1. If the append state equals PARSING_MEDIA_SEGMENT and the input buffer contains some complete coded frames, then run the coded frame processing algorithm until all of these complete coded frames have been processed.
// SourceBuffer.abort() has ensured that all complete coded frames have been
// processed. As such, we don't need to check for the value of mAppendState.
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethod(this, &TrackBuffersManager::CompleteResetParserState);
GetTaskQueue()->Dispatch(task.forget());
// However, we will wait until all coded frames have been processed regardless
// of the value of append state.
RefPtr<ResetTask> task = new ResetTask();
mQueue.Push(task);
ProcessTasks();
// 7. Set append state to WAITING_FOR_SEGMENT.
SetAppendState(AppendState::WAITING_FOR_SEGMENT);
// ResetParserState has some synchronous steps that much be performed now.
// The remaining steps will be performed once the ResetTask gets executed.
// 6. If the mode attribute equals "sequence", then set the group start timestamp to the group end timestamp
if (aAttributes.GetAppendMode() == SourceBufferAppendMode::Sequence) {
aAttributes.SetGroupStartTimestamp(aAttributes.GetGroupEndTimestamp());
}
// 8. Set append state to WAITING_FOR_SEGMENT.
aAttributes.SetAppendState(AppendState::WAITING_FOR_SEGMENT);
}
RefPtr<TrackBuffersManager::RangeRemovalPromise>
TrackBuffersManager::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(!mAppendRunning, "Append is running");
MSE_DEBUG("From %.2f to %.2f", aStart.ToSeconds(), aEnd.ToSeconds());
mEnded = false;
@ -199,14 +299,15 @@ TrackBuffersManager::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
}
TrackBuffersManager::EvictDataResult
TrackBuffersManager::EvictData(TimeUnit aPlaybackTime,
int64_t aThresholdReduct,
TimeUnit* aBufferStartTime)
TrackBuffersManager::EvictData(const TimeUnit& aPlaybackTime, int64_t aSize)
{
MOZ_ASSERT(NS_IsMainThread());
const int64_t toEvict = GetSize() -
std::max(EvictionThreshold() - aThresholdReduct, aThresholdReduct);
if (aSize > EvictionThreshold()) {
// We're adding more data than we can hold.
return EvictDataResult::BUFFER_FULL;
}
const int64_t toEvict = GetSize() + aSize - EvictionThreshold();
MSE_DEBUG("buffered=%lldkb, eviction threshold=%ukb, evict=%lldkb",
GetSize() / 1024, EvictionThreshold() / 1024, toEvict / 1024);
@ -225,28 +326,13 @@ TrackBuffersManager::EvictData(TimeUnit aPlaybackTime,
MSE_DEBUG("Reaching our size limit, schedule eviction of %lld bytes", toEvict);
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<TimeUnit, uint32_t>(
this, &TrackBuffersManager::DoEvictData,
aPlaybackTime, toEvict);
GetTaskQueue()->Dispatch(task.forget());
RefPtr<EvictDataTask> task = new EvictDataTask(aPlaybackTime, toEvict);
mQueue.Push(task);
ProcessTasks();
return EvictDataResult::NO_DATA_EVICTED;
}
void
TrackBuffersManager::EvictBefore(TimeUnit aTime)
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("");
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArg<TimeInterval>(
this, &TrackBuffersManager::CodedFrameRemoval,
TimeInterval(TimeUnit::FromSeconds(0), aTime));
GetTaskQueue()->Dispatch(task.forget());
}
TimeIntervals
TrackBuffersManager::Buffered()
{
@ -284,7 +370,7 @@ TrackBuffersManager::Buffered()
}
int64_t
TrackBuffersManager::GetSize()
TrackBuffersManager::GetSize() const
{
return mSizeSourceBuffer;
}
@ -300,6 +386,7 @@ TrackBuffersManager::Detach()
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("");
mDetached = true;
}
void
@ -310,10 +397,7 @@ TrackBuffersManager::CompleteResetParserState()
// We shouldn't change mInputDemuxer while a demuxer init/reset request is
// being processed. See bug 1239983.
NS_ASSERTION(!mDemuxerInitRequest.Exists(), "Previous AppendBuffer didn't complete");
if (mDemuxerInitRequest.Exists()) {
mDemuxerInitRequest.Disconnect();
}
MOZ_DIAGNOSTIC_ASSERT(!mDemuxerInitRequest.Exists(), "Previous AppendBuffer didn't complete");
for (auto& track : GetTracksList()) {
// 2. Unset the last decode timestamp on all track buffers.
@ -327,13 +411,7 @@ TrackBuffersManager::CompleteResetParserState()
track->mQueuedSamples.Clear();
}
// 6. If the mode attribute equals "sequence", then set the group start timestamp to the group end timestamp
if (mSourceBufferAttributes->GetAppendMode() == SourceBufferAppendMode::Sequence) {
mGroupStartTimestamp = Some(mGroupEndTimestamp);
}
// 7. Remove all bytes from the input buffer.
mIncomingBuffers.Clear();
mInputBuffer = nullptr;
if (mCurrentInputBuffer) {
mCurrentInputBuffer->EvictAll();
@ -356,12 +434,6 @@ TrackBuffersManager::CompleteResetParserState()
mInputBuffer->AppendElements(*mInitData);
}
RecreateParser(true);
// 8. Set append state to WAITING_FOR_SEGMENT.
SetAppendState(AppendState::WAITING_FOR_SEGMENT);
// Reject our promise immediately.
mAppendPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
}
int64_t
@ -404,6 +476,8 @@ TrackBuffersManager::DoEvictData(const TimeUnit& aPlaybackTime,
partialEvict += frame->ComputedSizeOfIncludingThis();
}
const int64_t finalSize = mSizeSourceBuffer - aSizeToEvict;
if (lastKeyFrameIndex > 0) {
MSE_DEBUG("Step1. Evicting %lld bytes prior currentTime",
aSizeToEvict - toEvict);
@ -412,8 +486,7 @@ TrackBuffersManager::DoEvictData(const TimeUnit& aPlaybackTime,
TimeUnit::FromMicroseconds(buffer[lastKeyFrameIndex]->mTime - 1)));
}
const int64_t finalSize = mSizeSourceBuffer - aSizeToEvict;
if (mSizeSourceBuffer <= finalSize || !buffer.Length()) {
if (mSizeSourceBuffer <= finalSize) {
return;
}
@ -448,8 +521,12 @@ RefPtr<TrackBuffersManager::RangeRemovalPromise>
TrackBuffersManager::CodedFrameRemovalWithPromise(TimeInterval aInterval)
{
MOZ_ASSERT(OnTaskQueue());
bool rv = CodedFrameRemoval(aInterval);
return RangeRemovalPromise::CreateAndResolve(rv, __func__);
RefPtr<RangeRemovalTask> task = new RangeRemovalTask(aInterval);
RefPtr<RangeRemovalPromise> p = task->mPromise.Ensure(__func__);
mQueue.Push(task);
ProcessTasks();
return p;
}
bool
@ -549,44 +626,6 @@ TrackBuffersManager::UpdateBufferedRanges()
DumpTimeRanges(mAudioTracks.mBufferedRanges).get());
}
#endif
mOfficialGroupEndTimestamp = mGroupEndTimestamp;
}
RefPtr<TrackBuffersManager::AppendPromise>
TrackBuffersManager::InitSegmentParserLoop()
{
MOZ_ASSERT(OnTaskQueue());
MOZ_DIAGNOSTIC_ASSERT(mAppendPromise.IsEmpty());
MSE_DEBUG("");
RefPtr<AppendPromise> p = mAppendPromise.Ensure(__func__);
AppendIncomingBuffers();
SegmentParserLoop();
return p;
}
void
TrackBuffersManager::AppendIncomingBuffers()
{
MOZ_ASSERT(OnTaskQueue());
MonitorAutoLock mon(mMonitor);
for (auto& incomingBuffer : mIncomingBuffers) {
if (!mInputBuffer) {
mInputBuffer = incomingBuffer.first();
} else if (!mInputBuffer->AppendElements(*incomingBuffer.first(), fallible)) {
RejectAppend(NS_ERROR_OUT_OF_MEMORY, __func__);
}
mTimestampOffset = incomingBuffer.second();
mLastTimestampOffset = mTimestampOffset;
}
mIncomingBuffers.Clear();
mAppendWindow =
TimeInterval(TimeUnit::FromSeconds(mSourceBufferAttributes->GetAppendWindowStart()),
TimeUnit::FromSeconds(mSourceBufferAttributes->GetAppendWindowEnd()));
}
void
@ -613,7 +652,7 @@ TrackBuffersManager::SegmentParserLoop()
// 4. If the append state equals WAITING_FOR_SEGMENT, then run the following
// steps:
if (mAppendState == AppendState::WAITING_FOR_SEGMENT) {
if (mSourceBufferAttributes->GetAppendState() == AppendState::WAITING_FOR_SEGMENT) {
if (mParser->IsInitSegmentPresent(mInputBuffer)) {
SetAppendState(AppendState::PARSING_INIT_SEGMENT);
if (mFirstInitializationSegmentReceived) {
@ -640,7 +679,7 @@ TrackBuffersManager::SegmentParserLoop()
// 5. If the append state equals PARSING_INIT_SEGMENT, then run the
// following steps:
if (mAppendState == AppendState::PARSING_INIT_SEGMENT) {
if (mSourceBufferAttributes->GetAppendState() == AppendState::PARSING_INIT_SEGMENT) {
if (mParser->InitSegmentRange().IsEmpty()) {
mInputBuffer = nullptr;
NeedMoreData();
@ -649,7 +688,7 @@ TrackBuffersManager::SegmentParserLoop()
InitializationSegmentReceived();
return;
}
if (mAppendState == AppendState::PARSING_MEDIA_SEGMENT) {
if (mSourceBufferAttributes->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT) {
// 1. If the first initialization segment received flag is false, then run the append error algorithm with the decode error parameter set to true and abort this algorithm.
if (!mFirstInitializationSegmentReceived) {
RejectAppend(NS_ERROR_FAILURE, __func__);
@ -716,32 +755,45 @@ void
TrackBuffersManager::NeedMoreData()
{
MSE_DEBUG("");
RestoreCachedVariables();
mAppendRunning = false;
{
// Wake-up any pending Abort()
MonitorAutoLock mon(mMonitor);
mon.NotifyAll();
if (mDetached) {
// We've been detached.
return;
}
mAppendPromise.ResolveIfExists(mActiveTrack, __func__);
MOZ_DIAGNOSTIC_ASSERT(mCurrentTask && mCurrentTask->GetType() == SourceBufferTask::Type::AppendBuffer);
MOZ_DIAGNOSTIC_ASSERT(mSourceBufferAttributes);
mCurrentTask->As<AppendBufferTask>()->mPromise.Resolve(
SourceBufferTask::AppendBufferResult(mActiveTrack,
*mSourceBufferAttributes),
__func__);
mSourceBufferAttributes = nullptr;
mCurrentTask = nullptr;
ProcessTasks();
}
void
TrackBuffersManager::RejectAppend(nsresult aRejectValue, const char* aName)
{
MSE_DEBUG("rv=%d", aRejectValue);
mAppendRunning = false;
{
// Wake-up any pending Abort()
MonitorAutoLock mon(mMonitor);
mon.NotifyAll();
if (mDetached) {
// We've been detached.
return;
}
mAppendPromise.RejectIfExists(aRejectValue, aName);
MOZ_DIAGNOSTIC_ASSERT(mCurrentTask && mCurrentTask->GetType() == SourceBufferTask::Type::AppendBuffer);
MOZ_DIAGNOSTIC_ASSERT(mSourceBufferAttributes);
mCurrentTask->As<AppendBufferTask>()->mPromise.Reject(aRejectValue, __func__);
mSourceBufferAttributes = nullptr;
mCurrentTask = nullptr;
ProcessTasks();
}
void
TrackBuffersManager::ScheduleSegmentParserLoop()
{
if (mDetached) {
return;
}
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethod(this, &TrackBuffersManager::SegmentParserLoop);
GetTaskQueue()->Dispatch(task.forget());
@ -770,13 +822,15 @@ TrackBuffersManager::CreateDemuxerforMIMEType()
{
ShutdownDemuxers();
if (mType.LowerCaseEqualsLiteral("video/webm") || mType.LowerCaseEqualsLiteral("audio/webm")) {
if (mType.LowerCaseEqualsLiteral("video/webm") ||
mType.LowerCaseEqualsLiteral("audio/webm")) {
mInputDemuxer = new WebMDemuxer(mCurrentInputBuffer, true /* IsMediaSource*/ );
return;
}
#ifdef MOZ_FMP4
if (mType.LowerCaseEqualsLiteral("video/mp4") || mType.LowerCaseEqualsLiteral("audio/mp4")) {
if (mType.LowerCaseEqualsLiteral("video/mp4") ||
mType.LowerCaseEqualsLiteral("audio/mp4")) {
mInputDemuxer = new MP4Demuxer(mCurrentInputBuffer);
return;
}
@ -821,14 +875,16 @@ TrackBuffersManager::OnDemuxerResetDone(nsresult)
uint32_t numVideos = mInputDemuxer->GetNumberTracks(TrackInfo::kVideoTrack);
if (numVideos) {
// We currently only handle the first video track.
mVideoTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
mVideoTracks.mDemuxer =
mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
MOZ_ASSERT(mVideoTracks.mDemuxer);
}
uint32_t numAudios = mInputDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
if (numAudios) {
// We currently only handle the first audio track.
mAudioTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
mAudioTracks.mDemuxer =
mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
MOZ_ASSERT(mAudioTracks.mDemuxer);
}
@ -884,21 +940,17 @@ void
TrackBuffersManager::OnDemuxerInitDone(nsresult)
{
MOZ_ASSERT(OnTaskQueue());
mDemuxerInitRequest.Complete();
MOZ_DIAGNOSTIC_ASSERT(mInputDemuxer, "mInputDemuxer has been destroyed");
if (!mInputDemuxer) {
// mInputDemuxer shouldn't have been destroyed while a demuxer init/reset
// request was being processed. See bug 1239983.
NS_ASSERTION(false, "mInputDemuxer has been destroyed");
RejectAppend(NS_ERROR_ABORT, __func__);
}
mDemuxerInitRequest.Complete();
MediaInfo info;
uint32_t numVideos = mInputDemuxer->GetNumberTracks(TrackInfo::kVideoTrack);
if (numVideos) {
// We currently only handle the first video track.
mVideoTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
mVideoTracks.mDemuxer =
mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
MOZ_ASSERT(mVideoTracks.mDemuxer);
info.mVideo = *mVideoTracks.mDemuxer->GetInfo()->GetAsVideoInfo();
info.mVideo.mTrackId = 2;
@ -907,7 +959,8 @@ TrackBuffersManager::OnDemuxerInitDone(nsresult)
uint32_t numAudios = mInputDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
if (numAudios) {
// We currently only handle the first audio track.
mAudioTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
mAudioTracks.mDemuxer =
mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
MOZ_ASSERT(mAudioTracks.mDemuxer);
info.mAudio = *mAudioTracks.mDemuxer->GetInfo()->GetAsAudioInfo();
info.mAudio.mTrackId = 1;
@ -1048,6 +1101,7 @@ TrackBuffersManager::OnDemuxerInitDone(nsresult)
(info.mAudio.mChannels != mAudioTracks.mInfo->GetAsAudioInfo()->mChannels ||
info.mAudio.mRate != mAudioTracks.mInfo->GetAsAudioInfo()->mRate)) {
RejectAppend(NS_ERROR_FAILURE, __func__);
return;
}
mAudioTracks.mLastInfo = new SharedTrackInfo(info.mAudio, streamID);
mVideoTracks.mLastInfo = new SharedTrackInfo(info.mVideo, streamID);
@ -1307,12 +1361,14 @@ void
TrackBuffersManager::CheckSequenceDiscontinuity(const TimeUnit& aPresentationTime)
{
if (mSourceBufferAttributes->GetAppendMode() == SourceBufferAppendMode::Sequence &&
mGroupStartTimestamp.isSome()) {
mTimestampOffset = mGroupStartTimestamp.ref() - aPresentationTime;
mGroupEndTimestamp = mGroupStartTimestamp.ref();
mSourceBufferAttributes->HaveGroupStartTimestamp()) {
mSourceBufferAttributes->SetTimestampOffset(
mSourceBufferAttributes->GetGroupStartTimestamp() - aPresentationTime);
mSourceBufferAttributes->SetGroupEndTimestamp(
mSourceBufferAttributes->GetGroupStartTimestamp());
mVideoTracks.mNeedRandomAccessPoint = true;
mAudioTracks.mNeedRandomAccessPoint = true;
mGroupStartTimestamp.reset();
mSourceBufferAttributes->ResetGroupStartTimestamp();
}
}
@ -1403,14 +1459,14 @@ TrackBuffersManager::ProcessFrames(TrackBuffer& aSamples, TrackData& aTrackData)
TimeInterval sampleInterval =
mSourceBufferAttributes->mGenerateTimestamps
? TimeInterval(mTimestampOffset,
mTimestampOffset + TimeUnit::FromMicroseconds(sample->mDuration))
: TimeInterval(TimeUnit::FromMicroseconds(sample->mTime) + mTimestampOffset,
TimeUnit::FromMicroseconds(sample->GetEndTime()) + mTimestampOffset);
? TimeInterval(mSourceBufferAttributes->GetTimestampOffset(),
mSourceBufferAttributes->GetTimestampOffset() + TimeUnit::FromMicroseconds(sample->mDuration))
: TimeInterval(TimeUnit::FromMicroseconds(sample->mTime) + mSourceBufferAttributes->GetTimestampOffset(),
TimeUnit::FromMicroseconds(sample->GetEndTime()) + mSourceBufferAttributes->GetTimestampOffset());
TimeUnit decodeTimestamp =
mSourceBufferAttributes->mGenerateTimestamps
? mTimestampOffset
: TimeUnit::FromMicroseconds(sample->mTimecode) + mTimestampOffset;
? mSourceBufferAttributes->GetTimestampOffset()
: TimeUnit::FromMicroseconds(sample->mTimecode) + mSourceBufferAttributes->GetTimestampOffset();
// 6. If last decode timestamp for track buffer is set and decode timestamp is less than last decode timestamp:
// OR
@ -1425,12 +1481,13 @@ TrackBuffersManager::ProcessFrames(TrackBuffer& aSamples, TrackData& aTrackData)
// 1a. If mode equals "segments":
if (appendMode == SourceBufferAppendMode::Segments) {
// Set group end timestamp to presentation timestamp.
mGroupEndTimestamp = sampleInterval.mStart;
mSourceBufferAttributes->SetGroupEndTimestamp(sampleInterval.mStart);
}
// 1b. If mode equals "sequence":
if (appendMode == SourceBufferAppendMode::Sequence) {
// Set group start timestamp equal to the group end timestamp.
mGroupStartTimestamp = Some(mGroupEndTimestamp);
mSourceBufferAttributes->SetGroupStartTimestamp(
mSourceBufferAttributes->GetGroupEndTimestamp());
}
for (auto& track : GetTracksList()) {
// 2. Unset the last decode timestamp on all track buffers.
@ -1451,18 +1508,18 @@ TrackBuffersManager::ProcessFrames(TrackBuffer& aSamples, TrackData& aTrackData)
continue;
}
if (appendMode == SourceBufferAppendMode::Sequence) {
// mTimestampOffset was modified during CheckSequenceDiscontinuity.
// mSourceBufferAttributes->GetTimestampOffset() was modified during CheckSequenceDiscontinuity.
// We need to update our variables.
sampleInterval =
mSourceBufferAttributes->mGenerateTimestamps
? TimeInterval(mTimestampOffset,
mTimestampOffset + TimeUnit::FromMicroseconds(sample->mDuration))
: TimeInterval(TimeUnit::FromMicroseconds(sample->mTime) + mTimestampOffset,
TimeUnit::FromMicroseconds(sample->GetEndTime()) + mTimestampOffset);
? TimeInterval(mSourceBufferAttributes->GetTimestampOffset(),
mSourceBufferAttributes->GetTimestampOffset() + TimeUnit::FromMicroseconds(sample->mDuration))
: TimeInterval(TimeUnit::FromMicroseconds(sample->mTime) + mSourceBufferAttributes->GetTimestampOffset(),
TimeUnit::FromMicroseconds(sample->GetEndTime()) + mSourceBufferAttributes->GetTimestampOffset());
decodeTimestamp =
mSourceBufferAttributes->mGenerateTimestamps
? mTimestampOffset
: TimeUnit::FromMicroseconds(sample->mTimecode) + mTimestampOffset;
? mSourceBufferAttributes->GetTimestampOffset()
: TimeUnit::FromMicroseconds(sample->mTimecode) + mSourceBufferAttributes->GetTimestampOffset();
}
trackBuffer.mNeedRandomAccessPoint = false;
needDiscontinuityCheck = false;
@ -1516,12 +1573,12 @@ TrackBuffersManager::ProcessFrames(TrackBuffer& aSamples, TrackData& aTrackData)
trackBuffer.mHighestEndTimestamp = Some(sampleInterval.mEnd);
}
// 20. If frame end timestamp is greater than group end timestamp, then set group end timestamp equal to frame end timestamp.
if (sampleInterval.mEnd > mGroupEndTimestamp) {
mGroupEndTimestamp = sampleInterval.mEnd;
if (sampleInterval.mEnd > mSourceBufferAttributes->GetGroupEndTimestamp()) {
mSourceBufferAttributes->SetGroupEndTimestamp(sampleInterval.mEnd);
}
// 21. If generate timestamps flag equals true, then set timestampOffset equal to frame end timestamp.
if (mSourceBufferAttributes->mGenerateTimestamps) {
mTimestampOffset = sampleInterval.mEnd;
mSourceBufferAttributes->SetTimestampOffset(sampleInterval.mEnd);
}
}
@ -1778,56 +1835,11 @@ TrackBuffersManager::GetTracksList()
}
void
TrackBuffersManager::RestoreCachedVariables()
{
MOZ_ASSERT(OnTaskQueue());
if (mTimestampOffset != mLastTimestampOffset) {
mSourceBufferAttributes->SetTimestampOffset(mTimestampOffset);
}
}
void
TrackBuffersManager::SetAppendState(TrackBuffersManager::AppendState aAppendState)
TrackBuffersManager::SetAppendState(SourceBufferAttributes::AppendState aAppendState)
{
MSE_DEBUG("AppendState changed from %s to %s",
AppendStateToStr(mAppendState), AppendStateToStr(aAppendState));
mAppendState = aAppendState;
}
void
TrackBuffersManager::SetGroupStartTimestamp(const TimeUnit& aGroupStartTimestamp)
{
if (NS_IsMainThread()) {
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArg<TimeUnit>(
this,
&TrackBuffersManager::SetGroupStartTimestamp,
aGroupStartTimestamp);
GetTaskQueue()->Dispatch(task.forget());
return;
}
MOZ_ASSERT(OnTaskQueue());
mGroupStartTimestamp = Some(aGroupStartTimestamp);
}
void
TrackBuffersManager::RestartGroupStartTimestamp()
{
if (NS_IsMainThread()) {
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethod(this, &TrackBuffersManager::RestartGroupStartTimestamp);
GetTaskQueue()->Dispatch(task.forget());
return;
}
MOZ_ASSERT(OnTaskQueue());
mGroupStartTimestamp = Some(mGroupEndTimestamp);
}
TimeUnit
TrackBuffersManager::GroupEndTimestamp()
{
MonitorAutoLock mon(mMonitor);
return mOfficialGroupEndTimestamp;
AppendStateToStr(mSourceBufferAttributes->GetAppendState()), AppendStateToStr(aAppendState));
mSourceBufferAttributes->SetAppendState(aAppendState);
}
MediaInfo

View File

@ -10,13 +10,16 @@
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/Monitor.h"
#include "mozilla/Pair.h"
#include "AutoTaskQueue.h"
#include "mozilla/dom/SourceBufferBinding.h"
#include "SourceBufferContentManager.h"
#include "MediaData.h"
#include "MediaDataDemuxer.h"
#include "MediaSourceDecoder.h"
#include "SourceBufferTask.h"
#include "TimeUnits.h"
#include "nsProxyRelease.h"
#include "nsString.h"
#include "nsTArray.h"
namespace mozilla {
@ -27,57 +30,102 @@ class MediaRawData;
class MediaSourceDemuxer;
class SourceBufferResource;
namespace dom {
class SourceBufferAttributes;
}
class TrackBuffersManager : public SourceBufferContentManager {
class SourceBufferTaskQueue {
public:
typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> CodedFrameProcessingPromise;
SourceBufferTaskQueue()
: mMonitor("SourceBufferTaskQueue")
{}
void Push(SourceBufferTask* aTask)
{
MonitorAutoLock mon(mMonitor);
mQueue.AppendElement(aTask);
}
already_AddRefed<SourceBufferTask> Pop()
{
MonitorAutoLock mon(mMonitor);
if (!mQueue.Length()) {
return nullptr;
}
RefPtr<SourceBufferTask> task = Move(mQueue[0]);
mQueue.RemoveElementAt(0);
return task.forget();
}
nsTArray<SourceBufferTask>::size_type Length() const
{
MonitorAutoLock mon(mMonitor);
return mQueue.Length();
}
private:
mutable Monitor mMonitor;
nsTArray<RefPtr<SourceBufferTask>> mQueue;
};
class TrackBuffersManager {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TrackBuffersManager);
enum class EvictDataResult : int8_t
{
NO_DATA_EVICTED,
CANT_EVICT,
BUFFER_FULL,
};
typedef TrackInfo::TrackType TrackType;
typedef MediaData::Type MediaType;
typedef nsTArray<RefPtr<MediaRawData>> TrackBuffer;
typedef SourceBufferTask::AppendPromise AppendPromise;
typedef SourceBufferTask::RangeRemovalPromise RangeRemovalPromise;
TrackBuffersManager(dom::SourceBufferAttributes* aAttributes,
MediaSourceDecoder* aParentDecoder,
// Interface for SourceBuffer
TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
const nsACString& aType);
bool AppendData(MediaByteBuffer* aData,
media::TimeUnit aTimestampOffset) override;
// Queue a task to add data to the end of the input buffer and run the MSE
// Buffer Append Algorithm
// 3.5.5 Buffer Append Algorithm.
// http://w3c.github.io/media-source/index.html#sourcebuffer-buffer-append
RefPtr<AppendPromise> AppendData(MediaByteBuffer* aData,
const SourceBufferAttributes& aAttributes);
RefPtr<AppendPromise> BufferAppend() override;
// Queue a task to abort any pending AppendData.
// Does nothing at this stage.
void AbortAppendData();
void AbortAppendData() override;
void ResetParserState() override;
// Queue a task to run MSE Reset Parser State Algorithm.
// 3.5.2 Reset Parser State
void ResetParserState(SourceBufferAttributes& aAttributes);
// Queue a task to run the MSE range removal algorithm.
// http://w3c.github.io/media-source/#sourcebuffer-coded-frame-removal
RefPtr<RangeRemovalPromise> RangeRemoval(media::TimeUnit aStart,
media::TimeUnit aEnd) override;
media::TimeUnit aEnd);
EvictDataResult
EvictData(media::TimeUnit aPlaybackTime,
int64_t aThresholdReduct,
media::TimeUnit* aBufferStartTime) override;
// Schedule data eviction if necessary as the next call to AppendData will
// add aSize bytes.
// Eviction is done in two steps, first remove data up to aPlaybackTime
// and if still more space is needed remove from the end.
EvictDataResult EvictData(const media::TimeUnit& aPlaybackTime, int64_t aSize);
void EvictBefore(media::TimeUnit aTime) override;
// Returns the buffered range currently managed.
// This may be called on any thread.
// Buffered must conform to http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
media::TimeIntervals Buffered();
media::TimeIntervals Buffered() override;
// Return the size of the data managed by this SourceBufferContentManager.
int64_t GetSize() const;
int64_t GetSize() override;
// Indicate that the MediaSource parent object got into "ended" state.
void Ended();
void Ended() override;
// The parent SourceBuffer is about to be destroyed.
void Detach();
void Detach() override;
AppendState GetAppendState() override
{
return mAppendState;
}
void SetGroupStartTimestamp(const media::TimeUnit& aGroupStartTimestamp) override;
void RestartGroupStartTimestamp() override;
media::TimeUnit GroupEndTimestamp() override;
int64_t EvictionThreshold() const override;
int64_t EvictionThreshold() const;
// Interface for MediaSourceDemuxer
MediaInfo GetMetadata();
@ -102,14 +150,16 @@ public:
void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes);
private:
typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> CodedFrameProcessingPromise;
// for MediaSourceDemuxer::GetMozDebugReaderData
friend class MediaSourceDemuxer;
virtual ~TrackBuffersManager();
// All following functions run on the taskqueue.
RefPtr<AppendPromise> InitSegmentParserLoop();
RefPtr<AppendPromise> DoAppendData(RefPtr<MediaByteBuffer> aData,
SourceBufferAttributes aAttributes);
void ScheduleSegmentParserLoop();
void SegmentParserLoop();
void AppendIncomingBuffers();
void InitializationSegmentReceived();
void ShutdownDemuxers();
void CreateDemuxerforMIMEType();
@ -125,7 +175,7 @@ private:
RefPtr<RangeRemovalPromise>
CodedFrameRemovalWithPromise(media::TimeInterval aInterval);
bool CodedFrameRemoval(media::TimeInterval aInterval);
void SetAppendState(AppendState aAppendState);
void SetAppendState(SourceBufferAttributes::AppendState aAppendState);
bool HasVideo() const
{
@ -136,25 +186,15 @@ private:
return mAudioTracks.mNumTracks > 0;
}
typedef Pair<RefPtr<MediaByteBuffer>, media::TimeUnit> IncomingBuffer;
void AppendIncomingBuffer(IncomingBuffer aData);
nsTArray<IncomingBuffer> mIncomingBuffers;
// The input buffer as per http://w3c.github.io/media-source/index.html#sourcebuffer-input-buffer
RefPtr<MediaByteBuffer> mInputBuffer;
// The current append state as per https://w3c.github.io/media-source/#sourcebuffer-append-state
// Accessed on both the main thread and the task queue.
Atomic<AppendState> mAppendState;
// Buffer full flag as per https://w3c.github.io/media-source/#sourcebuffer-buffer-full-flag.
// Accessed on both the main thread and the task queue.
// TODO: Unused for now.
Atomic<bool> mBufferFull;
bool mFirstInitializationSegmentReceived;
// Set to true once a new segment is started.
bool mNewMediaSegmentStarted;
bool mActiveTrack;
Maybe<media::TimeUnit> mGroupStartTimestamp;
media::TimeUnit mGroupEndTimestamp;
nsCString mType;
// ContainerParser objects and methods.
@ -201,7 +241,7 @@ private:
OnDemuxFailed(TrackType::kAudioTrack, aFailure);
}
void DoEvictData(const media::TimeUnit& aPlaybackTime, int64_t aThreshold);
void DoEvictData(const media::TimeUnit& aPlaybackTime, int64_t aSizeToEvict);
struct TrackData {
TrackData()
@ -304,8 +344,6 @@ private:
MozPromiseRequestHolder<CodedFrameProcessingPromise> mProcessingRequest;
MozPromiseHolder<CodedFrameProcessingPromise> mProcessingPromise;
MozPromiseHolder<AppendPromise> mAppendPromise;
// Trackbuffers definition.
nsTArray<TrackData*> GetTracksList();
TrackData& GetTracksData(TrackType aTrack)
@ -329,19 +367,31 @@ private:
{
return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
}
RefPtr<TaskQueue> mTaskQueue;
RefPtr<AutoTaskQueue> mTaskQueue;
// SourceBuffer Queues and running context.
SourceBufferTaskQueue mQueue;
void ProcessTasks();
void CancelAllTasks();
// Set if the TrackBuffersManager is currently processing a task.
// At this stage, this task is always a AppendBufferTask.
RefPtr<SourceBufferTask> mCurrentTask;
// Current SourceBuffer state for ongoing task.
// Its content is returned to the SourceBuffer once the AppendBufferTask has
// completed.
UniquePtr<SourceBufferAttributes> mSourceBufferAttributes;
// The current sourcebuffer append window. It's content is equivalent to
// mSourceBufferAttributes.mAppendWindowStart/End
media::TimeInterval mAppendWindow;
media::TimeUnit mTimestampOffset;
media::TimeUnit mLastTimestampOffset;
void RestoreCachedVariables();
// Strong references to external objects.
RefPtr<dom::SourceBufferAttributes> mSourceBufferAttributes;
nsMainThreadPtrHandle<MediaSourceDecoder> mParentDecoder;
// Set to true if mediasource state changed to ended.
Atomic<bool> mEnded;
// Set to true if the parent SourceBuffer has shutdown.
// We will not reschedule or process new task once mDetached is set.
Atomic<bool> mDetached;
// Global size of this source buffer content.
Atomic<int64_t> mSizeSourceBuffer;
@ -350,14 +400,10 @@ private:
Atomic<bool> mEvictionOccurred;
// Monitor to protect following objects accessed across multipple threads.
// mMonitor is also notified if the value of mAppendRunning becomes false.
mutable Monitor mMonitor;
// Set to true while a BufferAppend is running or is pending.
Atomic<bool> mAppendRunning;
// Stable audio and video track time ranges.
media::TimeIntervals mVideoBufferedRanges;
media::TimeIntervals mAudioBufferedRanges;
media::TimeUnit mOfficialGroupEndTimestamp;
// MediaInfo of the first init segment read.
MediaInfo mInfo;
};

View File

@ -7,9 +7,12 @@ MOCHITEST_MANIFESTS += ['test/mochitest.ini']
EXPORTS += [
'AsyncEventRunner.h',
'AutoTaskQueue.h',
'MediaSourceDecoder.h',
'MediaSourceDemuxer.h',
'SourceBufferContentManager.h',
'SourceBufferAttributes.h',
'SourceBufferTask.h',
'TrackBuffersManager.h',
]
EXPORTS.mozilla.dom += [
@ -26,7 +29,6 @@ UNIFIED_SOURCES += [
'MediaSourceUtils.cpp',
'ResourceQueue.cpp',
'SourceBuffer.cpp',
'SourceBufferContentManager.cpp',
'SourceBufferList.cpp',
'SourceBufferResource.cpp',
'TrackBuffersManager.cpp',

View File

@ -220,6 +220,9 @@ var gPlayTests = [
// Test playback of a WebM file with non-zero start time.
{ name:"split.webm", type:"video/webm", duration:1.967 },
// Test playback of a WebM file with resolution changes.
{ name:"resolution-change.webm", type:"video/webm", duration:6.533 },
// Test playback of a raw file
{ name:"seek.yuv", type:"video/x-raw-yuv", duration:1.833 },
@ -284,6 +287,11 @@ var gSnifferTests = [
{ name:"bogus.duh", type:"bogus/duh" }
];
// Files that contain resolution changes
var gResolutionChangeTests = [
{ name:"resolution-change.webm", type:"video/webm", duration:6.533 },
];
// Files we must reject as invalid.
var gInvalidTests = [
{ name:"invalid-m0c0.opus", type:"audio/ogg; codecs=opus"},

View File

@ -475,6 +475,8 @@ support-files =
redirect.sjs
referer.sjs
region.vtt
resolution-change.webm
resolution-change.webm^headers^
sample.3gp
sample.3g2
sample-fisbone-skeleton4.ogv
@ -772,6 +774,7 @@ skip-if = toolkit == 'gonk' # bug 1128845 on gonk
[test_reset_events_async.html]
[test_reset_src.html]
[test_video_dimensions.html]
[test_resolution_change.html]
tags=capturestream
[test_resume.html]
skip-if = true # bug 1021673

Binary file not shown.

View File

@ -0,0 +1 @@
Cache-Control: no-store

View File

@ -0,0 +1,52 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test playback of files with resolution changes</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="manifest.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
var manager = new MediaTestManager;
function loadedData(e) {
var v = e.target;
v.addEventListener("resize", resize, false);
v.play();
}
function resize(e) {
var v = e.target;
v.seenResolutionChange = true;
}
function ended(e) {
var v = e.target;
ok(v.seenResolutionChange, v.token + ": A resolution change should have ocurred by the end of playback");
removeNodeAndSource(v);
manager.finished(v.token);
}
function startTest(test, token) {
var v = document.createElement('video');
v.preload = "metadata";
v.token = token;
v.src = test.name;
v.seenResolutionChange = false;
v.addEventListener("loadeddata", loadedData, false)
v.addEventListener("ended", ended, false);
manager.started(token);
document.body.appendChild(v);
}
manager.runTests(gResolutionChangeTests, startTest);
</script>
</pre>
</body>
</html>

View File

@ -11,6 +11,7 @@
#include "WebMDemuxer.h"
#include "WebMBufferedParser.h"
#include "gfx2DGlue.h"
#include "mozilla/Atomics.h"
#include "mozilla/Endian.h"
#include "mozilla/Preferences.h"
#include "mozilla/SharedThreadPool.h"
@ -42,6 +43,8 @@ LazyLogModule gNesteggLog("Nestegg");
// files encountered appear to have keyframes located < 4s.
#define MAX_LOOK_AHEAD 10000000
static Atomic<uint32_t> sStreamSourceID(0u);
// Functions for reading and seeking using WebMDemuxer required for
// nestegg_io. The 'user data' passed to these functions is the
// demuxer.
@ -572,6 +575,22 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSampl
break;
}
isKeyframe = si.is_kf;
if (isKeyframe) {
// We only look for resolution changes on keyframes for both VP8 and
// VP9. Other resolution changes are invalid.
if (mLastSeenFrameWidth.isSome() && mLastSeenFrameHeight.isSome() &&
(si.w != mLastSeenFrameWidth.value() ||
si.h != mLastSeenFrameHeight.value())) {
// We ignore cropping information on resizes during streams.
// Cropping alone is rare, and we do not consider cropping to
// still be valid after a resolution change
mInfo.mVideo.mDisplay = nsIntSize(si.w, si.h);
mInfo.mVideo.mImage = nsIntRect(0, 0, si.w, si.h);
mSharedVideoTrackInfo = new SharedTrackInfo(mInfo.mVideo, ++sStreamSourceID);
}
mLastSeenFrameWidth = Some(si.w);
mLastSeenFrameHeight = Some(si.h);
}
}
WEBM_DEBUG("push sample tstamp: %ld next_tstamp: %ld length: %ld kf: %d",
@ -588,6 +607,9 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSampl
sample->mExtraData = new MediaByteBuffer;
sample->mExtraData->AppendElements(&c[0], 8);
}
if (aType == TrackInfo::kVideoTrack) {
sample->mTrackInfo = mSharedVideoTrackInfo;
}
aSamples->Push(sample);
}
return true;

View File

@ -201,6 +201,12 @@ private:
// as nestegg only performs 1-byte read at a time.
int64_t mLastWebMBlockOffset;
const bool mIsMediaSource;
Maybe<uint32_t> mLastSeenFrameWidth;
Maybe<uint32_t> mLastSeenFrameHeight;
// This will be populated only if a resolution change occurs, otherwise it
// will be left as null so the original metadata is used
RefPtr<SharedTrackInfo> mSharedVideoTrackInfo;
};
class WebMTrackDemuxer : public MediaTrackDemuxer

View File

@ -199,7 +199,7 @@ public:
{
int dev_index = DeviceIndex(aDeviceIndex);
if (dev_index != -1) {
aID = mDevices->device[dev_index]->device_id;
aID = mDevices->device[dev_index]->devid;
return true;
}
return false;

View File

@ -162,6 +162,7 @@ TCPSocket::TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t
, mSuspendCount(0)
, mTrackingNumber(0)
, mWaitingForStartTLS(false)
, mObserversActive(false)
#ifdef MOZ_WIDGET_GONK
, mTxBytes(0)
, mRxBytes(0)
@ -179,6 +180,13 @@ TCPSocket::TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t
TCPSocket::~TCPSocket()
{
if (mObserversActive) {
nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
if (obs) {
obs->RemoveObserver(this, "inner-window-destroyed");
obs->RemoveObserver(this, "profile-change-net-teardown");
}
}
}
nsresult
@ -258,7 +266,9 @@ TCPSocket::Init()
{
nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
if (obs) {
obs->AddObserver(this, "inner-window-destroyed", true);
mObserversActive = true;
obs->AddObserver(this, "inner-window-destroyed", true); // weak reference
obs->AddObserver(this, "profile-change-net-teardown", true); // weak ref
}
if (XRE_GetProcessType() == GeckoProcessType_Content) {
@ -376,6 +386,7 @@ NS_IMETHODIMP
CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
{
mOwner->NotifyCopyComplete(aStatus);
mOwner = nullptr;
return NS_OK;
}
} // unnamed namespace
@ -442,7 +453,10 @@ TCPSocket::NotifyCopyComplete(nsresult aStatus)
}
if (mReadyState == TCPReadyState::Closing) {
mSocketOutputStream->Close();
if (mSocketOutputStream) {
mSocketOutputStream->Close();
mSocketOutputStream = nullptr;
}
mReadyState = TCPReadyState::Closed;
FireEvent(NS_LITERAL_STRING("close"));
}
@ -634,6 +648,9 @@ TCPSocket::MaybeReportErrorAndCloseIfOpen(nsresult status) {
if (mReadyState == TCPReadyState::Closed) {
return NS_OK;
}
// go through ::Closing state and then mark ::Closed
Close();
mReadyState = TCPReadyState::Closed;
if (NS_FAILED(status)) {
@ -758,11 +775,19 @@ TCPSocket::Close()
}
uint32_t count = 0;
mMultiplexStream->GetCount(&count);
if (!count) {
mSocketOutputStream->Close();
if (mMultiplexStream) {
mMultiplexStream->GetCount(&count);
}
if (!count) {
if (mSocketOutputStream) {
mSocketOutputStream->Close();
mSocketOutputStream = nullptr;
}
}
if (mSocketInputStream) {
mSocketInputStream->Close();
mSocketInputStream = nullptr;
}
mSocketInputStream->Close();
}
void
@ -955,6 +980,9 @@ TCPSocket::Constructor(const GlobalObject& aGlobal,
nsresult
TCPSocket::CreateInputStreamPump()
{
if (!mSocketInputStream) {
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv;
mInputStreamPump = do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
@ -1176,13 +1204,10 @@ TCPSocket::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aD
}
if (innerID == mInnerWindowID) {
nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
if (obs) {
obs->RemoveObserver(this, "inner-window-destroyed");
}
Close();
}
} else if (!strcmp(aTopic, "profile-change-net-teardown")) {
Close();
}
return NS_OK;

View File

@ -239,6 +239,8 @@ private:
// The buffered data awaiting the TLS upgrade to finish.
nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataAfterStartTLS;
bool mObserversActive;
#ifdef MOZ_WIDGET_GONK
// Number of bytes sent.
uint32_t mTxBytes;

View File

@ -26,6 +26,9 @@ function runTest() {
ok(new TCPSocket('localhost', 80), "TCPSocket constructor should work for content that has the tcp-socket permission");
ok(navigator.mozTCPSocket.open('localhost', 80), "navigator.mozTCPSocket.open should work for content that has the tcp-socket permission");
// This just helps the test harness clean up quickly
SpecialPowers.forceCC();
SpecialPowers.forceGC();
SimpleTest.finish();
}
</script>

View File

@ -40,6 +40,8 @@ Test of legacy navigator interface for opening TCPSocket/TCPServerSocket.
-1);
listeningServer.onconnect = function(ev) {
ok(true, "got server connect");
listeningServer.close();
listeningServer = null;
ev.socket.close()
}
@ -48,7 +50,14 @@ Test of legacy navigator interface for opening TCPSocket/TCPServerSocket.
clientSocket.onopen = function() { ok(true, "got client open"); }
clientSocket.onclose = function() {
ok(true, "got client close");
SimpleTest.finish();
clientSocket.close();
clientSocket = null;
setTimeout(function() {
// This just helps the test harness clean up quickly
SpecialPowers.forceCC();
SpecialPowers.forceGC();
SimpleTest.finish();
}, 0);
}
}
</script>

View File

@ -2583,7 +2583,7 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
event.type = anEvent.mMessage == eMouseOver ?
EnterNotify : LeaveNotify;
event.root = root;
event.time = anEvent.time;
event.time = anEvent.mTime;
event.x = pluginPoint.x;
event.y = pluginPoint.y;
event.x_root = rootPoint.x;
@ -2602,7 +2602,7 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
XMotionEvent& event = pluginEvent.xmotion;
event.type = MotionNotify;
event.root = root;
event.time = anEvent.time;
event.time = anEvent.mTime;
event.x = pluginPoint.x;
event.y = pluginPoint.y;
event.x_root = rootPoint.x;
@ -2621,7 +2621,7 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
event.type = anEvent.mMessage == eMouseDown ?
ButtonPress : ButtonRelease;
event.root = root;
event.time = anEvent.time;
event.time = anEvent.mTime;
event.x = pluginPoint.x;
event.y = pluginPoint.y;
event.x_root = rootPoint.x;
@ -2658,7 +2658,7 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
XKeyEvent &event = pluginEvent.xkey;
#ifdef MOZ_WIDGET_GTK
event.root = GDK_ROOT_WINDOW();
event.time = anEvent.time;
event.time = anEvent.mTime;
const GdkEventKey* gdkEvent =
static_cast<const GdkEventKey*>(anEvent.mPluginEvent);
event.keycode = gdkEvent->hardware_keycode;

View File

@ -41,9 +41,19 @@ CSPService::~CSPService()
NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)
// Helper function to identify protocols not subject to CSP.
// Helper function to identify protocols and content types not subject to CSP.
bool
subjectToCSP(nsIURI* aURI) {
subjectToCSP(nsIURI* aURI, nsContentPolicyType aContentType) {
// These content types are not subject to CSP content policy checks:
// TYPE_CSP_REPORT -- csp can't block csp reports
// TYPE_REFRESH -- never passed to ShouldLoad (see nsIContentPolicy.idl)
// TYPE_DOCUMENT -- used for frame-ancestors
if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
aContentType == nsIContentPolicy::TYPE_REFRESH ||
aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
return false;
}
// The three protocols: data:, blob: and filesystem: share the same
// protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols, like
// chrome:, resource:, moz-icon:, but those three protocols get
@ -117,21 +127,11 @@ CSPService::ShouldLoad(uint32_t aContentType,
*aDecision = nsIContentPolicy::ACCEPT;
// No need to continue processing if CSP is disabled or if the protocol
// is *not* subject to CSP.
// or type is *not* subject to CSP.
// Please note, the correct way to opt-out of CSP using a custom
// protocolHandler is to set one of the nsIProtocolHandler flags
// that are whitelistet in subjectToCSP()
if (!sCSPEnabled || !subjectToCSP(aContentLocation)) {
return NS_OK;
}
// These content types are not subject to CSP content policy checks:
// TYPE_CSP_REPORT -- csp can't block csp reports
// TYPE_REFRESH -- never passed to ShouldLoad (see nsIContentPolicy.idl)
// TYPE_DOCUMENT -- used for frame-ancestors
if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
aContentType == nsIContentPolicy::TYPE_REFRESH ||
aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
if (!sCSPEnabled || !subjectToCSP(aContentLocation, aContentType)) {
return NS_OK;
}
@ -252,20 +252,20 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->GetLoadInfo();
// if no loadInfo on the channel, nothing for us to do
if (!loadInfo) {
return NS_OK;
}
// No need to continue processing if CSP is disabled or if the protocol
// is *not* subject to CSP.
// Please note, the correct way to opt-out of CSP using a custom
// protocolHandler is to set one of the nsIProtocolHandler flags
// that are whitelistet in subjectToCSP()
if (!sCSPEnabled || !subjectToCSP(newUri)) {
return NS_OK;
}
nsCOMPtr<nsILoadInfo> loadInfo;
rv = oldChannel->GetLoadInfo(getter_AddRefs(loadInfo));
// if no loadInfo on the channel, nothing for us to do
if (!loadInfo) {
nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
if (!sCSPEnabled || !subjectToCSP(newUri, policyType)) {
return NS_OK;
}
@ -279,7 +279,6 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
nsCOMPtr<nsIURI> originalUri;
rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
NS_ENSURE_SUCCESS(rv, rv);
nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
bool isPreload = nsContentUtils::IsPreloadType(policyType);

View File

@ -19,7 +19,7 @@ TimeEvent::TimeEvent(EventTarget* aOwner,
InternalSMILTimeEvent* aEvent)
: Event(aOwner, aPresContext,
aEvent ? aEvent : new InternalSMILTimeEvent(false, eVoidEvent))
, mDetail(mEvent->AsSMILTimeEvent()->detail)
, mDetail(mEvent->AsSMILTimeEvent()->mDetail)
{
if (aEvent) {
mEventIsInternal = false;

View File

@ -97,7 +97,7 @@ namespace
NS_IMETHOD Run()
{
InternalSMILTimeEvent event(true, mMsg);
event.detail = mDetail;
event.mDetail = mDetail;
nsPresContext* context = nullptr;
nsIDocument* doc = mTarget->GetCurrentDoc();

View File

@ -40,7 +40,7 @@ SVGZoomEvent::SVGZoomEvent(EventTarget* aOwner,
}
else {
mEventIsInternal = true;
mEvent->time = PR_Now();
mEvent->mTime = PR_Now();
}
// We must store the "Previous" and "New" values before this event is

View File

@ -80,7 +80,9 @@ nsXBLPrototypeResources::FlushSkinSheets()
// We have scoped stylesheets. Reload any chrome stylesheets we
// encounter. (If they aren't skin sheets, it doesn't matter, since
// they'll still be in the chrome cache.
// they'll still be in the chrome cache. Skip inline sheets, which
// skin sheets can't be, and which in any case don't have a usable
// URL to reload.)
nsTArray<StyleSheetHandle::RefPtr> oldSheets;
@ -94,7 +96,7 @@ nsXBLPrototypeResources::FlushSkinSheets()
nsIURI* uri = oldSheet->GetSheetURI();
StyleSheetHandle::RefPtr newSheet;
if (IsChromeURI(uri)) {
if (!oldSheet->IsInline() && IsChromeURI(uri)) {
if (NS_FAILED(cssLoader->LoadSheetSync(uri, &newSheet)))
continue;
}

View File

@ -1784,7 +1784,7 @@ public:
// Even if the change is caused by untrusted event, we need to dispatch
// trusted input event since it's a fact.
InternalEditorInputEvent inputEvent(true, eEditorInput, widget);
inputEvent.time = static_cast<uint64_t>(PR_Now() / 1000);
inputEvent.mTime = static_cast<uint64_t>(PR_Now() / 1000);
inputEvent.mIsComposing = mIsComposing;
nsEventStatus status = nsEventStatus_eIgnore;
nsresult rv =

View File

@ -17,6 +17,7 @@
#include "skia/include/core/SkColorFilter.h"
#include "skia/include/effects/SkBlurImageFilter.h"
#include "skia/include/effects/SkLayerRasterizer.h"
#include "Blur.h"
#include "Logging.h"
#include "Tools.h"
#include "DataSurfaceHelpers.h"
@ -432,16 +433,37 @@ DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface,
// to blur the image ourselves.
SkPaint shadowPaint;
SkAutoTUnref<SkImageFilter> blurFilter(SkBlurImageFilter::Create(aSigma, aSigma));
SkAutoTUnref<SkColorFilter> colorFilter(
SkColorFilter::CreateModeFilter(ColorToSkColor(aColor, 1.0), SkXfermode::kSrcIn_Mode));
shadowPaint.setXfermode(paint.getXfermode());
shadowPaint.setImageFilter(blurFilter.get());
shadowPaint.setColorFilter(colorFilter.get());
IntPoint shadowDest = RoundedToInt(aDest + aOffset);
mCanvas->drawBitmap(bitmap, shadowDest.x, shadowDest.y, &shadowPaint);
SkBitmap blurMask;
if (!UsingSkiaGPU() &&
bitmap.extractAlpha(&blurMask)) {
// Prefer using our own box blur instead of Skia's when we're
// not using the GPU. It currently performs much better than
// SkBlurImageFilter or SkBlurMaskFilter on the CPU.
AlphaBoxBlur blur(Rect(0, 0, blurMask.width(), blurMask.height()),
int32_t(blurMask.rowBytes()),
aSigma, aSigma);
blurMask.lockPixels();
blur.Blur(reinterpret_cast<uint8_t*>(blurMask.getPixels()));
blurMask.unlockPixels();
blurMask.notifyPixelsChanged();
shadowPaint.setColor(ColorToSkColor(aColor, 1.0f));
mCanvas->drawBitmap(blurMask, shadowDest.x, shadowDest.y, &shadowPaint);
} else {
SkAutoTUnref<SkImageFilter> blurFilter(SkBlurImageFilter::Create(aSigma, aSigma));
SkAutoTUnref<SkColorFilter> colorFilter(
SkColorFilter::CreateModeFilter(ColorToSkColor(aColor, 1.0f), SkXfermode::kSrcIn_Mode));
shadowPaint.setImageFilter(blurFilter.get());
shadowPaint.setColorFilter(colorFilter.get());
mCanvas->drawBitmap(bitmap, shadowDest.x, shadowDest.y, &shadowPaint);
}
// Composite the original image after the shadow
IntPoint dest = RoundedToInt(aDest);

View File

@ -132,6 +132,7 @@ enum class LogReason : int {
CannotDraw3D, // 20
IncompatibleBasicTexturedEffect,
InvalidFont,
PAllocTextureBackendMismatch,
// End
MustBeLessThanThis = 101,
};

View File

@ -398,6 +398,18 @@ Compositor::ComputeBackdropCopyRect(const gfx::Rect& aRect,
return result;
}
void
Compositor::SetInvalid()
{
mParent = nullptr;
}
bool
Compositor::IsValid() const
{
return !mParent;
}
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
void
Compositor::SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget)

View File

@ -517,6 +517,11 @@ public:
return mCompositeUntilTime;
}
// A stale Compositor has no CompositorBridgeParent; it will not process
// frames and should not be used.
void SetInvalid();
bool IsValid() const;
protected:
void DrawDiagnosticsInternal(DiagnosticFlags aFlags,
const gfx::Rect& aVisibleRect,

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