mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 02:14:43 +00:00
merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
commit
90ccc67e4e
@ -1 +1,4 @@
|
||||
BasedOnStyle: Mozilla
|
||||
|
||||
# Ignore all comments because they aren't reflowed properly.
|
||||
CommentPragmas: "^"
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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() { }
|
||||
|
@ -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
|
||||
|
@ -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>"
|
||||
|
@ -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>
|
||||
|
@ -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"));
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ public:
|
||||
|
||||
protected:
|
||||
bool IsDefaultBrowserVista(bool aCheckAllTypes, bool* aIsDefaultBrowser);
|
||||
nsresult LaunchControlPanelDefaultPrograms();
|
||||
nsresult LaunchControlPanelDefaultsSelectionUI();
|
||||
nsresult LaunchModernSettingsDialogDefaultApps();
|
||||
nsresult InvokeHTTPOpenAsVerb();
|
||||
nsresult LaunchHTTPHandlerPane();
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -495,6 +495,10 @@ DOMInterfaces = {
|
||||
'wrapperCache': False,
|
||||
},
|
||||
|
||||
'FontFaceSet': {
|
||||
'implicitJSContext': [ 'load' ],
|
||||
},
|
||||
|
||||
'FontFaceSetIterator': {
|
||||
'wrapperCache': False,
|
||||
},
|
||||
|
198
dom/cache/CacheStorage.cpp
vendored
198
dom/cache/CacheStorage.cpp
vendored
@ -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) {
|
||||
|
6
dom/cache/CacheStorage.h
vendored
6
dom/cache/CacheStorage.h
vendored
@ -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
4
dom/cache/moz.build
vendored
@ -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
1
dom/cache/test/mochitest/browser.ini
vendored
Normal file
@ -0,0 +1 @@
|
||||
[browser_cache_pb_window.js]
|
83
dom/cache/test/mochitest/browser_cache_pb_window.js
vendored
Normal file
83
dom/cache/test/mochitest/browser_cache_pb_window.js
vendored
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
1
dom/cache/test/mochitest/driver.js
vendored
1
dom/cache/test/mochitest/driver.js
vendored
@ -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]]
|
||||
|
1
dom/cache/test/mochitest/mochitest.ini
vendored
1
dom/cache/test/mochitest/mochitest.ini
vendored
@ -42,3 +42,4 @@ support-files =
|
||||
[test_cache_shrink.html]
|
||||
[test_cache_orphaned_cache.html]
|
||||
[test_cache_orphaned_body.html]
|
||||
[test_cache_untrusted.html]
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
40
dom/cache/test/mochitest/test_cache_untrusted.html
vendored
Normal file
40
dom/cache/test/mochitest/test_cache_untrusted.html
vendored
Normal 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>
|
@ -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/';
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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());
|
||||
|
@ -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, ¬ification);
|
||||
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, ¬ification);
|
||||
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, ¬ification);
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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")) {
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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().
|
||||
//
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ public:
|
||||
}
|
||||
|
||||
already_AddRefed<TimeRanges> GetBuffered(ErrorResult& aRv);
|
||||
TimeIntervals GetTimeIntervals();
|
||||
|
||||
double TimestampOffset() const
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user