merge mozilla-inbound to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-06-29 14:17:02 +02:00
commit 90ccc67e4e
611 changed files with 21632 additions and 4534 deletions

View File

@ -1 +1,4 @@
BasedOnStyle: Mozilla
# Ignore all comments because they aren't reflowed properly.
CommentPragmas: "^"

View File

@ -74,7 +74,7 @@ ROLE(CARET,
ROLE(ALERT,
"alert",
ATK_ROLE_ALERT,
NSAccessibilityWindowRole,
NSAccessibilityGroupRole,
ROLE_SYSTEM_ALERT,
ROLE_SYSTEM_ALERT,
eNoNameRule)
@ -211,7 +211,7 @@ ROLE(STATUSBAR,
ROLE(TABLE,
"table",
ATK_ROLE_TABLE,
NSAccessibilityGroupRole,
NSAccessibilityTableRole,
ROLE_SYSTEM_TABLE,
ROLE_SYSTEM_TABLE,
eNoNameRule)
@ -219,7 +219,7 @@ ROLE(TABLE,
ROLE(COLUMNHEADER,
"columnheader",
ATK_ROLE_COLUMN_HEADER,
NSAccessibilityGroupRole,
NSAccessibilityCellRole,
ROLE_SYSTEM_COLUMNHEADER,
ROLE_SYSTEM_COLUMNHEADER,
eNameFromSubtreeRule)
@ -227,7 +227,7 @@ ROLE(COLUMNHEADER,
ROLE(ROWHEADER,
"rowheader",
ATK_ROLE_ROW_HEADER,
NSAccessibilityGroupRole,
NSAccessibilityCellRole,
ROLE_SYSTEM_ROWHEADER,
ROLE_SYSTEM_ROWHEADER,
eNameFromSubtreeRule)
@ -251,7 +251,7 @@ ROLE(ROW,
ROLE(CELL,
"cell",
ATK_ROLE_TABLE_CELL,
NSAccessibilityGroupRole,
NSAccessibilityCellRole,
ROLE_SYSTEM_CELL,
ROLE_SYSTEM_CELL,
eNameFromSubtreeIfReqRule)

View File

@ -242,6 +242,27 @@ nsAccUtils::IsARIASelected(Accessible* aAccessible)
nsGkAtoms::_true, eCaseMatters);
}
Accessible*
nsAccUtils::TableFor(Accessible* aRow)
{
if (aRow) {
Accessible* table = aRow->Parent();
if (table) {
roles::Role tableRole = table->Role();
if (tableRole == roles::GROUPING) { // if there's a rowgroup.
table = table->Parent();
if (table)
tableRole = table->Role();
}
return tableRole == roles::TABLE || tableRole == roles::TREE_TABLE ?
table : nullptr;
}
}
return nullptr;
}
HyperTextAccessible*
nsAccUtils::GetTextContainer(nsINode* aNode)
{

View File

@ -134,6 +134,8 @@ public:
*/
static HyperTextAccessible* GetTextContainer(nsINode* aNode);
static Accessible* TableFor(Accessible* aRow);
/**
* Return true if the DOM node of given accessible has aria-selected="true"
* attribute.

View File

@ -1092,11 +1092,12 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
}
if (!newAcc && content->IsHTMLElement()) { // HTML accessibles
bool isARIATableOrCell = roleMapEntry &&
(roleMapEntry->accTypes & (eTableCell | eTable));
bool isARIATablePart = roleMapEntry &&
(roleMapEntry->accTypes & (eTableCell | eTableRow | eTable));
if (!isARIATableOrCell ||
if (!isARIATablePart ||
frame->AccessibleType() == eHTMLTableCellType ||
frame->AccessibleType() == eHTMLTableRowType ||
frame->AccessibleType() == eHTMLTableType) {
// Prefer to use markup to decide if and what kind of accessible to create,
const MarkupMapInfo* markupMap =
@ -1108,13 +1109,17 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
newAcc = CreateAccessibleByFrameType(frame, content, aContext);
}
// In case of ARIA grids use grid-specific classes if it's not native table
// based.
if (isARIATableOrCell && (!newAcc || newAcc->IsGenericHyperText())) {
// In case of ARIA grid or table use table-specific classes if it's not
// native table based.
if (isARIATablePart && (!newAcc || newAcc->IsGenericHyperText())) {
if ((roleMapEntry->accTypes & eTableCell)) {
if (aContext->IsTableRow())
newAcc = new ARIAGridCellAccessibleWrap(content, document);
} else if (roleMapEntry->IsOfType(eTableRow)) {
if (aContext->IsTable())
newAcc = new ARIARowAccessible(content, document);
} else if (roleMapEntry->IsOfType(eTable)) {
newAcc = new ARIAGridAccessibleWrap(content, document);
}

View File

@ -223,7 +223,7 @@ public:
* attribute or wrong value then false is returned.
*/
static bool GetUIntAttr(nsIContent *aContent, nsIAtom *aAttr,
int32_t *aUInt);
int32_t* aUInt);
/**
* Returns language for the given node.

View File

@ -10,35 +10,15 @@
#include "ARIAGridAccessible.h"
#include "AccIterator.h"
#include "nsAccUtils.h"
namespace mozilla {
namespace a11y {
inline Accessible*
ARIAGridCellAccessible::TableFor(Accessible* aRow) const
{
if (aRow) {
Accessible* table = aRow->Parent();
if (table) {
roles::Role tableRole = table->Role();
if (tableRole == roles::GROUPING) { // if there's a rowgroup.
table = table->Parent();
if (table)
tableRole = table->Role();
}
return tableRole == roles::TABLE || tableRole == roles::TREE_TABLE ?
table : nullptr;
}
}
return nullptr;
}
inline int32_t
ARIAGridCellAccessible::RowIndexFor(Accessible* aRow) const
{
Accessible* table = TableFor(aRow);
Accessible* table = nsAccUtils::TableFor(aRow);
if (table) {
int32_t rowIdx = 0;
Accessible* row = nullptr;

View File

@ -529,6 +529,34 @@ ARIAGridAccessible::SetARIASelected(Accessible* aAccessible,
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// ARIARowAccessible
////////////////////////////////////////////////////////////////////////////////
ARIARowAccessible::
ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc) :
AccessibleWrap(aContent, aDoc)
{
mGenericTypes |= eTableRow;
}
NS_IMPL_ISUPPORTS_INHERITED0(ARIARowAccessible, Accessible)
GroupPos
ARIARowAccessible::GroupPosition()
{
int32_t count = 0, index = 0;
if (nsCoreUtils::GetUIntAttr(nsAccUtils::TableFor(this)->GetContent(),
nsGkAtoms::aria_rowcount, &count) &&
nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
return GroupPos(0, index, count);
}
return AccessibleWrap::GroupPosition();
}
////////////////////////////////////////////////////////////////////////////////
// ARIAGridCellAccessible
////////////////////////////////////////////////////////////////////////////////
@ -552,7 +580,7 @@ NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridCellAccessible, HyperTextAccessible)
TableAccessible*
ARIAGridCellAccessible::Table() const
{
Accessible* table = TableFor(Row());
Accessible* table = nsAccUtils::TableFor(Row());
return table ? table->AsTable() : nullptr;
}
@ -657,3 +685,16 @@ ARIAGridCellAccessible::NativeAttributes()
return attributes.forget();
}
GroupPos
ARIAGridCellAccessible::GroupPosition()
{
int32_t count = 0, index = 0;
if (nsCoreUtils::GetUIntAttr(Table()->AsAccessible()->GetContent(),
nsGkAtoms::aria_colcount, &count) &&
nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
return GroupPos(0, index, count);
}
return GroupPos();
}

View File

@ -73,6 +73,24 @@ protected:
};
/**
* Accessible for ARIA row.
*/
class ARIARowAccessible : public AccessibleWrap
{
public:
ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc);
NS_DECL_ISUPPORTS_INHERITED
// Accessible
virtual mozilla::a11y::GroupPos GroupPosition() override;
protected:
virtual ~ARIARowAccessible() {}
};
/**
* Accessible for ARIA gridcell and rowheader/columnheader.
*/
@ -88,6 +106,7 @@ public:
virtual TableCellAccessible* AsTableCell() override { return this; }
virtual void ApplyARIAState(uint64_t* aState) const override;
virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
virtual mozilla::a11y::GroupPos GroupPosition() override;
protected:
virtual ~ARIAGridCellAccessible() {}
@ -98,14 +117,9 @@ protected:
Accessible* Row() const
{
Accessible* row = Parent();
return row && row->Role() == roles::ROW ? row : nullptr;
return row && row->IsTableRow() ? row : nullptr;
}
/**
* Return a table for the given row.
*/
Accessible* TableFor(Accessible* aRow) const;
/**
* Return index of the given row.
*/

View File

@ -82,6 +82,8 @@ enum ENameValueFlag {
struct GroupPos
{
GroupPos() : level(0), posInSet(0), setSize(0) { }
GroupPos(int32_t aLevel, int32_t aPosInSet, int32_t aSetSize) :
level(aLevel), posInSet(aPosInSet), setSize(aSetSize) { }
int32_t level;
int32_t posInSet;

View File

@ -143,6 +143,19 @@ HTMLTableCellAccessible::NativeAttributes()
return attributes.forget();
}
GroupPos
HTMLTableCellAccessible::GroupPosition()
{
int32_t count = 0, index = 0;
if (nsCoreUtils::GetUIntAttr(Table()->AsAccessible()->GetContent(),
nsGkAtoms::aria_colcount, &count) &&
nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
return GroupPos(0, index, count);
}
return HyperTextAccessibleWrap::GroupPosition();
}
////////////////////////////////////////////////////////////////////////////////
// HTMLTableCellAccessible: TableCellAccessible implementation
@ -356,6 +369,19 @@ HTMLTableRowAccessible::NativeRole()
return roles::ROW;
}
GroupPos
HTMLTableRowAccessible::GroupPosition()
{
int32_t count = 0, index = 0;
if (nsCoreUtils::GetUIntAttr(nsAccUtils::TableFor(this)->GetContent(),
nsGkAtoms::aria_rowcount, &count) &&
nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
return GroupPos(0, index, count);
}
return AccessibleWrap::GroupPosition();
}
////////////////////////////////////////////////////////////////////////////////
// HTMLTableAccessible
////////////////////////////////////////////////////////////////////////////////

View File

@ -33,6 +33,7 @@ public:
virtual uint64_t NativeState() override;
virtual uint64_t NativeInteractiveState() const override;
virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
virtual mozilla::a11y::GroupPos GroupPosition() override;
// TableCellAccessible
virtual TableAccessible* Table() const override;
@ -89,6 +90,7 @@ public:
// Accessible
virtual a11y::role NativeRole() override;
virtual mozilla::a11y::GroupPos GroupPosition() override;
protected:
virtual ~HTMLTableRowAccessible() { }

View File

@ -16,6 +16,8 @@
#include "Relation.h"
#include "Role.h"
#include "RootAccessible.h"
#include "TableAccessible.h"
#include "TableCellAccessible.h"
#include "mozilla/Services.h"
#include "nsRect.h"
@ -73,6 +75,24 @@ GetClosestInterestingAccessible(id anObject)
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
}
// convert an array of Gecko accessibles to an NSArray of native accessibles
static inline NSMutableArray*
ConvertToNSArray(nsTArray<Accessible*>& aArray)
{
NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
// iterate through the list, and get each native accessible.
uint32_t totalCount = aArray.Length();
for (uint32_t i = 0; i < totalCount; i++) {
Accessible* curAccessible = aArray.ElementAt(i);
mozAccessible* curNative = GetNativeFromGeckoAccessible(curAccessible);
if (curNative)
[nativeArray addObject:GetObjectOrRepresentedView(curNative)];
}
return nativeArray;
}
#pragma mark -
@implementation mozAccessible
@ -189,10 +209,15 @@ GetClosestInterestingAccessible(id anObject)
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
// if we're expired, we don't support any attributes.
if (![self getGeckoAccessible])
AccessibleWrap* accWrap = [self getGeckoAccessible];
if (!accWrap)
return [NSArray array];
static NSArray *generalAttributes = nil;
static NSArray* generalAttributes = nil;
static NSArray* tableAttrs = nil;
static NSArray* tableRowAttrs = nil;
static NSArray* tableCellAttrs = nil;
NSMutableArray* tempArray = nil;
if (!generalAttributes) {
// standard attributes that are shared and supported by all generic elements.
@ -218,7 +243,39 @@ GetClosestInterestingAccessible(id anObject)
nil];
}
if (!tableAttrs) {
tempArray = [[NSMutableArray alloc] initWithArray:generalAttributes];
[tempArray addObject:NSAccessibilityRowCountAttribute];
[tempArray addObject:NSAccessibilityColumnCountAttribute];
[tempArray addObject:NSAccessibilityRowsAttribute];
tableAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (!tableRowAttrs) {
tempArray = [[NSMutableArray alloc] initWithArray:generalAttributes];
[tempArray addObject:NSAccessibilityIndexAttribute];
tableRowAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (!tableCellAttrs) {
tempArray = [[NSMutableArray alloc] initWithArray:generalAttributes];
[tempArray addObject:NSAccessibilityRowIndexRangeAttribute];
[tempArray addObject:NSAccessibilityColumnIndexRangeAttribute];
[tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute];
[tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute];
tableCellAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
NSArray* objectAttributes = generalAttributes;
if (accWrap->IsTable())
objectAttributes = tableAttrs;
else if (accWrap->IsTableRow())
objectAttributes = tableRowAttrs;
else if (accWrap->IsTableCell())
objectAttributes = tableCellAttrs;
NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames];
if ([additionalAttributes count])
objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes];
@ -247,7 +304,8 @@ GetClosestInterestingAccessible(id anObject)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
if (![self getGeckoAccessible])
AccessibleWrap* accWrap = [self getGeckoAccessible];
if (!accWrap)
return nil;
#if DEBUG
@ -290,13 +348,64 @@ GetClosestInterestingAccessible(id anObject)
return [self title];
if ([attribute isEqualToString:NSAccessibilityTitleUIElementAttribute]) {
Relation rel =
[self getGeckoAccessible]->RelationByType(RelationType::LABELLED_BY);
accWrap->RelationByType(RelationType::LABELLED_BY);
Accessible* tempAcc = rel.Next();
return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil;
}
if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
return [self help];
if (accWrap->IsTable()) {
TableAccessible* table = accWrap->AsTable();
if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
return @(table->RowCount());
if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
return @(table->ColCount());
if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
// Create a new array with the list of table rows.
NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
uint32_t totalCount = accWrap->ChildCount();
for (uint32_t i = 0; i < totalCount; i++) {
if (accWrap->GetChildAt(i)->IsTableRow()) {
mozAccessible* curNative =
GetNativeFromGeckoAccessible(accWrap->GetChildAt(i));
if (curNative)
[nativeArray addObject:GetObjectOrRepresentedView(curNative)];
}
}
return nativeArray;
}
} else if (accWrap->IsTableRow()) {
if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
// Count the number of rows before that one to obtain the row index.
uint32_t index = 0;
for (int32_t i = accWrap->IndexInParent() - 1; i >= 0; i--) {
if (accWrap->GetChildAt(i)->IsTableRow()) {
index++;
}
}
return [NSNumber numberWithUnsignedInteger:index];
}
} else if (accWrap->IsTableCell()) {
TableCellAccessible* cell = accWrap->AsTableCell();
if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
return [NSValue valueWithRange:NSMakeRange(cell->RowIdx(),
cell->RowExtent())];
if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
return [NSValue valueWithRange:NSMakeRange(cell->ColIdx(),
cell->ColExtent())];
if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
nsAutoTArray<Accessible*, 10> headerCells;
cell->RowHeaderCells(&headerCells);
return ConvertToNSArray(headerCells);
}
if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
nsAutoTArray<Accessible*, 10> headerCells;
cell->ColHeaderCells(&headerCells);
return ConvertToNSArray(headerCells);
}
}
switch (mRole) {
case roles::MATHML_ROOT:
if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
@ -534,22 +643,10 @@ GetClosestInterestingAccessible(id anObject)
if (mChildren || !accWrap->AreChildrenCached())
return mChildren;
mChildren = [[NSMutableArray alloc] init];
// get the array of children.
nsAutoTArray<Accessible*, 10> childrenArray;
accWrap->GetUnignoredChildren(&childrenArray);
// now iterate through the children array, and get each native accessible.
uint32_t totalCount = childrenArray.Length();
for (uint32_t idx = 0; idx < totalCount; idx++) {
Accessible* curAccessible = childrenArray.ElementAt(idx);
if (curAccessible) {
mozAccessible *curNative = GetNativeFromGeckoAccessible(curAccessible);
if (curNative)
[mChildren addObject:GetObjectOrRepresentedView(curNative)];
}
}
mChildren = ConvertToNSArray(childrenArray);
#ifdef DEBUG_hakan
// make sure we're not returning any ignored accessibles.
@ -633,6 +730,7 @@ GetClosestInterestingAccessible(id anObject)
if (!accWrap)
return nil;
// Deal with landmarks first
nsIAtom* landmark = accWrap->LandmarkRole();
if (landmark) {
if (landmark == nsGkAtoms::application)
@ -655,6 +753,37 @@ GetClosestInterestingAccessible(id anObject)
return @"AXSearchField";
}
// Now, deal with widget roles
if (accWrap->HasARIARole()) {
nsRoleMapEntry* roleMap = accWrap->ARIARoleMap();
if (roleMap->Is(nsGkAtoms::alert))
return @"AXApplicationAlert";
if (roleMap->Is(nsGkAtoms::alertdialog))
return @"AXApplicationAlertDialog";
if (roleMap->Is(nsGkAtoms::article))
return @"AXDocumentArticle";
if (roleMap->Is(nsGkAtoms::dialog))
return @"AXApplicationDialog";
if (roleMap->Is(nsGkAtoms::document))
return @"AXDocument";
if (roleMap->Is(nsGkAtoms::log_))
return @"AXApplicationLog";
if (roleMap->Is(nsGkAtoms::marquee))
return @"AXApplicationMarquee";
if (roleMap->Is(nsGkAtoms::math))
return @"AXDocumentMath";
if (roleMap->Is(nsGkAtoms::note_))
return @"AXDocumentNote";
if (roleMap->Is(nsGkAtoms::region))
return @"AXDocumentRegion";
if (roleMap->Is(nsGkAtoms::status))
return @"AXApplicationStatus";
if (roleMap->Is(nsGkAtoms::timer))
return @"AXApplicationTimer";
if (roleMap->Is(nsGkAtoms::tooltip))
return @"AXUserInterfaceTooltip";
}
switch (mRole) {
case roles::LIST:
return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole;
@ -753,7 +882,19 @@ struct RoleDescrMap
};
static const RoleDescrMap sRoleDescrMap[] = {
{ @"AXApplicationAlert", NS_LITERAL_STRING("alert") },
{ @"AXApplicationAlertDialog", NS_LITERAL_STRING("alertDialog") },
{ @"AXApplicationLog", NS_LITERAL_STRING("log") },
{ @"AXApplicationMarquee", NS_LITERAL_STRING("marquee") },
{ @"AXApplicationStatus", NS_LITERAL_STRING("status") },
{ @"AXApplicationTimer", NS_LITERAL_STRING("timer") },
{ @"AXDefinition", NS_LITERAL_STRING("definition") },
{ @"AXDocument", NS_LITERAL_STRING("document") },
{ @"AXDocumentArticle", NS_LITERAL_STRING("article") },
{ @"AXDocumentMath", NS_LITERAL_STRING("math") },
{ @"AXDocumentNote", NS_LITERAL_STRING("note") },
{ @"AXDocumentRegion", NS_LITERAL_STRING("region") },
{ @"AXLandmarkApplication", NS_LITERAL_STRING("application") },
{ @"AXLandmarkBanner", NS_LITERAL_STRING("banner") },
{ @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") },
{ @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") },
@ -761,7 +902,8 @@ static const RoleDescrMap sRoleDescrMap[] = {
{ @"AXLandmarkNavigation", NS_LITERAL_STRING("navigation") },
{ @"AXLandmarkSearch", NS_LITERAL_STRING("search") },
{ @"AXSearchField", NS_LITERAL_STRING("searchTextField") },
{ @"AXTerm", NS_LITERAL_STRING("term") }
{ @"AXTerm", NS_LITERAL_STRING("term") },
{ @"AXUserInterfaceTooltip", NS_LITERAL_STRING("tooltip") }
};
struct RoleDescrComparator

View File

@ -44,6 +44,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=558036
testAttrs("sortDescending", {"sort" : "descending"}, true);
testAttrs("sortNone", {"sort" : "none"}, true);
testAttrs("sortOther", {"sort" : "other"}, true);
testAttrs("roledescr", {"roledescription" : "spreadshit"}, true);
testAttrs("currentPage", {"current" : "page"}, true);
// inherited attributes by subdocuments
var subdoc = getAccessible("iframe").firstChild;
@ -216,6 +218,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=558036
<div id="sortDescending" role="columnheader" aria-sort="descending"></div>
<div id="sortNone" role="columnheader" aria-sort="none"></div>
<div id="sortOther" role="columnheader" aria-sort="other"></div>
<div id="roledescr" aria-roledescription="spreadshit"></div>
<div id="currentPage" aria-current="page"></div>
<!-- inherited from iframe -->
<iframe id="iframe" src="data:text/html,<html><body></body></html>"

View File

@ -186,6 +186,11 @@
testGroupAttrs("combo1_opt3", 3, 4);
testGroupAttrs("combo1_opt4", 4, 4);
//////////////////////////////////////////////////////////////////////////
// ARIA table
testGroupAttrs("table_cell", 3, 4);
testGroupAttrs("table_row", 2, 2);
// Test that group position information updates after deleting node.
testGroupAttrs("tree4_ti1", 1, 2, 1);
testGroupAttrs("tree4_ti2", 2, 2, 1);
@ -442,5 +447,11 @@
<input type="radio" style="display: none;" name="group3">
<input type="radio" id="radio5" name="group3">
</form>
<div role="table" aria-colcount="4" aria-rowcount="2">
<div role="row" id="table_row" aria-rowindex="2">
<div role="cell" id="table_cell" aria-colindex="3">cell</div>
</div>
</div>
</body>
</html>

View File

@ -51,9 +51,11 @@
gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade")));
gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist")));
if (!MAC) {
gQueue.push(new synthDownKey("menulist", new nofocusChecker("ml_marmalade")));
gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker("ml_marmalade")));
gQueue.push(new synthEnterKey("ml_marmalade", new focusChecker("menulist")));
// On Windows, items get selected during navigation.
let expectedItem = WIN ? "ml_tangerine" : "ml_marmalade";
gQueue.push(new synthDownKey("menulist", new nofocusChecker(expectedItem)));
gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker(expectedItem)));
gQueue.push(new synthEnterKey(expectedItem, new focusChecker("menulist")));
} else {
todo(false, "Bug 746531 - timeouts of last three menulist tests on OS X");
}
@ -73,7 +75,7 @@ if (!MAC) {
gQueue.push(new changeCurrentItem("listbox", "lb_item1"));
gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1"));
if (!MAC) {
gQueue.push(new changeCurrentItem("menulist", "ml_tangerine"));
gQueue.push(new changeCurrentItem("menulist", WIN ? "ml_marmalade" : "ml_tangerine"));
}
gQueue.push(new changeCurrentItem("emenulist", "eml_tangerine"));

View File

@ -57,6 +57,8 @@ add_task(function*() {
yield openSelectPopup(selectPopup);
let isWindows = navigator.platform.indexOf("Win") >= 0;
is(menulist.selectedIndex, 1, "Initial selection");
is(selectPopup.firstChild.localName, "menucaption", "optgroup is caption");
is(selectPopup.firstChild.getAttribute("label"), "First Group", "optgroup label");
@ -65,17 +67,20 @@ add_task(function*() {
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(2), "Select item 2");
is(menulist.selectedIndex, isWindows ? 2 : 1, "Select item 2 selectedIndex");
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3");
is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
// On Windows, one can navigate on disabled menuitems
let expectedIndex = (navigator.platform.indexOf("Win") >= 0) ? 5 : 9;
let expectedIndex = isWindows ? 5 : 9;
is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(expectedIndex),
"Skip optgroup header and disabled items select item 7");
is(menulist.selectedIndex, isWindows ? 5 : 1, "Select or skip disabled item selectedIndex");
for (let i = 0; i < 10; i++) {
is(menulist.getItemAtIndex(i).disabled, i >= 4 && i <= 7, "item " + i + " disabled")
@ -83,6 +88,7 @@ add_task(function*() {
EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3 again");
is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
yield hideSelectPopup(selectPopup);

View File

@ -636,34 +636,19 @@ DynSHOpenWithDialog(HWND hwndParent, const OPENASINFO *poainfo)
}
nsresult
nsWindowsShellService::LaunchControlPanelDefaultPrograms()
nsWindowsShellService::LaunchControlPanelDefaultsSelectionUI()
{
// Build the path control.exe path safely
WCHAR controlEXEPath[MAX_PATH + 1] = { '\0' };
if (!GetSystemDirectoryW(controlEXEPath, MAX_PATH)) {
return NS_ERROR_FAILURE;
IApplicationAssociationRegistrationUI* pAARUI;
HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistrationUI,
NULL,
CLSCTX_INPROC,
IID_IApplicationAssociationRegistrationUI,
(void**)&pAARUI);
if (SUCCEEDED(hr)) {
hr = pAARUI->LaunchAdvancedAssociationUI(APP_REG_NAME);
pAARUI->Release();
}
LPCWSTR controlEXE = L"control.exe";
if (wcslen(controlEXEPath) + wcslen(controlEXE) >= MAX_PATH) {
return NS_ERROR_FAILURE;
}
if (!PathAppendW(controlEXEPath, controlEXE)) {
return NS_ERROR_FAILURE;
}
WCHAR params[] = L"control.exe /name Microsoft.DefaultPrograms /page pageDefaultProgram";
STARTUPINFOW si = {sizeof(si), 0};
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWDEFAULT;
PROCESS_INFORMATION pi = {0};
if (!CreateProcessW(controlEXEPath, params, nullptr, nullptr, FALSE,
0, nullptr, nullptr, &si, &pi)) {
return NS_ERROR_FAILURE;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return NS_OK;
return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
}
nsresult
@ -750,7 +735,7 @@ nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
if (IsWin10OrLater()) {
rv = LaunchModernSettingsDialogDefaultApps();
} else {
rv = LaunchControlPanelDefaultPrograms();
rv = LaunchControlPanelDefaultsSelectionUI();
}
// The above call should never really fail, but just in case
// fall back to showing the HTTP association screen only.
@ -784,7 +769,7 @@ nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
if (IsWin10OrLater()) {
rv = LaunchModernSettingsDialogDefaultApps();
} else {
rv = LaunchControlPanelDefaultPrograms();
rv = LaunchControlPanelDefaultsSelectionUI();
}
}
}

View File

@ -27,7 +27,7 @@ public:
protected:
bool IsDefaultBrowserVista(bool aCheckAllTypes, bool* aIsDefaultBrowser);
nsresult LaunchControlPanelDefaultPrograms();
nsresult LaunchControlPanelDefaultsSelectionUI();
nsresult LaunchModernSettingsDialogDefaultApps();
nsresult InvokeHTTPOpenAsVerb();
nsresult LaunchHTTPHandlerPane();

View File

@ -4,23 +4,15 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import with_statement
import codecs
import itertools
import json
import logging
import os
import re
import select
import shutil
import signal
import subprocess
import sys
import threading
import tempfile
import sqlite3
import zipfile
from datetime import datetime, timedelta
from string import Template
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
sys.path.insert(0, SCRIPT_DIR)
@ -39,8 +31,6 @@ if os.path.isdir(mozbase):
sys.path.append(package_path)
import mozcrash
from mozprofile import Profile, Preferences
from mozprofile.permissions import ServerLocations
# ---------------------------------------------------------------
@ -82,10 +72,6 @@ if _IS_WIN32:
else:
import errno
def getGlobalLog():
return _log
def resetGlobalLog(log):
while _log.handlers:
_log.removeHandler(_log.handlers[0])
@ -104,31 +90,6 @@ resetGlobalLog(sys.stdout)
# PROFILE SETUP #
#################
class SyntaxError(Exception):
"Signifies a syntax error on a particular line in server-locations.txt."
def __init__(self, lineno, msg = None):
self.lineno = lineno
self.msg = msg
def __str__(self):
s = "Syntax error on line " + str(self.lineno)
if self.msg:
s += ": %s." % self.msg
else:
s += "."
return s
class Location:
"Represents a location line in server-locations.txt."
def __init__(self, scheme, host, port, options):
self.scheme = scheme
self.host = host
self.port = port
self.options = options
class Automation(object):
"""
Runs the browser from a script, and provides useful utilities
@ -182,7 +143,6 @@ class Automation(object):
"log",
"runApp",
"Process",
"initializeProfile",
"DIST_BIN",
"DEFAULT_APP",
"CERTS_SRC_DIR",
@ -238,229 +198,6 @@ class Automation(object):
else:
os.kill(self.pid, signal.SIGKILL)
def readLocations(self, locationsPath = "server-locations.txt"):
"""
Reads the locations at which the Mochitest HTTP server is available from
server-locations.txt.
"""
locationFile = codecs.open(locationsPath, "r", "UTF-8")
# Perhaps more detail than necessary, but it's the easiest way to make sure
# we get exactly the format we want. See server-locations.txt for the exact
# format guaranteed here.
lineRe = re.compile(r"^(?P<scheme>[a-z][-a-z0-9+.]*)"
r"://"
r"(?P<host>"
r"\d+\.\d+\.\d+\.\d+"
r"|"
r"(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*"
r"[a-z](?:[-a-z0-9]*[a-z0-9])?"
r")"
r":"
r"(?P<port>\d+)"
r"(?:"
r"\s+"
r"(?P<options>\S+(?:,\S+)*)"
r")?$")
locations = []
lineno = 0
seenPrimary = False
for line in locationFile:
lineno += 1
if line.startswith("#") or line == "\n":
continue
match = lineRe.match(line)
if not match:
raise SyntaxError(lineno)
options = match.group("options")
if options:
options = options.split(",")
if "primary" in options:
if seenPrimary:
raise SyntaxError(lineno, "multiple primary locations")
seenPrimary = True
else:
options = []
locations.append(Location(match.group("scheme"), match.group("host"),
match.group("port"), options))
if not seenPrimary:
raise SyntaxError(lineno + 1, "missing primary location")
return locations
def setupPermissionsDatabase(self, profileDir, permissions):
# Included for reftest compatibility;
# see https://bugzilla.mozilla.org/show_bug.cgi?id=688667
# Open database and create table
permDB = sqlite3.connect(os.path.join(profileDir, "permissions.sqlite"))
cursor = permDB.cursor();
cursor.execute("PRAGMA user_version=4");
# SQL copied from nsPermissionManager.cpp
cursor.execute("""CREATE TABLE IF NOT EXISTS moz_hosts (
id INTEGER PRIMARY KEY,
host TEXT,
type TEXT,
permission INTEGER,
expireType INTEGER,
expireTime INTEGER,
modificationTime INTEGER,
appId INTEGER,
isInBrowserElement INTEGER)""")
# Insert desired permissions
for perm in permissions.keys():
for host,allow in permissions[perm]:
cursor.execute("INSERT INTO moz_hosts values(NULL, ?, ?, ?, 0, 0, 0, 0, 0)",
(host, perm, 1 if allow else 2))
# Commit and close
permDB.commit()
cursor.close()
def initializeProfile(self, profileDir,
extraPrefs=None,
useServerLocations=False,
prefsPath=_DEFAULT_PREFERENCE_FILE,
appsPath=_DEFAULT_APPS_FILE,
addons=None):
" Sets up the standard testing profile."
extraPrefs = extraPrefs or []
# create the profile
prefs = {}
locations = None
if useServerLocations:
locations = ServerLocations()
locations.read(os.path.abspath('server-locations.txt'), True)
else:
prefs['network.proxy.type'] = 0
prefs.update(Preferences.read_prefs(prefsPath))
for v in extraPrefs:
thispref = v.split("=", 1)
if len(thispref) < 2:
print "Error: syntax error in --setpref=" + v
sys.exit(1)
prefs[thispref[0]] = thispref[1]
interpolation = {"server": "%s:%s" % (self.webServer, self.httpPort)}
prefs = json.loads(json.dumps(prefs) % interpolation)
for pref in prefs:
prefs[pref] = Preferences.cast(prefs[pref])
# load apps
apps = None
if appsPath and os.path.exists(appsPath):
with open(appsPath, 'r') as apps_file:
apps = json.load(apps_file)
proxy = {'remote': str(self.webServer),
'http': str(self.httpPort),
'https': str(self.sslPort),
# use SSL port for legacy compatibility; see
# - https://bugzilla.mozilla.org/show_bug.cgi?id=688667#c66
# - https://bugzilla.mozilla.org/show_bug.cgi?id=899221
# 'ws': str(self.webSocketPort)
'ws': str(self.sslPort)
}
# return profile object
profile = Profile(profile=profileDir,
addons=addons,
locations=locations,
preferences=prefs,
restore=False,
apps=apps,
proxy=proxy)
return profile
def fillCertificateDB(self, profileDir, certPath, utilityPath, xrePath):
pwfilePath = os.path.join(profileDir, ".crtdbpw")
pwfile = open(pwfilePath, "w")
pwfile.write("\n")
pwfile.close()
# Create head of the ssltunnel configuration file
sslTunnelConfigPath = os.path.join(profileDir, "ssltunnel.cfg")
sslTunnelConfig = open(sslTunnelConfigPath, "w")
sslTunnelConfig.write("httpproxy:1\n")
sslTunnelConfig.write("certdbdir:%s\n" % certPath)
sslTunnelConfig.write("forward:127.0.0.1:%s\n" % self.httpPort)
sslTunnelConfig.write("websocketserver:%s:%s\n" % (self.webServer, self.webSocketPort))
sslTunnelConfig.write("listen:*:%s:pgo server certificate\n" % self.sslPort)
# Configure automatic certificate and bind custom certificates, client authentication
locations = self.readLocations()
locations.pop(0)
for loc in locations:
if loc.scheme == "https" and "nocert" not in loc.options:
customCertRE = re.compile("^cert=(?P<nickname>[0-9a-zA-Z_ ]+)")
clientAuthRE = re.compile("^clientauth=(?P<clientauth>[a-z]+)")
redirRE = re.compile("^redir=(?P<redirhost>[0-9a-zA-Z_ .]+)")
for option in loc.options:
match = customCertRE.match(option)
if match:
customcert = match.group("nickname");
sslTunnelConfig.write("listen:%s:%s:%s:%s\n" %
(loc.host, loc.port, self.sslPort, customcert))
match = clientAuthRE.match(option)
if match:
clientauth = match.group("clientauth");
sslTunnelConfig.write("clientauth:%s:%s:%s:%s\n" %
(loc.host, loc.port, self.sslPort, clientauth))
match = redirRE.match(option)
if match:
redirhost = match.group("redirhost")
sslTunnelConfig.write("redirhost:%s:%s:%s:%s\n" %
(loc.host, loc.port, self.sslPort, redirhost))
sslTunnelConfig.close()
# Pre-create the certification database for the profile
env = self.environment(xrePath = xrePath)
certutil = os.path.join(utilityPath, "certutil" + self.BIN_SUFFIX)
pk12util = os.path.join(utilityPath, "pk12util" + self.BIN_SUFFIX)
status = self.Process([certutil, "-N", "-d", profileDir, "-f", pwfilePath], env = env).wait()
automationutils.printstatus(status, "certutil")
if status != 0:
return status
# Walk the cert directory and add custom CAs and client certs
files = os.listdir(certPath)
for item in files:
root, ext = os.path.splitext(item)
if ext == ".ca":
trustBits = "CT,,"
if root.endswith("-object"):
trustBits = "CT,,CT"
status = self.Process([certutil, "-A", "-i", os.path.join(certPath, item),
"-d", profileDir, "-f", pwfilePath, "-n", root, "-t", trustBits],
env = env).wait()
automationutils.printstatus(status, "certutil")
if ext == ".client":
status = self.Process([pk12util, "-i", os.path.join(certPath, item), "-w",
pwfilePath, "-d", profileDir],
env = env).wait()
automationutils.printstatus(status, "pk12util")
os.unlink(pwfilePath)
return 0
def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None, lsanPath=None):
if xrePath == None:
xrePath = self.DIST_BIN
@ -787,8 +524,7 @@ class Automation(object):
def checkForCrashes(self, minidumpDir, symbolsPath):
return mozcrash.check_for_crashes(minidumpDir, symbolsPath, test_name=self.lastTestSeen)
def runApp(self, testURL, env, app, profileDir, extraArgs,
runSSLTunnel = False, utilityPath = None,
def runApp(self, testURL, env, app, profileDir, extraArgs, utilityPath = None,
xrePath = None, certPath = None,
debuggerInfo = None, symbolsPath = None,
timeout = -1, maxTime = None, onLaunch = None,
@ -814,19 +550,6 @@ class Automation(object):
os.close(tmpfd)
env["MOZ_PROCESS_LOG"] = processLog
if self.IS_TEST_BUILD and runSSLTunnel:
# create certificate database for the profile
certificateStatus = self.fillCertificateDB(profileDir, certPath, utilityPath, xrePath)
if certificateStatus != 0:
self.log.info("TEST-UNEXPECTED-FAIL | automation.py | Certificate integration failed")
return certificateStatus
# start ssltunnel to provide https:// URLs capability
ssltunnel = os.path.join(utilityPath, "ssltunnel" + self.BIN_SUFFIX)
ssltunnelProcess = self.Process([ssltunnel,
os.path.join(profileDir, "ssltunnel.cfg")],
env = self.environment(xrePath = xrePath))
self.log.info("INFO | automation.py | SSL tunnel pid: %d", ssltunnelProcess.pid)
cmd, args = self.buildCommandLine(app, debuggerInfo, profileDir, testURL, extraArgs)
startTime = datetime.now()
@ -868,96 +591,8 @@ class Automation(object):
if os.path.exists(processLog):
os.unlink(processLog)
if self.IS_TEST_BUILD and runSSLTunnel:
ssltunnelProcess.kill()
return status
def getExtensionIDFromRDF(self, rdfSource):
"""
Retrieves the extension id from an install.rdf file (or string).
"""
from xml.dom.minidom import parse, parseString, Node
if isinstance(rdfSource, file):
document = parse(rdfSource)
else:
document = parseString(rdfSource)
# Find the <em:id> element. There can be multiple <em:id> tags
# within <em:targetApplication> tags, so we have to check this way.
for rdfChild in document.documentElement.childNodes:
if rdfChild.nodeType == Node.ELEMENT_NODE and rdfChild.tagName == "Description":
for descChild in rdfChild.childNodes:
if descChild.nodeType == Node.ELEMENT_NODE and descChild.tagName == "em:id":
return descChild.childNodes[0].data
return None
def installExtension(self, extensionSource, profileDir, extensionID = None):
"""
Copies an extension into the extensions directory of the given profile.
extensionSource - the source location of the extension files. This can be either
a directory or a path to an xpi file.
profileDir - the profile directory we are copying into. We will create the
"extensions" directory there if it doesn't exist.
extensionID - the id of the extension to be used as the containing directory for the
extension, if extensionSource is a directory, i.e.
this is the name of the folder in the <profileDir>/extensions/<extensionID>
"""
if not os.path.isdir(profileDir):
self.log.info("INFO | automation.py | Cannot install extension, invalid profileDir at: %s", profileDir)
return
installRDFFilename = "install.rdf"
extensionsRootDir = os.path.join(profileDir, "extensions", "staged")
if not os.path.isdir(extensionsRootDir):
os.makedirs(extensionsRootDir)
if os.path.isfile(extensionSource):
reader = zipfile.ZipFile(extensionSource, "r")
for filename in reader.namelist():
# Sanity check the zip file.
if os.path.isabs(filename):
self.log.info("INFO | automation.py | Cannot install extension, bad files in xpi")
return
# We may need to dig the extensionID out of the zip file...
if extensionID is None and filename == installRDFFilename:
extensionID = self.getExtensionIDFromRDF(reader.read(filename))
# We must know the extensionID now.
if extensionID is None:
self.log.info("INFO | automation.py | Cannot install extension, missing extensionID")
return
# Make the extension directory.
extensionDir = os.path.join(extensionsRootDir, extensionID)
os.mkdir(extensionDir)
# Extract all files.
reader.extractall(extensionDir)
elif os.path.isdir(extensionSource):
if extensionID is None:
filename = os.path.join(extensionSource, installRDFFilename)
if os.path.isfile(filename):
with open(filename, "r") as installRDF:
extensionID = self.getExtensionIDFromRDF(installRDF)
if extensionID is None:
self.log.info("INFO | automation.py | Cannot install extension, missing extensionID")
return
# Copy extension tree into its own directory.
# "destination directory must not already exist".
shutil.copytree(extensionSource, os.path.join(extensionsRootDir, extensionID))
else:
self.log.info("INFO | automation.py | Cannot install extension, invalid extensionSource at: %s", extensionSource)
def elf_arm(self, filename):
data = open(filename, 'rb').read(20)
return data[:4] == "\x7fELF" and ord(data[18]) == 40 # EM_ARM

View File

@ -14,6 +14,7 @@ import signal
import tempfile
import time
import traceback
import zipfile
from automation import Automation
from mozlog.structured import get_default_logger
@ -73,10 +74,81 @@ class B2GRemoteAutomation(Automation):
def setRemoteLog(self, logfile):
self._remoteLog = logfile
def getExtensionIDFromRDF(self, rdfSource):
"""
Retrieves the extension id from an install.rdf file (or string).
"""
from xml.dom.minidom import parse, parseString, Node
if isinstance(rdfSource, file):
document = parse(rdfSource)
else:
document = parseString(rdfSource)
# Find the <em:id> element. There can be multiple <em:id> tags
# within <em:targetApplication> tags, so we have to check this way.
for rdfChild in document.documentElement.childNodes:
if rdfChild.nodeType == Node.ELEMENT_NODE and rdfChild.tagName == "Description":
for descChild in rdfChild.childNodes:
if descChild.nodeType == Node.ELEMENT_NODE and descChild.tagName == "em:id":
return descChild.childNodes[0].data
return None
def installExtension(self, extensionSource, profileDir, extensionID=None):
# Bug 827504 - installing special-powers extension separately causes problems in B2G
if extensionID != "special-powers@mozilla.org":
Automation.installExtension(self, extensionSource, profileDir, extensionID)
if not os.path.isdir(profileDir):
self.log.info("INFO | automation.py | Cannot install extension, invalid profileDir at: %s", profileDir)
return
installRDFFilename = "install.rdf"
extensionsRootDir = os.path.join(profileDir, "extensions", "staged")
if not os.path.isdir(extensionsRootDir):
os.makedirs(extensionsRootDir)
if os.path.isfile(extensionSource):
reader = zipfile.ZipFile(extensionSource, "r")
for filename in reader.namelist():
# Sanity check the zip file.
if os.path.isabs(filename):
self.log.info("INFO | automation.py | Cannot install extension, bad files in xpi")
return
# We may need to dig the extensionID out of the zip file...
if extensionID is None and filename == installRDFFilename:
extensionID = self.getExtensionIDFromRDF(reader.read(filename))
# We must know the extensionID now.
if extensionID is None:
self.log.info("INFO | automation.py | Cannot install extension, missing extensionID")
return
# Make the extension directory.
extensionDir = os.path.join(extensionsRootDir, extensionID)
os.mkdir(extensionDir)
# Extract all files.
reader.extractall(extensionDir)
elif os.path.isdir(extensionSource):
if extensionID is None:
filename = os.path.join(extensionSource, installRDFFilename)
if os.path.isfile(filename):
with open(filename, "r") as installRDF:
extensionID = self.getExtensionIDFromRDF(installRDF)
if extensionID is None:
self.log.info("INFO | automation.py | Cannot install extension, missing extensionID")
return
# Copy extension tree into its own directory.
# "destination directory must not already exist".
shutil.copytree(extensionSource, os.path.join(extensionsRootDir, extensionID))
else:
self.log.info("INFO | automation.py | Cannot install extension, invalid extensionSource at: %s", extensionSource)
# Set up what we need for the remote environment
def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False):

View File

@ -11,7 +11,6 @@
#include "plstr.h"
#include "nsXPIDLString.h"
#include "nsCOMPtr.h"
#include "nsIJSRuntimeService.h"
#include "nsIServiceManager.h"
#include "nsMemory.h"
#include "nsStringBuffer.h"

View File

@ -46,7 +46,6 @@
#include "nsIPrompt.h"
#include "nsIWindowWatcher.h"
#include "nsIConsoleService.h"
#include "nsIJSRuntimeService.h"
#include "nsIObserverService.h"
#include "nsIContent.h"
#include "nsAutoPtr.h"
@ -1257,8 +1256,7 @@ nsresult nsScriptSecurityManager::Init()
//-- Register security check callback in the JS engine
// Currently this is used to control access to function.caller
rv = nsXPConnect::XPConnect()->GetRuntime(&sRuntime);
NS_ENSURE_SUCCESS(rv, rv);
sRuntime = xpc::GetJSRuntime();
static const JSSecurityCallbacks securityCallbacks = {
ContentSecurityPolicyPermitsJSAction,

View File

@ -14028,6 +14028,11 @@ nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNavigate,
return NS_OK;
}
// No in private browsing
if (mInPrivateBrowsing) {
return NS_OK;
}
if (mSandboxFlags) {
// If we're sandboxed, don't intercept.
return NS_OK;

View File

@ -130,7 +130,6 @@
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIIOService.h"
#include "nsIJSRuntimeService.h"
#include "nsILineBreaker.h"
#include "nsILoadContext.h"
#include "nsILoadGroup.h"
@ -2989,18 +2988,29 @@ nsContentUtils::IsInPrivateBrowsing(nsIDocument* aDoc)
if (!aDoc) {
return false;
}
bool isPrivate = false;
nsCOMPtr<nsILoadGroup> loadGroup = aDoc->GetDocumentLoadGroup();
nsCOMPtr<nsIInterfaceRequestor> callbacks;
if (loadGroup) {
loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
if (callbacks) {
nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
isPrivate = loadContext && loadContext->UsePrivateBrowsing();
}
} else {
nsCOMPtr<nsIChannel> channel = aDoc->GetChannel();
isPrivate = channel && NS_UsePrivateBrowsing(channel);
return IsInPrivateBrowsing(loadGroup);
}
nsCOMPtr<nsIChannel> channel = aDoc->GetChannel();
return channel && NS_UsePrivateBrowsing(channel);
}
// static
bool
nsContentUtils::IsInPrivateBrowsing(nsILoadGroup* aLoadGroup)
{
if (!aLoadGroup) {
return false;
}
bool isPrivate = false;
nsCOMPtr<nsIInterfaceRequestor> callbacks;
aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
if (callbacks) {
nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
isPrivate = loadContext && loadContext->UsePrivateBrowsing();
}
return isPrivate;
}

View File

@ -69,6 +69,7 @@ class nsIImageLoadingContent;
class nsIInterfaceRequestor;
class nsIIOService;
class nsILineBreaker;
class nsILoadGroup;
class nsIMessageBroadcaster;
class nsNameSpaceManager;
class nsIObserver;
@ -730,6 +731,11 @@ public:
*/
static bool IsInPrivateBrowsing(nsIDocument* aDoc);
/**
* Returns true if this loadGroup uses Private Browsing.
*/
static bool IsInPrivateBrowsing(nsILoadGroup* aLoadGroup);
/**
* If aNode is not an element, return true exactly when aContent's binding
* parent is null.

View File

@ -1562,6 +1562,9 @@ nsIDocument::nsIDocument()
mAllowDNSPrefetch(true),
mIsBeingUsedAsImage(false),
mHasLinksToUpdate(false),
mFontFaceSetDirty(true),
mGetUserFontSetCalled(false),
mPostedFlushUserFontSet(false),
mPartID(0),
mDidFireDOMContentLoaded(true)
{
@ -1948,6 +1951,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
// Traverse all nsIDocument pointer members.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
// Traverse all nsDocument nsCOMPtrs.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
@ -2077,6 +2081,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMasterDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubImportLinks)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
tmp->mParentDocument = nullptr;
@ -3815,6 +3820,9 @@ nsDocument::doCreateShell(nsPresContext* aContext,
MaybeRescheduleAnimationFrameNotifications();
// Now that we have a shell, we might have @font-face rules.
RebuildUserFontSet();
return shell.forget();
}
@ -3901,6 +3909,10 @@ nsDocument::DeleteShell()
// no longer intend to paint.
mImageTracker.EnumerateRead(RequestDiscardEnumerator, nullptr);
// Now that we no longer have a shell, we need to forget about any FontFace
// objects for @font-face rules that came from the style set.
RebuildUserFontSet();
mPresShell = nullptr;
}
@ -12956,20 +12968,122 @@ nsAutoSyncOperation::~nsAutoSyncOperation()
nsContentUtils::SetMicroTaskLevel(mMicroTaskLevel);
}
FontFaceSet*
nsIDocument::GetFonts(ErrorResult& aRv)
gfxUserFontSet*
nsIDocument::GetUserFontSet()
{
nsIPresShell* shell = GetShell();
if (!shell) {
aRv.Throw(NS_ERROR_FAILURE);
// We want to initialize the user font set lazily the first time the
// user asks for it, rather than building it too early and forcing
// rule cascade creation. Thus we try to enforce the invariant that
// we *never* build the user font set until the first call to
// GetUserFontSet. However, once it's been requested, we can't wait
// for somebody to call GetUserFontSet in order to rebuild it (see
// comments below in RebuildUserFontSet for why).
#ifdef DEBUG
bool userFontSetGottenBefore = mGetUserFontSetCalled;
#endif
// Set mGetUserFontSetCalled up front, so that FlushUserFontSet will actually
// flush.
mGetUserFontSetCalled = true;
if (mFontFaceSetDirty) {
// If this assertion fails, and there have actually been changes to
// @font-face rules, then we will call StyleChangeReflow in
// FlushUserFontSet. If we're in the middle of reflow,
// that's a bad thing to do, and the caller was responsible for
// flushing first. If we're not (e.g., in frame construction), it's
// ok.
NS_ASSERTION(!userFontSetGottenBefore ||
!GetShell() ||
!GetShell()->IsReflowLocked(),
"FlushUserFontSet should have been called first");
FlushUserFontSet();
}
if (!mFontFaceSet) {
return nullptr;
}
nsPresContext* presContext = shell->GetPresContext();
if (!presContext) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
return presContext->Fonts();
return mFontFaceSet->GetUserFontSet();
}
void
nsIDocument::FlushUserFontSet()
{
if (!mGetUserFontSetCalled) {
return; // No one cares about this font set yet, but we want to be careful
// to not unset our mFontFaceSetDirty bit, so when someone really
// does we'll create it.
}
if (mFontFaceSetDirty) {
if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
nsTArray<nsFontFaceRuleContainer> rules;
nsIPresShell* shell = GetShell();
if (shell && !shell->StyleSet()->AppendFontFaceRules(rules)) {
return;
}
bool changed = false;
if (!mFontFaceSet && !rules.IsEmpty()) {
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetScopeObject());
mFontFaceSet = new FontFaceSet(window, this);
}
if (mFontFaceSet) {
changed = mFontFaceSet->UpdateRules(rules);
}
// We need to enqueue a style change reflow (for later) to
// reflect that we're modifying @font-face rules. (However,
// without a reflow, nothing will happen to start any downloads
// that are needed.)
if (changed && shell) {
nsPresContext* presContext = shell->GetPresContext();
if (presContext) {
presContext->UserFontSetUpdated();
}
}
}
mFontFaceSetDirty = false;
}
}
void
nsIDocument::RebuildUserFontSet()
{
if (!mGetUserFontSetCalled) {
// We want to lazily build the user font set the first time it's
// requested (so we don't force creation of rule cascades too
// early), so don't do anything now.
return;
}
mFontFaceSetDirty = true;
SetNeedStyleFlush();
// Somebody has already asked for the user font set, so we need to
// post an event to rebuild it. Setting the user font set to be dirty
// and lazily rebuilding it isn't sufficient, since it is only the act
// of rebuilding it that will trigger the style change reflow that
// calls GetUserFontSet. (This reflow causes rebuilding of text runs,
// which starts font loads, whose completion causes another style
// change reflow).
if (!mPostedFlushUserFontSet) {
nsCOMPtr<nsIRunnable> ev =
NS_NewRunnableMethod(this, &nsIDocument::HandleRebuildUserFontSet);
if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
mPostedFlushUserFontSet = true;
}
}
}
FontFaceSet*
nsIDocument::Fonts()
{
if (!mFontFaceSet) {
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetScopeObject());
mFontFaceSet = new FontFaceSet(window, this);
GetUserFontSet(); // this will cause the user font set to be created/updated
}
return mFontFaceSet;
}

View File

@ -26,7 +26,6 @@
#include "nsIMemoryReporter.h"
#include "nsIProtocolHandler.h"
#include "nsIScriptSecurityManager.h"
#include "nsIJSRuntimeService.h"
#include "nsIDOMClassInfo.h"
#include "xpcpublic.h"
#include "mozilla/CycleCollectedJSRuntime.h"
@ -1822,15 +1821,6 @@ nsMessageManagerScriptExecutor::InitChildGlobalInternal(
nsISupports* aScope,
const nsACString& aID)
{
nsCOMPtr<nsIJSRuntimeService> runtimeSvc =
do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
NS_ENSURE_TRUE(runtimeSvc, false);
JSRuntime* rt = nullptr;
runtimeSvc->GetRuntime(&rt);
NS_ENSURE_TRUE(rt, false);
AutoSafeJSContext cx;
nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(mPrincipal));

View File

@ -2241,6 +2241,8 @@ GK_ATOM(aria_atomic, "aria-atomic")
GK_ATOM(aria_autocomplete, "aria-autocomplete")
GK_ATOM(aria_busy, "aria-busy")
GK_ATOM(aria_checked, "aria-checked")
GK_ATOM(aria_colcount, "aria-colcount")
GK_ATOM(aria_colindex, "aria-colindex")
GK_ATOM(aria_controls, "aria-controls")
GK_ATOM(aria_describedby, "aria-describedby")
GK_ATOM(aria_disabled, "aria-disabled")
@ -2265,6 +2267,8 @@ GK_ATOM(aria_pressed, "aria-pressed")
GK_ATOM(aria_readonly, "aria-readonly")
GK_ATOM(aria_relevant, "aria-relevant")
GK_ATOM(aria_required, "aria-required")
GK_ATOM(aria_rowcount, "aria-rowcount")
GK_ATOM(aria_rowindex, "aria-rowindex")
GK_ATOM(aria_selected, "aria-selected")
GK_ATOM(aria_setsize, "aria-setsize")
GK_ATOM(aria_sort, "aria-sort")

View File

@ -10285,11 +10285,9 @@ nsGlobalWindow::GetSessionStorage(ErrorResult& aError)
return nullptr;
}
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
nsCOMPtr<nsIDOMStorage> storage;
aError = storageManager->CreateStorage(this, principal, documentURI,
loadContext && loadContext->UsePrivateBrowsing(),
IsPrivateBrowsing(),
getter_AddRefs(storage));
if (aError.Failed()) {
return nullptr;
@ -10365,12 +10363,9 @@ nsGlobalWindow::GetLocalStorage(ErrorResult& aError)
mDoc->GetDocumentURI(documentURI);
}
nsIDocShell* docShell = GetDocShell();
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
nsCOMPtr<nsIDOMStorage> storage;
aError = storageManager->CreateStorage(this, principal, documentURI,
loadContext && loadContext->UsePrivateBrowsing(),
IsPrivateBrowsing(),
getter_AddRefs(storage));
if (aError.Failed()) {
return nullptr;
@ -10500,8 +10495,17 @@ already_AddRefed<CacheStorage>
nsGlobalWindow::GetCaches(ErrorResult& aRv)
{
if (!mCacheStorage) {
bool forceTrustedOrigin = false;
if (IsOuterWindow()) {
forceTrustedOrigin = GetServiceWorkersTestingEnabled();
} else {
nsRefPtr<nsGlobalWindow> outer = GetOuterWindowInternal();
forceTrustedOrigin = outer->GetServiceWorkersTestingEnabled();
}
mCacheStorage = CacheStorage::CreateOnMainThread(cache::DEFAULT_NAMESPACE,
this, GetPrincipal(), aRv);
this, GetPrincipal(),
IsPrivateBrowsing(),
forceTrustedOrigin, aRv);
}
nsRefPtr<CacheStorage> ref = mCacheStorage;
@ -11200,9 +11204,7 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(GetDocShell());
bool isPrivate = loadContext && loadContext->UsePrivateBrowsing();
if (changingStorage->IsPrivate() != isPrivate) {
if (changingStorage->IsPrivate() != IsPrivateBrowsing()) {
return NS_OK;
}
@ -12634,6 +12636,13 @@ nsGlobalWindow::SecurityCheckURL(const char *aURL)
return NS_OK;
}
bool
nsGlobalWindow::IsPrivateBrowsing()
{
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(GetDocShell());
return loadContext && loadContext->UsePrivateBrowsing();
}
void
nsGlobalWindow::FlushPendingNotifications(mozFlushType aType)
{

View File

@ -1351,6 +1351,7 @@ public:
already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
nsresult SecurityCheckURL(const char *aURL);
bool IsPrivateBrowsing();
bool PopupWhitelisted();
PopupControlState RevisePopupAbuseLevel(PopupControlState);

View File

@ -34,6 +34,7 @@
#include "mozilla/CORSMode.h"
#include <bitset> // for member
class gfxUserFontSet;
class imgIRequest;
class nsAString;
class nsBindingManager;
@ -151,8 +152,8 @@ typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
} // namespace mozilla
#define NS_IDOCUMENT_IID \
{ 0x24fbaa06, 0x322b, 0x495f, \
{0x89, 0xcb, 0x33, 0xbc, 0x6f, 0x2d, 0xf6, 0x93} }
{ 0x21bbd52a, 0xc2d2, 0x4b2f, \
{ 0xbc, 0x6c, 0xc9, 0x52, 0xbe, 0x23, 0x6b, 0x19 } }
// Enum for requesting a particular type of document when creating a doc
enum DocumentFlavor {
@ -2560,8 +2561,13 @@ public:
}
}
gfxUserFontSet* GetUserFontSet();
void FlushUserFontSet();
void RebuildUserFontSet(); // asynchronously
mozilla::dom::FontFaceSet* GetFonts() { return mFontFaceSet; }
// FontFaceSource
mozilla::dom::FontFaceSet* GetFonts(mozilla::ErrorResult& aRv);
mozilla::dom::FontFaceSet* Fonts();
bool DidFireDOMContentLoaded() const { return mDidFireDOMContentLoaded; }
@ -2606,6 +2612,11 @@ protected:
mozilla::dom::XPathEvaluator* XPathEvaluator();
void HandleRebuildUserFontSet() {
mPostedFlushUserFontSet = false;
FlushUserFontSet();
}
nsCString mReferrer;
nsString mLastModified;
@ -2662,6 +2673,9 @@ protected:
// Our cached .children collection
nsCOMPtr<nsIHTMLCollection> mChildrenCollection;
// container for per-context fonts (downloadable, SVG, etc.)
nsRefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
// Compatibility mode
nsCompatibility mCompatMode;
@ -2794,6 +2808,15 @@ protected:
bool mIsLinkUpdateRegistrationsForbidden;
#endif
// Is the current mFontFaceSet valid?
bool mFontFaceSetDirty;
// Has GetUserFontSet() been called?
bool mGetUserFontSetCalled;
// Do we currently have an event posted to call FlushUserFontSet?
bool mPostedFlushUserFontSet;
enum Type {
eUnknown, // should never be used
eHTML,

View File

@ -10,7 +10,6 @@
#include "nsIInterfaceRequestorUtils.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsIJSRuntimeService.h"
#include "nsComponentManagerUtils.h"
#include "nsNetUtil.h"
#include "nsScriptLoader.h"

View File

@ -14,7 +14,6 @@
#include "nsDOMCID.h"
#include "nsIServiceManager.h"
#include "nsIXPConnect.h"
#include "nsIJSRuntimeService.h"
#include "nsCOMPtr.h"
#include "nsISupportsPrimitives.h"
#include "nsReadableUtils.h"
@ -197,11 +196,6 @@ static bool sDidPaintAfterPreviousICCSlice = false;
static nsScriptNameSpaceManager *gNameSpaceManager;
static nsIJSRuntimeService *sRuntimeService;
static const char kJSRuntimeServiceContractID[] =
"@mozilla.org/js/xpc/RuntimeService;1";
static PRTime sFirstCollectionTime;
static JSRuntime *sRuntime;
@ -651,10 +645,8 @@ nsJSContext::~nsJSContext()
if (!sContextCount && sDidShutdown) {
// The last context is being deleted, and we're already in the
// process of shutting down, release the JS runtime service, and
// the security manager.
// process of shutting down, release the security manager.
NS_IF_RELEASE(sRuntimeService);
NS_IF_RELEASE(sSecurityManager);
}
}
@ -2372,7 +2364,6 @@ mozilla::dom::StartupJSEnvironment()
sNeedsFullCC = false;
sNeedsGCAfterCC = false;
gNameSpaceManager = nullptr;
sRuntimeService = nullptr;
sRuntime = nullptr;
sIsInitialized = false;
sDidShutdown = false;
@ -2673,13 +2664,8 @@ nsJSContext::EnsureStatics()
MOZ_CRASH();
}
rv = CallGetService(kJSRuntimeServiceContractID, &sRuntimeService);
if (NS_FAILED(rv)) {
MOZ_CRASH();
}
rv = sRuntimeService->GetRuntime(&sRuntime);
if (NS_FAILED(rv)) {
sRuntime = xpc::GetJSRuntime();
if (!sRuntime) {
MOZ_CRASH();
}
@ -2850,9 +2836,7 @@ mozilla::dom::ShutdownJSEnvironment()
if (!sContextCount) {
// We're being shutdown, and there are no more contexts
// alive, release the JS runtime service and the security manager.
NS_IF_RELEASE(sRuntimeService);
// alive, release the security manager.
NS_IF_RELEASE(sSecurityManager);
}

View File

@ -12,6 +12,7 @@
#include "jsapi.h"
#include "jsfriendapi.h"
#include "xpcpublic.h"
#include "nsIUnicodeDecoder.h"
#include "nsIContent.h"
#include "nsJSUtils.h"
@ -19,7 +20,6 @@
#include "mozilla/dom/Element.h"
#include "nsGkAtoms.h"
#include "nsNetUtil.h"
#include "nsIJSRuntimeService.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptContext.h"
#include "nsIScriptSecurityManager.h"
@ -831,12 +831,7 @@ NotifyOffThreadScriptLoadCompletedRunnable::Run()
// The result of the off thread parse was not actually needed to process
// the request (disappearing window, some other error, ...). Finish the
// request to avoid leaks in the JS engine.
nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
JSRuntime *rt;
svc->GetRuntime(&rt);
NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
JS::FinishOffThreadScript(nullptr, rt, mToken);
JS::FinishOffThreadScript(nullptr, xpc::GetJSRuntime(), mToken);
}
return rv;

View File

@ -495,6 +495,10 @@ DOMInterfaces = {
'wrapperCache': False,
},
'FontFaceSet': {
'implicitJSContext': [ 'load' ],
},
'FontFaceSetIterator': {
'wrapperCache': False,
},

View File

@ -22,8 +22,10 @@
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsIDocument.h"
#include "nsIGlobalObject.h"
#include "nsIScriptSecurityManager.h"
#include "nsURLParsers.h"
#include "WorkerPrivate.h"
namespace mozilla {
@ -61,47 +63,118 @@ struct CacheStorage::Entry final
nsRefPtr<InternalRequest> mRequest;
};
namespace {
bool
IsTrusted(const PrincipalInfo& aPrincipalInfo, bool aTestingPrefEnabled)
{
// Can happen on main thread or worker thread
if (aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
return true;
}
// Require a ContentPrincipal to avoid null principal, etc.
//
// Also, an unknown appId means that this principal was created for the
// codebase without all the security information from the end document or
// worker. We require exact knowledge of this information before allowing
// the caller to touch the disk using the Cache API.
if (NS_WARN_IF(aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo ||
aPrincipalInfo.get_ContentPrincipalInfo().appId() ==
nsIScriptSecurityManager::UNKNOWN_APP_ID)) {
return false;
}
// If we're in testing mode, then don't do any more work to determing if
// the origin is trusted. We have to run some tests as http.
if (aTestingPrefEnabled) {
return true;
}
// Now parse the scheme of the principal's origin. This is a short term
// method for determining "trust". In the long term we need to implement
// the full algorithm here:
//
// https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-secure
//
// TODO: Implement full secure setting algorithm. (bug 1177856)
const nsCString& flatURL = aPrincipalInfo.get_ContentPrincipalInfo().spec();
const char* url = flatURL.get();
// off the main thread URL parsing using nsStdURLParser.
nsCOMPtr<nsIURLParser> urlParser = new nsStdURLParser();
uint32_t schemePos;
int32_t schemeLen;
uint32_t authPos;
int32_t authLen;
nsresult rv = urlParser->ParseURL(url, flatURL.Length(),
&schemePos, &schemeLen,
&authPos, &authLen,
nullptr, nullptr); // ignore path
if (NS_WARN_IF(NS_FAILED(rv))) { return false; }
nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen));
if (scheme.LowerCaseEqualsLiteral("https") ||
scheme.LowerCaseEqualsLiteral("app") ||
scheme.LowerCaseEqualsLiteral("file")) {
return true;
}
uint32_t hostPos;
int32_t hostLen;
rv = urlParser->ParseAuthority(url + authPos, authLen,
nullptr, nullptr, // ignore username
nullptr, nullptr, // ignore password
&hostPos, &hostLen,
nullptr); // ignore port
if (NS_WARN_IF(NS_FAILED(rv))) { return false; }
nsDependentCSubstring hostname(url + authPos + hostPos, hostLen);
return hostname.EqualsLiteral("localhost") ||
hostname.EqualsLiteral("127.0.0.1") ||
hostname.EqualsLiteral("::1");
}
} // anonymous namespace
// static
already_AddRefed<CacheStorage>
CacheStorage::CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal,
nsIPrincipal* aPrincipal, ErrorResult& aRv)
nsIPrincipal* aPrincipal, bool aPrivateBrowsing,
bool aForceTrustedOrigin, ErrorResult& aRv)
{
MOZ_ASSERT(aGlobal);
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(NS_IsMainThread());
bool nullPrincipal;
nsresult rv = aPrincipal->GetIsNullPrincipal(&nullPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return nullptr;
}
if (nullPrincipal) {
NS_WARNING("CacheStorage not supported on null principal.");
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
// An unknown appId means that this principal was created for the codebase
// without all the security information from the end document or worker.
// We require exact knowledge of this information before allowing the
// caller to touch the disk using the Cache API.
bool unknownAppId = false;
aPrincipal->GetUnknownAppId(&unknownAppId);
if (unknownAppId) {
NS_WARNING("CacheStorage not supported on principal with unknown appId.");
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
if (aPrivateBrowsing) {
NS_WARNING("CacheStorage not supported during private browsing.");
nsRefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
PrincipalInfo principalInfo;
rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return nullptr;
}
bool testingEnabled = aForceTrustedOrigin ||
Preferences::GetBool("dom.caches.testing.enabled", false) ||
Preferences::GetBool("dom.serviceWorkers.testing.enabled", false);
if (!IsTrusted(principalInfo, testingEnabled)) {
NS_WARNING("CacheStorage not supported on untrusted origins.");
nsRefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
nsRefPtr<CacheStorage> ref = new CacheStorage(aNamespace, aGlobal,
principalInfo, nullptr);
return ref.forget();
@ -116,6 +189,12 @@ CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
if (aWorkerPrivate->IsInPrivateBrowsing()) {
NS_WARNING("CacheStorage not supported during private browsing.");
nsRefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
nsRefPtr<Feature> feature = Feature::Create(aWorkerPrivate);
if (!feature) {
NS_WARNING("Worker thread is shutting down.");
@ -124,18 +203,15 @@ CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
}
const PrincipalInfo& principalInfo = aWorkerPrivate->GetPrincipalInfo();
if (principalInfo.type() == PrincipalInfo::TNullPrincipalInfo) {
NS_WARNING("CacheStorage not supported on null principal.");
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
if (principalInfo.type() == PrincipalInfo::TContentPrincipalInfo &&
principalInfo.get_ContentPrincipalInfo().appId() ==
nsIScriptSecurityManager::UNKNOWN_APP_ID) {
NS_WARNING("CacheStorage not supported on principal with unknown appId.");
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
bool testingEnabled = aWorkerPrivate->DOMCachesTestingEnabled() ||
aWorkerPrivate->ServiceWorkersTestingEnabled() ||
aWorkerPrivate->ServiceWorkersTestingInWindow();
if (!IsTrusted(principalInfo, testingEnabled)) {
NS_WARNING("CacheStorage not supported on untrusted origins.");
nsRefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
return ref.forget();
}
nsRefPtr<CacheStorage> ref = new CacheStorage(aNamespace, aGlobal,
@ -150,7 +226,7 @@ CacheStorage::CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
, mPrincipalInfo(MakeUnique<PrincipalInfo>(aPrincipalInfo))
, mFeature(aFeature)
, mActor(nullptr)
, mFailedActor(false)
, mStatus(NS_OK)
{
MOZ_ASSERT(mGlobal);
@ -171,14 +247,22 @@ CacheStorage::CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
}
}
CacheStorage::CacheStorage(nsresult aFailureResult)
: mNamespace(INVALID_NAMESPACE)
, mActor(nullptr)
, mStatus(aFailureResult)
{
MOZ_ASSERT(NS_FAILED(mStatus));
}
already_AddRefed<Promise>
CacheStorage::Match(const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(mFailedActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
@ -212,8 +296,8 @@ CacheStorage::Has(const nsAString& aKey, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(mFailedActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
@ -237,8 +321,8 @@ CacheStorage::Open(const nsAString& aKey, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(mFailedActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
@ -262,8 +346,8 @@ CacheStorage::Delete(const nsAString& aKey, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(mFailedActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
@ -287,8 +371,8 @@ CacheStorage::Keys(ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
if (NS_WARN_IF(mFailedActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
if (NS_WARN_IF(NS_FAILED(mStatus))) {
aRv.Throw(mStatus);
return nullptr;
}
@ -335,7 +419,21 @@ CacheStorage::Constructor(const GlobalObject& aGlobal,
Namespace ns = static_cast<Namespace>(aNamespace);
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
return CreateOnMainThread(ns, global, aPrincipal, aRv);
bool privateBrowsing = false;
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
if (window) {
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
if (doc) {
nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
privateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
}
}
// Create a CacheStorage object bypassing the trusted origin checks
// since this is a chrome-only constructor.
return CreateOnMainThread(ns, global, aPrincipal, privateBrowsing,
true /* force trusted origin */, aRv);
}
nsISupports*
@ -386,9 +484,9 @@ void
CacheStorage::ActorFailed()
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
MOZ_ASSERT(!mFailedActor);
MOZ_ASSERT(!NS_FAILED(mStatus));
mFailedActor = true;
mStatus = NS_ERROR_UNEXPECTED;
mFeature = nullptr;
for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {

View File

@ -49,7 +49,8 @@ class CacheStorage final : public nsIIPCBackgroundChildCreateCallback
public:
static already_AddRefed<CacheStorage>
CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal,
nsIPrincipal* aPrincipal, ErrorResult& aRv);
nsIPrincipal* aPrincipal, bool aPrivateBrowsing,
bool aForceTrustedOrigin, ErrorResult& aRv);
static already_AddRefed<CacheStorage>
CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
@ -94,6 +95,7 @@ public:
private:
CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo, Feature* aFeature);
explicit CacheStorage(nsresult aFailureResult);
~CacheStorage();
void MaybeRunPendingRequests();
@ -109,7 +111,7 @@ private:
struct Entry;
nsTArray<nsAutoPtr<Entry>> mPendingRequests;
bool mFailedActor;
nsresult mStatus;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS

4
dom/cache/moz.build vendored
View File

@ -100,3 +100,7 @@ MOCHITEST_MANIFESTS += [
MOCHITEST_CHROME_MANIFESTS += [
'test/mochitest/chrome.ini',
]
BROWSER_CHROME_MANIFESTS += [
'test/mochitest/browser.ini',
]

1
dom/cache/test/mochitest/browser.ini vendored Normal file
View File

@ -0,0 +1 @@
[browser_cache_pb_window.js]

View File

@ -0,0 +1,83 @@
var name = 'pb-window-cache';
function testMatch(win) {
return new Promise(function(resolve, reject) {
win.caches.match('http://foo.com').then(function(response) {
ok(false, 'caches.match() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.match() should throw SecurityError');
resolve();
});
});
}
function testHas(win) {
return new Promise(function(resolve, reject) {
win.caches.has(name).then(function(result) {
ok(false, 'caches.has() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.has() should throw SecurityError');
resolve();
});
});
}
function testOpen(win) {
return new Promise(function(resolve, reject) {
win.caches.open(name).then(function(c) {
ok(false, 'caches.open() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.open() should throw SecurityError');
resolve();
});
});
}
function testDelete(win) {
return new Promise(function(resolve, reject) {
win.caches.delete(name).then(function(result) {
ok(false, 'caches.delete() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.delete() should throw SecurityError');
resolve();
});
});
}
function testKeys(win) {
return new Promise(function(resolve, reject) {
win.caches.keys().then(function(names) {
ok(false, 'caches.keys() should not return success');
reject();
}).catch(function(err) {
is('SecurityError', err.name, 'caches.keys() should throw SecurityError');
resolve();
});
});
}
function test() {
waitForExplicitFinish();
SpecialPowers.pushPrefEnv({'set': [['browser.privatebrowing.autostart', true],
['dom.caches.enabled', true],
['dom.caches.testing.enabled', true]]},
function() {
var privateWin = OpenBrowserWindow({private: true});
privateWin.addEventListener('load', function() {
Promise.all([
testMatch(privateWin),
testHas(privateWin),
testOpen(privateWin),
testDelete(privateWin),
testKeys(privateWin)
]).then(function() {
privateWin.close();
finish();
});
});
});
}

View File

@ -21,6 +21,7 @@ function runTests(testFile, order) {
return new Promise(function(resolve, reject) {
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
["dom.serviceWorkers.exemptFromPerDomainMax", true]]

View File

@ -42,3 +42,4 @@ support-files =
[test_cache_shrink.html]
[test_cache_orphaned_cache.html]
[test_cache_orphaned_body.html]
[test_cache_untrusted.html]

View File

@ -76,6 +76,7 @@ function gc() {
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true],
["dom.quotaManager.testing", true]],
}, function() {
var name = 'orphanedBodyOwner';

View File

@ -76,6 +76,7 @@ function gc() {
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true],
["dom.quotaManager.testing", true]],
}, function() {
var name = 'toBeOrphaned';

View File

@ -39,6 +39,7 @@ function resetStorage() {
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true],
["dom.quotaManager.testing", true]],
}, function() {
var name = 'foo';

View File

@ -76,6 +76,7 @@ function gc() {
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true],
["dom.quotaManager.testing", true]],
}, function() {
var name = 'foo';

View File

@ -0,0 +1,40 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test Cache with QuotaManager Restart</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="large_url_list.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
function setupTestIframe() {
return new Promise(function(resolve) {
var iframe = document.createElement("iframe");
iframe.src = "empty.html";
iframe.onload = function() {
window.caches = iframe.contentWindow.caches;
resolve();
};
document.body.appendChild(iframe);
});
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true]],
}, function() {
setupTestIframe().then(function() {
return caches.open('foo');
}).then(function(usage) {
ok(false, 'caches should not be usable in untrusted http origin');
}).catch(function(err) {
is(err.name, 'SecurityError', 'caches should reject with SecurityError');
SimpleTest.finish();
});
});
</script>
</body>
</html>

View File

@ -14,7 +14,8 @@
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set": [["dom.caches.enabled", true]],
"set": [["dom.caches.enabled", true],
["dom.caches.testing.enabled", true]],
}, function() {
// attach to a different origin's CacheStorage
var url = 'http://example.com/';

View File

@ -264,7 +264,7 @@ IMEContentObserver::NotifyIMEOfBlur()
// Anyway, as far as we know, IME doesn't try to query content when it loses
// focus. So, this may not cause any problem.
mIMEHasFocus = false;
widget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget);
}
void
@ -513,7 +513,7 @@ IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext,
notification.mMouseButtonEventData.mButtons = aMouseEvent->buttons;
notification.mMouseButtonEventData.mModifiers = aMouseEvent->modifiers;
nsresult rv = mWidget->NotifyIME(notification);
nsresult rv = IMEStateManager::NotifyIME(notification, mWidget);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
@ -1659,7 +1659,8 @@ IMEContentObserver::FocusSetEvent::Run()
}
mIMEContentObserver->mIMEHasFocus = true;
mIMEContentObserver->mWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS));
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS),
mIMEContentObserver->mWidget);
return NS_OK;
}
@ -1704,7 +1705,7 @@ IMEContentObserver::SelectionChangeEvent::Run()
notification.mSelectionChangeData.mReversed = selection.mReply.mReversed;
notification.mSelectionChangeData.mCausedByComposition =
mCausedByComposition;
mIMEContentObserver->mWidget->NotifyIME(notification);
IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget);
return NS_OK;
}
@ -1730,7 +1731,7 @@ IMEContentObserver::TextChangeEvent::Run()
notification.mTextChangeData.mNewEndOffset = mData.mAddedEndOffset;
notification.mTextChangeData.mCausedByComposition =
mData.mCausedOnlyByComposition;
mIMEContentObserver->mWidget->NotifyIME(notification);
IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget);
return NS_OK;
}
@ -1750,8 +1751,8 @@ IMEContentObserver::PositionChangeEvent::Run()
return NS_OK;
}
mIMEContentObserver->mWidget->NotifyIME(
IMENotification(NOTIFY_IME_OF_POSITION_CHANGE));
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE),
mIMEContentObserver->mWidget);
return NS_OK;
}

View File

@ -169,6 +169,8 @@ GetNotifyIMEMessageName(IMEMessage aMessage)
return "NOTIFY_IME_OF_COMPOSITION_UPDATE";
case NOTIFY_IME_OF_POSITION_CHANGE:
return "NOTIFY_IME_OF_POSITION_CHANGE";
case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
return "NOTIFY_IME_OF_MOUSE_BUTTON_EVENT";
case REQUEST_TO_COMMIT_COMPOSITION:
return "REQUEST_TO_COMMIT_COMPOSITION";
case REQUEST_TO_CANCEL_COMPOSITION:
@ -180,9 +182,11 @@ GetNotifyIMEMessageName(IMEMessage aMessage)
nsIContent* IMEStateManager::sContent = nullptr;
nsPresContext* IMEStateManager::sPresContext = nullptr;
StaticRefPtr<nsIWidget> IMEStateManager::sFocusedIMEWidget;
bool IMEStateManager::sInstalledMenuKeyboardListener = false;
bool IMEStateManager::sIsGettingNewIMEState = false;
bool IMEStateManager::sCheckForIMEUnawareWebApps = false;
bool IMEStateManager::sRemoteHasFocus = false;
// sActiveIMEContentObserver points to the currently active IMEContentObserver.
// sActiveIMEContentObserver is null if there is no focused editor.
@ -1092,21 +1096,26 @@ IMEStateManager::OnCompositionEventDiscarded(
// static
nsresult
IMEStateManager::NotifyIME(IMEMessage aMessage,
nsIWidget* aWidget)
nsIWidget* aWidget,
bool aOriginIsRemote)
{
nsRefPtr<TextComposition> composition;
if (aWidget && sTextCompositions) {
composition = sTextCompositions->GetCompositionFor(aWidget);
}
bool isSynthesizedForTests =
composition && composition->IsSynthesizedForTests();
return IMEStateManager::NotifyIME(IMENotification(aMessage), aWidget,
aOriginIsRemote);
}
// static
nsresult
IMEStateManager::NotifyIME(const IMENotification& aNotification,
nsIWidget* aWidget,
bool aOriginIsRemote)
{
MOZ_LOG(sISMLog, LogLevel::Info,
("ISM: IMEStateManager::NotifyIME(aMessage=%s, aWidget=0x%p), "
"composition=0x%p, composition->IsSynthesizedForTests()=%s",
GetNotifyIMEMessageName(aMessage), aWidget, composition.get(),
GetBoolName(isSynthesizedForTests)));
("ISM: IMEStateManager::NotifyIME(aNotification={ mMessage=%s }, "
"aWidget=0x%p, aOriginIsRemote=%s), sFocusedIMEWidget=0x%p, "
"sRemoteHasFocus=%s",
GetNotifyIMEMessageName(aNotification.mMessage), aWidget,
GetBoolName(aOriginIsRemote), sFocusedIMEWidget.get(),
GetBoolName(sRemoteHasFocus)));
if (NS_WARN_IF(!aWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
@ -1114,7 +1123,114 @@ IMEStateManager::NotifyIME(IMEMessage aMessage,
return NS_ERROR_INVALID_ARG;
}
switch (aMessage) {
switch (aNotification.mMessage) {
case NOTIFY_IME_OF_FOCUS:
if (sFocusedIMEWidget) {
if (NS_WARN_IF(!sRemoteHasFocus && !aOriginIsRemote)) {
MOZ_LOG(sISMLog, LogLevel::Error,
("ISM: IMEStateManager::NotifyIME(), although, this process is "
"getting IME focus but there was focused IME widget"));
} else {
MOZ_LOG(sISMLog, LogLevel::Info,
("ISM: IMEStateManager::NotifyIME(), tries to notify IME of "
"blur first because remote process's blur notification hasn't "
"been received yet..."));
}
nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget);
sFocusedIMEWidget = nullptr;
sRemoteHasFocus = false;
focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
}
sRemoteHasFocus = aOriginIsRemote;
sFocusedIMEWidget = aWidget;
return aWidget->NotifyIME(aNotification);
case NOTIFY_IME_OF_BLUR: {
if (!sRemoteHasFocus && aOriginIsRemote) {
MOZ_LOG(sISMLog, LogLevel::Info,
("ISM: IMEStateManager::NotifyIME(), received blur notification "
"after another one has focus, nothing to do..."));
return NS_OK;
}
if (NS_WARN_IF(sRemoteHasFocus && !aOriginIsRemote)) {
MOZ_LOG(sISMLog, LogLevel::Error,
("ISM: IMEStateManager::NotifyIME(), FAILED, received blur "
"notification from this process but the remote has focus"));
return NS_OK;
}
if (!sFocusedIMEWidget && aOriginIsRemote) {
MOZ_LOG(sISMLog, LogLevel::Info,
("ISM: IMEStateManager::NotifyIME(), received blur notification "
"but the remote has already lost focus"));
return NS_OK;
}
if (NS_WARN_IF(!sFocusedIMEWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
("ISM: IMEStateManager::NotifyIME(), FAILED, received blur "
"notification but there is no focused IME widget"));
return NS_OK;
}
if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
("ISM: IMEStateManager::NotifyIME(), FAILED, received blur "
"notification but there is no focused IME widget"));
return NS_OK;
}
nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget);
sFocusedIMEWidget = nullptr;
sRemoteHasFocus = false;
return focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
}
case NOTIFY_IME_OF_SELECTION_CHANGE:
case NOTIFY_IME_OF_TEXT_CHANGE:
case NOTIFY_IME_OF_POSITION_CHANGE:
case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
if (!sRemoteHasFocus && aOriginIsRemote) {
MOZ_LOG(sISMLog, LogLevel::Info,
("ISM: IMEStateManager::NotifyIME(), received content change "
"notification from the remote but it's already lost focus"));
return NS_OK;
}
if (NS_WARN_IF(sRemoteHasFocus && !aOriginIsRemote)) {
MOZ_LOG(sISMLog, LogLevel::Error,
("ISM: IMEStateManager::NotifyIME(), FAILED, received content "
"change notification from this process but the remote has already "
"gotten focus"));
return NS_OK;
}
if (!sFocusedIMEWidget) {
MOZ_LOG(sISMLog, LogLevel::Info,
("ISM: IMEStateManager::NotifyIME(), received content change "
"notification but there is no focused IME widget"));
return NS_OK;
}
if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
("ISM: IMEStateManager::NotifyIME(), FAILED, received content "
"change notification for IME which has already lost focus, so, "
"nothing to do..."));
return NS_OK;
}
return aWidget->NotifyIME(aNotification);
default:
// Other notifications should be sent only when there is composition.
// So, we need to handle the others below.
break;
}
nsRefPtr<TextComposition> composition;
if (sTextCompositions) {
composition = sTextCompositions->GetCompositionFor(aWidget);
}
bool isSynthesizedForTests =
composition && composition->IsSynthesizedForTests();
MOZ_LOG(sISMLog, LogLevel::Info,
("ISM: IMEStateManager::NotifyIME(), composition=0x%p, "
"composition->IsSynthesizedForTests()=%s",
composition.get(), GetBoolName(isSynthesizedForTests)));
switch (aNotification.mMessage) {
case REQUEST_TO_COMMIT_COMPOSITION:
return composition ?
composition->RequestToCommit(aWidget, false) : NS_OK;
@ -1123,7 +1239,7 @@ IMEStateManager::NotifyIME(IMEMessage aMessage,
composition->RequestToCommit(aWidget, true) : NS_OK;
case NOTIFY_IME_OF_COMPOSITION_UPDATE:
return composition && !isSynthesizedForTests ?
aWidget->NotifyIME(IMENotification(aMessage)) : NS_OK;
aWidget->NotifyIME(aNotification) : NS_OK;
default:
MOZ_CRASH("Unsupported notification");
}
@ -1135,11 +1251,14 @@ IMEStateManager::NotifyIME(IMEMessage aMessage,
// static
nsresult
IMEStateManager::NotifyIME(IMEMessage aMessage,
nsPresContext* aPresContext)
nsPresContext* aPresContext,
bool aOriginIsRemote)
{
MOZ_LOG(sISMLog, LogLevel::Info,
("ISM: IMEStateManager::NotifyIME(aMessage=%s, aPresContext=0x%p)",
GetNotifyIMEMessageName(aMessage), aPresContext));
("ISM: IMEStateManager::NotifyIME(aMessage=%s, aPresContext=0x%p, "
"aOriginIsRemote=%s)",
GetNotifyIMEMessageName(aMessage), aPresContext,
GetBoolName(aOriginIsRemote)));
NS_ENSURE_TRUE(aPresContext, NS_ERROR_INVALID_ARG);
@ -1150,7 +1269,7 @@ IMEStateManager::NotifyIME(IMEMessage aMessage,
"nsPresContext"));
return NS_ERROR_NOT_AVAILABLE;
}
return NotifyIME(aMessage, widget);
return NotifyIME(aMessage, widget, aOriginIsRemote);
}
// static

View File

@ -8,6 +8,7 @@
#define mozilla_IMEStateManager_h_
#include "mozilla/EventForwards.h"
#include "mozilla/StaticPtr.h"
#include "nsIWidget.h"
class nsIContent;
@ -33,6 +34,7 @@ class TextComposition;
class IMEStateManager
{
typedef widget::IMEMessage IMEMessage;
typedef widget::IMENotification IMENotification;
typedef widget::IMEState IMEState;
typedef widget::InputContext InputContext;
typedef widget::InputContextAction InputContextAction;
@ -140,8 +142,15 @@ public:
* Send a notification to IME. It depends on the IME or platform spec what
* will occur (or not occur).
*/
static nsresult NotifyIME(IMEMessage aMessage, nsIWidget* aWidget);
static nsresult NotifyIME(IMEMessage aMessage, nsPresContext* aPresContext);
static nsresult NotifyIME(const IMENotification& aNotification,
nsIWidget* aWidget,
bool aOriginIsRemote = false);
static nsresult NotifyIME(IMEMessage aMessage,
nsIWidget* aWidget,
bool aOriginIsRemote = false);
static nsresult NotifyIME(IMEMessage aMessage,
nsPresContext* aPresContext,
bool aOriginIsRemote = false);
static nsINode* GetRootEditableNode(nsPresContext* aPresContext,
nsIContent* aContent);
@ -167,9 +176,11 @@ protected:
static nsIContent* sContent;
static nsPresContext* sPresContext;
static StaticRefPtr<nsIWidget> sFocusedIMEWidget;
static bool sInstalledMenuKeyboardListener;
static bool sIsGettingNewIMEState;
static bool sCheckForIMEUnawareWebApps;
static bool sRemoteHasFocus;
class MOZ_STACK_CLASS GettingNewIMEStateBlocker final
{

View File

@ -92,7 +92,6 @@
#include "nsDebugImpl.h"
#include "nsHashPropertyBag.h"
#include "nsLayoutStylesheetCache.h"
#include "nsIJSRuntimeService.h"
#include "nsThreadManager.h"
#include "nsAnonymousTemporaryFile.h"
#include "nsISpellChecker.h"

View File

@ -116,7 +116,6 @@
#include "nsIFormProcessor.h"
#include "nsIGfxInfo.h"
#include "nsIIdleService.h"
#include "nsIJSRuntimeService.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMemoryReporter.h"
#include "nsIMozBrowserFrame.h"

View File

@ -248,7 +248,16 @@ TabChildBase::InitializeRootMetrics()
// This is the root layer, so the cumulative resolution is the same
// as the resolution.
mLastRootMetrics.SetPresShellResolution(mLastRootMetrics.GetCumulativeResolution().ToScaleFactor().scale);
mLastRootMetrics.SetScrollOffset(CSSPoint(0, 0));
nsCOMPtr<nsIPresShell> shell = GetPresShell();
if (shell && shell->GetRootScrollFrameAsScrollable()) {
// The session history code might restore a scroll position when navigating
// back or forward, and we don't want to clobber that.
nsPoint pos = shell->GetRootScrollFrameAsScrollable()->GetScrollPosition();
mLastRootMetrics.SetScrollOffset(CSSPoint::FromAppUnits(pos));
} else {
mLastRootMetrics.SetScrollOffset(CSSPoint(0, 0));
}
TABC_LOG("After InitializeRootMetrics, mLastRootMetrics is %s\n",
Stringify(mLastRootMetrics).c_str());

View File

@ -22,6 +22,7 @@
#include "mozilla/EventStateManager.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Hal.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/ipc/DocumentRendererParent.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "mozilla/layers/CompositorParent.h"
@ -1937,7 +1938,7 @@ TabParent::RecvNotifyIMEFocus(const bool& aFocus,
IMENotification notification(aFocus ? NOTIFY_IME_OF_FOCUS :
NOTIFY_IME_OF_BLUR);
mContentCache.AssignContent(aContentCache, &notification);
widget->NotifyIME(notification);
IMEStateManager::NotifyIME(notification, widget, true);
if (aFocus) {
*aPreference = widget->GetIMEUpdatePreference();
@ -1972,7 +1973,7 @@ TabParent::RecvNotifyIMETextChange(const ContentCache& aContentCache,
notification.mTextChangeData.mCausedByComposition = aCausedByComposition;
mContentCache.AssignContent(aContentCache, &notification);
widget->NotifyIME(notification);
IMEStateManager::NotifyIME(notification, widget, true);
return true;
}
@ -1988,7 +1989,7 @@ TabParent::RecvNotifyIMESelectedCompositionRect(
IMENotification notification(NOTIFY_IME_OF_COMPOSITION_UPDATE);
mContentCache.AssignContent(aContentCache, &notification);
widget->NotifyIME(notification);
IMEStateManager::NotifyIME(notification, widget, true);
return true;
}
@ -2011,7 +2012,7 @@ TabParent::RecvNotifyIMESelection(const ContentCache& aContentCache,
mContentCache.InitNotification(notification);
notification.mSelectionChangeData.mCausedByComposition =
aCausedByComposition;
widget->NotifyIME(notification);
IMEStateManager::NotifyIME(notification, widget, true);
}
return true;
}
@ -2039,7 +2040,7 @@ TabParent::RecvNotifyIMEMouseButtonEvent(
*aConsumedByIME = false;
return true;
}
nsresult rv = widget->NotifyIME(aIMENotification);
nsresult rv = IMEStateManager::NotifyIME(aIMENotification, widget, true);
*aConsumedByIME = rv == NS_SUCCESS_EVENT_CONSUMED;
return true;
}
@ -2058,7 +2059,7 @@ TabParent::RecvNotifyIMEPositionChange(const ContentCache& aContentCache)
const nsIMEUpdatePreference updatePreference =
widget->GetIMEUpdatePreference();
if (updatePreference.WantPositionChanged()) {
widget->NotifyIME(notification);
IMEStateManager::NotifyIME(notification, widget, true);
}
return true;
}

View File

@ -15,8 +15,8 @@
#include "mozilla/dom/ipc/BlobChild.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "nsIJSRuntimeService.h"
#include "nsPrintfCString.h"
#include "xpcpublic.h"
using namespace mozilla::ipc;
using namespace mozilla::jsipc;
@ -27,14 +27,7 @@ namespace dom {
PJavaScriptChild*
nsIContentChild::AllocPJavaScriptChild()
{
nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
NS_ENSURE_TRUE(svc, nullptr);
JSRuntime *rt;
svc->GetRuntime(&rt);
NS_ENSURE_TRUE(svc, nullptr);
return NewJavaScriptChild(rt);
return NewJavaScriptChild(xpc::GetJSRuntime());
}
bool

View File

@ -19,8 +19,8 @@
#include "mozilla/unused.h"
#include "nsFrameMessageManager.h"
#include "nsIJSRuntimeService.h"
#include "nsPrintfCString.h"
#include "xpcpublic.h"
using namespace mozilla::jsipc;
@ -49,15 +49,7 @@ nsIContentParent::AsContentParent()
PJavaScriptParent*
nsIContentParent::AllocPJavaScriptParent()
{
nsCOMPtr<nsIJSRuntimeService> svc =
do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
NS_ENSURE_TRUE(svc, nullptr);
JSRuntime *rt;
svc->GetRuntime(&rt);
NS_ENSURE_TRUE(svc, nullptr);
return NewJavaScriptParent(rt);
return NewJavaScriptParent(xpc::GetJSRuntime());
}
bool

View File

@ -28,9 +28,23 @@ definition = definition
# The Role Description for an input type="search" text field
searchTextField = search text field
# The Role Description for WAI-ARIA Landmarks
application = application
search = search
banner = banner
navigation = navigation
complementary = complementary
content = content
main = main
# The (spoken) role description for various WAI-ARIA roles
alert = alert
alertDialog = alert dialog
article = article
document = document
log = log
marquee = marquee
math = math
note = note
region = region
status = application status
timer = timer
tooltip = tooltip

View File

@ -74,10 +74,16 @@ public:
virtual AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() { return nullptr; };
// Sets the duration of the media in microseconds. The MediaDecoder
// fires a durationchange event to its owner (e.g., an HTML audio
// tag).
virtual void UpdateEstimatedMediaDuration(int64_t aDuration) = 0;
protected:
virtual void UpdateEstimatedMediaDuration(int64_t aDuration) {};
public:
void DispatchUpdateEstimatedMediaDuration(int64_t aDuration)
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethodWithArg<int64_t>(this, &AbstractMediaDecoder::UpdateEstimatedMediaDuration,
aDuration);
NS_DispatchToMainThread(r);
}
// Set the media as being seekable or not.
virtual void SetMediaSeekable(bool aMediaSeekable) = 0;
@ -111,7 +117,8 @@ public:
// Called by the reader's MediaResource as data arrives over the network.
// Must be called on the main thread.
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) = 0;
virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset,
bool aThrottleUpdates) = 0;
// Set by Reader if the current audio track can be offloaded
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) {}

View File

@ -281,6 +281,16 @@ public:
}
}
bool operator== (const SelfType& aOther) const
{
return mIntervals == aOther.mIntervals;
}
bool operator!= (const SelfType& aOther) const
{
return mIntervals != aOther.mIntervals;
}
SelfType& operator= (const SelfType& aOther)
{
mIntervals = aOther.mIntervals;

View File

@ -466,7 +466,7 @@ nsresult MP3FrameParser::ParseBuffer(const uint8_t* aBuffer,
return NS_OK;
}
void MP3FrameParser::Parse(const char* aBuffer, uint32_t aLength, uint64_t aOffset)
void MP3FrameParser::Parse(const uint8_t* aBuffer, uint32_t aLength, uint64_t aOffset)
{
MutexAutoLock mon(mLock);
@ -475,7 +475,7 @@ void MP3FrameParser::Parse(const char* aBuffer, uint32_t aLength, uint64_t aOffs
return;
}
const uint8_t* buffer = reinterpret_cast<const uint8_t*>(aBuffer);
const uint8_t* buffer = aBuffer;
int32_t length = aLength;
uint64_t offset = aOffset;

View File

@ -112,7 +112,7 @@ public:
return mIsMP3 != NOT_MP3;
}
void Parse(const char* aBuffer, uint32_t aLength, uint64_t aStreamOffset);
void Parse(const uint8_t* aBuffer, uint32_t aLength, uint64_t aStreamOffset);
// Returns the duration, in microseconds. If the entire stream has not
// been parsed yet, this is an estimate based on the bitrate of the

View File

@ -328,6 +328,7 @@ bool MediaDecoder::IsInfinite()
MediaDecoder::MediaDecoder() :
mWatchManager(this, AbstractThread::MainThread()),
mBuffered(AbstractThread::MainThread(), TimeIntervals(), "MediaDecoder::mBuffered (Mirror)"),
mNextFrameStatus(AbstractThread::MainThread(),
MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
"MediaDecoder::mNextFrameStatus (Mirror)"),
@ -1099,6 +1100,8 @@ void MediaDecoder::DurationChanged()
void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
{
MOZ_ASSERT(NS_IsMainThread());
if (mPlayState <= PLAY_STATE_LOADING) {
return;
}
@ -1256,10 +1259,12 @@ MediaDecoder::SetStateMachine(MediaDecoderStateMachine* aStateMachine)
if (mDecoderStateMachine) {
mStateMachineDuration.Connect(mDecoderStateMachine->CanonicalDuration());
mBuffered.Connect(mDecoderStateMachine->CanonicalBuffered());
mNextFrameStatus.Connect(mDecoderStateMachine->CanonicalNextFrameStatus());
mCurrentPosition.Connect(mDecoderStateMachine->CanonicalCurrentPosition());
} else {
mStateMachineDuration.DisconnectIfConnected();
mBuffered.DisconnectIfConnected();
mNextFrameStatus.DisconnectIfConnected();
mCurrentPosition.DisconnectIfConnected();
}
@ -1291,8 +1296,7 @@ void MediaDecoder::Invalidate()
// Constructs the time ranges representing what segments of the media
// are buffered and playable.
media::TimeIntervals MediaDecoder::GetBuffered() {
NS_ENSURE_TRUE(mDecoderStateMachine && !mShuttingDown, media::TimeIntervals::Invalid());
return mDecoderStateMachine->GetBuffered();
return mBuffered.Ref();
}
size_t MediaDecoder::SizeOfVideoQueue() {
@ -1309,9 +1313,11 @@ size_t MediaDecoder::SizeOfAudioQueue() {
return 0;
}
void MediaDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {
void MediaDecoder::NotifyDataArrived(uint32_t aLength, int64_t aOffset, bool aThrottleUpdates) {
MOZ_ASSERT(NS_IsMainThread());
if (mDecoderStateMachine) {
mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset);
mDecoderStateMachine->DispatchNotifyDataArrived(aLength, aOffset, aThrottleUpdates);
}
// ReadyState computation depends on MediaDecoder::CanPlayThrough, which

View File

@ -434,7 +434,8 @@ public:
// Called as data arrives on the stream and is read into the cache. Called
// on the main thread only.
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) override;
virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset,
bool aThrottleUpdates) override;
// Called by MediaResource when the principal of the resource has
// changed. Called on main thread only.
@ -454,6 +455,7 @@ public:
// Call on the main thread only.
virtual bool IsEndedOrShutdown() const;
protected:
// Updates the media duration. This is called while the media is being
// played, calls before the media has reached loaded metadata are ignored.
// The duration is assumed to be an estimate, and so a degree of
@ -463,6 +465,7 @@ public:
// changed, this causes a durationchanged event to fire to the media
// element.
void UpdateEstimatedMediaDuration(int64_t aDuration) override;
public:
// Set a flag indicating whether seeking is supported
virtual void SetMediaSeekable(bool aMediaSeekable) override;
@ -890,6 +893,9 @@ protected:
// State-watching manager.
WatchManager<MediaDecoder> mWatchManager;
// Buffered range, mirrored from the reader.
Mirror<media::TimeIntervals> mBuffered;
// NextFrameStatus, mirrored from the state machine.
Mirror<MediaDecoderOwner::NextFrameStatus> mNextFrameStatus;

View File

@ -69,7 +69,12 @@ MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder,
, mTaskQueue(aBorrowedTaskQueue ? aBorrowedTaskQueue
: new MediaTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
/* aSupportsTailDispatch = */ true))
, mWatchManager(this, mTaskQueue)
, mTimer(new MediaTimer())
, mBuffered(mTaskQueue, TimeIntervals(), "MediaDecoderReader::mBuffered (Canonical)")
, mDuration(mTaskQueue, NullableTimeUnit(), "MediaDecoderReader::mDuration (Mirror)")
, mThrottleDuration(TimeDuration::FromMilliseconds(500))
, mLastThrottledNotify(TimeStamp::Now() - mThrottleDuration)
, mIgnoreAudioOutputFormat(false)
, mStartTime(-1)
, mHitAudioDecodeError(false)
@ -92,6 +97,9 @@ MediaDecoderReader::InitializationTask()
if (mDecoder->CanonicalDurationOrNull()) {
mDuration.Connect(mDecoder->CanonicalDurationOrNull());
}
// Initialize watchers.
mWatchManager.Watch(mDuration, &MediaDecoderReader::UpdateBuffered);
}
MediaDecoderReader::~MediaDecoderReader()
@ -161,16 +169,65 @@ VideoData* MediaDecoderReader::DecodeToFirstVideoData()
}
void
MediaDecoderReader::SetStartTime(int64_t aStartTime)
MediaDecoderReader::UpdateBuffered()
{
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
MOZ_ASSERT(mStartTime == -1);
mStartTime = aStartTime;
MOZ_ASSERT(OnTaskQueue());
NS_ENSURE_TRUE_VOID(!mShutdown);
mBuffered = GetBuffered();
}
void
MediaDecoderReader::ThrottledNotifyDataArrived(const Interval<int64_t>& aInterval)
{
MOZ_ASSERT(OnTaskQueue());
NS_ENSURE_TRUE_VOID(!mShutdown);
if (mThrottledInterval.isNothing()) {
mThrottledInterval.emplace(aInterval);
} else if (!mThrottledInterval.ref().Contiguous(aInterval)) {
DoThrottledNotify();
mThrottledInterval.emplace(aInterval);
} else {
mThrottledInterval = Some(mThrottledInterval.ref().Span(aInterval));
}
// If it's been long enough since our last update, do it.
if (TimeStamp::Now() - mLastThrottledNotify > mThrottleDuration) {
DoThrottledNotify();
} else if (!mThrottledNotify.Exists()) {
// Otherwise, schedule an update if one isn't scheduled already.
nsRefPtr<MediaDecoderReader> self = this;
mThrottledNotify.Begin(
mTimer->WaitUntil(mLastThrottledNotify + mThrottleDuration, __func__)
->Then(TaskQueue(), __func__,
[self] () -> void {
self->mThrottledNotify.Complete();
NS_ENSURE_TRUE_VOID(!self->mShutdown);
self->DoThrottledNotify();
},
[self] () -> void {
self->mThrottledNotify.Complete();
NS_WARNING("throttle callback rejected");
})
);
}
}
void
MediaDecoderReader::DoThrottledNotify()
{
MOZ_ASSERT(OnTaskQueue());
mLastThrottledNotify = TimeStamp::Now();
mThrottledNotify.DisconnectIfExists();
Interval<int64_t> interval = mThrottledInterval.ref();
mThrottledInterval.reset();
NotifyDataArrived(interval);
}
media::TimeIntervals
MediaDecoderReader::GetBuffered()
{
MOZ_ASSERT(OnTaskQueue());
NS_ENSURE_TRUE(mStartTime >= 0, media::TimeIntervals());
AutoPinned<MediaResource> stream(mDecoder->GetResource());
@ -356,8 +413,14 @@ MediaDecoderReader::Shutdown()
mBaseAudioPromise.RejectIfExists(END_OF_STREAM, __func__);
mBaseVideoPromise.RejectIfExists(END_OF_STREAM, __func__);
mThrottledNotify.DisconnectIfExists();
ReleaseMediaResources();
mDuration.DisconnectIfConnected();
mBuffered.DisconnectAll();
// Shut down the watch manager before shutting down our task queue.
mWatchManager.Shutdown();
nsRefPtr<ShutdownPromise> p;
@ -373,6 +436,7 @@ MediaDecoderReader::Shutdown()
p = ShutdownPromise::CreateAndResolve(true, __func__);
}
mTimer = nullptr;
mDecoder = nullptr;
return p;

View File

@ -11,7 +11,9 @@
#include "MediaData.h"
#include "MediaPromise.h"
#include "MediaQueue.h"
#include "MediaTimer.h"
#include "AudioCompactor.h"
#include "Intervals.h"
#include "TimeUnits.h"
namespace mozilla {
@ -203,8 +205,10 @@ public:
mIgnoreAudioOutputFormat = true;
}
// Populates aBuffered with the time ranges which are buffered. This function
// is called on the main, decode, and state machine threads.
// Populates aBuffered with the time ranges which are buffered. This may only
// be called on the decode task queue, and should only be used internally by
// UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
// else.
//
// This base implementation in MediaDecoderReader estimates the time ranges
// buffered by interpolating the cached byte ranges with the duration
@ -219,6 +223,9 @@ public:
// called.
virtual media::TimeIntervals GetBuffered();
// Recomputes mBuffered.
virtual void UpdateBuffered();
// MediaSourceReader opts out of the start-time-guessing mechanism.
virtual bool ForceZeroStartTime() const { return false; }
@ -239,9 +246,38 @@ public:
virtual size_t SizeOfVideoQueueInFrames();
virtual size_t SizeOfAudioQueueInFrames();
// Only used by WebMReader and MediaOmxReader for now, so stub here rather
// than in every reader than inherits from MediaDecoderReader.
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {}
protected:
friend class TrackBuffer;
virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) { }
void NotifyDataArrived(const media::Interval<int64_t>& aInfo)
{
MOZ_ASSERT(OnTaskQueue());
NS_ENSURE_TRUE_VOID(!mShutdown);
NotifyDataArrivedInternal(aInfo.Length(), aInfo.mStart);
UpdateBuffered();
}
// Invokes NotifyDataArrived while throttling the calls to occur at most every mThrottleDuration ms.
void ThrottledNotifyDataArrived(const media::Interval<int64_t>& aInterval);
void DoThrottledNotify();
public:
// In situations where these notifications come from stochastic network
// activity, we can save significant recomputation by throttling the delivery
// of these updates to the reader implementation. We don't want to do this
// throttling when the update comes from MSE code, since that code needs the
// updates to be observable immediately, and is generally less
// trigger-happy with notifications anyway.
void DispatchNotifyDataArrived(uint32_t aLength, int64_t aOffset, bool aThrottleUpdates)
{
RefPtr<nsRunnable> r =
NS_NewRunnableMethodWithArg<media::Interval<int64_t>>(this, aThrottleUpdates ? &MediaDecoderReader::ThrottledNotifyDataArrived
: &MediaDecoderReader::NotifyDataArrived,
media::Interval<int64_t>(aOffset, aOffset + aLength));
TaskQueue()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
}
// Notify the reader that data from the resource was evicted (MediaSource only)
virtual void NotifyDataRemoved() {}
virtual int64_t GetEvictionOffset(double aTime) { return -1; }
@ -262,7 +298,20 @@ public:
// Indicates if the media is seekable.
// ReadMetada should be called before calling this method.
virtual bool IsMediaSeekable() = 0;
void SetStartTime(int64_t aStartTime);
void DispatchSetStartTime(int64_t aStartTime)
{
nsRefPtr<MediaDecoderReader> self = this;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction([self, aStartTime] () -> void
{
MOZ_ASSERT(self->OnTaskQueue());
MOZ_ASSERT(self->mStartTime == -1);
self->mStartTime = aStartTime;
self->UpdateBuffered();
});
TaskQueue()->Dispatch(r.forget());
}
MediaTaskQueue* TaskQueue() {
return mTaskQueue;
@ -321,12 +370,30 @@ protected:
// Decode task queue.
nsRefPtr<MediaTaskQueue> mTaskQueue;
// State-watching manager.
WatchManager<MediaDecoderReader> mWatchManager;
// MediaTimer.
nsRefPtr<MediaTimer> mTimer;
// Buffered range.
Canonical<media::TimeIntervals> mBuffered;
public:
AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() { return &mBuffered; }
protected:
// Stores presentation info required for playback.
MediaInfo mInfo;
// Duration, mirrored from the state machine task queue.
Mirror<media::NullableTimeUnit> mDuration;
// State for ThrottledNotifyDataArrived.
MediaPromiseRequestHolder<MediaTimerPromise> mThrottledNotify;
const TimeDuration mThrottleDuration;
TimeStamp mLastThrottledNotify;
Maybe<media::Interval<int64_t>> mThrottledInterval;
// Whether we should accept media that we know we can't play
// directly, because they have a number of channel higher than
// what we support.

View File

@ -187,6 +187,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mDelayedScheduler(this),
mState(DECODER_STATE_DECODING_NONE, "MediaDecoderStateMachine::mState"),
mPlayDuration(0),
mBuffered(mTaskQueue, TimeIntervals(), "MediaDecoderStateMachine::mBuffered (Mirror)"),
mDuration(mTaskQueue, NullableTimeUnit(), "MediaDecoderStateMachine::mDuration (Canonical"),
mEstimatedDuration(mTaskQueue, NullableTimeUnit(),
"MediaDecoderStateMachine::mEstimatedDuration (Mirror)"),
@ -296,6 +297,7 @@ MediaDecoderStateMachine::InitializationTask()
MOZ_ASSERT(OnTaskQueue());
// Connect mirrors.
mBuffered.Connect(mReader->CanonicalBuffered());
mEstimatedDuration.Connect(mDecoder->CanonicalEstimatedDuration());
mExplicitDuration.Connect(mDecoder->CanonicalExplicitDuration());
mPlayState.Connect(mDecoder->CanonicalPlayState());
@ -306,6 +308,7 @@ MediaDecoderStateMachine::InitializationTask()
mPreservesPitch.Connect(mDecoder->CanonicalPreservesPitch());
// Initialize watchers.
mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
@ -1639,33 +1642,20 @@ void MediaDecoderStateMachine::LogicallySeekingChanged()
ScheduleStateMachine();
}
void MediaDecoderStateMachine::NotifyDataArrived(const char* aBuffer,
uint32_t aLength,
int64_t aOffset)
void MediaDecoderStateMachine::BufferedRangeUpdated()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
MOZ_ASSERT(OnTaskQueue());
// While playing an unseekable stream of unknown duration, mDuration is
// updated (in AdvanceFrame()) as we play. But if data is being downloaded
// faster than played, mDuration won't reflect the end of playable data
// While playing an unseekable stream of unknown duration, mObservedDuration
// is updated (in AdvanceFrame()) as we play. But if data is being downloaded
// faster than played, mObserved won't reflect the end of playable data
// since we haven't played the frame at the end of buffered data. So update
// mDuration here as new data is downloaded to prevent such a lag.
//
// Make sure to only do this if we have a start time, otherwise the reader
// doesn't know how to compute GetBuffered.
if (!mDecoder->IsInfinite() || !HaveStartTime())
{
return;
}
media::TimeIntervals buffered{mDecoder->GetBuffered()};
if (!buffered.IsInvalid()) {
// mObservedDuration here as new data is downloaded to prevent such a lag.
if (!mBuffered.Ref().IsInvalid()) {
bool exists;
media::TimeUnit end{buffered.GetEnd(&exists)};
media::TimeUnit end{mBuffered.Ref().GetEnd(&exists)};
if (exists) {
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mDuration = Some(std::max<TimeUnit>(Duration(), end));
mObservedDuration = std::max(mObservedDuration.Ref(), end);
}
}
}
@ -2060,17 +2050,16 @@ bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs)
MOZ_ASSERT(OnTaskQueue());
AssertCurrentThreadInMonitor();
NS_ASSERTION(mState > DECODER_STATE_DECODING_FIRSTFRAME,
"Must have loaded first frame for GetBuffered() to work");
"Must have loaded first frame for mBuffered to be valid");
// If we don't have a duration, GetBuffered is probably not going to produce
// If we don't have a duration, mBuffered is probably not going to have
// a useful buffered range. Return false here so that we don't get stuck in
// buffering mode for live streams.
if (Duration().IsInfinite()) {
return false;
}
media::TimeIntervals buffered{mReader->GetBuffered()};
if (buffered.IsInvalid()) {
if (mBuffered.Ref().IsInvalid()) {
return false;
}
@ -2092,7 +2081,7 @@ bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs)
}
media::TimeInterval interval(media::TimeUnit::FromMicroseconds(endOfDecodedData),
media::TimeUnit::FromMicroseconds(std::min(endOfDecodedData + aUsecs, Duration().ToMicroseconds())));
return endOfDecodedData != INT64_MAX && !buffered.Contains(interval);
return endOfDecodedData != INT64_MAX && !mBuffered.Ref().Contains(interval);
}
void
@ -2144,8 +2133,7 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
mStartTimeRendezvous->AwaitStartTime()->Then(TaskQueue(), __func__,
[self] () -> void {
NS_ENSURE_TRUE_VOID(!self->IsShutdown());
ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
self->mReader->SetStartTime(self->StartTime());
self->mReader->DispatchSetStartTime(self->StartTime());
},
[] () -> void { NS_WARNING("Setting start time on reader failed"); }
);
@ -2365,7 +2353,6 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame()
// So we need to check if this has occurred, else our decode pipeline won't
// run (since it doesn't need to) and we won't detect end of stream.
CheckIfDecodeComplete();
MaybeStartPlayback();
if (mQueuedSeek.Exists()) {
mPendingSeek.Steal(mQueuedSeek);
@ -2493,6 +2480,7 @@ MediaDecoderStateMachine::FinishShutdown()
VideoQueue().ClearListeners();
// Disconnect canonicals and mirrors before shutting down our task queue.
mBuffered.DisconnectIfConnected();
mEstimatedDuration.DisconnectIfConnected();
mExplicitDuration.DisconnectIfConnected();
mPlayState.DisconnectIfConnected();
@ -2933,12 +2921,6 @@ void MediaDecoderStateMachine::AdvanceFrame()
}
}
// We've got enough data to keep playing until at least the next frame.
// Start playing now if need be.
if ((mFragmentEndTime >= 0 && clock_time < mFragmentEndTime) || mFragmentEndTime < 0) {
MaybeStartPlayback();
}
// Cap the current time to the larger of the audio and video end time.
// This ensures that if we're running off the system clock, we don't
// advance the clock to after the media end time.

View File

@ -252,11 +252,6 @@ public:
return mState == DECODER_STATE_SEEKING;
}
media::TimeIntervals GetBuffered() {
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
return mReader->GetBuffered();
}
size_t SizeOfVideoQueue() {
if (mReader) {
return mReader->SizeOfVideoQueueInBytes();
@ -271,7 +266,12 @@ public:
return 0;
}
void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
void DispatchNotifyDataArrived(uint32_t aLength, int64_t aOffset, bool aThrottleUpdates)
{
mReader->DispatchNotifyDataArrived(aLength, aOffset, aThrottleUpdates);
}
AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() { return mReader->CanonicalBuffered(); }
// Returns the state machine task queue.
MediaTaskQueue* TaskQueue() const { return mTaskQueue; }
@ -393,6 +393,8 @@ protected:
void SetState(State aState);
void BufferedRangeUpdated();
// Inserts MediaData* samples into their respective MediaQueues.
// aSample must not be null.
void Push(AudioData* aSample);
@ -938,6 +940,9 @@ private:
// buffering.
TimeStamp mBufferingStart;
// The buffered range. Mirrored from the decoder thread.
Mirror<media::TimeIntervals> mBuffered;
// Duration of the media. This is guaranteed to be non-null after we finish
// decoding the first frame.
Canonical<media::NullableTimeUnit> mDuration;

View File

@ -73,7 +73,6 @@ MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
, mSeekable(false)
, mIsEncrypted(false)
, mTrackDemuxersMayBlock(false)
, mCachedTimeRangesStale(true)
#if defined(READER_DORMANT_HEURISTIC)
, mDormantEnabled(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false))
#endif
@ -794,11 +793,8 @@ MediaFormatReader::UpdateReceivedNewData(TrackType aTrack)
decoder.mWaitingForData = false;
bool hasLastEnd;
media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
{
MonitorAutoLock lock(decoder.mMonitor);
// Update our cached TimeRange.
decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered();
}
// Update our cached TimeRange.
decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered();
if (decoder.mTimeRanges.Length() &&
(!hasLastEnd || decoder.mTimeRanges.GetEnd() > lastEnd)) {
// New data was added after our previous end, we can clear the EOS flag.
@ -1402,6 +1398,7 @@ MediaFormatReader::GetEvictionOffset(double aTime)
media::TimeIntervals
MediaFormatReader::GetBuffered()
{
MOZ_ASSERT(OnTaskQueue());
media::TimeIntervals videoti;
media::TimeIntervals audioti;
media::TimeIntervals intervals;
@ -1415,54 +1412,25 @@ MediaFormatReader::GetBuffered()
NS_ENSURE_TRUE(mStartTime >= 0, media::TimeIntervals());
startTime = mStartTime;
}
if (NS_IsMainThread()) {
if (mCachedTimeRangesStale) {
MOZ_ASSERT(mMainThreadDemuxer);
if (!mDataRange.IsEmpty()) {
mMainThreadDemuxer->NotifyDataArrived(mDataRange.Length(), mDataRange.mStart);
}
if (mVideoTrackDemuxer) {
videoti = mVideoTrackDemuxer->GetBuffered();
}
if (mAudioTrackDemuxer) {
audioti = mAudioTrackDemuxer->GetBuffered();
}
if (HasAudio() && HasVideo()) {
mCachedTimeRanges = media::Intersection(Move(videoti), Move(audioti));
} else if (HasAudio()) {
mCachedTimeRanges = Move(audioti);
} else if (HasVideo()) {
mCachedTimeRanges = Move(videoti);
}
mDataRange = ByteInterval();
mCachedTimeRangesStale = false;
}
intervals = mCachedTimeRanges;
} else {
if (OnTaskQueue()) {
// Ensure we have up to date buffered time range.
if (HasVideo()) {
UpdateReceivedNewData(TrackType::kVideoTrack);
}
if (HasAudio()) {
UpdateReceivedNewData(TrackType::kAudioTrack);
}
}
if (HasVideo()) {
MonitorAutoLock lock(mVideo.mMonitor);
videoti = mVideo.mTimeRanges;
}
if (HasAudio()) {
MonitorAutoLock lock(mAudio.mMonitor);
audioti = mAudio.mTimeRanges;
}
if (HasAudio() && HasVideo()) {
intervals = media::Intersection(Move(videoti), Move(audioti));
} else if (HasAudio()) {
intervals = Move(audioti);
} else if (HasVideo()) {
intervals = Move(videoti);
}
// Ensure we have up to date buffered time range.
if (HasVideo()) {
UpdateReceivedNewData(TrackType::kVideoTrack);
}
if (HasAudio()) {
UpdateReceivedNewData(TrackType::kAudioTrack);
}
if (HasVideo()) {
videoti = mVideo.mTimeRanges;
}
if (HasAudio()) {
audioti = mAudio.mTimeRanges;
}
if (HasAudio() && HasVideo()) {
intervals = media::Intersection(Move(videoti), Move(audioti));
} else if (HasAudio()) {
intervals = Move(audioti);
} else if (HasVideo()) {
intervals = Move(videoti);
}
return intervals.Shift(media::TimeUnit::FromMicroseconds(-startTime));
@ -1539,51 +1507,43 @@ MediaFormatReader::NotifyDemuxer(uint32_t aLength, int64_t aOffset)
}
void
MediaFormatReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
MediaFormatReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aBuffer || aLength);
if (mDataRange.IsEmpty()) {
mDataRange = ByteInterval(aOffset, aOffset + aLength);
} else {
mDataRange = mDataRange.Span(ByteInterval(aOffset, aOffset + aLength));
}
mCachedTimeRangesStale = true;
MOZ_ASSERT(OnTaskQueue());
MOZ_ASSERT(aLength);
if (!mInitDone) {
return;
}
// Queue a task to notify our main demuxer.
RefPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<int32_t, uint64_t>(
this, &MediaFormatReader::NotifyDemuxer,
// Queue a task to notify our main thread demuxer.
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<uint32_t, int64_t>(
mMainThreadDemuxer, &MediaDataDemuxer::NotifyDataArrived,
aLength, aOffset);
TaskQueue()->Dispatch(task.forget());
AbstractThread::MainThread()->Dispatch(task.forget());
NotifyDemuxer(aLength, aOffset);
}
void
MediaFormatReader::NotifyDataRemoved()
{
MOZ_ASSERT(NS_IsMainThread());
mDataRange = ByteInterval();
mCachedTimeRangesStale = true;
MOZ_ASSERT(OnTaskQueue());
if (!mInitDone) {
return;
}
MOZ_ASSERT(mMainThreadDemuxer);
mMainThreadDemuxer->NotifyDataRemoved();
// Queue a task to notify our main demuxer.
RefPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<int32_t, uint64_t>(
this, &MediaFormatReader::NotifyDemuxer,
0, 0);
TaskQueue()->Dispatch(task.forget());
// Queue a task to notify our main thread demuxer.
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethod(
mMainThreadDemuxer, &MediaDataDemuxer::NotifyDataRemoved);
AbstractThread::MainThread()->Dispatch(task.forget());
NotifyDemuxer(0, 0);
}
bool

View File

@ -9,7 +9,6 @@
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/Monitor.h"
#include "MediaDataDemuxer.h"
#include "MediaDecoderReader.h"
#include "MediaTaskQueue.h"
@ -68,9 +67,9 @@ public:
}
int64_t GetEvictionOffset(double aTime) override;
void NotifyDataArrived(const char* aBuffer,
uint32_t aLength,
int64_t aOffset) override;
protected:
void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) override;
public:
void NotifyDataRemoved() override;
media::TimeIntervals GetBuffered() override;
@ -207,8 +206,6 @@ private:
, mNumSamplesOutput(0)
, mSizeOfQueue(0)
, mLastStreamSourceID(UINT32_MAX)
, mMonitor(aType == MediaData::AUDIO_DATA ? "audio decoder data"
: "video decoder data")
{}
MediaFormatReader* mOwner;
@ -295,9 +292,6 @@ private:
Atomic<size_t> mSizeOfQueue;
// Sample format monitoring.
uint32_t mLastStreamSourceID;
// Monitor that protects all non-threadsafe state; the primitives
// that follow.
Monitor mMonitor;
media::TimeIntervals mTimeRanges;
nsRefPtr<SharedTrackInfo> mInfo;
};
@ -420,9 +414,6 @@ private:
nsRefPtr<MediaDataDemuxer> mMainThreadDemuxer;
nsRefPtr<MediaTrackDemuxer> mAudioTrackDemuxer;
nsRefPtr<MediaTrackDemuxer> mVideoTrackDemuxer;
ByteInterval mDataRange;
media::TimeIntervals mCachedTimeRanges;
bool mCachedTimeRangesStale;
#if defined(READER_DORMANT_HEURISTIC)
const bool mDormantEnabled;

View File

@ -463,7 +463,8 @@ ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
{
CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
closure->mResource->mDecoder->NotifyDataArrived(aCount, closure->mResource->mOffset,
/* aThrottleUpdates = */ true);
// Keep track of where we're up to.
RESOURCE_LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add "
@ -757,6 +758,29 @@ nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
return rv;
}
already_AddRefed<MediaByteBuffer>
ChannelMediaResource::SilentReadAt(int64_t aOffset, uint32_t aCount)
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
nsRefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
bool ok = bytes->SetCapacity(aCount, fallible);
NS_ENSURE_TRUE(ok, nullptr);
int64_t pos = mCacheStream.Tell();
char* curr = reinterpret_cast<char*>(bytes->Elements());
while (aCount > 0) {
uint32_t bytesRead;
nsresult rv = mCacheStream.ReadAt(aOffset, curr, aCount, &bytesRead);
NS_ENSURE_SUCCESS(rv, nullptr);
NS_ENSURE_TRUE(bytesRead > 0, nullptr);
aOffset += bytesRead;
aCount -= bytesRead;
curr += bytesRead;
}
mCacheStream.Seek(nsISeekableStream::NS_SEEK_SET, pos);
return bytes.forget();
}
nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
@ -1193,6 +1217,7 @@ public:
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
uint32_t aCount, uint32_t* aBytes) override;
virtual already_AddRefed<MediaByteBuffer> SilentReadAt(int64_t aOffset, uint32_t aCount) override;
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override;
virtual int64_t Tell() override;
@ -1516,6 +1541,34 @@ nsresult FileMediaResource::ReadAt(int64_t aOffset, char* aBuffer,
return rv;
}
already_AddRefed<MediaByteBuffer>
FileMediaResource::SilentReadAt(int64_t aOffset, uint32_t aCount)
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
MutexAutoLock lock(mLock);
nsRefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
bool ok = bytes->SetCapacity(aCount, fallible);
NS_ENSURE_TRUE(ok, nullptr);
int64_t pos = 0;
NS_ENSURE_TRUE(mSeekable, nullptr);
nsresult rv = mSeekable->Tell(&pos);
NS_ENSURE_SUCCESS(rv, nullptr);
rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset);
NS_ENSURE_SUCCESS(rv, nullptr);
char* curr = reinterpret_cast<char*>(bytes->Elements());
while (aCount > 0) {
uint32_t bytesRead;
rv = UnsafeRead(curr, aCount, &bytesRead);
NS_ENSURE_SUCCESS(rv, nullptr);
NS_ENSURE_TRUE(bytesRead > 0, nullptr);
aCount -= bytesRead;
curr += bytesRead;
}
UnsafeSeek(nsISeekableStream::NS_SEEK_SET, pos);
return bytes.forget();
}
nsresult FileMediaResource::Seek(int32_t aWhence, int64_t aOffset)
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");

View File

@ -9,11 +9,13 @@
#include "mozilla/Mutex.h"
#include "nsIChannel.h"
#include "nsIURI.h"
#include "nsISeekableStream.h"
#include "nsIStreamingProtocolController.h"
#include "nsIStreamListener.h"
#include "nsIChannelEventSink.h"
#include "nsIInterfaceRequestor.h"
#include "MediaCache.h"
#include "MediaData.h"
#include "mozilla/Attributes.h"
#include "mozilla/TimeStamp.h"
#include "nsThreadUtils.h"
@ -287,6 +289,34 @@ public:
// results and requirements are the same as per the Read method.
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
uint32_t aCount, uint32_t* aBytes) = 0;
// ReadAt without side-effects. Given that our MediaResource infrastructure
// is very side-effecty, this accomplishes its job by checking the initial
// position and seeking back to it. If the seek were to fail, a side-effect
// might be observable.
//
// This method returns null if anything fails, including the failure to read
// aCount bytes. Otherwise, it returns an owned buffer.
virtual already_AddRefed<MediaByteBuffer> SilentReadAt(int64_t aOffset, uint32_t aCount)
{
nsRefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
bool ok = bytes->SetCapacity(aCount, fallible);
NS_ENSURE_TRUE(ok, nullptr);
int64_t pos = Tell();
char* curr = reinterpret_cast<char*>(bytes->Elements());
while (aCount > 0) {
uint32_t bytesRead;
nsresult rv = ReadAt(aOffset, curr, aCount, &bytesRead);
NS_ENSURE_SUCCESS(rv, nullptr);
NS_ENSURE_TRUE(bytesRead > 0, nullptr);
aOffset += bytesRead;
aCount -= bytesRead;
curr += bytesRead;
}
Seek(nsISeekableStream::NS_SEEK_SET, pos);
return bytes.forget();
}
// Seek to the given bytes offset in the stream. aWhence can be
// one of:
// NS_SEEK_SET
@ -610,6 +640,7 @@ public:
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
virtual nsresult ReadAt(int64_t offset, char* aBuffer,
uint32_t aCount, uint32_t* aBytes) override;
virtual already_AddRefed<MediaByteBuffer> SilentReadAt(int64_t aOffset, uint32_t aCount) override;
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override;
virtual int64_t Tell() override;

View File

@ -39,7 +39,11 @@ void
MediaTimer::DispatchDestroy()
{
nsCOMPtr<nsIRunnable> task = NS_NewNonOwningRunnableMethod(this, &MediaTimer::Destroy);
nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL);
// Hold a strong reference to the thread so that it doesn't get deleted in
// Destroy(), which may run completely before the stack if Dispatch() begins
// to unwind.
nsCOMPtr<nsIEventTarget> thread = mThread;
nsresult rv = thread->Dispatch(task, NS_DISPATCH_NORMAL);
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
(void) rv;
}

View File

@ -254,7 +254,7 @@ public:
return TimeIntervals(TimeInterval(TimeUnit::FromMicroseconds(INT64_MIN),
TimeUnit::FromMicroseconds(INT64_MIN)));
}
bool IsInvalid()
bool IsInvalid() const
{
return Length() == 1 && Start(0).ToMicroseconds() == INT64_MIN &&
End(0).ToMicroseconds() == INT64_MIN;

View File

@ -387,7 +387,7 @@ AppleMP3Reader::ReadMetadata(MediaInfo* aInfo,
bytes,
0 /* flags */);
mMP3FrameParser.Parse(bytes, numBytes, offset);
mMP3FrameParser.Parse(reinterpret_cast<uint8_t*>(bytes), numBytes, offset);
offset += numBytes;
@ -525,16 +525,16 @@ AppleMP3Reader::Seek(int64_t aTime, int64_t aEndTime)
}
void
AppleMP3Reader::NotifyDataArrived(const char* aBuffer,
uint32_t aLength,
int64_t aOffset)
AppleMP3Reader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(OnTaskQueue());
if (!mMP3FrameParser.NeedsData()) {
return;
}
mMP3FrameParser.Parse(aBuffer, aLength, aOffset);
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
NS_ENSURE_TRUE_VOID(bytes);
mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
if (!mMP3FrameParser.IsMP3()) {
return;
}
@ -543,9 +543,8 @@ AppleMP3Reader::NotifyDataArrived(const char* aBuffer,
if (duration != mDuration) {
LOGD("Updating media duration to %lluus\n", duration);
MOZ_ASSERT(mDecoder);
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mDuration = duration;
mDecoder->UpdateEstimatedMediaDuration(duration);
mDecoder->DispatchUpdateEstimatedMediaDuration(duration);
}
}

View File

@ -45,9 +45,10 @@ public:
AudioFileStreamPropertyID aPropertyID,
UInt32 *aFlags);
virtual void NotifyDataArrived(const char* aBuffer,
uint32_t aLength,
int64_t aOffset) override;
protected:
virtual void NotifyDataArrivedInternal(uint32_t aLength,
int64_t aOffset) override;
public:
virtual bool IsMediaSeekable() override;

View File

@ -84,7 +84,7 @@ ParseMP3Headers(MP3FrameParser *aParser, MediaResource *aResource)
return NS_ERROR_FAILURE;
}
aParser->Parse(buffer, bytesRead, offset);
aParser->Parse(reinterpret_cast<uint8_t*>(buffer), bytesRead, offset);
offset += bytesRead;
}
@ -401,14 +401,16 @@ DirectShowReader::SeekInternal(int64_t aTargetUs)
}
void
DirectShowReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
DirectShowReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(OnTaskQueue());
if (!mMP3FrameParser.NeedsData()) {
return;
}
mMP3FrameParser.Parse(aBuffer, aLength, aOffset);
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
NS_ENSURE_TRUE_VOID(bytes);
mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
if (!mMP3FrameParser.IsMP3()) {
return;
}
@ -416,9 +418,8 @@ DirectShowReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64
int64_t duration = mMP3FrameParser.GetDuration();
if (duration != mDuration) {
MOZ_ASSERT(mDecoder);
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mDuration = duration;
mDecoder->UpdateEstimatedMediaDuration(mDuration);
mDecoder->DispatchUpdateEstimatedMediaDuration(mDuration);
}
}

View File

@ -58,9 +58,10 @@ public:
nsRefPtr<SeekPromise>
Seek(int64_t aTime, int64_t aEndTime) override;
void NotifyDataArrived(const char* aBuffer,
uint32_t aLength,
int64_t aOffset) override;
protected:
void NotifyDataArrivedInternal(uint32_t aLength,
int64_t aOffset) override;
public:
bool IsMediaSeekable() override;

View File

@ -97,7 +97,8 @@ HaveGMPFor(mozIGeckoMediaPluginService* aGMPService,
#ifdef XP_WIN
static bool
AdobePluginDLLExists(const nsACString& aVersionStr)
AdobePluginFileExists(const nsACString& aVersionStr,
const nsAString& aFilename)
{
nsCOMPtr<nsIFile> path;
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(path));
@ -115,7 +116,7 @@ AdobePluginDLLExists(const nsACString& aVersionStr)
return false;
}
rv = path->Append(NS_LITERAL_STRING("eme-adobe.dll"));
rv = path->Append(aFilename);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
@ -123,13 +124,25 @@ AdobePluginDLLExists(const nsACString& aVersionStr)
bool exists = false;
return NS_SUCCEEDED(path->Exists(&exists)) && exists;
}
static bool
AdobePluginDLLExists(const nsACString& aVersionStr)
{
return AdobePluginFileExists(aVersionStr, NS_LITERAL_STRING("eme-adobe.dll"));
}
static bool
AdobePluginVoucherExists(const nsACString& aVersionStr)
{
return AdobePluginFileExists(aVersionStr, NS_LITERAL_STRING("eme-adobe.voucher"));
}
#endif
static MediaKeySystemStatus
EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService,
const nsAString& aKeySystem,
int32_t aMinCdmVersion,
bool aCheckForV6=false)
nsACString& aOutMessage)
{
nsTArray<nsCString> tags;
tags.AppendElement(NS_ConvertUTF16toUTF8(aKeySystem));
@ -138,17 +151,13 @@ EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService,
if (NS_FAILED(aGMPService->GetPluginVersionForAPI(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
&tags,
&hasPlugin,
versionStr)) ||
// XXX to be removed later in bug 1147692
(aCheckForV6 && !hasPlugin &&
NS_FAILED(aGMPService->GetPluginVersionForAPI(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT),
&tags,
&hasPlugin,
versionStr)))) {
versionStr))) {
aOutMessage = NS_LITERAL_CSTRING("GetPluginVersionForAPI failed");
return MediaKeySystemStatus::Error;
}
if (!hasPlugin) {
aOutMessage = NS_LITERAL_CSTRING("CDM is not installed");
return MediaKeySystemStatus::Cdm_not_installed;
}
@ -159,6 +168,7 @@ EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService,
nsresult rv;
int32_t version = versionStr.ToInteger(&rv);
if (NS_FAILED(rv) || version < 0 || aMinCdmVersion > version) {
aOutMessage = NS_LITERAL_CSTRING("Installed CDM version insufficient");
return MediaKeySystemStatus::Cdm_insufficient_version;
}
@ -167,8 +177,17 @@ EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService,
aKeySystem.EqualsLiteral("com.adobe.primetime")) {
// Verify that anti-virus hasn't "helpfully" deleted the Adobe GMP DLL,
// as we suspect may happen (Bug 1160382).
bool somethingMissing = false;
if (!AdobePluginDLLExists(versionStr)) {
NS_WARNING("Adobe EME plugin disappeared from disk!");
aOutMessage = NS_LITERAL_CSTRING("Adobe DLL was expected to be on disk but was not");
somethingMissing = true;
}
if (!AdobePluginVoucherExists(versionStr)) {
aOutMessage = NS_LITERAL_CSTRING("Adobe plugin voucher was expected to be on disk but was not");
somethingMissing = true;
}
if (somethingMissing) {
NS_WARNING("Adobe EME plugin or voucher disappeared from disk!");
// Reset the prefs that Firefox's GMP downloader sets, so that
// Firefox will try to download the plugin next time the updater runs.
Preferences::ClearUser("media.gmp-eme-adobe.lastUpdate");
@ -184,20 +203,23 @@ EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService,
/* static */
MediaKeySystemStatus
MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem,
int32_t aMinCdmVersion)
int32_t aMinCdmVersion,
nsACString& aOutMessage)
{
MOZ_ASSERT(Preferences::GetBool("media.eme.enabled", false));
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
if (NS_WARN_IF(!mps)) {
aOutMessage = NS_LITERAL_CSTRING("Failed to get GMP service");
return MediaKeySystemStatus::Error;
}
if (aKeySystem.EqualsLiteral("org.w3.clearkey")) {
if (!Preferences::GetBool("media.eme.clearkey.enabled", true)) {
aOutMessage = NS_LITERAL_CSTRING("ClearKey was disabled");
return MediaKeySystemStatus::Cdm_disabled;
}
return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion);
return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage);
}
#ifdef XP_WIN
@ -205,20 +227,21 @@ MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem,
aKeySystem.EqualsLiteral("com.adobe.primetime"))) {
// Win Vista and later only.
if (!IsVistaOrLater()) {
aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version not met for Adobe EME");
return MediaKeySystemStatus::Cdm_not_supported;
}
if (!Preferences::GetBool("media.gmp-eme-adobe.enabled", false)) {
aOutMessage = NS_LITERAL_CSTRING("Adobe EME disabled");
return MediaKeySystemStatus::Cdm_disabled;
}
if (!MP4Decoder::CanCreateH264Decoder() ||
!MP4Decoder::CanCreateAACDecoder() ||
!EMEVoucherFileExists()) {
if (!EMEVoucherFileExists()) {
// The system doesn't have the codecs that Adobe EME relies
// on installed, or doesn't have a voucher for the plugin-container.
// Adobe EME isn't going to work, so don't advertise that it will.
aOutMessage = NS_LITERAL_CSTRING("Plugin-container voucher not present");
return MediaKeySystemStatus::Cdm_not_supported;
}
return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, true);
return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage);
}
#endif
@ -260,25 +283,15 @@ IsPlayableWithGMP(mozIGeckoMediaPluginService* aGMPS,
return false;
}
return (!hasAAC ||
!(HaveGMPFor(aGMPS,
NS_ConvertUTF16toUTF8(aKeySystem),
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
NS_LITERAL_CSTRING("aac")) ||
// XXX remove later in bug 1147692
HaveGMPFor(aGMPS,
NS_ConvertUTF16toUTF8(aKeySystem),
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT),
NS_LITERAL_CSTRING("aac")))) &&
!HaveGMPFor(aGMPS,
NS_ConvertUTF16toUTF8(aKeySystem),
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
NS_LITERAL_CSTRING("aac"))) &&
(!hasH264 ||
!(HaveGMPFor(aGMPS,
NS_ConvertUTF16toUTF8(aKeySystem),
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
NS_LITERAL_CSTRING("h264")) ||
// XXX remove later in bug 1147692
HaveGMPFor(aGMPS,
NS_ConvertUTF16toUTF8(aKeySystem),
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT),
NS_LITERAL_CSTRING("h264"))));
!HaveGMPFor(aGMPS,
NS_ConvertUTF16toUTF8(aKeySystem),
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
NS_LITERAL_CSTRING("h264")));
#else
return false;
#endif

View File

@ -44,8 +44,11 @@ public:
already_AddRefed<Promise> CreateMediaKeys(ErrorResult& aRv);
static MediaKeySystemStatus GetKeySystemStatus(const nsAString& aKeySystem,
int32_t aMinCdmVersion);
int32_t aMinCdmVersion,
nsACString& aOutExceptionMessage);
static bool IsSupported(const nsAString& aKeySystem,
const Sequence<MediaKeySystemOptions>& aOptions);

View File

@ -105,7 +105,8 @@ MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
return;
}
MediaKeySystemStatus status = MediaKeySystemAccess::GetKeySystemStatus(keySystem, minCdmVersion);
nsAutoCString message;
MediaKeySystemStatus status = MediaKeySystemAccess::GetKeySystemStatus(keySystem, minCdmVersion, message);
if ((status == MediaKeySystemStatus::Cdm_not_installed ||
status == MediaKeySystemStatus::Cdm_insufficient_version) &&
keySystem.EqualsLiteral("com.adobe.primetime")) {

View File

@ -1075,6 +1075,7 @@ MP4Reader::GetEvictionOffset(double aTime)
media::TimeIntervals
MP4Reader::GetBuffered()
{
MOZ_ASSERT(OnTaskQueue());
MonitorAutoLock mon(mDemuxerMonitor);
media::TimeIntervals buffered;
if (!mIndexReady) {
@ -1146,14 +1147,9 @@ MP4Reader::VideoIsHardwareAccelerated() const
}
void
MP4Reader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
MP4Reader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
{
MOZ_ASSERT(NS_IsMainThread());
if (mShutdown) {
return;
}
MOZ_ASSERT(OnTaskQueue());
if (mLastSeenEnd < 0) {
MonitorAutoLock mon(mDemuxerMonitor);
mLastSeenEnd = mDecoder->GetResource()->GetLength();

View File

@ -62,7 +62,10 @@ public:
virtual bool IsMediaSeekable() override;
virtual int64_t GetEvictionOffset(double aTime) override;
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) override;
protected:
virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) override;
public:
virtual media::TimeIntervals GetBuffered() override;

View File

@ -118,11 +118,6 @@ GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
void* session = nullptr;
GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, host, &session);
if (err != GMPNoErr && !session) {
// XXX to remove in bug 1147692
err = mGMPChild->GetAPI(GMP_API_DECRYPTOR_COMPAT, host, &session);
}
if (err != GMPNoErr || !session) {
return false;
}

View File

@ -818,8 +818,7 @@ GMPParent::ReadGMPMetaData()
}
}
if (cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR) ||
cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_COMPAT)) {
if (cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) {
mCanDecrypt = true;
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)

View File

@ -668,13 +668,6 @@ GeckoMediaPluginServiceParent::SelectPluginForAPI(const nsACString& aNodeId,
return clone;
}
if (aAPI.EqualsLiteral(GMP_API_DECRYPTOR)) {
// XXX to remove in bug 1147692
return SelectPluginForAPI(aNodeId,
NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT),
aTags);
}
return nullptr;
}

View File

@ -240,9 +240,6 @@ enum GMPSessionType {
#define GMP_API_DECRYPTOR "eme-decrypt-v7"
// XXX remove in bug 1147692
#define GMP_API_DECRYPTOR_COMPAT "eme-decrypt-v6"
// API exposed by plugin library to manage decryption sessions.
// When the Host requests this by calling GMPGetAPIFunc().
//

View File

@ -335,7 +335,7 @@ nsresult GStreamerReader::ParseMP3Headers()
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(bytesRead, NS_ERROR_FAILURE);
mMP3FrameParser.Parse(bytes, bytesRead, offset);
mMP3FrameParser.Parse(reinterpret_cast<uint8_t*>(bytes), bytesRead, offset);
offset += bytesRead;
} while (!mMP3FrameParser.ParsedHeaders());
@ -875,6 +875,7 @@ GStreamerReader::Seek(int64_t aTarget, int64_t aEndTime)
media::TimeIntervals GStreamerReader::GetBuffered()
{
MOZ_ASSERT(OnTaskQueue());
media::TimeIntervals buffered;
if (!mInfo.HasValidMedia()) {
return buffered;
@ -1272,11 +1273,10 @@ GStreamerReader::AutoplugSortCb(GstElement* aElement, GstPad* aPad,
* If this is an MP3 stream, pass any new data we get to the MP3 frame parser
* for duration estimation.
*/
void GStreamerReader::NotifyDataArrived(const char *aBuffer,
uint32_t aLength,
int64_t aOffset)
void GStreamerReader::NotifyDataArrivedInternal(uint32_t aLength,
int64_t aOffset)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(OnTaskQueue());
if (HasVideo()) {
return;
}
@ -1284,7 +1284,9 @@ void GStreamerReader::NotifyDataArrived(const char *aBuffer,
return;
}
mMP3FrameParser.Parse(aBuffer, aLength, aOffset);
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
NS_ENSURE_TRUE_VOID(bytes);
mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
if (!mMP3FrameParser.IsMP3()) {
return;
}
@ -1292,9 +1294,8 @@ void GStreamerReader::NotifyDataArrived(const char *aBuffer,
int64_t duration = mMP3FrameParser.GetDuration();
if (duration != mLastParserDuration && mUseParserDuration) {
MOZ_ASSERT(mDecoder);
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mLastParserDuration = duration;
mDecoder->UpdateEstimatedMediaDuration(mLastParserDuration);
mDecoder->DispatchUpdateEstimatedMediaDuration(mLastParserDuration);
}
}

View File

@ -51,9 +51,10 @@ public:
Seek(int64_t aTime, int64_t aEndTime) override;
virtual media::TimeIntervals GetBuffered() override;
virtual void NotifyDataArrived(const char *aBuffer,
uint32_t aLength,
int64_t aOffset) override;
protected:
virtual void NotifyDataArrivedInternal(uint32_t aLength,
int64_t aOffset) override;
public:
virtual bool HasAudio() override {
return mInfo.HasAudio();

View File

@ -36,12 +36,9 @@ public:
decoder->SetResource(resource);
reader->Init(nullptr);
{
// This needs to be done before invoking GetBuffered. This is normally
// done by MediaDecoderStateMachine.
ReentrantMonitorAutoEnter mon(decoder->GetReentrantMonitor());
reader->SetStartTime(0);
}
// This needs to be done before invoking GetBuffered. This is normally
// done by MediaDecoderStateMachine.
reader->DispatchSetStartTime(0);
}
void Init() {

View File

@ -87,7 +87,7 @@ MediaSourceDecoder::GetSeekable()
if (IsNaN(duration)) {
// Return empty range.
} else if (duration > 0 && mozilla::IsInfinite(duration)) {
media::TimeIntervals buffered = mReader->GetBuffered();
media::TimeIntervals buffered = GetBuffered();
if (buffered.Length()) {
seekable += media::TimeInterval(buffered.GetStart(), buffered.GetEnd());
}
@ -99,6 +99,44 @@ MediaSourceDecoder::GetSeekable()
return seekable;
}
media::TimeIntervals
MediaSourceDecoder::GetBuffered()
{
MOZ_ASSERT(NS_IsMainThread());
dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers();
media::TimeUnit highestEndTime;
nsTArray<media::TimeIntervals> activeRanges;
media::TimeIntervals buffered;
for (uint32_t i = 0; i < sourceBuffers->Length(); i++) {
bool found;
dom::SourceBuffer* sb = sourceBuffers->IndexedGetter(i, found);
MOZ_ASSERT(found);
activeRanges.AppendElement(sb->GetTimeIntervals());
highestEndTime =
std::max(highestEndTime, activeRanges.LastElement().GetEnd());
}
buffered +=
media::TimeInterval(media::TimeUnit::FromMicroseconds(0), highestEndTime);
for (auto& range : activeRanges) {
if (mEnded && range.Length()) {
// Set the end time on the last range to highestEndTime by adding a
// new range spanning the current end time to highestEndTime, which
// Normalize() will then merge with the old last range.
range +=
media::TimeInterval(range.GetEnd(), highestEndTime);
}
buffered.Intersection(range);
}
MSE_DEBUG("ranges=%s", DumpTimeRanges(buffered).get());
return buffered;
}
void
MediaSourceDecoder::Shutdown()
{
@ -298,37 +336,6 @@ MediaSourceDecoder::GetDuration()
return ExplicitDuration();
}
already_AddRefed<SourceBufferDecoder>
MediaSourceDecoder::SelectDecoder(int64_t aTarget,
int64_t aTolerance,
const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders)
{
MOZ_ASSERT(!mIsUsingFormatReader);
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
media::TimeUnit target{media::TimeUnit::FromMicroseconds(aTarget)};
media::TimeUnit tolerance{media::TimeUnit::FromMicroseconds(aTolerance + aTarget)};
// aTolerance gives a slight bias toward the start of a range only.
// Consider decoders in order of newest to oldest, as a newer decoder
// providing a given buffered range is expected to replace an older one.
for (int32_t i = aTrackDecoders.Length() - 1; i >= 0; --i) {
nsRefPtr<SourceBufferDecoder> newDecoder = aTrackDecoders[i];
media::TimeIntervals ranges = newDecoder->GetBuffered();
for (uint32_t j = 0; j < ranges.Length(); j++) {
if (target < ranges.End(j) && tolerance >= ranges.Start(j)) {
return newDecoder.forget();
}
}
MSE_DEBUGV("SelectDecoder(%lld fuzz:%lld) newDecoder=%p (%d/%d) target not in ranges=%s",
aTarget, aTolerance, newDecoder.get(), i+1,
aTrackDecoders.Length(), DumpTimeRanges(ranges).get());
}
return nullptr;
}
#undef MSE_DEBUG
#undef MSE_DEBUGV

View File

@ -41,6 +41,7 @@ public:
virtual MediaDecoderStateMachine* CreateStateMachine() override;
virtual nsresult Load(nsIStreamListener**, MediaDecoder*) override;
virtual media::TimeIntervals GetSeekable() override;
media::TimeIntervals GetBuffered() override;
virtual void Shutdown() override;
@ -91,12 +92,6 @@ public:
// reader in this decoders MediaSourceReader.
bool IsActiveReader(MediaDecoderReader* aReader);
// Return a decoder from the set available in aTrackDecoders that has data
// available in the range requested by aTarget.
already_AddRefed<SourceBufferDecoder> SelectDecoder(int64_t aTarget /* microseconds */,
int64_t aTolerance /* microseconds */,
const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
// Returns a string describing the state of the MediaSource internal
// buffered data. Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString);

View File

@ -553,10 +553,32 @@ MediaSourceReader::BreakCycles()
already_AddRefed<SourceBufferDecoder>
MediaSourceReader::SelectDecoder(int64_t aTarget,
int64_t aTolerance,
const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders)
TrackBuffer* aTrackBuffer)
{
return static_cast<MediaSourceDecoder*>(mDecoder)
->SelectDecoder(aTarget, aTolerance, aTrackDecoders);
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
media::TimeUnit target{media::TimeUnit::FromMicroseconds(aTarget)};
media::TimeUnit tolerance{media::TimeUnit::FromMicroseconds(aTolerance + aTarget)};
const nsTArray<nsRefPtr<SourceBufferDecoder>>& decoders{aTrackBuffer->Decoders()};
// aTolerance gives a slight bias toward the start of a range only.
// Consider decoders in order of newest to oldest, as a newer decoder
// providing a given buffered range is expected to replace an older one.
for (int32_t i = decoders.Length() - 1; i >= 0; --i) {
nsRefPtr<SourceBufferDecoder> newDecoder = decoders[i];
media::TimeIntervals ranges = aTrackBuffer->GetBuffered(newDecoder);
for (uint32_t j = 0; j < ranges.Length(); j++) {
if (target < ranges.End(j) && tolerance >= ranges.Start(j)) {
return newDecoder.forget();
}
}
MSE_DEBUGV("SelectDecoder(%lld fuzz:%lld) newDecoder=%p (%d/%d) target not in ranges=%s",
aTarget, aTolerance, newDecoder.get(), i+1,
decoders.Length(), DumpTimeRanges(ranges).get());
}
return nullptr;
}
bool
@ -564,7 +586,7 @@ MediaSourceReader::HaveData(int64_t aTarget, MediaData::Type aType)
{
TrackBuffer* trackBuffer = aType == MediaData::AUDIO_DATA ? mAudioTrack : mVideoTrack;
MOZ_ASSERT(trackBuffer);
nsRefPtr<SourceBufferDecoder> decoder = SelectDecoder(aTarget, EOS_FUZZ_US, trackBuffer->Decoders());
nsRefPtr<SourceBufferDecoder> decoder = SelectDecoder(aTarget, EOS_FUZZ_US, trackBuffer);
return !!decoder;
}
@ -582,9 +604,9 @@ MediaSourceReader::SwitchAudioSource(int64_t* aTarget)
// reader and skip the last few samples of the current one.
bool usedFuzz = false;
nsRefPtr<SourceBufferDecoder> newDecoder =
SelectDecoder(*aTarget, /* aTolerance = */ 0, mAudioTrack->Decoders());
SelectDecoder(*aTarget, /* aTolerance = */ 0, mAudioTrack);
if (!newDecoder) {
newDecoder = SelectDecoder(*aTarget, EOS_FUZZ_US, mAudioTrack->Decoders());
newDecoder = SelectDecoder(*aTarget, EOS_FUZZ_US, mAudioTrack);
usedFuzz = true;
}
if (GetAudioReader() && mAudioSourceDecoder != newDecoder) {
@ -627,9 +649,9 @@ MediaSourceReader::SwitchVideoSource(int64_t* aTarget)
// reader and skip the last few samples of the current one.
bool usedFuzz = false;
nsRefPtr<SourceBufferDecoder> newDecoder =
SelectDecoder(*aTarget, /* aTolerance = */ 0, mVideoTrack->Decoders());
SelectDecoder(*aTarget, /* aTolerance = */ 0, mVideoTrack);
if (!newDecoder) {
newDecoder = SelectDecoder(*aTarget, EOS_FUZZ_US, mVideoTrack->Decoders());
newDecoder = SelectDecoder(*aTarget, EOS_FUZZ_US, mVideoTrack);
usedFuzz = true;
}
if (GetVideoReader() && mVideoSourceDecoder != newDecoder) {
@ -732,10 +754,7 @@ MediaSourceReader::CreateSubDecoder(const nsACString& aType, int64_t aTimestampO
// MSE uses a start time of 0 everywhere. Set that immediately on the
// subreader to make sure that it's always in a state where we can invoke
// GetBuffered on it.
{
ReentrantMonitorAutoEnter mon(decoder->GetReentrantMonitor());
reader->SetStartTime(0);
}
reader->DispatchSetStartTime(0);
#ifdef MOZ_FMP4
reader->SetSharedDecoderManager(mSharedDecoderManager);
@ -1013,6 +1032,7 @@ MediaSourceReader::DoVideoSeek()
media::TimeIntervals
MediaSourceReader::GetBuffered()
{
MOZ_ASSERT(OnTaskQueue());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
media::TimeIntervals buffered;
@ -1256,25 +1276,27 @@ MediaSourceReader::GetMozDebugReaderData(nsAString& aString)
if (mAudioTrack) {
result += nsPrintfCString("\tDumping Audio Track Decoders: - mLastAudioTime: %f\n", double(mLastAudioTime) / USECS_PER_S);
for (int32_t i = mAudioTrack->Decoders().Length() - 1; i >= 0; --i) {
nsRefPtr<MediaDecoderReader> newReader = mAudioTrack->Decoders()[i]->GetReader();
media::TimeIntervals ranges = mAudioTrack->Decoders()[i]->GetBuffered();
const nsRefPtr<SourceBufferDecoder>& newDecoder{mAudioTrack->Decoders()[i]};
media::TimeIntervals ranges = mAudioTrack->GetBuffered(newDecoder);
result += nsPrintfCString("\t\tReader %d: %p ranges=%s active=%s size=%lld\n",
i, newReader.get(), DumpTimeRanges(ranges).get(),
newReader.get() == GetAudioReader() ? "true" : "false",
mAudioTrack->Decoders()[i]->GetResource()->GetSize());
i,
newDecoder->GetReader(),
DumpTimeRanges(ranges).get(),
newDecoder->GetReader() == GetAudioReader() ? "true" : "false",
newDecoder->GetResource()->GetSize());
}
}
if (mVideoTrack) {
result += nsPrintfCString("\tDumping Video Track Decoders - mLastVideoTime: %f\n", double(mLastVideoTime) / USECS_PER_S);
for (int32_t i = mVideoTrack->Decoders().Length() - 1; i >= 0; --i) {
nsRefPtr<MediaDecoderReader> newReader = mVideoTrack->Decoders()[i]->GetReader();
media::TimeIntervals ranges = mVideoTrack->Decoders()[i]->GetBuffered();
const nsRefPtr<SourceBufferDecoder>& newDecoder{mVideoTrack->Decoders()[i]};
media::TimeIntervals ranges = mVideoTrack->GetBuffered(newDecoder);
result += nsPrintfCString("\t\tReader %d: %p ranges=%s active=%s size=%lld\n",
i, newReader.get(), DumpTimeRanges(ranges).get(),
newReader.get() == GetVideoReader() ? "true" : "false",
i,
newDecoder->GetReader(),
DumpTimeRanges(ranges).get(),
newDecoder->GetReader() == GetVideoReader() ? "true" : "false",
mVideoTrack->Decoders()[i]->GetResource()->GetSize());
}
}

View File

@ -219,9 +219,10 @@ private:
// Return a decoder from the set available in aTrackDecoders that has data
// available in the range requested by aTarget.
friend class TrackBuffer;
already_AddRefed<SourceBufferDecoder> SelectDecoder(int64_t aTarget /* microseconds */,
int64_t aTolerance /* microseconds */,
const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
TrackBuffer* aTrackBuffer);
bool HaveData(int64_t aTarget, MediaData::Type aType);
already_AddRefed<SourceBufferDecoder> FirstDecoder(MediaData::Type aType);

View File

@ -147,6 +147,12 @@ SourceBuffer::GetBuffered(ErrorResult& aRv)
return tr.forget();
}
media::TimeIntervals
SourceBuffer::GetTimeIntervals()
{
return mContentManager->Buffered();
}
void
SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv)
{
@ -297,7 +303,7 @@ SourceBuffer::Ended()
mContentManager->Ended();
// We want the MediaSourceReader to refresh its buffered range as it may
// have been modified (end lined up).
mMediaSource->GetDecoder()->NotifyDataArrived(nullptr, 1, mReportedOffset++);
mMediaSource->GetDecoder()->NotifyDataArrived(1, mReportedOffset++, /* aThrottleUpdates = */ false);
}
SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
@ -492,7 +498,7 @@ SourceBuffer::AppendDataCompletedWithSuccess(bool aHasActiveTracks)
// Tell our parent decoder that we have received new data.
// The information provided do not matter much so long as it is monotonically
// increasing.
mMediaSource->GetDecoder()->NotifyDataArrived(nullptr, 1, mReportedOffset++);
mMediaSource->GetDecoder()->NotifyDataArrived(1, mReportedOffset++, /* aThrottleUpdates = */ false);
// Send progress event.
mMediaSource->GetDecoder()->NotifyBytesDownloaded();
}

View File

@ -60,6 +60,7 @@ public:
}
already_AddRefed<TimeRanges> GetBuffered(ErrorResult& aRv);
TimeIntervals GetTimeIntervals();
double TimestampOffset() const
{

View File

@ -178,12 +178,6 @@ SourceBufferDecoder::Trim(int64_t aDuration)
mTrimmedOffset = (double)aDuration / USECS_PER_S;
}
void
SourceBufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
{
MSE_DEBUG("UNIMPLEMENTED");
}
void
SourceBufferDecoder::SetMediaSeekable(bool aMediaSeekable)
{
@ -203,15 +197,8 @@ SourceBufferDecoder::GetOwner()
}
void
SourceBufferDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
SourceBufferDecoder::NotifyDataArrived(uint32_t aLength, int64_t aOffset, bool aThrottleUpdates)
{
mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
// XXX: Params make no sense to parent decoder as it relates to a
// specific SourceBufferDecoder's data stream. Pass bogus values here to
// force parent decoder's state machine to recompute end time for
// infinite length media.
mParentDecoder->NotifyDataArrived(nullptr, 0, 0);
}
media::TimeIntervals

View File

@ -47,14 +47,13 @@ public:
virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
MediaDecoderEventVisibility aEventVisibility) final override;
virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) final override;
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) final override;
virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset, bool aThrottleUpdates) final override;
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded, uint32_t aDropped) final override;
virtual void NotifyWaitingForResourcesStatusChanged() final override;
virtual void OnReadMetadataCompleted() final override;
virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) final override;
virtual void RemoveMediaTracks() final override;
virtual void SetMediaSeekable(bool aMediaSeekable) final override;
virtual void UpdateEstimatedMediaDuration(int64_t aDuration) final override;
virtual bool HasInitializationData() final override;
// SourceBufferResource specific interface below.

View File

@ -36,6 +36,9 @@ extern PRLogModuleInfo* GetMediaSourceLog();
#define EOS_FUZZ_US 125000
using media::TimeIntervals;
using media::Interval;
namespace mozilla {
TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType)
@ -242,44 +245,96 @@ TrackBuffer::BufferAppend()
mAdjustedTimestamp = starttu;
}
if (!AppendDataToCurrentResource(mInputBuffer, end - start)) {
int64_t offset = AppendDataToCurrentResource(mInputBuffer, end - start);
if (offset < 0) {
mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
return p;
}
mLastAppendRange =
Interval<int64_t>(offset, offset + int64_t(mInputBuffer->Length()));
if (decoders.Length()) {
// We're going to have to wait for the decoder to initialize, the promise
// will be resolved once initialization completes.
return p;
}
// Tell our reader that we have more data to ensure that playback starts if
// required when data is appended.
NotifyTimeRangesChanged();
nsRefPtr<TrackBuffer> self = this;
ProxyMediaCall(mParentDecoder->GetReader()->TaskQueue(), this, __func__,
&TrackBuffer::UpdateBufferedRanges,
mLastAppendRange, /* aNotifyParent */ true)
->Then(mParentDecoder->GetReader()->TaskQueue(), __func__,
[self] {
self->mInitializationPromise.ResolveIfExists(self->HasInitSegment(), __func__);
},
[self] (nsresult) { MOZ_CRASH("Never called."); });
mInitializationPromise.Resolve(HasInitSegment(), __func__);
return p;
}
bool
int64_t
TrackBuffer::AppendDataToCurrentResource(MediaByteBuffer* aData, uint32_t aDuration)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mCurrentDecoder) {
return false;
return -1;
}
SourceBufferResource* resource = mCurrentDecoder->GetResource();
int64_t appendOffset = resource->GetLength();
resource->AppendData(aData);
mCurrentDecoder->SetRealMediaDuration(mCurrentDecoder->GetRealMediaDuration() + aDuration);
// XXX: For future reference: NDA call must run on the main thread.
mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData->Elements()),
aData->Length(), appendOffset);
mParentDecoder->NotifyBytesDownloaded();
return appendOffset;
}
nsRefPtr<TrackBuffer::BufferedRangesUpdatedPromise>
TrackBuffer::UpdateBufferedRanges(Interval<int64_t> aByteRange, bool aNotifyParent)
{
if (aByteRange.Length()) {
mCurrentDecoder->GetReader()->NotifyDataArrived(aByteRange);
}
// Recalculate and cache our new buffered range.
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
TimeIntervals buffered;
for (auto& decoder : mInitializedDecoders) {
TimeIntervals decoderBuffered(decoder->GetBuffered());
mReadersBuffered[decoder] = decoderBuffered;
buffered += decoderBuffered;
}
// mParser may not be initialized yet, and will only be so if we have a
// buffered range.
if (buffered.Length()) {
buffered.SetFuzz(TimeUnit::FromMicroseconds(mParser->GetRoundingError()));
}
mBufferedRanges = buffered;
}
if (aNotifyParent) {
nsRefPtr<MediaSourceDecoder> parent = mParentDecoder;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([parent] () {
// XXX: Params make no sense to parent decoder as it relates to a
// specific SourceBufferDecoder's data stream. Pass bogus values here to
// force parent decoder's state machine to recompute end time for
// infinite length media.
parent->NotifyDataArrived(0, 0, /* aThrottleUpdates = */ false);
parent->NotifyBytesDownloaded();
});
AbstractThread::MainThread()->Dispatch(task.forget());
}
// Tell our reader that we have more data to ensure that playback starts if
// required when data is appended.
NotifyTimeRangesChanged();
return true;
return BufferedRangesUpdatedPromise::CreateAndResolve(true, __func__);
}
void
@ -291,24 +346,49 @@ TrackBuffer::NotifyTimeRangesChanged()
mParentDecoder->GetReader()->TaskQueue()->Dispatch(task.forget());
}
void
TrackBuffer::NotifyReaderDataRemoved(MediaDecoderReader* aReader)
{
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<TrackBuffer> self = this;
nsRefPtr<MediaDecoderReader> reader = aReader;
RefPtr<nsIRunnable> task =
NS_NewRunnableFunction([self, reader] () {
reader->NotifyDataRemoved();
self->UpdateBufferedRanges(Interval<int64_t>(), /* aNotifyParent */ false);
});
aReader->TaskQueue()->Dispatch(task.forget());
}
class DecoderSorter
{
public:
explicit DecoderSorter(const TrackBuffer::DecoderBufferedMap& aMap)
: mMap(aMap)
{}
bool LessThan(SourceBufferDecoder* aFirst, SourceBufferDecoder* aSecond) const
{
TimeIntervals first = aFirst->GetBuffered();
TimeIntervals second = aSecond->GetBuffered();
MOZ_ASSERT(mMap.find(aFirst) != mMap.end());
MOZ_ASSERT(mMap.find(aSecond) != mMap.end());
const TimeIntervals& first = mMap.find(aFirst)->second;
const TimeIntervals& second = mMap.find(aSecond)->second;
return first.GetStart() < second.GetStart();
}
bool Equals(SourceBufferDecoder* aFirst, SourceBufferDecoder* aSecond) const
{
TimeIntervals first = aFirst->GetBuffered();
TimeIntervals second = aSecond->GetBuffered();
MOZ_ASSERT(mMap.find(aFirst) != mMap.end());
MOZ_ASSERT(mMap.find(aSecond) != mMap.end());
const TimeIntervals& first = mMap.find(aFirst)->second;
const TimeIntervals& second = mMap.find(aSecond)->second;
return first.GetStart() == second.GetStart();
}
const TrackBuffer::DecoderBufferedMap& mMap;
};
TrackBuffer::EvictDataResult
@ -331,14 +411,24 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
}
// Get a list of initialized decoders.
nsTArray<SourceBufferDecoder*> decoders;
nsTArray<nsRefPtr<SourceBufferDecoder>> decoders;
decoders.AppendElements(mInitializedDecoders);
const TimeUnit evictThresholdTime{TimeUnit::FromSeconds(MSE_EVICT_THRESHOLD_TIME)};
// Find the reader currently being played with.
SourceBufferDecoder* playingDecoder = nullptr;
for (const auto& decoder : decoders) {
if (mParentDecoder->IsActiveReader(decoder->GetReader())) {
playingDecoder = decoder;
break;
}
}
TimeUnit playingDecoderStartTime{GetBuffered(playingDecoder).GetStart()};
// First try to evict data before the current play position, starting
// with the oldest decoder.
for (uint32_t i = 0; i < decoders.Length() && toEvict > 0; ++i) {
TimeIntervals buffered = decoders[i]->GetBuffered();
TimeIntervals buffered = GetBuffered(decoders[i]);
MSE_DEBUG("Step1. decoder=%u/%u threshold=%u toEvict=%lld",
i, decoders.Length(), aThreshold, toEvict);
@ -366,6 +456,10 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
buffered.GetEnd().ToSeconds(), aPlaybackTime.ToSeconds(),
time, playbackOffset, decoders[i]->GetResource()->GetSize());
if (playbackOffset > 0) {
if (decoders[i] == playingDecoder) {
// This is an approximation only, likely pessimistic.
playingDecoderStartTime = time;
}
ErrorResult rv;
toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset,
playbackOffset,
@ -376,7 +470,7 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
}
}
}
decoders[i]->GetReader()->NotifyDataRemoved();
NotifyReaderDataRemoved(decoders[i]->GetReader());
}
}
@ -384,13 +478,15 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
for (uint32_t i = 0; i < decoders.Length() && toEvict > 0; ++i) {
MSE_DEBUG("Step2. decoder=%u/%u threshold=%u toEvict=%lld",
i, decoders.Length(), aThreshold, toEvict);
if (mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
if (decoders[i] == playingDecoder) {
break;
}
if (decoders[i] == mCurrentDecoder) {
continue;
}
TimeIntervals buffered = decoders[i]->GetBuffered();
// The buffered value is potentially stale should eviction occurred in
// step 1. However this is only used for logging.
TimeIntervals buffered = GetBuffered(decoders[i]);
// Remove data from older decoders than the current one.
MSE_DEBUG("evicting all "
@ -398,7 +494,7 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds(),
aPlaybackTime, decoders[i]->GetResource()->GetSize());
toEvict -= decoders[i]->GetResource()->EvictAll();
decoders[i]->GetReader()->NotifyDataRemoved();
NotifyReaderDataRemoved(decoders[i]->GetReader());
}
// Evict all data from future decoders, starting furthest away from
@ -409,26 +505,21 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
// TODO: This step should be done using RangeRemoval:
// Something like: RangeRemoval(aPlaybackTime + 60s, End);
// Find the reader currently being played with.
SourceBufferDecoder* playingDecoder = nullptr;
for (uint32_t i = 0; i < decoders.Length() && toEvict > 0; ++i) {
if (mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
playingDecoder = decoders[i];
break;
}
}
// Find the next decoder we're likely going to play with.
nsRefPtr<SourceBufferDecoder> nextPlayingDecoder = nullptr;
if (playingDecoder) {
TimeIntervals buffered = playingDecoder->GetBuffered();
// The buffered value is potentially stale should eviction occurred in
// step 1. However step 1 modified the start of the range value, and now
// will use the end value.
TimeIntervals buffered = GetBuffered(playingDecoder);
nextPlayingDecoder =
mParentDecoder->SelectDecoder(buffered.GetEnd().ToMicroseconds() + 1,
EOS_FUZZ_US,
mInitializedDecoders);
mParentDecoder->GetReader()->SelectDecoder(buffered.GetEnd().ToMicroseconds() + 1,
EOS_FUZZ_US,
this);
}
// Sort decoders by their start times.
decoders.Sort(DecoderSorter());
decoders.Sort(DecoderSorter{mReadersBuffered});
for (int32_t i = int32_t(decoders.Length()) - 1; i >= 0 && toEvict > 0; --i) {
MSE_DEBUG("Step3. decoder=%u/%u threshold=%u toEvict=%lld",
@ -437,14 +528,17 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
decoders[i] == mCurrentDecoder) {
continue;
}
TimeIntervals buffered = decoders[i]->GetBuffered();
// The buffered value is potentially stale should eviction occurred in
// step 1 and 2. However step 3 is a last resort step where we will remove
// all content and the buffered value is only used for logging.
TimeIntervals buffered = GetBuffered(decoders[i]);
MSE_DEBUG("evicting all "
"bufferedStart=%f bufferedEnd=%f aPlaybackTime=%f size=%lld",
buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds(),
aPlaybackTime, decoders[i]->GetResource()->GetSize());
toEvict -= decoders[i]->GetResource()->EvictAll();
decoders[i]->GetReader()->NotifyDataRemoved();
NotifyReaderDataRemoved(decoders[i]->GetReader());
}
RemoveEmptyDecoders(decoders);
@ -452,8 +546,8 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
bool evicted = toEvict < (totalSize - aThreshold);
if (evicted) {
if (playingDecoder) {
TimeIntervals ranges = playingDecoder->GetBuffered();
*aBufferStartTime = std::max(TimeUnit::FromSeconds(0), ranges.GetStart());
*aBufferStartTime =
std::max(TimeUnit::FromSeconds(0), playingDecoderStartTime);
} else {
// We do not currently have data to play yet.
// Avoid evicting anymore data to minimize rebuffering time.
@ -461,38 +555,37 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
}
}
if (evicted) {
NotifyTimeRangesChanged();
}
return evicted ?
EvictDataResult::DATA_EVICTED :
(HasOnlyIncompleteMedia() ? EvictDataResult::CANT_EVICT : EvictDataResult::NO_DATA_EVICTED);
}
void
TrackBuffer::RemoveEmptyDecoders(nsTArray<mozilla::SourceBufferDecoder*>& aDecoders)
TrackBuffer::RemoveEmptyDecoders(const nsTArray<nsRefPtr<mozilla::SourceBufferDecoder>>& aDecoders)
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
nsRefPtr<TrackBuffer> self = this;
nsTArray<nsRefPtr<mozilla::SourceBufferDecoder>> decoders(aDecoders);
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction([self, decoders] () {
if (!self->mParentDecoder) {
return;
}
ReentrantMonitorAutoEnter mon(self->mParentDecoder->GetReentrantMonitor());
// Remove decoders that have no data in them
for (uint32_t i = 0; i < aDecoders.Length(); ++i) {
TimeIntervals buffered = aDecoders[i]->GetBuffered();
MSE_DEBUG("maybe remove empty decoders=%d "
"size=%lld start=%f end=%f",
i, aDecoders[i]->GetResource()->GetSize(),
buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds());
if (aDecoders[i] == mCurrentDecoder ||
mParentDecoder->IsActiveReader(aDecoders[i]->GetReader())) {
continue;
}
if (aDecoders[i]->GetResource()->GetSize() == 0 || !buffered.Length() ||
buffered[0].IsEmpty()) {
MSE_DEBUG("remove empty decoders=%d", i);
RemoveDecoder(aDecoders[i]);
}
}
// Remove decoders that have decoders data in them
for (uint32_t i = 0; i < decoders.Length(); ++i) {
if (decoders[i] == self->mCurrentDecoder ||
self->mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
continue;
}
TimeIntervals buffered = self->GetBuffered(decoders[i]);
if (decoders[i]->GetResource()->GetSize() == 0 || !buffered.Length() ||
buffered[0].IsEmpty()) {
self->RemoveDecoder(decoders[i]);
}
}
});
AbstractThread::MainThread()->Dispatch(task.forget());
}
int64_t
@ -511,7 +604,7 @@ TrackBuffer::HasOnlyIncompleteMedia()
if (!mCurrentDecoder) {
return false;
}
TimeIntervals buffered = mCurrentDecoder->GetBuffered();
TimeIntervals buffered = GetBuffered(mCurrentDecoder);
MSE_DEBUG("mCurrentDecoder.size=%lld, start=%f end=%f",
mCurrentDecoder->GetResource()->GetSize(),
buffered.GetStart(), buffered.GetEnd());
@ -534,10 +627,9 @@ TrackBuffer::EvictBefore(TimeUnit aTime)
rv.SuppressException();
return;
}
mInitializedDecoders[i]->GetReader()->NotifyDataRemoved();
NotifyReaderDataRemoved(mInitializedDecoders[i]->GetReader());
}
}
NotifyTimeRangesChanged();
}
TimeIntervals
@ -545,18 +637,20 @@ TrackBuffer::Buffered()
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
TimeIntervals buffered;
return mBufferedRanges;
}
for (auto& decoder : mInitializedDecoders) {
buffered += decoder->GetBuffered();
}
// mParser may not be initialized yet, and will only be so if we have a
// buffered range.
if (buffered.Length()) {
buffered.SetFuzz(TimeUnit::FromMicroseconds(mParser->GetRoundingError()));
}
TimeIntervals
TrackBuffer::GetBuffered(SourceBufferDecoder* aDecoder)
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
return buffered;
DecoderBufferedMap::const_iterator val = mReadersBuffered.find(aDecoder);
if (val == mReadersBuffered.end()) {
return TimeIntervals::Invalid();
}
return val->second;
}
already_AddRefed<SourceBufferDecoder>
@ -667,6 +761,8 @@ TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
MSE_DEBUG("Initializing subdecoder %p reader %p",
aDecoder, reader);
reader->NotifyDataArrived(mLastAppendRange);
// HACK WARNING:
// We only reach this point once we know that we have a complete init segment.
// We don't want the demuxer to do a blocking read as no more data can be
@ -823,7 +919,6 @@ TrackBuffer::CompleteInitializeDecoder(SourceBufferDecoder* aDecoder)
return;
}
int64_t duration = mInfo.mMetadataDuration.isSome()
? mInfo.mMetadataDuration.ref().ToMicroseconds() : -1;
if (!duration) {
@ -838,7 +933,15 @@ TrackBuffer::CompleteInitializeDecoder(SourceBufferDecoder* aDecoder)
MSE_DEBUG("Reader %p activated",
aDecoder->GetReader());
mInitializationPromise.ResolveIfExists(true, __func__);
nsRefPtr<TrackBuffer> self = this;
ProxyMediaCall(mParentDecoder->GetReader()->TaskQueue(), this, __func__,
&TrackBuffer::UpdateBufferedRanges,
Interval<int64_t>(), /* aNotifyParent */ true)
->Then(mParentDecoder->GetReader()->TaskQueue(), __func__,
[self] {
self->mInitializationPromise.ResolveIfExists(self->HasInitSegment(), __func__);
},
[self] (nsresult) { MOZ_CRASH("Never called."); });
}
bool
@ -934,7 +1037,7 @@ TrackBuffer::ContainsTime(int64_t aTime, int64_t aTolerance)
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
TimeUnit time{TimeUnit::FromMicroseconds(aTime)};
for (auto& decoder : mInitializedDecoders) {
TimeIntervals r = decoder->GetBuffered();
TimeIntervals r = GetBuffered(decoder);
r.SetFuzz(TimeUnit::FromMicroseconds(aTolerance));
if (r.Contains(time)) {
return true;
@ -1087,6 +1190,8 @@ TrackBuffer::RemoveDecoder(SourceBufferDecoder* aDecoder)
MOZ_ASSERT(!mParentDecoder->IsActiveReader(aDecoder->GetReader()));
mInitializedDecoders.RemoveElement(aDecoder);
mDecoders.RemoveElement(aDecoder);
// Remove associated buffered range from our cache.
mReadersBuffered.erase(aDecoder);
}
aDecoder->GetReader()->TaskQueue()->Dispatch(task.forget());
}
@ -1113,13 +1218,13 @@ TrackBuffer::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
return RangeRemovalPromise::CreateAndResolve(false, __func__);
}
nsTArray<SourceBufferDecoder*> decoders;
nsTArray<nsRefPtr<SourceBufferDecoder>> decoders;
decoders.AppendElements(mInitializedDecoders);
if (aStart <= bufferedStart && aEnd < bufferedEnd) {
// Evict data from beginning.
for (size_t i = 0; i < decoders.Length(); ++i) {
TimeIntervals buffered = decoders[i]->GetBuffered();
TimeIntervals buffered = GetBuffered(decoders[i]);
if (buffered.GetEnd() < aEnd) {
// Can be fully removed.
MSE_DEBUG("remove all bufferedEnd=%f size=%lld",
@ -1140,7 +1245,7 @@ TrackBuffer::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
}
}
}
decoders[i]->GetReader()->NotifyDataRemoved();
NotifyReaderDataRemoved(decoders[i]->GetReader());
}
} else {
// Only trimming existing buffers.
@ -1151,14 +1256,26 @@ TrackBuffer::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
} else {
decoders[i]->Trim(aStart.ToMicroseconds());
}
decoders[i]->GetReader()->NotifyDataRemoved();
NotifyReaderDataRemoved(decoders[i]->GetReader());
}
}
RemoveEmptyDecoders(decoders);
NotifyTimeRangesChanged();
return RangeRemovalPromise::CreateAndResolve(true, __func__);
nsRefPtr<RangeRemovalPromise> p = mRangeRemovalPromise.Ensure(__func__);
// Make sure our buffered ranges got updated before resolving promise.
nsRefPtr<TrackBuffer> self = this;
ProxyMediaCall(mParentDecoder->GetReader()->TaskQueue(), this, __func__,
&TrackBuffer::UpdateBufferedRanges,
Interval<int64_t>(), /* aNotifyParent */ false)
->Then(mParentDecoder->GetReader()->TaskQueue(), __func__,
[self] {
self->mRangeRemovalPromise.ResolveIfExists(true, __func__);
},
[self] (nsresult) { MOZ_CRASH("Never called."); });
return p;
}
void

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