mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-27 04:38:02 +00:00
merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
commit
66f9f77e3d
@ -98,6 +98,16 @@ static nsRoleMapEntry sWAIRoleMaps[] =
|
||||
kNoReqStates
|
||||
// eARIAPressed is auto applied on any button
|
||||
},
|
||||
{ // cell
|
||||
&nsGkAtoms::cell,
|
||||
roles::CELL,
|
||||
kUseMapRole,
|
||||
eNoValue,
|
||||
eNoAction,
|
||||
eNoLiveAttr,
|
||||
eTableCell,
|
||||
kNoReqStates
|
||||
},
|
||||
{ // checkbox
|
||||
&nsGkAtoms::checkbox,
|
||||
roles::CHECKBUTTON,
|
||||
@ -631,6 +641,17 @@ static nsRoleMapEntry sWAIRoleMaps[] =
|
||||
kNoReqStates,
|
||||
eARIASelectable
|
||||
},
|
||||
{ // table
|
||||
&nsGkAtoms::table,
|
||||
roles::TABLE,
|
||||
kUseMapRole,
|
||||
eNoValue,
|
||||
eNoAction,
|
||||
eNoLiveAttr,
|
||||
eTable,
|
||||
kNoReqStates,
|
||||
eARIASelectable
|
||||
},
|
||||
{ // tablist
|
||||
&nsGkAtoms::tablist,
|
||||
roles::PAGETABLIST,
|
||||
|
@ -48,8 +48,9 @@ uint32_t
|
||||
filters::GetCell(Accessible* aAccessible)
|
||||
{
|
||||
a11y::role role = aAccessible->Role();
|
||||
return role == roles::GRID_CELL || role == roles::ROWHEADER ||
|
||||
role == roles::COLUMNHEADER ? eMatch : eSkipSubtree;
|
||||
return role == roles::CELL || role == roles::GRID_CELL ||
|
||||
role == roles::ROWHEADER || role == roles::COLUMNHEADER ?
|
||||
eMatch : eSkipSubtree;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
|
@ -68,7 +68,7 @@ ARIAGridAccessible::RowCount()
|
||||
|
||||
Accessible*
|
||||
ARIAGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex)
|
||||
{
|
||||
{
|
||||
Accessible* row = GetRowAt(aRowIndex);
|
||||
if (!row)
|
||||
return nullptr;
|
||||
@ -79,6 +79,9 @@ ARIAGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex)
|
||||
bool
|
||||
ARIAGridAccessible::IsColSelected(uint32_t aColIdx)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return false;
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
Accessible* row = rowIter.Next();
|
||||
if (!row)
|
||||
@ -98,6 +101,9 @@ ARIAGridAccessible::IsColSelected(uint32_t aColIdx)
|
||||
bool
|
||||
ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return false;
|
||||
|
||||
Accessible* row = GetRowAt(aRowIdx);
|
||||
if(!row)
|
||||
return false;
|
||||
@ -117,6 +123,9 @@ ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx)
|
||||
bool
|
||||
ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return false;
|
||||
|
||||
Accessible* row = GetRowAt(aRowIdx);
|
||||
if(!row)
|
||||
return false;
|
||||
@ -133,6 +142,9 @@ ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
|
||||
uint32_t
|
||||
ARIAGridAccessible::SelectedCellCount()
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return 0;
|
||||
|
||||
uint32_t count = 0, colCount = ColCount();
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
@ -159,6 +171,9 @@ ARIAGridAccessible::SelectedCellCount()
|
||||
uint32_t
|
||||
ARIAGridAccessible::SelectedColCount()
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return 0;
|
||||
|
||||
uint32_t colCount = ColCount();
|
||||
if (!colCount)
|
||||
return 0;
|
||||
@ -193,6 +208,9 @@ ARIAGridAccessible::SelectedColCount()
|
||||
uint32_t
|
||||
ARIAGridAccessible::SelectedRowCount()
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return 0;
|
||||
|
||||
uint32_t count = 0;
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
@ -227,6 +245,9 @@ ARIAGridAccessible::SelectedRowCount()
|
||||
void
|
||||
ARIAGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return;
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
|
||||
Accessible* row = nullptr;
|
||||
@ -251,6 +272,9 @@ ARIAGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
|
||||
void
|
||||
ARIAGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return;
|
||||
|
||||
uint32_t colCount = ColCount();
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
@ -275,6 +299,9 @@ ARIAGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
|
||||
void
|
||||
ARIAGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return;
|
||||
|
||||
uint32_t colCount = ColCount();
|
||||
if (!colCount)
|
||||
return;
|
||||
@ -309,6 +336,9 @@ ARIAGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
|
||||
void
|
||||
ARIAGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return;
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
Accessible* row = nullptr;
|
||||
for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
|
||||
@ -338,6 +368,9 @@ ARIAGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
|
||||
void
|
||||
ARIAGridAccessible::SelectRow(uint32_t aRowIdx)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return;
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
|
||||
Accessible* row = nullptr;
|
||||
@ -350,6 +383,9 @@ ARIAGridAccessible::SelectRow(uint32_t aRowIdx)
|
||||
void
|
||||
ARIAGridAccessible::SelectCol(uint32_t aColIdx)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return;
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
|
||||
Accessible* row = nullptr;
|
||||
@ -368,8 +404,10 @@ ARIAGridAccessible::SelectCol(uint32_t aColIdx)
|
||||
void
|
||||
ARIAGridAccessible::UnselectRow(uint32_t aRowIdx)
|
||||
{
|
||||
Accessible* row = GetRowAt(aRowIdx);
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return;
|
||||
|
||||
Accessible* row = GetRowAt(aRowIdx);
|
||||
if (row)
|
||||
SetARIASelected(row, false);
|
||||
}
|
||||
@ -377,6 +415,9 @@ ARIAGridAccessible::UnselectRow(uint32_t aRowIdx)
|
||||
void
|
||||
ARIAGridAccessible::UnselectCol(uint32_t aColIdx)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return;
|
||||
|
||||
AccIterator rowIter(this, filters::GetRow);
|
||||
|
||||
Accessible* row = nullptr;
|
||||
@ -421,6 +462,9 @@ nsresult
|
||||
ARIAGridAccessible::SetARIASelected(Accessible* aAccessible,
|
||||
bool aIsSelected, bool aNotify)
|
||||
{
|
||||
if (IsARIARole(nsGkAtoms::table))
|
||||
return NS_OK;
|
||||
|
||||
nsIContent *content = aAccessible->GetContent();
|
||||
NS_ENSURE_STATE(content);
|
||||
|
||||
@ -524,8 +568,8 @@ ARIAGridCellAccessible::ColIdx() const
|
||||
for (int32_t idx = 0; idx < indexInRow; idx++) {
|
||||
Accessible* cell = row->GetChildAt(idx);
|
||||
roles::Role role = cell->Role();
|
||||
if (role == roles::GRID_CELL || role == roles::ROWHEADER ||
|
||||
role == roles::COLUMNHEADER)
|
||||
if (role == roles::CELL || role == roles::GRID_CELL ||
|
||||
role == roles::ROWHEADER || role == roles::COLUMNHEADER)
|
||||
colIdx++;
|
||||
}
|
||||
|
||||
@ -593,8 +637,8 @@ ARIAGridCellAccessible::NativeAttributes()
|
||||
colIdx = colCount;
|
||||
|
||||
roles::Role role = child->Role();
|
||||
if (role == roles::GRID_CELL || role == roles::ROWHEADER ||
|
||||
role == roles::COLUMNHEADER)
|
||||
if (role == roles::CELL || role == roles::GRID_CELL ||
|
||||
role == roles::ROWHEADER || role == roles::COLUMNHEADER)
|
||||
colCount++;
|
||||
}
|
||||
|
||||
|
@ -1755,6 +1755,42 @@ HyperTextAccessible::RemoveChild(Accessible* aAccessible)
|
||||
return Accessible::RemoveChild(aAccessible);
|
||||
}
|
||||
|
||||
Relation
|
||||
HyperTextAccessible::RelationByType(RelationType aType)
|
||||
{
|
||||
Relation rel = Accessible::RelationByType(aType);
|
||||
|
||||
switch (aType) {
|
||||
case RelationType::NODE_CHILD_OF:
|
||||
if (mContent->IsMathMLElement()) {
|
||||
Accessible* parent = Parent();
|
||||
if (parent) {
|
||||
nsIContent* parentContent = parent->GetContent();
|
||||
if (parentContent->IsMathMLElement(nsGkAtoms::mroot_)) {
|
||||
// Add a relation pointing to the parent <mroot>.
|
||||
rel.AppendTarget(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RelationType::NODE_PARENT_OF:
|
||||
if (mContent->IsMathMLElement(nsGkAtoms::mroot_)) {
|
||||
Accessible* base = GetChildAt(0);
|
||||
Accessible* index = GetChildAt(1);
|
||||
if (base && index) {
|
||||
// Append the <mroot> children in the order index, base.
|
||||
rel.AppendTarget(index);
|
||||
rel.AppendTarget(base);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rel;
|
||||
}
|
||||
|
||||
void
|
||||
HyperTextAccessible::CacheChildren()
|
||||
{
|
||||
|
@ -62,6 +62,7 @@ public:
|
||||
|
||||
virtual void InvalidateChildren() override;
|
||||
virtual bool RemoveChild(Accessible* aAccessible) override;
|
||||
virtual Relation RelationByType(RelationType aType) override;
|
||||
|
||||
// HyperTextAccessible (static helper method)
|
||||
|
||||
|
@ -124,6 +124,9 @@ static const uintptr_t IS_PROXY = 1;
|
||||
- (void)valueDidChange;
|
||||
- (void)selectedTextDidChange;
|
||||
|
||||
// internal method to retrieve a child at a given index.
|
||||
- (id)childAt:(uint32_t)i;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// invalidates and removes all our children from our cached array.
|
||||
|
@ -27,6 +27,23 @@
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
#define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand"
|
||||
#define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex"
|
||||
#define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator"
|
||||
#define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator"
|
||||
#define NSAccessibilityMathBaseAttribute @"AXMathBase"
|
||||
#define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript"
|
||||
#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript"
|
||||
#define NSAccessibilityMathUnderAttribute @"AXMathUnder"
|
||||
#define NSAccessibilityMathOverAttribute @"AXMathOver"
|
||||
// XXX WebKit also defines the following attributes.
|
||||
// See bugs 1176970, 1176973 and 1176983.
|
||||
// - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen"
|
||||
// - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose"
|
||||
// - NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness"
|
||||
// - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts"
|
||||
// - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts"
|
||||
|
||||
// returns the passed in object if it is not ignored. if it's ignored, will return
|
||||
// the first unignored ancestor.
|
||||
static inline id
|
||||
@ -121,6 +138,52 @@ GetClosestInterestingAccessible(id anObject)
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
|
||||
}
|
||||
|
||||
- (NSArray*)additionalAccessibilityAttributeNames
|
||||
{
|
||||
NSMutableArray* additional = [NSMutableArray array];
|
||||
switch (mRole) {
|
||||
case roles::MATHML_ROOT:
|
||||
[additional addObject:NSAccessibilityMathRootIndexAttribute];
|
||||
[additional addObject:NSAccessibilityMathRootRadicandAttribute];
|
||||
break;
|
||||
case roles::MATHML_SQUARE_ROOT:
|
||||
[additional addObject:NSAccessibilityMathRootRadicandAttribute];
|
||||
break;
|
||||
case roles::MATHML_FRACTION:
|
||||
[additional addObject:NSAccessibilityMathFractionNumeratorAttribute];
|
||||
[additional addObject:NSAccessibilityMathFractionDenominatorAttribute];
|
||||
// XXX bug 1176973
|
||||
// WebKit also defines NSAccessibilityMathLineThicknessAttribute
|
||||
break;
|
||||
case roles::MATHML_SUB:
|
||||
case roles::MATHML_SUP:
|
||||
case roles::MATHML_SUB_SUP:
|
||||
[additional addObject:NSAccessibilityMathBaseAttribute];
|
||||
[additional addObject:NSAccessibilityMathSubscriptAttribute];
|
||||
[additional addObject:NSAccessibilityMathSuperscriptAttribute];
|
||||
break;
|
||||
case roles::MATHML_UNDER:
|
||||
case roles::MATHML_OVER:
|
||||
case roles::MATHML_UNDER_OVER:
|
||||
[additional addObject:NSAccessibilityMathBaseAttribute];
|
||||
[additional addObject:NSAccessibilityMathUnderAttribute];
|
||||
[additional addObject:NSAccessibilityMathOverAttribute];
|
||||
break;
|
||||
// XXX bug 1176983
|
||||
// roles::MATHML_MULTISCRIPTS should also have the following attributes:
|
||||
// - NSAccessibilityMathPrescriptsAttribute
|
||||
// - NSAccessibilityMathPostscriptsAttribute
|
||||
// XXX bug 1176970
|
||||
// roles::MATHML_FENCED should also have the following attributes:
|
||||
// - NSAccessibilityMathFencedOpenAttribute
|
||||
// - NSAccessibilityMathFencedCloseAttribute
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return additional;
|
||||
}
|
||||
|
||||
- (NSArray*)accessibilityAttributeNames
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
@ -155,7 +218,27 @@ GetClosestInterestingAccessible(id anObject)
|
||||
nil];
|
||||
}
|
||||
|
||||
return generalAttributes;
|
||||
NSArray* objectAttributes = generalAttributes;
|
||||
NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames];
|
||||
if ([additionalAttributes count])
|
||||
objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes];
|
||||
|
||||
return objectAttributes;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
}
|
||||
|
||||
- (id)childAt:(uint32_t)i
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
AccessibleWrap* accWrap = [self getGeckoAccessible];
|
||||
if (accWrap) {
|
||||
Accessible* acc = accWrap->GetChildAt(i);
|
||||
return acc ? GetNativeFromGeckoAccessible(acc) : nil;
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
}
|
||||
@ -214,6 +297,93 @@ GetClosestInterestingAccessible(id anObject)
|
||||
if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
|
||||
return [self help];
|
||||
|
||||
switch (mRole) {
|
||||
case roles::MATHML_ROOT:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
|
||||
return [self childAt:0];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathRootIndexAttribute])
|
||||
return [self childAt:1];
|
||||
break;
|
||||
case roles::MATHML_SQUARE_ROOT:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
|
||||
return [self childAt:0];
|
||||
break;
|
||||
case roles::MATHML_FRACTION:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathFractionNumeratorAttribute])
|
||||
return [self childAt:0];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathFractionDenominatorAttribute])
|
||||
return [self childAt:1];
|
||||
// XXX bug 1176973
|
||||
// WebKit also defines NSAccessibilityMathLineThicknessAttribute
|
||||
break;
|
||||
case roles::MATHML_SUB:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
|
||||
return [self childAt:0];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
|
||||
return [self childAt:1];
|
||||
#ifdef DEBUG
|
||||
if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
|
||||
return nil;
|
||||
#endif
|
||||
break;
|
||||
case roles::MATHML_SUP:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
|
||||
return [self childAt:0];
|
||||
#ifdef DEBUG
|
||||
if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
|
||||
return nil;
|
||||
#endif
|
||||
if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
|
||||
return [self childAt:1];
|
||||
break;
|
||||
case roles::MATHML_SUB_SUP:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
|
||||
return [self childAt:0];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
|
||||
return [self childAt:1];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
|
||||
return [self childAt:2];
|
||||
break;
|
||||
case roles::MATHML_UNDER:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
|
||||
return [self childAt:0];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
|
||||
return [self childAt:1];
|
||||
#ifdef DEBUG
|
||||
if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
|
||||
return nil;
|
||||
#endif
|
||||
break;
|
||||
case roles::MATHML_OVER:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
|
||||
return [self childAt:0];
|
||||
#ifdef DEBUG
|
||||
if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
|
||||
return nil;
|
||||
#endif
|
||||
if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
|
||||
return [self childAt:1];
|
||||
break;
|
||||
case roles::MATHML_UNDER_OVER:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
|
||||
return [self childAt:0];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
|
||||
return [self childAt:1];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
|
||||
return [self childAt:2];
|
||||
break;
|
||||
// XXX bug 1176983
|
||||
// roles::MATHML_MULTISCRIPTS should also have the following attributes:
|
||||
// - NSAccessibilityMathPrescriptsAttribute
|
||||
// - NSAccessibilityMathPostscriptsAttribute
|
||||
// XXX bug 1176970
|
||||
// roles::MATHML_FENCED should also have the following attributes:
|
||||
// - NSAccessibilityMathFencedOpenAttribute
|
||||
// - NSAccessibilityMathFencedCloseAttribute
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
NSLog (@"!!! %@ can't respond to attribute %@", self, attribute);
|
||||
#endif
|
||||
@ -510,7 +680,8 @@ GetClosestInterestingAccessible(id anObject)
|
||||
return @"AXMathFraction";
|
||||
|
||||
case roles::MATHML_FENCED:
|
||||
// XXX This should be AXMathFence, but doing so without implementing the
|
||||
// XXX bug 1176970
|
||||
// This should be AXMathFence, but doing so without implementing the
|
||||
// whole fence interface seems to make VoiceOver crash, so we present it
|
||||
// as a row for now.
|
||||
return @"AXMathRow";
|
||||
@ -557,6 +728,8 @@ GetClosestInterestingAccessible(id anObject)
|
||||
// NS_MATHML_OPERATOR_SEPARATOR bits of nsOperatorFlags, but currently they
|
||||
// are only available from the MathML layout code. Hence we just fallback
|
||||
// to subrole AXMathOperator for now.
|
||||
// XXX bug 1175747 WebKit also creates anonymous operators for <mfenced>
|
||||
// which have subroles AXMathSeparatorOperator and AXMathFenceOperator.
|
||||
case roles::MATHML_OPERATOR:
|
||||
return @"AXMathOperator";
|
||||
|
||||
@ -607,10 +780,12 @@ struct RoleDescrComparator
|
||||
|
||||
NSString* subrole = [self subrole];
|
||||
|
||||
size_t idx = 0;
|
||||
if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
|
||||
RoleDescrComparator(subrole), &idx)) {
|
||||
return utils::LocalizedString(sRoleDescrMap[idx].description);
|
||||
if (subrole) {
|
||||
size_t idx = 0;
|
||||
if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
|
||||
RoleDescrComparator(subrole), &idx)) {
|
||||
return utils::LocalizedString(sRoleDescrMap[idx].description);
|
||||
}
|
||||
}
|
||||
|
||||
return NSAccessibilityRoleDescription([self role], subrole);
|
||||
|
@ -125,6 +125,19 @@
|
||||
|
||||
obj = {
|
||||
role: ROLE_MATHML_ROOT,
|
||||
relations: {
|
||||
RELATION_NODE_PARENT_OF: ["mroot_index", "mroot_base"]
|
||||
},
|
||||
children: [
|
||||
{
|
||||
role: ROLE_MATHML_IDENTIFIER,
|
||||
relations: { RELATION_NODE_CHILD_OF: "mroot" }
|
||||
},
|
||||
{
|
||||
role: ROLE_MATHML_NUMBER,
|
||||
relations: { RELATION_NODE_CHILD_OF: "mroot" }
|
||||
}
|
||||
]
|
||||
};
|
||||
testElm("mroot", obj);
|
||||
|
||||
@ -386,8 +399,8 @@
|
||||
<mn>2</mn>
|
||||
</mfrac>
|
||||
<mroot id="mroot">
|
||||
<mi>x</mi>
|
||||
<mn>5</mn>
|
||||
<mi id="mroot_base">x</mi>
|
||||
<mn id="mroot_index">5</mn>
|
||||
</mroot>
|
||||
<mspace width="1em"/>
|
||||
<mfenced id="mfenced" close="[" open="]" separators=".">
|
||||
|
@ -1,6 +1,7 @@
|
||||
[DEFAULT]
|
||||
|
||||
[test_headers_ariagrid.html]
|
||||
[test_headers_ariatable.html]
|
||||
[test_headers_listbox.xul]
|
||||
[test_headers_table.html]
|
||||
[test_headers_tree.xul]
|
||||
|
96
accessible/tests/mochitest/table/test_headers_ariatable.html
Normal file
96
accessible/tests/mochitest/table/test_headers_ariatable.html
Normal file
@ -0,0 +1,96 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
|
||||
<html>
|
||||
<head>
|
||||
<title>Table header information cells for ARIA table</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../table.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
function doTest()
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// column and row headers from markup
|
||||
|
||||
headerInfoMap = [
|
||||
{
|
||||
cell: "table_dc_1",
|
||||
rowHeaderCells: [ "table_rh_1" ],
|
||||
columnHeaderCells: [ "table_ch_2" ]
|
||||
},
|
||||
{
|
||||
cell: "table_dc_2",
|
||||
rowHeaderCells: [ "table_rh_1" ],
|
||||
columnHeaderCells: [ "table_ch_3" ]
|
||||
},
|
||||
{
|
||||
cell: "table_dc_3",
|
||||
rowHeaderCells: [ "table_rh_2" ],
|
||||
columnHeaderCells: [ "table_ch_2" ]
|
||||
},
|
||||
{
|
||||
cell: "table_dc_4",
|
||||
rowHeaderCells: [ "table_rh_2" ],
|
||||
columnHeaderCells: [ "table_ch_3" ]
|
||||
},
|
||||
{
|
||||
cell: "table_rh_1",
|
||||
rowHeaderCells: [],
|
||||
columnHeaderCells: [ "table_ch_1" ]
|
||||
},
|
||||
{
|
||||
cell: "table_rh_2",
|
||||
rowHeaderCells: [],
|
||||
columnHeaderCells: [ "table_ch_1" ]
|
||||
}
|
||||
];
|
||||
|
||||
testHeaderCells(headerInfoMap);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a target="_blank"
|
||||
title="support ARIA table and cell roles"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">Bug 1173364</a>
|
||||
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
|
||||
<div role="table">
|
||||
<div role="row">
|
||||
<span id="table_ch_1" role="columnheader">col_1</span>
|
||||
<span id="table_ch_2" role="columnheader">col_2</span>
|
||||
<span id="table_ch_3" role="columnheader">col_3</span>
|
||||
</div>
|
||||
<div role="row">
|
||||
<span id="table_rh_1" role="rowheader">row_1</span>
|
||||
<span id="table_dc_1" role="cell">cell1</span>
|
||||
<span id="table_dc_2" role="cell">cell2</span>
|
||||
</div>
|
||||
<div role="row">
|
||||
<span id="table_rh_2" role="rowheader">row_2</span>
|
||||
<span id="table_dc_3" role="cell">cell3</span>
|
||||
<span id="table_dc_4" role="cell">cell4</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -11,6 +11,7 @@ skip-if = true # Bug 561508
|
||||
[test_aria_list.html]
|
||||
[test_aria_menu.html]
|
||||
[test_aria_presentation.html]
|
||||
[test_aria_table.html]
|
||||
[test_brokencontext.html]
|
||||
[test_button.xul]
|
||||
[test_canvas.html]
|
||||
|
63
accessible/tests/mochitest/tree/test_aria_table.html
Normal file
63
accessible/tests/mochitest/tree/test_aria_table.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ARIA table tests</title>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../role.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
function doTest()
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// table having rowgroups
|
||||
|
||||
var accTree =
|
||||
{ TABLE: [
|
||||
{ GROUPING: [
|
||||
{ ROW: [
|
||||
{ CELL: [
|
||||
{ TEXT_LEAF: [ ] }
|
||||
] }
|
||||
] }
|
||||
] },
|
||||
] };
|
||||
|
||||
testAccessibleTree("table", accTree);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a target="_blank"
|
||||
title="support ARIA table and cell roles"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">
|
||||
Bug 1173364
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
|
||||
<div id="table" role="table">
|
||||
<div role="rowgroup">
|
||||
<div role="row">
|
||||
<div role="cell">cell</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -3132,9 +3132,12 @@ var PrintPreviewListener = {
|
||||
|
||||
getPrintPreviewBrowser: function () {
|
||||
if (!this._printPreviewTab) {
|
||||
let browser = gBrowser.selectedTab.linkedBrowser;
|
||||
let forceNotRemote = gMultiProcessBrowser && !browser.isRemoteBrowser;
|
||||
this._tabBeforePrintPreview = gBrowser.selectedTab;
|
||||
this._printPreviewTab = gBrowser.loadOneTab("about:blank",
|
||||
{ inBackground: false });
|
||||
{ inBackground: false,
|
||||
forceNotRemote });
|
||||
gBrowser.selectedTab = this._printPreviewTab;
|
||||
}
|
||||
return gBrowser.getBrowserForTab(this._printPreviewTab);
|
||||
|
@ -21,28 +21,28 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using dom::URLSearchParams;
|
||||
using dom::URLParams;
|
||||
|
||||
void
|
||||
OriginAttributes::CreateSuffix(nsACString& aStr) const
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
|
||||
|
||||
nsRefPtr<URLSearchParams> usp = new URLSearchParams(nullptr);
|
||||
UniquePtr<URLParams> params(new URLParams());
|
||||
nsAutoString value;
|
||||
|
||||
if (mAppId != nsIScriptSecurityManager::NO_APP_ID) {
|
||||
value.AppendInt(mAppId);
|
||||
usp->Set(NS_LITERAL_STRING("appId"), value);
|
||||
params->Set(NS_LITERAL_STRING("appId"), value);
|
||||
}
|
||||
|
||||
if (mInBrowser) {
|
||||
usp->Set(NS_LITERAL_STRING("inBrowser"), NS_LITERAL_STRING("1"));
|
||||
params->Set(NS_LITERAL_STRING("inBrowser"), NS_LITERAL_STRING("1"));
|
||||
}
|
||||
|
||||
aStr.Truncate();
|
||||
|
||||
usp->Serialize(value);
|
||||
params->Serialize(value);
|
||||
if (!value.IsEmpty()) {
|
||||
aStr.AppendLiteral("!");
|
||||
aStr.Append(NS_ConvertUTF16toUTF8(value));
|
||||
@ -52,7 +52,7 @@ OriginAttributes::CreateSuffix(nsACString& aStr) const
|
||||
namespace {
|
||||
|
||||
class MOZ_STACK_CLASS PopulateFromSuffixIterator final
|
||||
: public URLSearchParams::ForEachIterator
|
||||
: public URLParams::ForEachIterator
|
||||
{
|
||||
public:
|
||||
explicit PopulateFromSuffixIterator(OriginAttributes* aOriginAttributes)
|
||||
@ -61,8 +61,8 @@ public:
|
||||
MOZ_ASSERT(aOriginAttributes);
|
||||
}
|
||||
|
||||
bool URLSearchParamsIterator(const nsString& aName,
|
||||
const nsString& aValue) override
|
||||
bool URLParamsIterator(const nsString& aName,
|
||||
const nsString& aValue) override
|
||||
{
|
||||
if (aName.EqualsLiteral("appId")) {
|
||||
nsresult rv;
|
||||
@ -108,11 +108,28 @@ OriginAttributes::PopulateFromSuffix(const nsACString& aStr)
|
||||
return false;
|
||||
}
|
||||
|
||||
nsRefPtr<URLSearchParams> usp = new URLSearchParams(nullptr);
|
||||
usp->ParseInput(Substring(aStr, 1, aStr.Length() - 1));
|
||||
UniquePtr<URLParams> params(new URLParams());
|
||||
params->ParseInput(Substring(aStr, 1, aStr.Length() - 1));
|
||||
|
||||
PopulateFromSuffixIterator iterator(this);
|
||||
return usp->ForEach(iterator);
|
||||
return params->ForEach(iterator);
|
||||
}
|
||||
|
||||
bool
|
||||
OriginAttributes::PopulateFromOrigin(const nsACString& aOrigin,
|
||||
nsACString& aOriginNoSuffix)
|
||||
{
|
||||
// RFindChar is only available on nsCString.
|
||||
nsCString origin(aOrigin);
|
||||
int32_t pos = origin.RFindChar('!');
|
||||
|
||||
if (pos == kNotFound) {
|
||||
aOriginNoSuffix = origin;
|
||||
return true;
|
||||
}
|
||||
|
||||
aOriginNoSuffix = Substring(origin, 0, pos);
|
||||
return PopulateFromSuffix(Substring(origin, pos));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -48,6 +48,11 @@ public:
|
||||
bool PopulateFromSuffix(const nsACString& aStr);
|
||||
|
||||
void CookieJar(nsACString& aStr);
|
||||
|
||||
// Populates the attributes from a string like
|
||||
// |uri!key1=value1&key2=value2| and returns the uri without the suffix.
|
||||
bool PopulateFromOrigin(const nsACString& aOrigin,
|
||||
nsACString& aOriginNoSuffix);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -12,106 +12,134 @@
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mObserver)
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(URLSearchParams)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(URLSearchParams)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
URLSearchParams::URLSearchParams(URLSearchParamsObserver* aObserver)
|
||||
: mObserver(aObserver)
|
||||
bool
|
||||
URLParams::Has(const nsAString& aName)
|
||||
{
|
||||
}
|
||||
for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
|
||||
if (mParams[i].mKey.Equals(aName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
URLSearchParams::~URLSearchParams()
|
||||
{
|
||||
DeleteAll();
|
||||
}
|
||||
|
||||
JSObject*
|
||||
URLSearchParams::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
return URLSearchParamsBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<URLSearchParams>
|
||||
URLSearchParams::Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aInit,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<URLSearchParams> sp = new URLSearchParams(nullptr);
|
||||
sp->ParseInput(NS_ConvertUTF16toUTF8(aInit));
|
||||
return sp.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<URLSearchParams>
|
||||
URLSearchParams::Constructor(const GlobalObject& aGlobal,
|
||||
URLSearchParams& aInit,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<URLSearchParams> sp = new URLSearchParams(nullptr);
|
||||
sp->mSearchParams = aInit.mSearchParams;
|
||||
return sp.forget();
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::ParseInput(const nsACString& aInput)
|
||||
URLParams::Get(const nsAString& aName, nsString& aRetval)
|
||||
{
|
||||
// Remove all the existing data before parsing a new input.
|
||||
DeleteAll();
|
||||
SetDOMStringToNull(aRetval);
|
||||
|
||||
nsACString::const_iterator start, end;
|
||||
aInput.BeginReading(start);
|
||||
aInput.EndReading(end);
|
||||
nsACString::const_iterator iter(start);
|
||||
|
||||
while (start != end) {
|
||||
nsAutoCString string;
|
||||
|
||||
if (FindCharInReadable('&', iter, end)) {
|
||||
string.Assign(Substring(start, iter));
|
||||
start = ++iter;
|
||||
} else {
|
||||
string.Assign(Substring(start, end));
|
||||
start = end;
|
||||
for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
|
||||
if (mParams[i].mKey.Equals(aName)) {
|
||||
aRetval.Assign(mParams[i].mValue);
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsACString::const_iterator eqStart, eqEnd;
|
||||
string.BeginReading(eqStart);
|
||||
string.EndReading(eqEnd);
|
||||
nsACString::const_iterator eqIter(eqStart);
|
||||
|
||||
nsAutoCString name;
|
||||
nsAutoCString value;
|
||||
|
||||
if (FindCharInReadable('=', eqIter, eqEnd)) {
|
||||
name.Assign(Substring(eqStart, eqIter));
|
||||
|
||||
++eqIter;
|
||||
value.Assign(Substring(eqIter, eqEnd));
|
||||
} else {
|
||||
name.Assign(string);
|
||||
}
|
||||
|
||||
nsAutoString decodedName;
|
||||
DecodeString(name, decodedName);
|
||||
|
||||
nsAutoString decodedValue;
|
||||
DecodeString(value, decodedValue);
|
||||
|
||||
AppendInternal(decodedName, decodedValue);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::DecodeString(const nsACString& aInput, nsAString& aOutput)
|
||||
URLParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval)
|
||||
{
|
||||
aRetval.Clear();
|
||||
|
||||
for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
|
||||
if (mParams[i].mKey.Equals(aName)) {
|
||||
aRetval.AppendElement(mParams[i].mValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
URLParams::Append(const nsAString& aName, const nsAString& aValue)
|
||||
{
|
||||
Param* param = mParams.AppendElement();
|
||||
param->mKey = aName;
|
||||
param->mValue = aValue;
|
||||
}
|
||||
|
||||
void
|
||||
URLParams::Set(const nsAString& aName, const nsAString& aValue)
|
||||
{
|
||||
Param* param = nullptr;
|
||||
for (uint32_t i = 0, len = mParams.Length(); i < len;) {
|
||||
if (!mParams[i].mKey.Equals(aName)) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (!param) {
|
||||
param = &mParams[i];
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
// Remove duplicates.
|
||||
mParams.RemoveElementAt(i);
|
||||
--len;
|
||||
}
|
||||
|
||||
if (!param) {
|
||||
param = mParams.AppendElement();
|
||||
param->mKey = aName;
|
||||
}
|
||||
|
||||
param->mValue = aValue;
|
||||
}
|
||||
|
||||
bool
|
||||
URLParams::Delete(const nsAString& aName)
|
||||
{
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; i < mParams.Length();) {
|
||||
if (mParams[i].mKey.Equals(aName)) {
|
||||
mParams.RemoveElementAt(i);
|
||||
found = true;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void
|
||||
URLParams::ConvertString(const nsACString& aInput, nsAString& aOutput)
|
||||
{
|
||||
aOutput.Truncate();
|
||||
|
||||
if (!mDecoder) {
|
||||
mDecoder = EncodingUtils::DecoderForEncoding("UTF-8");
|
||||
if (!mDecoder) {
|
||||
MOZ_ASSERT(mDecoder, "Failed to create a decoder.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t inputLength = aInput.Length();
|
||||
int32_t outputLength = 0;
|
||||
|
||||
nsresult rv = mDecoder->GetMaxLength(aInput.BeginReading(), inputLength,
|
||||
&outputLength);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aOutput.SetLength(outputLength, fallible)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t newOutputLength = outputLength;
|
||||
rv = mDecoder->Convert(aInput.BeginReading(), &inputLength,
|
||||
aOutput.BeginWriting(), &newOutputLength);
|
||||
if (NS_FAILED(rv)) {
|
||||
aOutput.Truncate();
|
||||
return;
|
||||
}
|
||||
if (newOutputLength < outputLength) {
|
||||
aOutput.Truncate(newOutputLength);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
URLParams::DecodeString(const nsACString& aInput, nsAString& aOutput)
|
||||
{
|
||||
nsACString::const_iterator start, end;
|
||||
aInput.BeginReading(start);
|
||||
@ -168,147 +196,56 @@ URLSearchParams::DecodeString(const nsACString& aInput, nsAString& aOutput)
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::ConvertString(const nsACString& aInput, nsAString& aOutput)
|
||||
URLParams::ParseInput(const nsACString& aInput)
|
||||
{
|
||||
aOutput.Truncate();
|
||||
// Remove all the existing data before parsing a new input.
|
||||
DeleteAll();
|
||||
|
||||
if (!mDecoder) {
|
||||
mDecoder = EncodingUtils::DecoderForEncoding("UTF-8");
|
||||
if (!mDecoder) {
|
||||
MOZ_ASSERT(mDecoder, "Failed to create a decoder.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
nsACString::const_iterator start, end;
|
||||
aInput.BeginReading(start);
|
||||
aInput.EndReading(end);
|
||||
nsACString::const_iterator iter(start);
|
||||
|
||||
int32_t inputLength = aInput.Length();
|
||||
int32_t outputLength = 0;
|
||||
while (start != end) {
|
||||
nsAutoCString string;
|
||||
|
||||
nsresult rv = mDecoder->GetMaxLength(aInput.BeginReading(), inputLength,
|
||||
&outputLength);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aOutput.SetLength(outputLength, fallible)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t newOutputLength = outputLength;
|
||||
rv = mDecoder->Convert(aInput.BeginReading(), &inputLength,
|
||||
aOutput.BeginWriting(), &newOutputLength);
|
||||
if (NS_FAILED(rv)) {
|
||||
aOutput.Truncate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (newOutputLength < outputLength) {
|
||||
aOutput.Truncate(newOutputLength);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Get(const nsAString& aName, nsString& aRetval)
|
||||
{
|
||||
SetDOMStringToNull(aRetval);
|
||||
|
||||
for (uint32_t i = 0, len = mSearchParams.Length(); i < len; ++i) {
|
||||
if (mSearchParams[i].mKey.Equals(aName)) {
|
||||
aRetval.Assign(mSearchParams[i].mValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval)
|
||||
{
|
||||
aRetval.Clear();
|
||||
|
||||
for (uint32_t i = 0, len = mSearchParams.Length(); i < len; ++i) {
|
||||
if (mSearchParams[i].mKey.Equals(aName)) {
|
||||
aRetval.AppendElement(mSearchParams[i].mValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Set(const nsAString& aName, const nsAString& aValue)
|
||||
{
|
||||
Param* param = nullptr;
|
||||
for (uint32_t i = 0, len = mSearchParams.Length(); i < len;) {
|
||||
if (!mSearchParams[i].mKey.Equals(aName)) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (!param) {
|
||||
param = &mSearchParams[i];
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
// Remove duplicates.
|
||||
mSearchParams.RemoveElementAt(i);
|
||||
--len;
|
||||
}
|
||||
|
||||
if (!param) {
|
||||
param = mSearchParams.AppendElement();
|
||||
param->mKey = aName;
|
||||
}
|
||||
|
||||
param->mValue = aValue;
|
||||
|
||||
NotifyObserver();
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Append(const nsAString& aName, const nsAString& aValue)
|
||||
{
|
||||
AppendInternal(aName, aValue);
|
||||
NotifyObserver();
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::AppendInternal(const nsAString& aName, const nsAString& aValue)
|
||||
{
|
||||
Param* param = mSearchParams.AppendElement();
|
||||
param->mKey = aName;
|
||||
param->mValue = aValue;
|
||||
}
|
||||
|
||||
bool
|
||||
URLSearchParams::Has(const nsAString& aName)
|
||||
{
|
||||
for (uint32_t i = 0, len = mSearchParams.Length(); i < len; ++i) {
|
||||
if (mSearchParams[i].mKey.Equals(aName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Delete(const nsAString& aName)
|
||||
{
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; i < mSearchParams.Length();) {
|
||||
if (mSearchParams[i].mKey.Equals(aName)) {
|
||||
mSearchParams.RemoveElementAt(i);
|
||||
found = true;
|
||||
if (FindCharInReadable('&', iter, end)) {
|
||||
string.Assign(Substring(start, iter));
|
||||
start = ++iter;
|
||||
} else {
|
||||
++i;
|
||||
string.Assign(Substring(start, end));
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
NotifyObserver();
|
||||
}
|
||||
}
|
||||
if (string.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::DeleteAll()
|
||||
{
|
||||
mSearchParams.Clear();
|
||||
nsACString::const_iterator eqStart, eqEnd;
|
||||
string.BeginReading(eqStart);
|
||||
string.EndReading(eqEnd);
|
||||
nsACString::const_iterator eqIter(eqStart);
|
||||
|
||||
nsAutoCString name;
|
||||
nsAutoCString value;
|
||||
|
||||
if (FindCharInReadable('=', eqIter, eqEnd)) {
|
||||
name.Assign(Substring(eqStart, eqIter));
|
||||
|
||||
++eqIter;
|
||||
value.Assign(Substring(eqIter, eqEnd));
|
||||
} else {
|
||||
name.Assign(string);
|
||||
}
|
||||
|
||||
nsAutoString decodedName;
|
||||
DecodeString(name, decodedName);
|
||||
|
||||
nsAutoString decodedValue;
|
||||
DecodeString(value, decodedValue);
|
||||
|
||||
Append(decodedName, decodedValue);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
@ -338,24 +275,131 @@ void SerializeString(const nsCString& aInput, nsAString& aValue)
|
||||
} // anonymous namespace
|
||||
|
||||
void
|
||||
URLSearchParams::Serialize(nsAString& aValue) const
|
||||
URLParams::Serialize(nsAString& aValue) const
|
||||
{
|
||||
aValue.Truncate();
|
||||
bool first = true;
|
||||
|
||||
for (uint32_t i = 0, len = mSearchParams.Length(); i < len; ++i) {
|
||||
for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
aValue.Append('&');
|
||||
}
|
||||
|
||||
SerializeString(NS_ConvertUTF16toUTF8(mSearchParams[i].mKey), aValue);
|
||||
SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mKey), aValue);
|
||||
aValue.Append('=');
|
||||
SerializeString(NS_ConvertUTF16toUTF8(mSearchParams[i].mValue), aValue);
|
||||
SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mValue), aValue);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mObserver)
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(URLSearchParams)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(URLSearchParams)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
URLSearchParams::URLSearchParams(URLSearchParamsObserver* aObserver)
|
||||
: mParams(new URLParams()), mObserver(aObserver)
|
||||
{
|
||||
}
|
||||
|
||||
URLSearchParams::URLSearchParams(const URLSearchParams& aOther)
|
||||
: mParams(new URLParams(*aOther.mParams.get())), mObserver(aOther.mObserver)
|
||||
{
|
||||
}
|
||||
|
||||
URLSearchParams::~URLSearchParams()
|
||||
{
|
||||
DeleteAll();
|
||||
}
|
||||
|
||||
JSObject*
|
||||
URLSearchParams::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
return URLSearchParamsBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<URLSearchParams>
|
||||
URLSearchParams::Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aInit,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<URLSearchParams> sp = new URLSearchParams(nullptr);
|
||||
sp->ParseInput(NS_ConvertUTF16toUTF8(aInit));
|
||||
return sp.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<URLSearchParams>
|
||||
URLSearchParams::Constructor(const GlobalObject& aGlobal,
|
||||
URLSearchParams& aInit,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<URLSearchParams> sp = new URLSearchParams(aInit);
|
||||
return sp.forget();
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::ParseInput(const nsACString& aInput)
|
||||
{
|
||||
mParams->ParseInput(aInput);
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Get(const nsAString& aName, nsString& aRetval)
|
||||
{
|
||||
return mParams->Get(aName, aRetval);
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval)
|
||||
{
|
||||
return mParams->GetAll(aName, aRetval);
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Set(const nsAString& aName, const nsAString& aValue)
|
||||
{
|
||||
mParams->Set(aName, aValue);
|
||||
NotifyObserver();
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Append(const nsAString& aName, const nsAString& aValue)
|
||||
{
|
||||
mParams->Append(aName, aValue);
|
||||
NotifyObserver();
|
||||
}
|
||||
|
||||
bool
|
||||
URLSearchParams::Has(const nsAString& aName)
|
||||
{
|
||||
return mParams->Has(aName);
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Delete(const nsAString& aName)
|
||||
{
|
||||
if (mParams->Delete(aName)) {
|
||||
NotifyObserver();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::DeleteAll()
|
||||
{
|
||||
mParams->DeleteAll();
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::Serialize(nsAString& aValue) const
|
||||
{
|
||||
mParams->Serialize(aValue);
|
||||
}
|
||||
|
||||
void
|
||||
URLSearchParams::NotifyObserver()
|
||||
{
|
||||
|
@ -31,6 +31,80 @@ public:
|
||||
// attributes are kept in the correct order. If this changes, please, update
|
||||
// BasePrincipal code.
|
||||
|
||||
class URLParams final
|
||||
{
|
||||
public:
|
||||
URLParams() {}
|
||||
|
||||
~URLParams()
|
||||
{
|
||||
DeleteAll();
|
||||
}
|
||||
|
||||
explicit URLParams(const URLParams& aOther)
|
||||
: mParams(aOther.mParams)
|
||||
{}
|
||||
|
||||
explicit URLParams(const URLParams&& aOther)
|
||||
: mParams(Move(aOther.mParams))
|
||||
{}
|
||||
|
||||
class ForEachIterator
|
||||
{
|
||||
public:
|
||||
virtual bool
|
||||
URLParamsIterator(const nsString& aName, const nsString& aValue) = 0;
|
||||
};
|
||||
|
||||
void
|
||||
ParseInput(const nsACString& aInput);
|
||||
|
||||
bool
|
||||
ForEach(ForEachIterator& aIterator) const
|
||||
{
|
||||
for (uint32_t i = 0; i < mParams.Length(); ++i) {
|
||||
if (!aIterator.URLParamsIterator(mParams[i].mKey, mParams[i].mValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Serialize(nsAString& aValue) const;
|
||||
|
||||
void Get(const nsAString& aName, nsString& aRetval);
|
||||
|
||||
void GetAll(const nsAString& aName, nsTArray<nsString >& aRetval);
|
||||
|
||||
void Set(const nsAString& aName, const nsAString& aValue);
|
||||
|
||||
void Append(const nsAString& aName, const nsAString& aValue);
|
||||
|
||||
bool Has(const nsAString& aName);
|
||||
|
||||
// Returns true if aName was found and deleted, false otherwise.
|
||||
bool Delete(const nsAString& aName);
|
||||
|
||||
void DeleteAll()
|
||||
{
|
||||
mParams.Clear();
|
||||
}
|
||||
|
||||
private:
|
||||
void DecodeString(const nsACString& aInput, nsAString& aOutput);
|
||||
void ConvertString(const nsACString& aInput, nsAString& aOutput);
|
||||
|
||||
struct Param
|
||||
{
|
||||
nsString mKey;
|
||||
nsString mValue;
|
||||
};
|
||||
|
||||
nsTArray<Param> mParams;
|
||||
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
|
||||
};
|
||||
|
||||
class URLSearchParams final : public nsISupports,
|
||||
public nsWrapperCache
|
||||
{
|
||||
@ -42,6 +116,8 @@ public:
|
||||
|
||||
explicit URLSearchParams(URLSearchParamsObserver* aObserver);
|
||||
|
||||
explicit URLSearchParams(const URLSearchParams& aOther);
|
||||
|
||||
// WebIDL methods
|
||||
nsISupports* GetParentObject() const
|
||||
{
|
||||
@ -65,7 +141,7 @@ public:
|
||||
|
||||
void Get(const nsAString& aName, nsString& aRetval);
|
||||
|
||||
void GetAll(const nsAString& aName, nsTArray<nsString >& aRetval);
|
||||
void GetAll(const nsAString& aName, nsTArray<nsString>& aRetval);
|
||||
|
||||
void Set(const nsAString& aName, const nsAString& aValue);
|
||||
|
||||
@ -80,22 +156,12 @@ public:
|
||||
Serialize(aRetval);
|
||||
}
|
||||
|
||||
class ForEachIterator
|
||||
{
|
||||
public:
|
||||
virtual bool
|
||||
URLSearchParamsIterator(const nsString& aName, const nsString& aValue) = 0;
|
||||
};
|
||||
typedef URLParams::ForEachIterator ForEachIterator;
|
||||
|
||||
bool
|
||||
ForEach(ForEachIterator& aIterator)
|
||||
ForEach(ForEachIterator& aIterator) const
|
||||
{
|
||||
for (uint32_t i = 0; i < mSearchParams.Length(); ++i) {
|
||||
if (!aIterator.URLSearchParamsIterator(mSearchParams[i].mKey,
|
||||
mSearchParams[i].mValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return mParams->ForEach(aIterator);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -105,21 +171,10 @@ private:
|
||||
|
||||
void DeleteAll();
|
||||
|
||||
void DecodeString(const nsACString& aInput, nsAString& aOutput);
|
||||
void ConvertString(const nsACString& aInput, nsAString& aOutput);
|
||||
|
||||
void NotifyObserver();
|
||||
|
||||
struct Param
|
||||
{
|
||||
nsString mKey;
|
||||
nsString mValue;
|
||||
};
|
||||
|
||||
nsTArray<Param> mSearchParams;
|
||||
|
||||
UniquePtr<URLParams> mParams;
|
||||
nsRefPtr<URLSearchParamsObserver> mObserver;
|
||||
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
@ -446,6 +446,9 @@ nsContentSink::ProcessLinkHeader(const nsAString& aLinkData)
|
||||
nsAutoString type;
|
||||
nsAutoString media;
|
||||
nsAutoString anchor;
|
||||
nsAutoString crossOrigin;
|
||||
|
||||
crossOrigin.SetIsVoid(true);
|
||||
|
||||
// copy to work buffer
|
||||
nsAutoString stringList(aLinkData);
|
||||
@ -620,6 +623,12 @@ nsContentSink::ProcessLinkHeader(const nsAString& aLinkData)
|
||||
anchor = value;
|
||||
anchor.StripWhitespace();
|
||||
}
|
||||
} else if (attr.LowerCaseEqualsLiteral("crossorigin")) {
|
||||
if (crossOrigin.IsVoid()) {
|
||||
crossOrigin.SetIsVoid(false);
|
||||
crossOrigin = value;
|
||||
crossOrigin.StripWhitespace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -633,7 +642,7 @@ nsContentSink::ProcessLinkHeader(const nsAString& aLinkData)
|
||||
rv = ProcessLink(anchor, href, rel,
|
||||
// prefer RFC 5987 variant over non-I18zed version
|
||||
titleStar.IsEmpty() ? title : titleStar,
|
||||
type, media);
|
||||
type, media, crossOrigin);
|
||||
}
|
||||
|
||||
href.Truncate();
|
||||
@ -642,6 +651,7 @@ nsContentSink::ProcessLinkHeader(const nsAString& aLinkData)
|
||||
type.Truncate();
|
||||
media.Truncate();
|
||||
anchor.Truncate();
|
||||
crossOrigin.SetIsVoid(true);
|
||||
|
||||
seenParameters = false;
|
||||
}
|
||||
@ -654,7 +664,7 @@ nsContentSink::ProcessLinkHeader(const nsAString& aLinkData)
|
||||
rv = ProcessLink(anchor, href, rel,
|
||||
// prefer RFC 5987 variant over non-I18zed version
|
||||
titleStar.IsEmpty() ? title : titleStar,
|
||||
type, media);
|
||||
type, media, crossOrigin);
|
||||
}
|
||||
|
||||
return rv;
|
||||
@ -664,7 +674,8 @@ nsContentSink::ProcessLinkHeader(const nsAString& aLinkData)
|
||||
nsresult
|
||||
nsContentSink::ProcessLink(const nsSubstring& aAnchor, const nsSubstring& aHref,
|
||||
const nsSubstring& aRel, const nsSubstring& aTitle,
|
||||
const nsSubstring& aType, const nsSubstring& aMedia)
|
||||
const nsSubstring& aType, const nsSubstring& aMedia,
|
||||
const nsSubstring& aCrossOrigin)
|
||||
{
|
||||
uint32_t linkTypes =
|
||||
nsStyleLinkElement::ParseLinkTypes(aRel, mDocument->NodePrincipal());
|
||||
@ -688,7 +699,7 @@ nsContentSink::ProcessLink(const nsSubstring& aAnchor, const nsSubstring& aHref,
|
||||
}
|
||||
|
||||
if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::ePRECONNECT)) {
|
||||
Preconnect(aHref);
|
||||
Preconnect(aHref, aCrossOrigin);
|
||||
}
|
||||
|
||||
// is it a stylesheet link?
|
||||
@ -875,7 +886,7 @@ nsContentSink::PrefetchDNS(const nsAString &aHref)
|
||||
}
|
||||
|
||||
void
|
||||
nsContentSink::Preconnect(const nsAString &aHref)
|
||||
nsContentSink::Preconnect(const nsAString& aHref, const nsAString& aCrossOrigin)
|
||||
{
|
||||
// construct URI using document charset
|
||||
const nsACString& charset = mDocument->GetDocumentCharacterSet();
|
||||
@ -885,7 +896,7 @@ nsContentSink::Preconnect(const nsAString &aHref)
|
||||
mDocument->GetDocBaseURI());
|
||||
|
||||
if (uri && mDocument) {
|
||||
mDocument->MaybePreconnect(uri);
|
||||
mDocument->MaybePreconnect(uri, dom::Element::StringToCORSMode(aCrossOrigin));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ protected:
|
||||
nsresult ProcessLink(const nsSubstring& aAnchor,
|
||||
const nsSubstring& aHref, const nsSubstring& aRel,
|
||||
const nsSubstring& aTitle, const nsSubstring& aType,
|
||||
const nsSubstring& aMedia);
|
||||
const nsSubstring& aMedia, const nsSubstring& aCrossOrigin);
|
||||
|
||||
virtual nsresult ProcessStyleLink(nsIContent* aElement,
|
||||
const nsSubstring& aHref,
|
||||
@ -225,7 +225,7 @@ public:
|
||||
|
||||
// For Preconnect() aHref can either be the usual
|
||||
// URI format or of the form "//www.hostname.com" without a scheme.
|
||||
void Preconnect(const nsAString &aHref);
|
||||
void Preconnect(const nsAString& aHref, const nsAString& aCrossOrigin);
|
||||
|
||||
protected:
|
||||
// Tries to scroll to the URI's named anchor. Once we've successfully
|
||||
|
@ -7862,3 +7862,54 @@ nsContentUtils::InternalContentPolicyTypeToExternal(nsContentPolicyType aType)
|
||||
return aType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
|
||||
nsIDocument* aDoc,
|
||||
nsIHttpChannel* aChannel)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aPrincipal);
|
||||
NS_ENSURE_ARG_POINTER(aChannel);
|
||||
|
||||
nsCOMPtr<nsIURI> principalURI;
|
||||
|
||||
if (IsSystemPrincipal(aPrincipal)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
aPrincipal->GetURI(getter_AddRefs(principalURI));
|
||||
|
||||
if (!aDoc) {
|
||||
return aChannel->SetReferrerWithPolicy(principalURI, net::RP_Default);
|
||||
}
|
||||
|
||||
// If it weren't for history.push/replaceState, we could just use the
|
||||
// principal's URI here. But since we want changes to the URI effected
|
||||
// by push/replaceState to be reflected in the XHR referrer, we have to
|
||||
// be more clever.
|
||||
//
|
||||
// If the document's original URI (before any push/replaceStates) matches
|
||||
// our principal, then we use the document's current URI (after
|
||||
// push/replaceStates). Otherwise (if the document is, say, a data:
|
||||
// URI), we just use the principal's URI.
|
||||
nsCOMPtr<nsIURI> docCurURI = aDoc->GetDocumentURI();
|
||||
nsCOMPtr<nsIURI> docOrigURI = aDoc->GetOriginalURI();
|
||||
|
||||
nsCOMPtr<nsIURI> referrerURI;
|
||||
|
||||
if (principalURI && docCurURI && docOrigURI) {
|
||||
bool equal = false;
|
||||
principalURI->Equals(docOrigURI, &equal);
|
||||
if (equal) {
|
||||
referrerURI = docCurURI;
|
||||
}
|
||||
}
|
||||
|
||||
if (!referrerURI) {
|
||||
referrerURI = principalURI;
|
||||
}
|
||||
|
||||
net::ReferrerPolicy referrerPolicy = aDoc->GetReferrerPolicy();
|
||||
return aChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
|
||||
}
|
@ -2406,6 +2406,25 @@ public:
|
||||
|
||||
static already_AddRefed<nsPIWindowRoot> GetWindowRoot(nsIDocument* aDoc);
|
||||
|
||||
/*
|
||||
* Implements step 3.1 and 3.3 of the Determine request's Referrer algorithm
|
||||
* from the Referrer Policy specification.
|
||||
*
|
||||
* The referrer policy of the document is applied by Necko when using
|
||||
* channels.
|
||||
*
|
||||
* For documents representing an iframe srcdoc attribute, the document sets
|
||||
* its own URI correctly, so this method simply uses the document's original
|
||||
* or current URI as appropriate.
|
||||
*
|
||||
* aDoc may be null.
|
||||
*
|
||||
* https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrer
|
||||
*/
|
||||
static nsresult SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
|
||||
nsIDocument* aDoc,
|
||||
nsIHttpChannel* aChannel);
|
||||
|
||||
private:
|
||||
static bool InitializeEventTable();
|
||||
|
||||
|
@ -9763,8 +9763,26 @@ nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr,
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::MaybePreconnect(nsIURI* uri)
|
||||
nsDocument::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
if (NS_FAILED(aOrigURI->Clone(getter_AddRefs(uri)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The URI created here is used in 2 contexts. One is nsISpeculativeConnect
|
||||
// which ignores the path and uses only the origin. The other is for the
|
||||
// document mPreloadedPreconnects de-duplication hash. Anonymous vs
|
||||
// non-Anonymous preconnects create different connections on the wire and
|
||||
// therefore should not be considred duplicates of each other and we
|
||||
// normalize the path before putting it in the hash to accomplish that.
|
||||
|
||||
if (aCORSMode == CORS_ANONYMOUS) {
|
||||
uri->SetPath(NS_LITERAL_CSTRING("/anonymous"));
|
||||
} else {
|
||||
uri->SetPath(NS_LITERAL_CSTRING("/"));
|
||||
}
|
||||
|
||||
if (mPreloadedPreconnects.Contains(uri)) {
|
||||
return;
|
||||
}
|
||||
@ -9776,7 +9794,11 @@ nsDocument::MaybePreconnect(nsIURI* uri)
|
||||
return;
|
||||
}
|
||||
|
||||
speculator->SpeculativeConnect(uri, nullptr);
|
||||
if (aCORSMode == CORS_ANONYMOUS) {
|
||||
speculator->SpeculativeAnonymousConnect(uri, nullptr);
|
||||
} else {
|
||||
speculator->SpeculativeConnect(uri, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1141,7 +1141,8 @@ public:
|
||||
ReferrerPolicy aReferrerPolicy) override;
|
||||
virtual void ForgetImagePreload(nsIURI* aURI) override;
|
||||
|
||||
virtual void MaybePreconnect(nsIURI* uri) override;
|
||||
virtual void MaybePreconnect(nsIURI* uri,
|
||||
mozilla::CORSMode aCORSMode) override;
|
||||
|
||||
virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
|
||||
const nsAString& aCrossOriginAttr,
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "nsClassHashtable.h"
|
||||
#include "prclist.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/CORSMode.h"
|
||||
#include <bitset> // for member
|
||||
|
||||
class imgIRequest;
|
||||
@ -2037,7 +2038,8 @@ public:
|
||||
/**
|
||||
* Called by Parser for link rel=preconnect
|
||||
*/
|
||||
virtual void MaybePreconnect(nsIURI* uri) = 0;
|
||||
virtual void MaybePreconnect(nsIURI* uri,
|
||||
mozilla::CORSMode aCORSMode) = 0;
|
||||
|
||||
enum DocumentTheme {
|
||||
Doc_Theme_Uninitialized, // not determined yet
|
||||
|
@ -2682,50 +2682,10 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
|
||||
httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
|
||||
|
||||
if (!IsSystemXHR()) {
|
||||
// Get the referrer for the request.
|
||||
//
|
||||
// If it weren't for history.push/replaceState, we could just use the
|
||||
// principal's URI here. But since we want changes to the URI effected
|
||||
// by push/replaceState to be reflected in the XHR referrer, we have to
|
||||
// be more clever.
|
||||
//
|
||||
// If the document's original URI (before any push/replaceStates) matches
|
||||
// our principal, then we use the document's current URI (after
|
||||
// push/replaceStates). Otherwise (if the document is, say, a data:
|
||||
// URI), we just use the principal's URI.
|
||||
|
||||
nsCOMPtr<nsIURI> principalURI;
|
||||
mPrincipal->GetURI(getter_AddRefs(principalURI));
|
||||
|
||||
nsIScriptContext* sc = GetContextForEventHandlers(&rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIDocument> doc =
|
||||
nsContentUtils::GetDocumentFromScriptContext(sc);
|
||||
|
||||
nsCOMPtr<nsIURI> docCurURI;
|
||||
nsCOMPtr<nsIURI> docOrigURI;
|
||||
net::ReferrerPolicy referrerPolicy = net::RP_Default;
|
||||
|
||||
if (doc) {
|
||||
docCurURI = doc->GetDocumentURI();
|
||||
docOrigURI = doc->GetOriginalURI();
|
||||
referrerPolicy = doc->GetReferrerPolicy();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> referrerURI;
|
||||
|
||||
if (principalURI && docCurURI && docOrigURI) {
|
||||
bool equal = false;
|
||||
principalURI->Equals(docOrigURI, &equal);
|
||||
if (equal) {
|
||||
referrerURI = docCurURI;
|
||||
}
|
||||
}
|
||||
|
||||
if (!referrerURI)
|
||||
referrerURI = principalURI;
|
||||
|
||||
httpChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
|
||||
nsCOMPtr<nsPIDOMWindow> owner = GetOwner();
|
||||
nsCOMPtr<nsIDocument> doc = owner ? owner->GetExtantDoc() : nullptr;
|
||||
nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal, doc,
|
||||
httpChannel);
|
||||
}
|
||||
|
||||
// Some extensions override the http protocol handler and provide their own
|
||||
|
@ -13404,7 +13404,7 @@ class CGExampleClass(CGBindingImplClass):
|
||||
ccImpl = dedent("""
|
||||
|
||||
// Only needed for refcounted objects.
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_0(${nativeType}, ${parentType})
|
||||
#error "If you don't have members that need cycle collection, then remove all the cycle collection bits from this implementation and the corresponding header. If you do, you want NS_IMPL_CYCLE_COLLECTION_INHERITED(${nativeType}, ${parentType}, your, members, here)"
|
||||
NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType})
|
||||
NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType})
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType})
|
||||
|
2
dom/cache/CacheTypes.ipdlh
vendored
2
dom/cache/CacheTypes.ipdlh
vendored
@ -7,6 +7,7 @@ include protocol PCachePushStream;
|
||||
include protocol PCacheStreamControl;
|
||||
include InputStreamParams;
|
||||
include ChannelInfo;
|
||||
include PBackgroundSharedTypes;
|
||||
|
||||
using HeadersGuardEnum from "mozilla/dom/cache/IPCUtils.h";
|
||||
using RequestCredentials from "mozilla/dom/cache/IPCUtils.h";
|
||||
@ -81,6 +82,7 @@ struct CacheResponse
|
||||
HeadersGuardEnum headersGuard;
|
||||
CacheReadStreamOrVoid body;
|
||||
IPCChannelInfo channelInfo;
|
||||
OptionalPrincipalInfo principalInfo;
|
||||
};
|
||||
|
||||
union CacheResponseOrVoid
|
||||
|
32
dom/cache/Context.cpp
vendored
32
dom/cache/Context.cpp
vendored
@ -9,6 +9,7 @@
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/dom/cache/Action.h"
|
||||
#include "mozilla/dom/cache/FileUtils.h"
|
||||
#include "mozilla/dom/cache/Manager.h"
|
||||
#include "mozilla/dom/cache/ManagerId.h"
|
||||
#include "mozilla/dom/cache/OfflineStorage.h"
|
||||
@ -155,6 +156,7 @@ public:
|
||||
MOZ_ASSERT(mData);
|
||||
MOZ_ASSERT(mTarget);
|
||||
MOZ_ASSERT(mInitiatingThread);
|
||||
MOZ_ASSERT(mInitAction);
|
||||
}
|
||||
|
||||
nsresult Dispatch()
|
||||
@ -402,11 +404,6 @@ Context::QuotaInitRunnable::Run()
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mInitAction) {
|
||||
resolver->Resolve(NS_OK);
|
||||
break;
|
||||
}
|
||||
|
||||
mState = STATE_RUN_ON_TARGET;
|
||||
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
|
||||
@ -427,15 +424,20 @@ Context::QuotaInitRunnable::Run()
|
||||
|
||||
mData = nullptr;
|
||||
|
||||
// If the database was opened, then we should always succeed when creating
|
||||
// the marker file. If it wasn't opened successfully, then no need to
|
||||
// create a marker file anyway.
|
||||
if (NS_SUCCEEDED(resolver->Result())) {
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(CreateMarkerFile(mQuotaInfo)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// -------------------
|
||||
case STATE_COMPLETING:
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
|
||||
if (mInitAction) {
|
||||
mInitAction->CompleteOnInitiatingThread(mResult);
|
||||
}
|
||||
mInitAction->CompleteOnInitiatingThread(mResult);
|
||||
mContext->OnQuotaInit(mResult, mQuotaInfo, mOfflineStorage);
|
||||
mState = STATE_COMPLETE;
|
||||
|
||||
@ -819,6 +821,7 @@ Context::Context(Manager* aManager, nsIThread* aTarget)
|
||||
, mTarget(aTarget)
|
||||
, mData(new Data(aTarget))
|
||||
, mState(STATE_CONTEXT_PREINIT)
|
||||
, mOrphanedData(false)
|
||||
{
|
||||
MOZ_ASSERT(mManager);
|
||||
}
|
||||
@ -927,8 +930,13 @@ Context::~Context()
|
||||
mThreadsafeHandle->ContextDestroyed(this);
|
||||
}
|
||||
|
||||
// Note, this may set the mOrphanedData flag.
|
||||
mManager->RemoveContext(this);
|
||||
|
||||
if (mQuotaInfo.mDir && !mOrphanedData) {
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(DeleteMarkerFile(mQuotaInfo)));
|
||||
}
|
||||
|
||||
if (mNextContext) {
|
||||
mNextContext->Start();
|
||||
}
|
||||
@ -1050,6 +1058,14 @@ Context::RemoveActivity(Activity* aActivity)
|
||||
MOZ_ASSERT(!mActivityList.Contains(aActivity));
|
||||
}
|
||||
|
||||
void
|
||||
Context::NoteOrphanedData()
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(Context);
|
||||
// This may be called more than once
|
||||
mOrphanedData = true;
|
||||
}
|
||||
|
||||
already_AddRefed<Context::ThreadsafeHandle>
|
||||
Context::CreateThreadsafeHandle()
|
||||
{
|
||||
|
6
dom/cache/Context.h
vendored
6
dom/cache/Context.h
vendored
@ -152,6 +152,11 @@ public:
|
||||
return mQuotaInfo;
|
||||
}
|
||||
|
||||
// Tell the Context that some state information has been orphaned in the
|
||||
// data store and won't be cleaned up. The Context will leave the marker
|
||||
// in place to trigger cleanup the next times its opened.
|
||||
void NoteOrphanedData();
|
||||
|
||||
private:
|
||||
class Data;
|
||||
class QuotaInitRunnable;
|
||||
@ -192,6 +197,7 @@ private:
|
||||
nsCOMPtr<nsIThread> mTarget;
|
||||
nsRefPtr<Data> mData;
|
||||
State mState;
|
||||
bool mOrphanedData;
|
||||
QuotaInfo mQuotaInfo;
|
||||
nsRefPtr<QuotaInitRunnable> mInitRunnable;
|
||||
nsTArray<PendingAction> mPendingActions;
|
||||
|
108
dom/cache/DBSchema.cpp
vendored
108
dom/cache/DBSchema.cpp
vendored
@ -19,6 +19,7 @@
|
||||
#include "nsCRT.h"
|
||||
#include "nsHttp.h"
|
||||
#include "nsICryptoHash.h"
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
#include "mozilla/dom/HeadersBinding.h"
|
||||
#include "mozilla/dom/RequestBinding.h"
|
||||
#include "mozilla/dom/ResponseBinding.h"
|
||||
@ -282,6 +283,7 @@ CreateSchema(mozIStorageConnection* aConn)
|
||||
"response_headers_guard INTEGER NOT NULL, "
|
||||
"response_body_id TEXT NULL, "
|
||||
"response_security_info_id INTEGER NULL REFERENCES security_info(id), "
|
||||
"response_principal_info TEXT NOT NULL, "
|
||||
"response_redirected INTEGER NOT NULL, "
|
||||
// Note that response_redirected_url is either going to be empty, or
|
||||
// it's going to be a URL different than response_url.
|
||||
@ -535,6 +537,62 @@ IsCacheOrphaned(mozIStorageConnection* aConn, CacheId aCacheId,
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FindOrphanedCacheIds(mozIStorageConnection* aConn,
|
||||
nsTArray<CacheId>& aOrphanedListOut)
|
||||
{
|
||||
nsCOMPtr<mozIStorageStatement> state;
|
||||
nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT id FROM caches "
|
||||
"WHERE id NOT IN (SELECT cache_id from storage);"
|
||||
), getter_AddRefs(state));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
bool hasMoreData = false;
|
||||
while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
|
||||
CacheId cacheId = INVALID_CACHE_ID;
|
||||
rv = state->GetInt64(0, &cacheId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
aOrphanedListOut.AppendElement(cacheId);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
GetKnownBodyIds(mozIStorageConnection* aConn, nsTArray<nsID>& aBodyIdListOut)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(aConn);
|
||||
|
||||
nsCOMPtr<mozIStorageStatement> state;
|
||||
nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT request_body_id, response_body_id FROM entries;"
|
||||
), getter_AddRefs(state));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
bool hasMoreData = false;
|
||||
while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
|
||||
// extract 0 to 2 nsID structs per row
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
bool isNull = false;
|
||||
|
||||
rv = state->GetIsNull(i, &isNull);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
if (!isNull) {
|
||||
nsID id;
|
||||
rv = ExtractId(state, i, &id);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
aBodyIdListOut.AppendElement(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId,
|
||||
const CacheRequest& aRequest,
|
||||
@ -1477,6 +1535,7 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
|
||||
"response_headers_guard, "
|
||||
"response_body_id, "
|
||||
"response_security_info_id, "
|
||||
"response_principal_info, "
|
||||
"response_redirected, "
|
||||
"response_redirected_url, "
|
||||
"cache_id "
|
||||
@ -1500,6 +1559,7 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
|
||||
":response_headers_guard, "
|
||||
":response_body_id, "
|
||||
":response_security_info_id, "
|
||||
":response_principal_info, "
|
||||
":response_redirected, "
|
||||
":response_redirected_url, "
|
||||
":cache_id "
|
||||
@ -1593,6 +1653,28 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
|
||||
}
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
nsAutoCString serializedInfo;
|
||||
// We only allow content serviceworkers right now.
|
||||
if (aResponse.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) {
|
||||
const mozilla::ipc::PrincipalInfo& principalInfo =
|
||||
aResponse.principalInfo().get_PrincipalInfo();
|
||||
MOZ_ASSERT(principalInfo.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
|
||||
const mozilla::ipc::ContentPrincipalInfo& cInfo =
|
||||
principalInfo.get_ContentPrincipalInfo();
|
||||
|
||||
serializedInfo.Append(cInfo.spec());
|
||||
|
||||
MOZ_ASSERT(cInfo.appId() != nsIScriptSecurityManager::UNKNOWN_APP_ID);
|
||||
OriginAttributes attrs(cInfo.appId(), cInfo.isInBrowserElement());
|
||||
nsAutoCString suffix;
|
||||
attrs.CreateSuffix(suffix);
|
||||
serializedInfo.Append(suffix);
|
||||
}
|
||||
|
||||
rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_principal_info"),
|
||||
serializedInfo);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_redirected"),
|
||||
aResponse.channelInfo().redirected() ? 1 : 0);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
@ -1692,6 +1774,7 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
|
||||
"entries.response_status_text, "
|
||||
"entries.response_headers_guard, "
|
||||
"entries.response_body_id, "
|
||||
"entries.response_principal_info, "
|
||||
"entries.response_redirected, "
|
||||
"entries.response_redirected_url, "
|
||||
"security_info.data "
|
||||
@ -1741,15 +1824,32 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
}
|
||||
|
||||
int32_t redirected;
|
||||
rv = state->GetInt32(6, &redirected);
|
||||
nsAutoCString serializedInfo;
|
||||
rv = state->GetUTF8String(6, serializedInfo);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
aSavedResponseOut->mValue.principalInfo() = void_t();
|
||||
if (!serializedInfo.IsEmpty()) {
|
||||
nsAutoCString originNoSuffix;
|
||||
OriginAttributes attrs;
|
||||
fprintf(stderr, "\n%s\n", serializedInfo.get());
|
||||
if (!attrs.PopulateFromOrigin(serializedInfo, originNoSuffix)) {
|
||||
NS_WARNING("Something went wrong parsing a serialized principal!");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aSavedResponseOut->mValue.principalInfo() =
|
||||
mozilla::ipc::ContentPrincipalInfo(attrs.mAppId, attrs.mInBrowser, originNoSuffix);
|
||||
}
|
||||
|
||||
int32_t redirected;
|
||||
rv = state->GetInt32(7, &redirected);
|
||||
aSavedResponseOut->mValue.channelInfo().redirected() = !!redirected;
|
||||
|
||||
rv = state->GetUTF8String(7, aSavedResponseOut->mValue.channelInfo().redirectedURI());
|
||||
rv = state->GetUTF8String(8, aSavedResponseOut->mValue.channelInfo().redirectedURI());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = state->GetBlobAsUTF8String(8, aSavedResponseOut->mValue.channelInfo().securityInfo());
|
||||
rv = state->GetBlobAsUTF8String(9, aSavedResponseOut->mValue.channelInfo().securityInfo());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
|
||||
|
7
dom/cache/DBSchema.h
vendored
7
dom/cache/DBSchema.h
vendored
@ -48,6 +48,13 @@ nsresult
|
||||
IsCacheOrphaned(mozIStorageConnection* aConn, CacheId aCacheId,
|
||||
bool* aOrphanedOut);
|
||||
|
||||
nsresult
|
||||
FindOrphanedCacheIds(mozIStorageConnection* aConn,
|
||||
nsTArray<CacheId>& aOrphanedListOut);
|
||||
|
||||
nsresult
|
||||
GetKnownBodyIds(mozIStorageConnection* aConn, nsTArray<nsID>& aBodyIdListOut);
|
||||
|
||||
nsresult
|
||||
CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId,
|
||||
const CacheRequest& aRequest, const CacheQueryParams& aParams,
|
||||
|
176
dom/cache/FileUtils.cpp
vendored
176
dom/cache/FileUtils.cpp
vendored
@ -319,6 +319,182 @@ BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType,
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
nsresult
|
||||
BodyDeleteOrphanedFiles(nsIFile* aBaseDir, nsTArray<nsID>& aKnownBodyIdList)
|
||||
{
|
||||
MOZ_ASSERT(aBaseDir);
|
||||
|
||||
// body files are stored in a directory structure like:
|
||||
//
|
||||
// /morgue/01/{01fdddb2-884d-4c3d-95ba-0c8062f6c325}.final
|
||||
// /morgue/02/{02fdddb2-884d-4c3d-95ba-0c8062f6c325}.tmp
|
||||
|
||||
nsCOMPtr<nsIFile> dir;
|
||||
nsresult rv = aBaseDir->Clone(getter_AddRefs(dir));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
// Add the root morgue directory
|
||||
rv = dir->Append(NS_LITERAL_STRING("morgue"));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
nsCOMPtr<nsISimpleEnumerator> entries;
|
||||
rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
// Iterate over all the intermediate morgue subdirs
|
||||
bool hasMore = false;
|
||||
while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
|
||||
nsCOMPtr<nsISupports> entry;
|
||||
rv = entries->GetNext(getter_AddRefs(entry));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
nsCOMPtr<nsIFile> subdir = do_QueryInterface(entry);
|
||||
|
||||
bool isDir = false;
|
||||
rv = subdir->IsDirectory(&isDir);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
// If a file got in here somehow, try to remove it and move on
|
||||
if (NS_WARN_IF(!isDir)) {
|
||||
rv = subdir->Remove(false /* recursive */);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
continue;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISimpleEnumerator> subEntries;
|
||||
rv = subdir->GetDirectoryEntries(getter_AddRefs(subEntries));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
// Now iterate over all the files in the subdir
|
||||
bool subHasMore = false;
|
||||
while(NS_SUCCEEDED(rv = subEntries->HasMoreElements(&subHasMore)) &&
|
||||
subHasMore) {
|
||||
nsCOMPtr<nsISupports> subEntry;
|
||||
rv = subEntries->GetNext(getter_AddRefs(subEntry));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
nsCOMPtr<nsIFile> file = do_QueryInterface(subEntry);
|
||||
|
||||
nsAutoCString leafName;
|
||||
rv = file->GetNativeLeafName(leafName);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
// Delete all tmp files regardless of known bodies. These are
|
||||
// all considered orphans.
|
||||
if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".tmp"))) {
|
||||
// remove recursively in case its somehow a directory
|
||||
rv = file->Remove(true /* recursive */);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
continue;
|
||||
}
|
||||
|
||||
nsCString suffix(NS_LITERAL_CSTRING(".final"));
|
||||
|
||||
// Otherwise, it must be a .final file. If its not, then just
|
||||
// skip it.
|
||||
if (NS_WARN_IF(!StringEndsWith(leafName, suffix) ||
|
||||
leafName.Length() != NSID_LENGTH - 1 + suffix.Length())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Finally, parse the uuid out of the name. If its fails to parse,
|
||||
// the ignore the file.
|
||||
nsID id;
|
||||
if (NS_WARN_IF(!id.Parse(leafName.BeginReading()))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!aKnownBodyIdList.Contains(id)) {
|
||||
// remove recursively in case its somehow a directory
|
||||
rv = file->Remove(true /* recursive */);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
nsresult
|
||||
GetMarkerFileHandle(const QuotaInfo& aQuotaInfo, nsIFile** aFileOut)
|
||||
{
|
||||
MOZ_ASSERT(aFileOut);
|
||||
|
||||
nsCOMPtr<nsIFile> marker;
|
||||
nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(marker));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = marker->Append(NS_LITERAL_STRING("cache"));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = marker->Append(NS_LITERAL_STRING("context_open.marker"));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
marker.forget(aFileOut);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
nsresult
|
||||
CreateMarkerFile(const QuotaInfo& aQuotaInfo)
|
||||
{
|
||||
nsCOMPtr<nsIFile> marker;
|
||||
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = marker->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
|
||||
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
|
||||
rv = NS_OK;
|
||||
}
|
||||
|
||||
// Note, we don't need to fsync here. We only care about actually
|
||||
// writing the marker if later modifications to the Cache are
|
||||
// actually flushed to the disk. If the OS crashes before the marker
|
||||
// is written then we are ensured no other changes to the Cache were
|
||||
// flushed either.
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DeleteMarkerFile(const QuotaInfo& aQuotaInfo)
|
||||
{
|
||||
nsCOMPtr<nsIFile> marker;
|
||||
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = marker->Remove(/* recursive = */ false);
|
||||
if (rv == NS_ERROR_FILE_NOT_FOUND ||
|
||||
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
|
||||
rv = NS_OK;
|
||||
}
|
||||
|
||||
// Again, no fsync is necessary. If the OS crashes before the file
|
||||
// removal is flushed, then the Cache will search for stale data on
|
||||
// startup. This will cause the next Cache access to be a bit slow, but
|
||||
// it seems appropriate after an OS crash.
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
MarkerFileExists(const QuotaInfo& aQuotaInfo)
|
||||
{
|
||||
nsCOMPtr<nsIFile> marker;
|
||||
nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return false; }
|
||||
|
||||
bool exists = false;
|
||||
rv = marker->Exists(&exists);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return false; }
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
} // namespace cache
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
12
dom/cache/FileUtils.h
vendored
12
dom/cache/FileUtils.h
vendored
@ -50,6 +50,18 @@ BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId,
|
||||
nsresult
|
||||
BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray<nsID>& aIdList);
|
||||
|
||||
nsresult
|
||||
BodyDeleteOrphanedFiles(nsIFile* aBaseDir, nsTArray<nsID>& aKnownBodyIdList);
|
||||
|
||||
nsresult
|
||||
CreateMarkerFile(const QuotaInfo& aQuotaInfo);
|
||||
|
||||
nsresult
|
||||
DeleteMarkerFile(const QuotaInfo& aQuotaInfo);
|
||||
|
||||
bool
|
||||
MarkerFileExists(const QuotaInfo& aQuotaInfo);
|
||||
|
||||
} // namespace cache
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
132
dom/cache/Manager.cpp
vendored
132
dom/cache/Manager.cpp
vendored
@ -30,15 +30,12 @@
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsTObserverArray.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using mozilla::unused;
|
||||
using mozilla::dom::cache::Action;
|
||||
using mozilla::dom::cache::BodyCreateDir;
|
||||
using mozilla::dom::cache::BodyDeleteFiles;
|
||||
using mozilla::dom::cache::QuotaInfo;
|
||||
using mozilla::dom::cache::SyncDBAction;
|
||||
using mozilla::dom::cache::db::CreateSchema;
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace cache {
|
||||
|
||||
namespace {
|
||||
|
||||
// An Action that is executed when a Context is first created. It ensures that
|
||||
// the directory and database are setup properly. This lets other actions
|
||||
@ -54,23 +51,56 @@ public:
|
||||
RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
|
||||
mozIStorageConnection* aConn) override
|
||||
{
|
||||
// TODO: init maintainance marker (bug 1110446)
|
||||
// TODO: perform maintainance if necessary (bug 1110446)
|
||||
// TODO: find orphaned caches in database (bug 1110446)
|
||||
// TODO: have Context create/delete marker files in constructor/destructor
|
||||
// and only do expensive maintenance if that marker is present (bug 1110446)
|
||||
|
||||
nsresult rv = BodyCreateDir(aDBDir);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
mozStorageTransaction trans(aConn, false,
|
||||
mozIStorageConnection::TRANSACTION_IMMEDIATE);
|
||||
{
|
||||
mozStorageTransaction trans(aConn, false,
|
||||
mozIStorageConnection::TRANSACTION_IMMEDIATE);
|
||||
|
||||
rv = CreateSchema(aConn);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
rv = db::CreateSchema(aConn);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = trans.Commit();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
rv = trans.Commit();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
}
|
||||
|
||||
// If the Context marker file exists, then the last session was
|
||||
// not cleanly shutdown. In these cases sqlite will ensure that
|
||||
// the database is valid, but we might still orphan data. Both
|
||||
// Cache objects and body files can be referenced by DOM objects
|
||||
// after they are "removed" from their parent. So we need to
|
||||
// look and see if any of these late access objects have been
|
||||
// orphaned.
|
||||
//
|
||||
// Note, this must be done after any schema version updates to
|
||||
// ensure our DBSchema methods work correctly.
|
||||
if (MarkerFileExists(aQuotaInfo)) {
|
||||
NS_WARNING("Cache not shutdown cleanly! Cleaning up stale data...");
|
||||
mozStorageTransaction trans(aConn, false,
|
||||
mozIStorageConnection::TRANSACTION_IMMEDIATE);
|
||||
|
||||
// Clean up orphaned Cache objects
|
||||
nsAutoTArray<CacheId, 8> orphanedCacheIdList;
|
||||
nsresult rv = db::FindOrphanedCacheIds(aConn, orphanedCacheIdList);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
for (uint32_t i = 0; i < orphanedCacheIdList.Length(); ++i) {
|
||||
nsAutoTArray<nsID, 16> deletedBodyIdList;
|
||||
rv = db::DeleteCacheId(aConn, orphanedCacheIdList[i], deletedBodyIdList);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = BodyDeleteFiles(aDBDir, deletedBodyIdList);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
}
|
||||
|
||||
// Clean up orphaned body objects
|
||||
nsAutoTArray<nsID, 64> knownBodyIdList;
|
||||
rv = db::GetKnownBodyIds(aConn, knownBodyIdList);
|
||||
|
||||
rv = BodyDeleteOrphanedFiles(aDBDir, knownBodyIdList);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
@ -124,14 +154,6 @@ private:
|
||||
nsTArray<nsID> mDeletedBodyIdList;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace cache {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsHeadRequest(CacheRequest aRequest, CacheQueryParams aParams)
|
||||
{
|
||||
return !aParams.ignoreMethod() && aRequest.method().LowerCaseEqualsLiteral("head");
|
||||
@ -1326,8 +1348,9 @@ public:
|
||||
// no outstanding references, delete immediately
|
||||
nsRefPtr<Context> context = mManager->mContext;
|
||||
|
||||
// TODO: note that we need to check this cache for staleness on startup (bug 1110446)
|
||||
if (!context->IsCanceled()) {
|
||||
if (context->IsCanceled()) {
|
||||
context->NoteOrphanedData();
|
||||
} else {
|
||||
context->CancelForCacheId(mCacheId);
|
||||
nsRefPtr<Action> action =
|
||||
new DeleteOrphanedCacheAction(mManager, mCacheId);
|
||||
@ -1480,9 +1503,26 @@ Manager::RemoveContext(Context* aContext)
|
||||
|
||||
// Whether the Context destruction was triggered from the Manager going
|
||||
// idle or the underlying storage being invalidated, we should know we
|
||||
// are closing before the Conext is destroyed.
|
||||
// are closing before the Context is destroyed.
|
||||
MOZ_ASSERT(mState == Closing);
|
||||
|
||||
// Before forgetting the Context, check to see if we have any outstanding
|
||||
// cache or body objects waiting for deletion. If so, note that we've
|
||||
// orphaned data so it will be cleaned up on the next open.
|
||||
for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) {
|
||||
if (mCacheIdRefs[i].mOrphaned) {
|
||||
aContext->NoteOrphanedData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < mBodyIdRefs.Length(); ++i) {
|
||||
if (mBodyIdRefs[i].mOrphaned) {
|
||||
aContext->NoteOrphanedData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mContext = nullptr;
|
||||
|
||||
// Once the context is gone, we can immediately remove ourself from the
|
||||
@ -1534,13 +1574,18 @@ Manager::ReleaseCacheId(CacheId aCacheId)
|
||||
if (mCacheIdRefs[i].mCount == 0) {
|
||||
bool orphaned = mCacheIdRefs[i].mOrphaned;
|
||||
mCacheIdRefs.RemoveElementAt(i);
|
||||
// TODO: note that we need to check this cache for staleness on startup (bug 1110446)
|
||||
nsRefPtr<Context> context = mContext;
|
||||
if (orphaned && context && !context->IsCanceled()) {
|
||||
context->CancelForCacheId(aCacheId);
|
||||
nsRefPtr<Action> action = new DeleteOrphanedCacheAction(this,
|
||||
aCacheId);
|
||||
context->Dispatch(action);
|
||||
// If the context is already gone, then orphan flag should have been
|
||||
// set in RemoveContext().
|
||||
if (orphaned && context) {
|
||||
if (context->IsCanceled()) {
|
||||
context->NoteOrphanedData();
|
||||
} else {
|
||||
context->CancelForCacheId(aCacheId);
|
||||
nsRefPtr<Action> action = new DeleteOrphanedCacheAction(this,
|
||||
aCacheId);
|
||||
context->Dispatch(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
MaybeAllowContextToClose();
|
||||
@ -1578,11 +1623,16 @@ Manager::ReleaseBodyId(const nsID& aBodyId)
|
||||
if (mBodyIdRefs[i].mCount < 1) {
|
||||
bool orphaned = mBodyIdRefs[i].mOrphaned;
|
||||
mBodyIdRefs.RemoveElementAt(i);
|
||||
// TODO: note that we need to check this body for staleness on startup (bug 1110446)
|
||||
nsRefPtr<Context> context = mContext;
|
||||
if (orphaned && context && !context->IsCanceled()) {
|
||||
nsRefPtr<Action> action = new DeleteOrphanedBodyAction(aBodyId);
|
||||
context->Dispatch(action);
|
||||
// If the context is already gone, then orphan flag should have been
|
||||
// set in RemoveContext().
|
||||
if (orphaned && context) {
|
||||
if (context->IsCanceled()) {
|
||||
context->NoteOrphanedData();
|
||||
} else {
|
||||
nsRefPtr<Action> action = new DeleteOrphanedBodyAction(aBodyId);
|
||||
context->Dispatch(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
MaybeAllowContextToClose();
|
||||
|
5
dom/cache/QuotaClient.cpp
vendored
5
dom/cache/QuotaClient.cpp
vendored
@ -169,10 +169,11 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore transient sqlite files
|
||||
// Ignore transient sqlite files and marker files
|
||||
if (leafName.EqualsLiteral("caches.sqlite-journal") ||
|
||||
leafName.EqualsLiteral("caches.sqlite-shm") ||
|
||||
leafName.Find(NS_LITERAL_CSTRING("caches.sqlite-mj"), false, 0, 0) == 0) {
|
||||
leafName.Find(NS_LITERAL_CSTRING("caches.sqlite-mj"), false, 0, 0) == 0 ||
|
||||
leafName.EqualsLiteral("context_open.marker")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
9
dom/cache/TypeUtils.cpp
vendored
9
dom/cache/TypeUtils.cpp
vendored
@ -223,6 +223,11 @@ TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
|
||||
ToHeadersEntryList(aOut.headers(), headers);
|
||||
aOut.headersGuard() = headers->Guard();
|
||||
aOut.channelInfo() = aIn.GetChannelInfo().AsIPCChannelInfo();
|
||||
if (aIn.GetPrincipalInfo()) {
|
||||
aOut.principalInfo() = *aIn.GetPrincipalInfo();
|
||||
} else {
|
||||
aOut.principalInfo() = void_t();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -289,6 +294,10 @@ TypeUtils::ToResponse(const CacheResponse& aIn)
|
||||
MOZ_ASSERT(!result.Failed());
|
||||
|
||||
ir->InitChannelInfo(aIn.channelInfo());
|
||||
if (aIn.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) {
|
||||
UniquePtr<mozilla::ipc::PrincipalInfo> info(new mozilla::ipc::PrincipalInfo(aIn.principalInfo().get_PrincipalInfo()));
|
||||
ir->SetPrincipalInfo(Move(info));
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
|
||||
ir->SetBody(stream);
|
||||
|
2
dom/cache/test/mochitest/mochitest.ini
vendored
2
dom/cache/test/mochitest/mochitest.ini
vendored
@ -40,3 +40,5 @@ support-files =
|
||||
skip-if = buildapp == 'b2g' # bug 1162353
|
||||
[test_cache_restart.html]
|
||||
[test_cache_shrink.html]
|
||||
[test_cache_orphaned_cache.html]
|
||||
[test_cache_orphaned_body.html]
|
||||
|
178
dom/cache/test/mochitest/test_cache_orphaned_body.html
vendored
Normal file
178
dom/cache/test/mochitest/test_cache_orphaned_body.html
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
<!-- 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);
|
||||
});
|
||||
}
|
||||
|
||||
function clearStorage() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var principal = SpecialPowers.wrap(document).nodePrincipal;
|
||||
var appId, inBrowser;
|
||||
var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
|
||||
if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
|
||||
principal.appId != nsIPrincipal.NO_APP_ID) {
|
||||
appId = principal.appId;
|
||||
inBrowser = principal.isInBrowserElement;
|
||||
}
|
||||
SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId,
|
||||
inBrowser);
|
||||
});
|
||||
}
|
||||
|
||||
function storageUsage() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var principal = SpecialPowers.wrap(document).nodePrincipal;
|
||||
var appId, inBrowser;
|
||||
var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
|
||||
if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
|
||||
principal.appId != nsIPrincipal.NO_APP_ID) {
|
||||
appId = principal.appId;
|
||||
inBrowser = principal.isInBrowserElement;
|
||||
}
|
||||
SpecialPowers.getStorageUsageForURI(document.documentURI, resolve, appId,
|
||||
inBrowser);
|
||||
});
|
||||
}
|
||||
|
||||
function resetStorage() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var principal = SpecialPowers.wrap(document).nodePrincipal;
|
||||
var appId, inBrowser;
|
||||
var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
|
||||
if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
|
||||
principal.appId != nsIPrincipal.NO_APP_ID) {
|
||||
appId = principal.appId;
|
||||
inBrowser = principal.isInBrowserElement;
|
||||
}
|
||||
SpecialPowers.resetStorageForURI(document.documentURI, resolve, appId,
|
||||
inBrowser);
|
||||
});
|
||||
}
|
||||
|
||||
function gc() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
SpecialPowers.exactGC(window, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({
|
||||
"set": [["dom.caches.enabled", true],
|
||||
["dom.quotaManager.testing", true]],
|
||||
}, function() {
|
||||
var name = 'orphanedBodyOwner';
|
||||
var cache = null;
|
||||
var response = null;
|
||||
var initialUsage = 0;
|
||||
var fullUsage = 0;
|
||||
var resetUsage = 0;
|
||||
var endUsage = 0;
|
||||
var url = 'cache_add.js';
|
||||
|
||||
// start from a fresh origin directory so other tests do not influence our
|
||||
// results
|
||||
setupTestIframe().then(function() {
|
||||
return clearStorage();
|
||||
}).then(function() {
|
||||
return storageUsage();
|
||||
}).then(function(usage) {
|
||||
is(0, usage, 'disk usage should be zero to start');
|
||||
})
|
||||
|
||||
// Initialize and populate an initial cache to get the base sqlite pages
|
||||
// and directory structure allocated.
|
||||
.then(function() {
|
||||
return caches.open(name);
|
||||
}).then(function(c) {
|
||||
return c.add(url);
|
||||
}).then(function() {
|
||||
return gc();
|
||||
}).then(function() {
|
||||
return caches.delete(name);
|
||||
}).then(function(deleted) {
|
||||
ok(deleted, 'cache should be deleted');
|
||||
})
|
||||
|
||||
// Now measure initial disk usage
|
||||
.then(function() {
|
||||
return resetStorage();
|
||||
}).then(function() {
|
||||
return storageUsage();
|
||||
}).then(function(usage) {
|
||||
initialUsage = usage;
|
||||
})
|
||||
|
||||
// Now re-populate the Cache object
|
||||
.then(function() {
|
||||
return caches.open(name);
|
||||
}).then(function(c) {
|
||||
cache = c;
|
||||
return cache.add(url);
|
||||
})
|
||||
|
||||
// Get a reference to the body we've stored in the Cache.
|
||||
.then(function() {
|
||||
return cache.match(url);
|
||||
}).then(function(r) {
|
||||
response = r;
|
||||
return cache.delete(url);
|
||||
}).then(function(result) {
|
||||
ok(result, "Cache entry should be deleted");
|
||||
})
|
||||
|
||||
// Reset the quota dir while the cache entry is deleted, but still referenced
|
||||
// from the DOM. This forces the body to be orphaned.
|
||||
.then(function() {
|
||||
return resetStorage();
|
||||
}).then(function() {
|
||||
return storageUsage();
|
||||
}).then(function(usage) {
|
||||
fullUsage = usage;
|
||||
ok(fullUsage > initialUsage, 'disk usage should have grown');
|
||||
})
|
||||
|
||||
// Now perform a new Cache operation that will reopen the origin. This
|
||||
// should clean up the orphaned body.
|
||||
.then(function() {
|
||||
return caches.match(url);
|
||||
}).then(function(r) {
|
||||
ok(!r, 'response should not exist in storage');
|
||||
})
|
||||
|
||||
// Finally, verify orphaned data was cleaned up by re-checking the disk
|
||||
// usage. Reset the storage first to ensure any WAL transaction files
|
||||
// are flushed before measuring the usage.
|
||||
.then(function() {
|
||||
return resetStorage();
|
||||
}).then(function() {
|
||||
return storageUsage();
|
||||
}).then(function(usage) {
|
||||
endUsage = usage;
|
||||
dump("### ### initial:" + initialUsage + ", full:" + fullUsage +
|
||||
", end:" + endUsage + "\n");
|
||||
ok(endUsage < fullUsage, 'disk usage should have shrank');
|
||||
is(endUsage, initialUsage, 'disk usage should return to original');
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
171
dom/cache/test/mochitest/test_cache_orphaned_cache.html
vendored
Normal file
171
dom/cache/test/mochitest/test_cache_orphaned_cache.html
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
<!-- 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);
|
||||
});
|
||||
}
|
||||
|
||||
function clearStorage() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var principal = SpecialPowers.wrap(document).nodePrincipal;
|
||||
var appId, inBrowser;
|
||||
var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
|
||||
if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
|
||||
principal.appId != nsIPrincipal.NO_APP_ID) {
|
||||
appId = principal.appId;
|
||||
inBrowser = principal.isInBrowserElement;
|
||||
}
|
||||
SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId,
|
||||
inBrowser);
|
||||
});
|
||||
}
|
||||
|
||||
function storageUsage() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var principal = SpecialPowers.wrap(document).nodePrincipal;
|
||||
var appId, inBrowser;
|
||||
var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
|
||||
if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
|
||||
principal.appId != nsIPrincipal.NO_APP_ID) {
|
||||
appId = principal.appId;
|
||||
inBrowser = principal.isInBrowserElement;
|
||||
}
|
||||
SpecialPowers.getStorageUsageForURI(document.documentURI, resolve, appId,
|
||||
inBrowser);
|
||||
});
|
||||
}
|
||||
|
||||
function resetStorage() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var principal = SpecialPowers.wrap(document).nodePrincipal;
|
||||
var appId, inBrowser;
|
||||
var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
|
||||
if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
|
||||
principal.appId != nsIPrincipal.NO_APP_ID) {
|
||||
appId = principal.appId;
|
||||
inBrowser = principal.isInBrowserElement;
|
||||
}
|
||||
SpecialPowers.resetStorageForURI(document.documentURI, resolve, appId,
|
||||
inBrowser);
|
||||
});
|
||||
}
|
||||
|
||||
function gc() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
SpecialPowers.exactGC(window, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({
|
||||
"set": [["dom.caches.enabled", true],
|
||||
["dom.quotaManager.testing", true]],
|
||||
}, function() {
|
||||
var name = 'toBeOrphaned';
|
||||
var cache = null;
|
||||
var initialUsage = 0;
|
||||
var fullUsage = 0;
|
||||
var resetUsage = 0;
|
||||
var endUsage = 0;
|
||||
var url = 'cache_add.js';
|
||||
|
||||
// start from a fresh origin directory so other tests do not influence our
|
||||
// results
|
||||
setupTestIframe().then(function() {
|
||||
return clearStorage();
|
||||
}).then(function() {
|
||||
return storageUsage();
|
||||
}).then(function(usage) {
|
||||
is(0, usage, 'disk usage should be zero to start');
|
||||
})
|
||||
|
||||
// Initialize and populate an initial cache to get the base sqlite pages
|
||||
// and directory structure allocated.
|
||||
.then(function() {
|
||||
return caches.open(name);
|
||||
}).then(function(c) {
|
||||
return c.add(url);
|
||||
}).then(function() {
|
||||
return gc();
|
||||
}).then(function() {
|
||||
return caches.delete(name);
|
||||
}).then(function(deleted) {
|
||||
ok(deleted, 'cache should be deleted');
|
||||
})
|
||||
|
||||
// Now measure initial disk usage
|
||||
.then(function() {
|
||||
return resetStorage();
|
||||
}).then(function() {
|
||||
return storageUsage();
|
||||
}).then(function(usage) {
|
||||
initialUsage = usage;
|
||||
})
|
||||
|
||||
// Now re-populate the Cache object
|
||||
.then(function() {
|
||||
return caches.open(name);
|
||||
}).then(function(c) {
|
||||
cache = c;
|
||||
return cache.add(url);
|
||||
}).then(function() {
|
||||
return caches.delete(name);
|
||||
}).then(function(deleted) {
|
||||
ok(deleted, 'cache should be deleted');
|
||||
})
|
||||
|
||||
// Reset the quota dir while the cache is deleted, but still referenced
|
||||
// from the DOM. This forces it to be orphaned.
|
||||
.then(function() {
|
||||
return resetStorage();
|
||||
}).then(function() {
|
||||
return storageUsage();
|
||||
}).then(function(usage) {
|
||||
fullUsage = usage;
|
||||
ok(fullUsage > initialUsage, 'disk usage should have grown');
|
||||
})
|
||||
|
||||
// Now perform a new Cache operation that will reopen the origin. This
|
||||
// should clean up the orphaned Cache data.
|
||||
.then(function() {
|
||||
return caches.has(name);
|
||||
}).then(function(result) {
|
||||
ok(!result, 'cache should not exist in storage');
|
||||
})
|
||||
|
||||
// Finally, verify orphaned data was cleaned up by re-checking the disk
|
||||
// usage. Reset the storage first to ensure any WAL transaction files
|
||||
// are flushed before measuring the usage.
|
||||
.then(function() {
|
||||
return resetStorage();
|
||||
}).then(function() {
|
||||
return storageUsage();
|
||||
}).then(function(usage) {
|
||||
endUsage = usage;
|
||||
dump("### ### initial:" + initialUsage + ", full:" + fullUsage +
|
||||
", end:" + endUsage + "\n");
|
||||
ok(endUsage < fullUsage, 'disk usage should have shrank');
|
||||
is(endUsage, initialUsage, 'disk usage should return to original');
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -64,7 +64,7 @@ Event::ConstructorInit(EventTarget* aOwner,
|
||||
WidgetEvent* aEvent)
|
||||
{
|
||||
SetOwner(aOwner);
|
||||
mIsMainThreadEvent = mOwner || NS_IsMainThread();
|
||||
mIsMainThreadEvent = NS_IsMainThread();
|
||||
|
||||
if (mIsMainThreadEvent && !sReturnHighResTimeStampIsSet) {
|
||||
Preferences::AddBoolVarCache(&sReturnHighResTimeStamp,
|
||||
|
@ -217,11 +217,6 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
|
||||
|
||||
nsRefPtr<InternalRequest> r = request->GetInternalRequest();
|
||||
|
||||
aRv = UpdateRequestReferrer(aGlobal, r);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
|
||||
nsCOMPtr<nsIDocument> doc;
|
||||
@ -398,58 +393,6 @@ WorkerFetchResolver::OnResponseEnd()
|
||||
}
|
||||
}
|
||||
|
||||
// This method sets the request's referrerURL, as specified by the "determine
|
||||
// request's referrer" steps from Referrer Policy [1].
|
||||
// The actual referrer policy and stripping is dealt with by HttpBaseChannel,
|
||||
// this always sets the full API referrer URL of the relevant global if it is
|
||||
// not already a url or no-referrer.
|
||||
// [1]: https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrer
|
||||
nsresult
|
||||
UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest)
|
||||
{
|
||||
nsAutoString originalReferrer;
|
||||
aRequest->GetReferrer(originalReferrer);
|
||||
// If it is no-referrer ("") or a URL, don't modify.
|
||||
if (!originalReferrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
|
||||
if (window) {
|
||||
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
|
||||
if (doc) {
|
||||
nsAutoString referrer;
|
||||
doc->GetReferrer(referrer);
|
||||
aRequest->SetReferrer(referrer);
|
||||
}
|
||||
} else if (NS_IsMainThread()) {
|
||||
// Pull the principal from the global for non-worker scripts.
|
||||
nsIPrincipal *principal = aGlobal->PrincipalOrNull();
|
||||
bool isNull;
|
||||
// Only set the referrer if the principal is present,
|
||||
// and the principal is not null or the system principal.
|
||||
if (principal &&
|
||||
NS_SUCCEEDED(principal->GetIsNullPrincipal(&isNull)) && !isNull &&
|
||||
!nsContentUtils::IsSystemPrincipal(principal)) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
if (NS_SUCCEEDED(principal->GetURI(getter_AddRefs(uri))) && uri) {
|
||||
nsAutoCString referrer;
|
||||
if (NS_SUCCEEDED(uri->GetSpec(referrer))) {
|
||||
aRequest->SetReferrer(NS_ConvertUTF8toUTF16(referrer));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
||||
MOZ_ASSERT(worker);
|
||||
worker->AssertIsOnWorkerThread();
|
||||
WorkerPrivate::LocationInfo& info = worker->GetLocationInfo();
|
||||
aRequest->SetReferrer(NS_ConvertUTF8toUTF16(info.mHref));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace {
|
||||
nsresult
|
||||
ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
|
||||
@ -558,8 +501,8 @@ public:
|
||||
MOZ_ASSERT(aFormData);
|
||||
}
|
||||
|
||||
bool URLSearchParamsIterator(const nsString& aName,
|
||||
const nsString& aValue) override
|
||||
bool URLParamsIterator(const nsString& aName,
|
||||
const nsString& aValue) override
|
||||
{
|
||||
mFormData->Append(aName, aValue);
|
||||
return true;
|
||||
|
@ -416,21 +416,37 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
|
||||
// Step 2. Set the referrer.
|
||||
nsAutoString referrer;
|
||||
mRequest->GetReferrer(referrer);
|
||||
// The referrer should have already been resolved to a URL by the caller.
|
||||
MOZ_ASSERT(!referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR));
|
||||
if (!referrer.IsEmpty()) {
|
||||
nsCOMPtr<nsIURI> refURI;
|
||||
rv = NS_NewURI(getter_AddRefs(refURI), referrer, nullptr, nullptr);
|
||||
if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
|
||||
rv = nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal,
|
||||
mDocument,
|
||||
httpChan);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return FailWithNetworkError();
|
||||
}
|
||||
} else if (referrer.IsEmpty()) {
|
||||
rv = httpChan->SetReferrerWithPolicy(nullptr, net::RP_No_Referrer);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return FailWithNetworkError();
|
||||
}
|
||||
} else {
|
||||
// From "Determine request's Referrer" step 3
|
||||
// "If request's referrer is a URL, let referrerSource be request's
|
||||
// referrer."
|
||||
//
|
||||
// XXXnsm - We never actually hit this from a fetch() call since both
|
||||
// fetch and Request() create a new internal request whose referrer is
|
||||
// always set to about:client. Should we just crash here instead until
|
||||
// someone tries to use FetchDriver for non-fetch() APIs?
|
||||
nsCOMPtr<nsIURI> referrerURI;
|
||||
rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return FailWithNetworkError();
|
||||
}
|
||||
|
||||
net::ReferrerPolicy referrerPolicy = net::RP_Default;
|
||||
if (mDocument) {
|
||||
referrerPolicy = mDocument->GetReferrerPolicy();
|
||||
}
|
||||
|
||||
rv = httpChan->SetReferrerWithPolicy(refURI, referrerPolicy);
|
||||
rv =
|
||||
httpChan->SetReferrerWithPolicy(referrerURI,
|
||||
mDocument ? mDocument->GetReferrerPolicy() :
|
||||
net::RP_Default);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return FailWithNetworkError();
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include "InternalResponse.h"
|
||||
|
||||
#include "mozilla/dom/InternalHeaders.h"
|
||||
#include "mozilla/dom/cache/CacheTypes.h"
|
||||
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
||||
#include "nsStreamUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -20,6 +22,10 @@ InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusTe
|
||||
{
|
||||
}
|
||||
|
||||
InternalResponse::~InternalResponse()
|
||||
{
|
||||
}
|
||||
|
||||
already_AddRefed<InternalResponse>
|
||||
InternalResponse::Clone()
|
||||
{
|
||||
@ -73,5 +79,41 @@ InternalResponse::CORSResponse()
|
||||
return cors.forget();
|
||||
}
|
||||
|
||||
void
|
||||
InternalResponse::SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo)
|
||||
{
|
||||
mPrincipalInfo = Move(aPrincipalInfo);
|
||||
}
|
||||
|
||||
already_AddRefed<InternalResponse>
|
||||
InternalResponse::OpaqueResponse()
|
||||
{
|
||||
MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response");
|
||||
nsRefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
|
||||
response->mType = ResponseType::Opaque;
|
||||
response->mTerminationReason = mTerminationReason;
|
||||
response->mURL = mURL;
|
||||
response->mChannelInfo = mChannelInfo;
|
||||
if (mPrincipalInfo) {
|
||||
response->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
|
||||
}
|
||||
response->mWrappedResponse = this;
|
||||
return response.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<InternalResponse>
|
||||
InternalResponse::CreateIncompleteCopy()
|
||||
{
|
||||
nsRefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText);
|
||||
copy->mType = mType;
|
||||
copy->mTerminationReason = mTerminationReason;
|
||||
copy->mURL = mURL;
|
||||
copy->mChannelInfo = mChannelInfo;
|
||||
if (mPrincipalInfo) {
|
||||
copy->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
|
||||
}
|
||||
return copy.forget();
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
@ -12,8 +12,13 @@
|
||||
|
||||
#include "mozilla/dom/ResponseBinding.h"
|
||||
#include "mozilla/dom/ChannelInfo.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
class PrincipalInfo;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
class InternalHeaders;
|
||||
@ -41,17 +46,7 @@ public:
|
||||
}
|
||||
|
||||
already_AddRefed<InternalResponse>
|
||||
OpaqueResponse()
|
||||
{
|
||||
MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response");
|
||||
nsRefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
|
||||
response->mType = ResponseType::Opaque;
|
||||
response->mTerminationReason = mTerminationReason;
|
||||
response->mURL = mURL;
|
||||
response->mChannelInfo = mChannelInfo;
|
||||
response->mWrappedResponse = this;
|
||||
return response.forget();
|
||||
}
|
||||
OpaqueResponse();
|
||||
|
||||
already_AddRefed<InternalResponse>
|
||||
BasicResponse();
|
||||
@ -174,9 +169,18 @@ public:
|
||||
return mChannelInfo;
|
||||
}
|
||||
|
||||
const UniquePtr<mozilla::ipc::PrincipalInfo>&
|
||||
GetPrincipalInfo() const
|
||||
{
|
||||
return mPrincipalInfo;
|
||||
}
|
||||
|
||||
// Takes ownership of the principal info.
|
||||
void
|
||||
SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo);
|
||||
|
||||
private:
|
||||
~InternalResponse()
|
||||
{ }
|
||||
~InternalResponse();
|
||||
|
||||
explicit InternalResponse(const InternalResponse& aOther) = delete;
|
||||
InternalResponse& operator=(const InternalResponse&) = delete;
|
||||
@ -184,15 +188,7 @@ private:
|
||||
// Returns an instance of InternalResponse which is a copy of this
|
||||
// InternalResponse, except headers, body and wrapped response (if any) which
|
||||
// are left uninitialized. Used for cloning and filtering.
|
||||
already_AddRefed<InternalResponse> CreateIncompleteCopy()
|
||||
{
|
||||
nsRefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText);
|
||||
copy->mType = mType;
|
||||
copy->mTerminationReason = mTerminationReason;
|
||||
copy->mURL = mURL;
|
||||
copy->mChannelInfo = mChannelInfo;
|
||||
return copy.forget();
|
||||
}
|
||||
already_AddRefed<InternalResponse> CreateIncompleteCopy();
|
||||
|
||||
ResponseType mType;
|
||||
nsCString mTerminationReason;
|
||||
@ -202,6 +198,7 @@ private:
|
||||
nsRefPtr<InternalHeaders> mHeaders;
|
||||
nsCOMPtr<nsIInputStream> mBody;
|
||||
ChannelInfo mChannelInfo;
|
||||
UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
|
||||
|
||||
// For filtered responses.
|
||||
// Cache, and SW interception should always serialize/access the underlying
|
||||
|
@ -17,6 +17,10 @@
|
||||
#include "InternalResponse.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
class PrincipalInfo;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
class Headers;
|
||||
@ -90,6 +94,12 @@ public:
|
||||
return mInternalResponse->GetChannelInfo();
|
||||
}
|
||||
|
||||
const UniquePtr<mozilla::ipc::PrincipalInfo>&
|
||||
GetPrincipalInfo() const
|
||||
{
|
||||
return mInternalResponse->GetPrincipalInfo();
|
||||
}
|
||||
|
||||
Headers* Headers_();
|
||||
|
||||
void
|
||||
|
@ -316,7 +316,7 @@ HTMLLinkElement::UpdatePreconnect()
|
||||
if (owner) {
|
||||
nsCOMPtr<nsIURI> uri = GetHrefURI();
|
||||
if (uri) {
|
||||
owner->MaybePreconnect(uri);
|
||||
owner->MaybePreconnect(uri, GetCORSMode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2201,7 +2201,11 @@ HTMLMediaElement::Play(ErrorResult& aRv)
|
||||
{
|
||||
// Prevent media element from being auto-started by a script when
|
||||
// media.autoplay.enabled=false
|
||||
if (!IsAutoplayEnabled() && !EventStateManager::IsHandlingUserInput() && !nsContentUtils::IsCallerChrome()) {
|
||||
nsRefPtr<TimeRanges> played(Played());
|
||||
if (played->Length() == 0
|
||||
&& !IsAutoplayEnabled()
|
||||
&& !EventStateManager::IsHandlingUserInput()
|
||||
&& !nsContentUtils::IsCallerChrome()) {
|
||||
LOG(LogLevel::Debug, ("%p Blocked attempt to autoplay media.", this));
|
||||
return;
|
||||
}
|
||||
|
@ -896,70 +896,104 @@ IDBDatabase::AbortTransactions(bool aShouldWarn)
|
||||
|
||||
class MOZ_STACK_CLASS Helper final
|
||||
{
|
||||
typedef nsAutoTArray<nsRefPtr<IDBTransaction>, 20> StrongTransactionArray;
|
||||
typedef nsAutoTArray<IDBTransaction*, 20> WeakTransactionArray;
|
||||
|
||||
public:
|
||||
static void
|
||||
AbortTransactions(nsTHashtable<nsPtrHashKey<IDBTransaction>>& aTable,
|
||||
nsTArray<nsRefPtr<IDBTransaction>>& aAbortedTransactions)
|
||||
AbortTransactions(IDBDatabase* aDatabase, const bool aShouldWarn)
|
||||
{
|
||||
const uint32_t count = aTable.Count();
|
||||
if (!count) {
|
||||
MOZ_ASSERT(aDatabase);
|
||||
aDatabase->AssertIsOnOwningThread();
|
||||
|
||||
nsTHashtable<nsPtrHashKey<IDBTransaction>>& transactionTable =
|
||||
aDatabase->mTransactions;
|
||||
|
||||
if (!transactionTable.Count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoTArray<nsRefPtr<IDBTransaction>, 20> transactions;
|
||||
transactions.SetCapacity(count);
|
||||
StrongTransactionArray transactionsToAbort;
|
||||
transactionsToAbort.SetCapacity(transactionTable.Count());
|
||||
|
||||
aTable.EnumerateEntries(Collect, &transactions);
|
||||
transactionTable.EnumerateEntries(Collect, &transactionsToAbort);
|
||||
MOZ_ASSERT(transactionsToAbort.Length() <= transactionTable.Count());
|
||||
|
||||
MOZ_ASSERT(transactions.Length() == count);
|
||||
if (transactionsToAbort.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t index = 0; index < count; index++) {
|
||||
nsRefPtr<IDBTransaction> transaction = Move(transactions[index]);
|
||||
// We want to abort transactions as soon as possible so we iterate the
|
||||
// transactions once and abort them all first, collecting the transactions
|
||||
// that need to have a warning issued along the way. Those that need a
|
||||
// warning will be a subset of those that are aborted, so we don't need
|
||||
// additional strong references here.
|
||||
WeakTransactionArray transactionsThatNeedWarning;
|
||||
|
||||
for (nsRefPtr<IDBTransaction>& transaction : transactionsToAbort) {
|
||||
MOZ_ASSERT(transaction);
|
||||
MOZ_ASSERT(!transaction->IsDone());
|
||||
|
||||
if (aShouldWarn) {
|
||||
switch (transaction->GetMode()) {
|
||||
// We ignore transactions that could not have written any data.
|
||||
case IDBTransaction::READ_ONLY:
|
||||
break;
|
||||
|
||||
// We warn for any transactions that could have written data.
|
||||
case IDBTransaction::READ_WRITE:
|
||||
case IDBTransaction::READ_WRITE_FLUSH:
|
||||
case IDBTransaction::VERSION_CHANGE:
|
||||
transactionsThatNeedWarning.AppendElement(transaction);
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unknown mode!");
|
||||
}
|
||||
}
|
||||
|
||||
transaction->Abort(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
|
||||
// We only care about warning for write transactions.
|
||||
if (transaction->GetMode() != IDBTransaction::READ_ONLY) {
|
||||
aAbortedTransactions.AppendElement(Move(transaction));
|
||||
}
|
||||
static const char kWarningMessage[] =
|
||||
"IndexedDBTransactionAbortNavigation";
|
||||
|
||||
for (IDBTransaction* transaction : transactionsThatNeedWarning) {
|
||||
MOZ_ASSERT(transaction);
|
||||
|
||||
nsString filename;
|
||||
uint32_t lineNo;
|
||||
transaction->GetCallerLocation(filename, &lineNo);
|
||||
|
||||
aDatabase->LogWarning(kWarningMessage, filename, lineNo);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static PLDHashOperator
|
||||
Collect(nsPtrHashKey<IDBTransaction>* aTransaction, void* aClosure)
|
||||
Collect(nsPtrHashKey<IDBTransaction>* aTransactionKey, void* aClosure)
|
||||
{
|
||||
MOZ_ASSERT(aTransaction);
|
||||
aTransaction->GetKey()->AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aTransactionKey);
|
||||
MOZ_ASSERT(aClosure);
|
||||
|
||||
auto* array = static_cast<nsTArray<nsRefPtr<IDBTransaction>>*>(aClosure);
|
||||
array->AppendElement(aTransaction->GetKey());
|
||||
IDBTransaction* transaction = aTransactionKey->GetKey();
|
||||
MOZ_ASSERT(transaction);
|
||||
|
||||
transaction->AssertIsOnOwningThread();
|
||||
|
||||
// Transactions that are already done can simply be ignored. Otherwise
|
||||
// there is a race here and it's possible that the transaction has not
|
||||
// been successfully committed yet so we will warn the user.
|
||||
if (!transaction->IsDone()) {
|
||||
auto* array = static_cast<StrongTransactionArray*>(aClosure);
|
||||
array->AppendElement(transaction);
|
||||
}
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
};
|
||||
|
||||
nsAutoTArray<nsRefPtr<IDBTransaction>, 5> abortedTransactions;
|
||||
Helper::AbortTransactions(mTransactions, abortedTransactions);
|
||||
|
||||
if (aShouldWarn && !abortedTransactions.IsEmpty()) {
|
||||
static const char kWarningMessage[] = "IndexedDBTransactionAbortNavigation";
|
||||
|
||||
for (uint32_t count = abortedTransactions.Length(), index = 0;
|
||||
index < count;
|
||||
index++) {
|
||||
nsRefPtr<IDBTransaction>& transaction = abortedTransactions[index];
|
||||
MOZ_ASSERT(transaction);
|
||||
|
||||
nsString filename;
|
||||
uint32_t lineNo;
|
||||
transaction->GetCallerLocation(filename, &lineNo);
|
||||
|
||||
LogWarning(kWarningMessage, filename, lineNo);
|
||||
}
|
||||
}
|
||||
Helper::AbortTransactions(this, aShouldWarn);
|
||||
}
|
||||
|
||||
PBackgroundIDBDatabaseFileChild*
|
||||
|
@ -1437,7 +1437,7 @@ IDBObjectStore::Index(const nsAString& aName, ErrorResult &aRv)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mTransaction->IsFinished()) {
|
||||
if (mTransaction->IsCommittingOrDone() || mDeletedSpec) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -448,7 +448,7 @@ IDBTransaction::SendCommit()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(NS_SUCCEEDED(mAbortCode));
|
||||
MOZ_ASSERT(IsFinished());
|
||||
MOZ_ASSERT(IsCommittingOrDone());
|
||||
MOZ_ASSERT(!mSentCommitOrAbort);
|
||||
MOZ_ASSERT(!mPendingRequestCount);
|
||||
|
||||
@ -481,7 +481,7 @@ IDBTransaction::SendAbort(nsresult aResultCode)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(NS_FAILED(aResultCode));
|
||||
MOZ_ASSERT(IsFinished());
|
||||
MOZ_ASSERT(IsCommittingOrDone());
|
||||
MOZ_ASSERT(!mSentCommitOrAbort);
|
||||
|
||||
// Don't do this in the macro because we always need to increment the serial
|
||||
@ -640,14 +640,10 @@ IDBTransaction::AbortInternal(nsresult aAbortCode,
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(NS_FAILED(aAbortCode));
|
||||
MOZ_ASSERT(!IsCommittingOrDone());
|
||||
|
||||
nsRefPtr<DOMError> error = aError;
|
||||
|
||||
if (IsFinished()) {
|
||||
// Already finished, nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
const bool isVersionChange = mMode == VERSION_CHANGE;
|
||||
const bool isInvalidated = mDatabase->IsInvalidated();
|
||||
bool needToSendAbort = mReadyState == INITIAL && !isInvalidated;
|
||||
@ -740,6 +736,12 @@ IDBTransaction::Abort(IDBRequest* aRequest)
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aRequest);
|
||||
|
||||
if (IsCommittingOrDone()) {
|
||||
// Already started (and maybe finished) the commit or abort so there is
|
||||
// nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMError> error = aRequest->GetError(rv);
|
||||
|
||||
@ -751,6 +753,12 @@ IDBTransaction::Abort(nsresult aErrorCode)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (IsCommittingOrDone()) {
|
||||
// Already started (and maybe finished) the commit or abort so there is
|
||||
// nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<DOMError> error = new DOMError(GetOwner(), aErrorCode);
|
||||
AbortInternal(aErrorCode, error.forget());
|
||||
}
|
||||
@ -760,7 +768,7 @@ IDBTransaction::Abort(ErrorResult& aRv)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (IsFinished()) {
|
||||
if (IsCommittingOrDone()) {
|
||||
aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
||||
return;
|
||||
}
|
||||
@ -905,7 +913,7 @@ IDBTransaction::ObjectStore(const nsAString& aName, ErrorResult& aRv)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (IsFinished()) {
|
||||
if (IsCommittingOrDone()) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
@ -1029,11 +1037,12 @@ WorkerFeature::Notify(JSContext* aCx, Status aStatus)
|
||||
if (mTransaction && aStatus > Terminating) {
|
||||
mTransaction->AssertIsOnOwningThread();
|
||||
|
||||
nsRefPtr<IDBTransaction> transaction = mTransaction;
|
||||
mTransaction = nullptr;
|
||||
nsRefPtr<IDBTransaction> transaction = Move(mTransaction);
|
||||
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, nullptr);
|
||||
if (!transaction->IsCommittingOrDone()) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -166,10 +166,19 @@ public:
|
||||
IsOpen() const;
|
||||
|
||||
bool
|
||||
IsFinished() const
|
||||
IsCommittingOrDone() const
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
return mReadyState > LOADING;
|
||||
|
||||
return mReadyState == COMMITTING || mReadyState == DONE;
|
||||
}
|
||||
|
||||
bool
|
||||
IsDone() const
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
return mReadyState == DONE;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -469,7 +469,7 @@ protected:
|
||||
|
||||
// IME
|
||||
static TabParent *mIMETabParent;
|
||||
ContentCache mContentCache;
|
||||
ContentCacheInParent mContentCache;
|
||||
|
||||
nsIntRect mRect;
|
||||
ScreenIntSize mDimensions;
|
||||
|
@ -323,25 +323,30 @@ RTCPeerConnection.prototype = {
|
||||
__init: function(rtcConfig) {
|
||||
this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
|
||||
if (!rtcConfig.iceServers ||
|
||||
!Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) {
|
||||
rtcConfig.iceServers =
|
||||
JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"));
|
||||
}
|
||||
// Normalize iceServers input
|
||||
rtcConfig.iceServers.forEach(server => {
|
||||
if (typeof server.urls === "string") {
|
||||
server.urls = [server.urls];
|
||||
} else if (!server.urls && server.url) {
|
||||
// TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
|
||||
server.urls = [server.url];
|
||||
this.logWarning("RTCIceServer.url is deprecated! Use urls instead.", null, 0);
|
||||
try {
|
||||
rtcConfig.iceServers =
|
||||
JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers") || "[]");
|
||||
} catch (e) {
|
||||
this.logWarning(
|
||||
"Ignoring invalid media.peerconnection.default_iceservers in about:config",
|
||||
null, 0);
|
||||
rtcConfig.iceServers = [];
|
||||
}
|
||||
});
|
||||
this._mustValidateRTCConfiguration(rtcConfig,
|
||||
try {
|
||||
this._mustValidateRTCConfiguration(rtcConfig,
|
||||
"Ignoring invalid media.peerconnection.default_iceservers in about:config");
|
||||
} catch (e) {
|
||||
this.logWarning(e.message, null, 0);
|
||||
rtcConfig.iceServers = [];
|
||||
}
|
||||
} else {
|
||||
// This gets executed in the typical case when iceServers
|
||||
// are passed in through the web page.
|
||||
this._mustValidateRTCConfiguration(rtcConfig,
|
||||
"RTCPeerConnection constructor passed invalid RTCConfiguration");
|
||||
|
||||
}
|
||||
// Save the appId
|
||||
this._appId = Cu.getWebIDLCallerPrincipal().appId;
|
||||
|
||||
@ -463,12 +468,23 @@ RTCPeerConnection.prototype = {
|
||||
* { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
|
||||
* username:"jib", credential:"mypass"} ] }
|
||||
*
|
||||
* WebIDL normalizes structure for us, so we test well-formed stun/turn urls,
|
||||
* but not validity of servers themselves, before passing along to C++.
|
||||
*
|
||||
* This function normalizes the structure of the input for rtcConfig.iceServers for us,
|
||||
* so we test well-formed stun/turn urls before passing along to C++.
|
||||
* msg - Error message to detail which array-entry failed, if any.
|
||||
*/
|
||||
_mustValidateRTCConfiguration: function(rtcConfig, msg) {
|
||||
|
||||
// Normalize iceServers input
|
||||
rtcConfig.iceServers.forEach(server => {
|
||||
if (typeof server.urls === "string") {
|
||||
server.urls = [server.urls];
|
||||
} else if (!server.urls && server.url) {
|
||||
// TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
|
||||
server.urls = [server.url];
|
||||
this.logWarning("RTCIceServer.url is deprecated! Use urls instead.", null, 0);
|
||||
}
|
||||
});
|
||||
|
||||
let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
|
||||
|
||||
let nicerNewURI = uriStr => {
|
||||
|
@ -72,7 +72,21 @@ runNetworkTest(() => {
|
||||
"mozRTCPeerConnection() constructor has readable exceptions");
|
||||
}
|
||||
|
||||
networkTestFinished();
|
||||
// Below tests are setting the about:config User preferences for default
|
||||
// ice servers and checking the outputs when mozRTCPeerConnection() is
|
||||
// invoked. See Bug 1167922 for more information.
|
||||
// Note - We use promises here since the SpecialPowers API will be
|
||||
// performed asynchronously.
|
||||
var push = prefs => new Promise(resolve =>
|
||||
SpecialPowers.pushPrefEnv(prefs, resolve));
|
||||
|
||||
push({ set: [['media.peerconnection.default_iceservers', ""]] })
|
||||
.then(() => makePC())
|
||||
.then(() => push({ set: [['media.peerconnection.default_iceservers', "k"]] }))
|
||||
.then(() => makePC())
|
||||
.then(() => push({ set: [['media.peerconnection.default_iceservers', "[{\"urls\": [\"stun:stun.services.mozilla.com\"]}]"]] }))
|
||||
.then(() => makePC())
|
||||
.then(networkTestFinished);
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
|
@ -35,16 +35,13 @@ var testsRemaining = tests.length;
|
||||
tests.forEach(function(e) {
|
||||
e.ac = new AudioContext();
|
||||
var a = new Audio();
|
||||
a.loop = true;
|
||||
if (e.cors) {
|
||||
a.crossOrigin = e.cors;
|
||||
}
|
||||
a.src = e.url;
|
||||
a.controls = true;
|
||||
var measn = e.ac.createMediaElementSource(a);
|
||||
var sp = e.ac.createScriptProcessor(2048, 1);
|
||||
// Set a couple expandos to track the status of the test
|
||||
sp.iterationsLeft = 300;
|
||||
sp.seenSound = false;
|
||||
|
||||
measn.connect(sp);
|
||||
@ -52,9 +49,8 @@ tests.forEach(function(e) {
|
||||
document.body.appendChild(a);
|
||||
|
||||
function checkFinished(sp) {
|
||||
if (--sp.iterationsLeft == 0) {
|
||||
if (a.ended) {
|
||||
sp.onaudioprocess = null;
|
||||
a.pause();
|
||||
var not = e.expectSilence ? "" : "not";
|
||||
is(e.expectSilence, !sp.seenSound,
|
||||
"Buffer is " + not + " silent as expected, for " +
|
||||
@ -82,9 +78,7 @@ tests.forEach(function(e) {
|
||||
return silent;
|
||||
}
|
||||
|
||||
a.onplaying = function () {
|
||||
sp.onaudioprocess = checkBufferSilent;
|
||||
}
|
||||
sp.onaudioprocess = checkBufferSilent;
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
|
472
dom/media/webm/AudioDecoder.cpp
Normal file
472
dom/media/webm/AudioDecoder.cpp
Normal file
@ -0,0 +1,472 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WebMReader.h"
|
||||
|
||||
#ifdef MOZ_TREMOR
|
||||
#include "tremor/ivorbiscodec.h"
|
||||
#else
|
||||
#include "vorbis/codec.h"
|
||||
#endif
|
||||
|
||||
#include "OpusParser.h"
|
||||
|
||||
#include "VorbisUtils.h"
|
||||
#include "OggReader.h"
|
||||
|
||||
#undef LOG
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
#include "prprf.h"
|
||||
#define LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
|
||||
ogg_packet InitOggPacket(const unsigned char* aData, size_t aLength,
|
||||
bool aBOS, bool aEOS,
|
||||
int64_t aGranulepos, int64_t aPacketNo)
|
||||
{
|
||||
ogg_packet packet;
|
||||
packet.packet = const_cast<unsigned char*>(aData);
|
||||
packet.bytes = aLength;
|
||||
packet.b_o_s = aBOS;
|
||||
packet.e_o_s = aEOS;
|
||||
packet.granulepos = aGranulepos;
|
||||
packet.packetno = aPacketNo;
|
||||
return packet;
|
||||
}
|
||||
|
||||
class VorbisDecoder : public WebMAudioDecoder
|
||||
{
|
||||
public:
|
||||
nsresult Init();
|
||||
void Shutdown();
|
||||
nsresult ResetDecode();
|
||||
nsresult DecodeHeader(const unsigned char* aData, size_t aLength);
|
||||
nsresult FinishInit(AudioInfo& aInfo);
|
||||
bool Decode(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
int64_t aDiscardPadding, int32_t* aTotalFrames);
|
||||
explicit VorbisDecoder(WebMReader* aReader);
|
||||
~VorbisDecoder();
|
||||
private:
|
||||
nsRefPtr<WebMReader> mReader;
|
||||
|
||||
// Vorbis decoder state
|
||||
vorbis_info mVorbisInfo;
|
||||
vorbis_comment mVorbisComment;
|
||||
vorbis_dsp_state mVorbisDsp;
|
||||
vorbis_block mVorbisBlock;
|
||||
int64_t mPacketCount;
|
||||
};
|
||||
|
||||
VorbisDecoder::VorbisDecoder(WebMReader* aReader)
|
||||
: mReader(aReader)
|
||||
, mPacketCount(0)
|
||||
{
|
||||
// Zero these member vars to avoid crashes in Vorbis clear functions when
|
||||
// destructor is called before |Init|.
|
||||
PodZero(&mVorbisBlock);
|
||||
PodZero(&mVorbisDsp);
|
||||
PodZero(&mVorbisInfo);
|
||||
PodZero(&mVorbisComment);
|
||||
}
|
||||
|
||||
VorbisDecoder::~VorbisDecoder()
|
||||
{
|
||||
vorbis_block_clear(&mVorbisBlock);
|
||||
vorbis_dsp_clear(&mVorbisDsp);
|
||||
vorbis_info_clear(&mVorbisInfo);
|
||||
vorbis_comment_clear(&mVorbisComment);
|
||||
}
|
||||
|
||||
void
|
||||
VorbisDecoder::Shutdown()
|
||||
{
|
||||
mReader = nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VorbisDecoder::Init()
|
||||
{
|
||||
vorbis_info_init(&mVorbisInfo);
|
||||
vorbis_comment_init(&mVorbisComment);
|
||||
PodZero(&mVorbisDsp);
|
||||
PodZero(&mVorbisBlock);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VorbisDecoder::ResetDecode()
|
||||
{
|
||||
// Ignore failed results from vorbis_synthesis_restart. They
|
||||
// aren't fatal and it fails when ResetDecode is called at a
|
||||
// time when no vorbis data has been read.
|
||||
vorbis_synthesis_restart(&mVorbisDsp);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VorbisDecoder::DecodeHeader(const unsigned char* aData, size_t aLength)
|
||||
{
|
||||
bool bos = mPacketCount == 0;
|
||||
ogg_packet pkt = InitOggPacket(aData, aLength, bos, false, 0, mPacketCount++);
|
||||
MOZ_ASSERT(mPacketCount <= 3);
|
||||
|
||||
int r = vorbis_synthesis_headerin(&mVorbisInfo,
|
||||
&mVorbisComment,
|
||||
&pkt);
|
||||
return r == 0 ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VorbisDecoder::FinishInit(AudioInfo& aInfo)
|
||||
{
|
||||
MOZ_ASSERT(mPacketCount == 3);
|
||||
|
||||
int r = vorbis_synthesis_init(&mVorbisDsp, &mVorbisInfo);
|
||||
if (r) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock);
|
||||
if (r) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aInfo.mRate = mVorbisDsp.vi->rate;
|
||||
aInfo.mChannels = mVorbisDsp.vi->channels;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
VorbisDecoder::Decode(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
int64_t aDiscardPadding, int32_t* aTotalFrames)
|
||||
{
|
||||
MOZ_ASSERT(mPacketCount >= 3);
|
||||
ogg_packet pkt = InitOggPacket(aData, aLength, false, false, -1, mPacketCount++);
|
||||
bool first_packet = mPacketCount == 4;
|
||||
|
||||
if (vorbis_synthesis(&mVorbisBlock, &pkt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vorbis_synthesis_blockin(&mVorbisDsp,
|
||||
&mVorbisBlock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VorbisPCMValue** pcm = 0;
|
||||
int32_t frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm);
|
||||
// If the first packet of audio in the media produces no data, we
|
||||
// still need to produce an AudioData for it so that the correct media
|
||||
// start time is calculated. Otherwise we'd end up with a media start
|
||||
// time derived from the timecode of the first packet that produced
|
||||
// data.
|
||||
if (frames == 0 && first_packet) {
|
||||
mReader->AudioQueue().Push(new AudioData(aOffset, aTstampUsecs, 0, 0, nullptr,
|
||||
mVorbisDsp.vi->channels,
|
||||
mVorbisDsp.vi->rate));
|
||||
}
|
||||
while (frames > 0) {
|
||||
uint32_t channels = mVorbisDsp.vi->channels;
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames*channels]);
|
||||
for (uint32_t j = 0; j < channels; ++j) {
|
||||
VorbisPCMValue* channel = pcm[j];
|
||||
for (uint32_t i = 0; i < uint32_t(frames); ++i) {
|
||||
buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]);
|
||||
}
|
||||
}
|
||||
|
||||
CheckedInt64 duration = FramesToUsecs(frames, mVorbisDsp.vi->rate);
|
||||
if (!duration.isValid()) {
|
||||
NS_WARNING("Int overflow converting WebM audio duration");
|
||||
return false;
|
||||
}
|
||||
CheckedInt64 total_duration = FramesToUsecs(*aTotalFrames,
|
||||
mVorbisDsp.vi->rate);
|
||||
if (!total_duration.isValid()) {
|
||||
NS_WARNING("Int overflow converting WebM audio total_duration");
|
||||
return false;
|
||||
}
|
||||
|
||||
CheckedInt64 time = total_duration + aTstampUsecs;
|
||||
if (!time.isValid()) {
|
||||
NS_WARNING("Int overflow adding total_duration and aTstampUsecs");
|
||||
return false;
|
||||
};
|
||||
|
||||
*aTotalFrames += frames;
|
||||
mReader->AudioQueue().Push(new AudioData(aOffset,
|
||||
time.value(),
|
||||
duration.value(),
|
||||
frames,
|
||||
buffer.forget(),
|
||||
mVorbisDsp.vi->channels,
|
||||
mVorbisDsp.vi->rate));
|
||||
if (vorbis_synthesis_read(&mVorbisDsp, frames)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
class OpusDecoder : public WebMAudioDecoder
|
||||
{
|
||||
public:
|
||||
nsresult Init();
|
||||
void Shutdown();
|
||||
nsresult ResetDecode();
|
||||
nsresult DecodeHeader(const unsigned char* aData, size_t aLength);
|
||||
nsresult FinishInit(AudioInfo& aInfo);
|
||||
bool Decode(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
int64_t aDiscardPadding, int32_t* aTotalFrames);
|
||||
explicit OpusDecoder(WebMReader* aReader);
|
||||
~OpusDecoder();
|
||||
private:
|
||||
nsRefPtr<WebMReader> mReader;
|
||||
|
||||
// Opus decoder state
|
||||
nsAutoPtr<OpusParser> mOpusParser;
|
||||
OpusMSDecoder* mOpusDecoder;
|
||||
uint16_t mSkip; // Samples left to trim before playback.
|
||||
bool mDecodedHeader;
|
||||
|
||||
// Opus padding should only be discarded on the final packet. Once this
|
||||
// is set to true, if the reader attempts to decode any further packets it
|
||||
// will raise an error so we can indicate that the file is invalid.
|
||||
bool mPaddingDiscarded;
|
||||
};
|
||||
|
||||
OpusDecoder::OpusDecoder(WebMReader* aReader)
|
||||
: mReader(aReader)
|
||||
, mOpusDecoder(nullptr)
|
||||
, mSkip(0)
|
||||
, mDecodedHeader(false)
|
||||
, mPaddingDiscarded(false)
|
||||
{
|
||||
}
|
||||
|
||||
OpusDecoder::~OpusDecoder()
|
||||
{
|
||||
if (mOpusDecoder) {
|
||||
opus_multistream_decoder_destroy(mOpusDecoder);
|
||||
mOpusDecoder = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OpusDecoder::Shutdown()
|
||||
{
|
||||
mReader = nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OpusDecoder::Init()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OpusDecoder::ResetDecode()
|
||||
{
|
||||
if (mOpusDecoder) {
|
||||
// Reset the decoder.
|
||||
opus_multistream_decoder_ctl(mOpusDecoder, OPUS_RESET_STATE);
|
||||
mSkip = mOpusParser->mPreSkip;
|
||||
mPaddingDiscarded = false;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OpusDecoder::DecodeHeader(const unsigned char* aData, size_t aLength)
|
||||
{
|
||||
MOZ_ASSERT(!mOpusParser);
|
||||
MOZ_ASSERT(!mOpusDecoder);
|
||||
MOZ_ASSERT(!mDecodedHeader);
|
||||
mDecodedHeader = true;
|
||||
|
||||
mOpusParser = new OpusParser;
|
||||
if (!mOpusParser->DecodeHeader(const_cast<unsigned char*>(aData), aLength)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OpusDecoder::FinishInit(AudioInfo& aInfo)
|
||||
{
|
||||
MOZ_ASSERT(mDecodedHeader);
|
||||
|
||||
int r;
|
||||
mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate,
|
||||
mOpusParser->mChannels,
|
||||
mOpusParser->mStreams,
|
||||
mOpusParser->mCoupledStreams,
|
||||
mOpusParser->mMappingTable,
|
||||
&r);
|
||||
mSkip = mOpusParser->mPreSkip;
|
||||
mPaddingDiscarded = false;
|
||||
|
||||
if (int64_t(mReader->GetCodecDelay()) != FramesToUsecs(mOpusParser->mPreSkip,
|
||||
mOpusParser->mRate).value()) {
|
||||
LOG(LogLevel::Warning,
|
||||
("Invalid Opus header: CodecDelay and pre-skip do not match!"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aInfo.mRate = mOpusParser->mRate;
|
||||
aInfo.mChannels = mOpusParser->mChannels;
|
||||
|
||||
return r == OPUS_OK ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
bool
|
||||
OpusDecoder::Decode(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
int64_t aDiscardPadding, int32_t* aTotalFrames)
|
||||
{
|
||||
uint32_t channels = mOpusParser->mChannels;
|
||||
// No channel mapping for more than 8 channels.
|
||||
if (channels > 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mPaddingDiscarded) {
|
||||
// Discard padding should be used only on the final packet, so
|
||||
// decoding after a padding discard is invalid.
|
||||
LOG(LogLevel::Debug, ("Opus error, discard padding on interstitial packet"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Maximum value is 63*2880, so there's no chance of overflow.
|
||||
int32_t frames_number = opus_packet_get_nb_frames(aData, aLength);
|
||||
if (frames_number <= 0) {
|
||||
return false; // Invalid packet header.
|
||||
}
|
||||
|
||||
int32_t samples =
|
||||
opus_packet_get_samples_per_frame(aData, opus_int32(mOpusParser->mRate));
|
||||
|
||||
// A valid Opus packet must be between 2.5 and 120 ms long (48kHz).
|
||||
int32_t frames = frames_number*samples;
|
||||
if (frames < 120 || frames > 5760)
|
||||
return false;
|
||||
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]);
|
||||
|
||||
// Decode to the appropriate sample type.
|
||||
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
|
||||
int ret = opus_multistream_decode_float(mOpusDecoder,
|
||||
aData, aLength,
|
||||
buffer, frames, false);
|
||||
#else
|
||||
int ret = opus_multistream_decode(mOpusDecoder,
|
||||
aData, aLength,
|
||||
buffer, frames, false);
|
||||
#endif
|
||||
if (ret < 0)
|
||||
return false;
|
||||
NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
|
||||
CheckedInt64 startTime = aTstampUsecs;
|
||||
|
||||
// Trim the initial frames while the decoder is settling.
|
||||
if (mSkip > 0) {
|
||||
int32_t skipFrames = std::min<int32_t>(mSkip, frames);
|
||||
int32_t keepFrames = frames - skipFrames;
|
||||
LOG(LogLevel::Debug, ("Opus decoder skipping %d of %d frames",
|
||||
skipFrames, frames));
|
||||
PodMove(buffer.get(),
|
||||
buffer.get() + skipFrames * channels,
|
||||
keepFrames * channels);
|
||||
startTime = startTime + FramesToUsecs(skipFrames, mOpusParser->mRate);
|
||||
frames = keepFrames;
|
||||
mSkip -= skipFrames;
|
||||
}
|
||||
|
||||
if (aDiscardPadding < 0) {
|
||||
// Negative discard padding is invalid.
|
||||
LOG(LogLevel::Debug, ("Opus error, negative discard padding"));
|
||||
return false;
|
||||
}
|
||||
if (aDiscardPadding > 0) {
|
||||
CheckedInt64 discardFrames = UsecsToFrames(aDiscardPadding / NS_PER_USEC,
|
||||
mOpusParser->mRate);
|
||||
if (!discardFrames.isValid()) {
|
||||
NS_WARNING("Int overflow in DiscardPadding");
|
||||
return false;
|
||||
}
|
||||
if (discardFrames.value() > frames) {
|
||||
// Discarding more than the entire packet is invalid.
|
||||
LOG(LogLevel::Debug, ("Opus error, discard padding larger than packet"));
|
||||
return false;
|
||||
}
|
||||
LOG(LogLevel::Debug, ("Opus decoder discarding %d of %d frames",
|
||||
int32_t(discardFrames.value()), frames));
|
||||
// Padding discard is only supposed to happen on the final packet.
|
||||
// Record the discard so we can return an error if another packet is
|
||||
// decoded.
|
||||
mPaddingDiscarded = true;
|
||||
int32_t keepFrames = frames - discardFrames.value();
|
||||
frames = keepFrames;
|
||||
}
|
||||
|
||||
// Apply the header gain if one was specified.
|
||||
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
|
||||
if (mOpusParser->mGain != 1.0f) {
|
||||
float gain = mOpusParser->mGain;
|
||||
int samples = frames * channels;
|
||||
for (int i = 0; i < samples; i++) {
|
||||
buffer[i] *= gain;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (mOpusParser->mGain_Q16 != 65536) {
|
||||
int64_t gain_Q16 = mOpusParser->mGain_Q16;
|
||||
int samples = frames * channels;
|
||||
for (int i = 0; i < samples; i++) {
|
||||
int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16);
|
||||
buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CheckedInt64 duration = FramesToUsecs(frames, mOpusParser->mRate);
|
||||
if (!duration.isValid()) {
|
||||
NS_WARNING("Int overflow converting WebM audio duration");
|
||||
return false;
|
||||
}
|
||||
CheckedInt64 time = startTime - mReader->GetCodecDelay();
|
||||
if (!time.isValid()) {
|
||||
NS_WARNING("Int overflow shifting tstamp by codec delay");
|
||||
return false;
|
||||
};
|
||||
mReader->AudioQueue().Push(new AudioData(aOffset,
|
||||
time.value(),
|
||||
duration.value(),
|
||||
frames,
|
||||
buffer.forget(),
|
||||
mOpusParser->mChannels,
|
||||
mOpusParser->mRate));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -200,7 +200,7 @@ IntelWebMVideoDecoder::Demux(nsRefPtr<VP8Sample>& aSample, bool* aEOS)
|
||||
}
|
||||
|
||||
vpx_codec_stream_info_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
PodZero(&si);
|
||||
si.sz = sizeof(si);
|
||||
if (mReader->GetVideoCodec() == NESTEGG_CODEC_VP8) {
|
||||
vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), data, length, &si);
|
||||
|
@ -30,7 +30,7 @@ SoftwareWebMVideoDecoder::SoftwareWebMVideoDecoder(WebMReader* aReader)
|
||||
mReader(aReader)
|
||||
{
|
||||
MOZ_COUNT_CTOR(SoftwareWebMVideoDecoder);
|
||||
memset(&mVPX, 0, sizeof(vpx_codec_ctx_t));
|
||||
PodZero(&mVPX);
|
||||
}
|
||||
|
||||
SoftwareWebMVideoDecoder::~SoftwareWebMVideoDecoder()
|
||||
@ -128,7 +128,7 @@ SoftwareWebMVideoDecoder::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
}
|
||||
|
||||
vpx_codec_stream_info_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
PodZero(&si);
|
||||
si.sz = sizeof(si);
|
||||
if (mReader->GetVideoCodec() == NESTEGG_CODEC_VP8) {
|
||||
vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), data, length, &si);
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "SoftwareWebMVideoDecoder.h"
|
||||
#include "WebMReader.h"
|
||||
#include "WebMBufferedParser.h"
|
||||
#include "VorbisUtils.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "Layers.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
@ -22,8 +21,6 @@
|
||||
#include "vpx/vp8dx.h"
|
||||
#include "vpx/vpx_decoder.h"
|
||||
|
||||
#include "OggReader.h"
|
||||
|
||||
// IntelWebMVideoDecoder uses the WMF backend, which is Windows Vista+ only.
|
||||
#if defined(MOZ_PDM_VPX)
|
||||
#include "IntelWebMVideoDecoder.h"
|
||||
@ -143,20 +140,6 @@ static void webm_log(nestegg * context,
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
ogg_packet InitOggPacket(const unsigned char* aData, size_t aLength,
|
||||
bool aBOS, bool aEOS,
|
||||
int64_t aGranulepos, int64_t aPacketNo)
|
||||
{
|
||||
ogg_packet packet;
|
||||
packet.packet = const_cast<unsigned char*>(aData);
|
||||
packet.bytes = aLength;
|
||||
packet.b_o_s = aBOS;
|
||||
packet.e_o_s = aEOS;
|
||||
packet.granulepos = aGranulepos;
|
||||
packet.packetno = aPacketNo;
|
||||
return packet;
|
||||
}
|
||||
|
||||
#if defined(MOZ_PDM_VPX)
|
||||
static bool sIsIntelDecoderEnabled = false;
|
||||
#endif
|
||||
@ -164,32 +147,22 @@ static bool sIsIntelDecoderEnabled = false;
|
||||
WebMReader::WebMReader(AbstractMediaDecoder* aDecoder, MediaTaskQueue* aBorrowedTaskQueue)
|
||||
: MediaDecoderReader(aDecoder, aBorrowedTaskQueue)
|
||||
, mContext(nullptr)
|
||||
, mPacketCount(0)
|
||||
, mOpusDecoder(nullptr)
|
||||
, mSkip(0)
|
||||
, mSeekPreroll(0)
|
||||
, mVideoTrack(0)
|
||||
, mAudioTrack(0)
|
||||
, mAudioStartUsec(-1)
|
||||
, mAudioFrames(0)
|
||||
, mSeekPreroll(0)
|
||||
, mLastVideoFrameTime(0)
|
||||
, mAudioCodec(-1)
|
||||
, mVideoCodec(-1)
|
||||
, mLayersBackendType(layers::LayersBackend::LAYERS_NONE)
|
||||
, mHasVideo(false)
|
||||
, mHasAudio(false)
|
||||
, mPaddingDiscarded(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(WebMReader);
|
||||
if (!gNesteggLog) {
|
||||
gNesteggLog = PR_NewLogModule("Nestegg");
|
||||
}
|
||||
// Zero these member vars to avoid crashes in VP8 destroy and Vorbis clear
|
||||
// functions when destructor is called before |Init|.
|
||||
memset(&mVorbisBlock, 0, sizeof(vorbis_block));
|
||||
memset(&mVorbisDsp, 0, sizeof(vorbis_dsp_state));
|
||||
memset(&mVorbisInfo, 0, sizeof(vorbis_info));
|
||||
memset(&mVorbisComment, 0, sizeof(vorbis_comment));
|
||||
|
||||
#if defined(MOZ_PDM_VPX)
|
||||
sIsIntelDecoderEnabled = Preferences::GetBool("media.webm.intel_decoder.enabled", false);
|
||||
@ -201,14 +174,7 @@ WebMReader::~WebMReader()
|
||||
Cleanup();
|
||||
mVideoPackets.Reset();
|
||||
mAudioPackets.Reset();
|
||||
vorbis_block_clear(&mVorbisBlock);
|
||||
vorbis_dsp_clear(&mVorbisDsp);
|
||||
vorbis_info_clear(&mVorbisInfo);
|
||||
vorbis_comment_clear(&mVorbisComment);
|
||||
if (mOpusDecoder) {
|
||||
opus_multistream_decoder_destroy(mOpusDecoder);
|
||||
mOpusDecoder = nullptr;
|
||||
}
|
||||
MOZ_ASSERT(!mAudioDecoder);
|
||||
MOZ_ASSERT(!mVideoDecoder);
|
||||
MOZ_COUNT_DTOR(WebMReader);
|
||||
}
|
||||
@ -222,6 +188,10 @@ WebMReader::Shutdown()
|
||||
mVideoTaskQueue->AwaitShutdownAndIdle();
|
||||
}
|
||||
#endif
|
||||
if (mAudioDecoder) {
|
||||
mAudioDecoder->Shutdown();
|
||||
mAudioDecoder = nullptr;
|
||||
}
|
||||
|
||||
if (mVideoDecoder) {
|
||||
mVideoDecoder->Shutdown();
|
||||
@ -233,11 +203,6 @@ WebMReader::Shutdown()
|
||||
|
||||
nsresult WebMReader::Init(MediaDecoderReader* aCloneDonor)
|
||||
{
|
||||
vorbis_info_init(&mVorbisInfo);
|
||||
vorbis_comment_init(&mVorbisComment);
|
||||
memset(&mVorbisDsp, 0, sizeof(vorbis_dsp_state));
|
||||
memset(&mVorbisBlock, 0, sizeof(vorbis_block));
|
||||
|
||||
#if defined(MOZ_PDM_VPX)
|
||||
if (sIsIntelDecoderEnabled) {
|
||||
PlatformDecoderModule::Init();
|
||||
@ -293,18 +258,8 @@ nsresult WebMReader::ResetDecode()
|
||||
res = NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
|
||||
// Ignore failed results from vorbis_synthesis_restart. They
|
||||
// aren't fatal and it fails when ResetDecode is called at a
|
||||
// time when no vorbis data has been read.
|
||||
vorbis_synthesis_restart(&mVorbisDsp);
|
||||
} else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
|
||||
if (mOpusDecoder) {
|
||||
// Reset the decoder.
|
||||
opus_multistream_decoder_ctl(mOpusDecoder, OPUS_RESET_STATE);
|
||||
mSkip = mOpusParser->mPreSkip;
|
||||
mPaddingDiscarded = false;
|
||||
}
|
||||
if (mAudioDecoder) {
|
||||
mAudioDecoder->ResetDecode();
|
||||
}
|
||||
|
||||
mVideoPackets.Reset();
|
||||
@ -461,84 +416,42 @@ nsresult WebMReader::ReadMetadata(MediaInfo* aInfo,
|
||||
mHasAudio = true;
|
||||
mAudioCodec = nestegg_track_codec_id(mContext, track);
|
||||
mCodecDelay = params.codec_delay / NS_PER_USEC;
|
||||
mSeekPreroll = params.seek_preroll;
|
||||
|
||||
if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
|
||||
// Get the Vorbis header data
|
||||
unsigned int nheaders = 0;
|
||||
r = nestegg_track_codec_data_count(mContext, track, &nheaders);
|
||||
if (r == -1 || nheaders != 3) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
for (uint32_t header = 0; header < nheaders; ++header) {
|
||||
unsigned char* data = 0;
|
||||
size_t length = 0;
|
||||
|
||||
r = nestegg_track_codec_data(mContext, track, header, &data, &length);
|
||||
if (r == -1) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
ogg_packet opacket = InitOggPacket(data, length, header == 0, false,
|
||||
0, mPacketCount++);
|
||||
|
||||
r = vorbis_synthesis_headerin(&mVorbisInfo,
|
||||
&mVorbisComment,
|
||||
&opacket);
|
||||
if (r != 0) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
r = vorbis_synthesis_init(&mVorbisDsp, &mVorbisInfo);
|
||||
if (r != 0) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock);
|
||||
if (r != 0) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mInfo.mAudio.mRate = mVorbisDsp.vi->rate;
|
||||
mInfo.mAudio.mChannels = mVorbisDsp.vi->channels;
|
||||
mAudioDecoder = new VorbisDecoder(this);
|
||||
} else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
|
||||
mAudioDecoder = new OpusDecoder(this);
|
||||
} else {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (NS_FAILED(mAudioDecoder->Init())) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
unsigned int nheaders = 0;
|
||||
r = nestegg_track_codec_data_count(mContext, track, &nheaders);
|
||||
if (r == -1) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
for (uint32_t header = 0; header < nheaders; ++header) {
|
||||
unsigned char* data = 0;
|
||||
size_t length = 0;
|
||||
r = nestegg_track_codec_data(mContext, track, 0, &data, &length);
|
||||
r = nestegg_track_codec_data(mContext, track, header, &data, &length);
|
||||
if (r == -1) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mOpusParser = new OpusParser;
|
||||
if (!mOpusParser->DecodeHeader(data, length)) {
|
||||
if (NS_FAILED(mAudioDecoder->DecodeHeader(data, length))) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!InitOpusDecoder()) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (int64_t(mCodecDelay) != FramesToUsecs(mOpusParser->mPreSkip,
|
||||
mOpusParser->mRate).value()) {
|
||||
LOG(LogLevel::Warning,
|
||||
("Invalid Opus header: CodecDelay and pre-skip do not match!"));
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mInfo.mAudio.mRate = mOpusParser->mRate;
|
||||
|
||||
mInfo.mAudio.mChannels = mOpusParser->mChannels;
|
||||
mSeekPreroll = params.seek_preroll;
|
||||
} else {
|
||||
}
|
||||
if (NS_FAILED(mAudioDecoder->FinishInit(mInfo.mAudio))) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -558,24 +471,6 @@ WebMReader::IsMediaSeekable()
|
||||
return mContext && nestegg_has_cues(mContext);
|
||||
}
|
||||
|
||||
bool WebMReader::InitOpusDecoder()
|
||||
{
|
||||
int r;
|
||||
|
||||
NS_ASSERTION(mOpusDecoder == nullptr, "leaking OpusDecoder");
|
||||
|
||||
mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate,
|
||||
mOpusParser->mChannels,
|
||||
mOpusParser->mStreams,
|
||||
mOpusParser->mCoupledStreams,
|
||||
mOpusParser->mMappingTable,
|
||||
&r);
|
||||
mSkip = mOpusParser->mPreSkip;
|
||||
mPaddingDiscarded = false;
|
||||
|
||||
return r == OPUS_OK;
|
||||
}
|
||||
|
||||
bool WebMReader::DecodeAudioPacket(NesteggPacketHolder* aHolder)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
@ -617,7 +512,6 @@ bool WebMReader::DecodeAudioPacket(NesteggPacketHolder* aHolder)
|
||||
usecs.isValid() ? usecs.value() : -1,
|
||||
gap_frames));
|
||||
#endif
|
||||
mPacketCount++;
|
||||
mAudioStartUsec = tstamp;
|
||||
mAudioFrames = 0;
|
||||
}
|
||||
@ -630,227 +524,16 @@ bool WebMReader::DecodeAudioPacket(NesteggPacketHolder* aHolder)
|
||||
if (r == -1) {
|
||||
return false;
|
||||
}
|
||||
if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
|
||||
if (!DecodeVorbis(data, length, aHolder->Offset(), tstamp, &total_frames)) {
|
||||
return false;
|
||||
}
|
||||
} else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
|
||||
if (!DecodeOpus(data, length, aHolder->Offset(), tstamp, aHolder->Packet())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
int64_t discardPadding = 0;
|
||||
(void) nestegg_packet_discard_padding(aHolder->Packet(), &discardPadding);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebMReader::DecodeVorbis(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
int32_t* aTotalFrames)
|
||||
{
|
||||
ogg_packet opacket = InitOggPacket(aData, aLength, false, false, -1,
|
||||
mPacketCount++);
|
||||
|
||||
if (vorbis_synthesis(&mVorbisBlock, &opacket) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vorbis_synthesis_blockin(&mVorbisDsp,
|
||||
&mVorbisBlock) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VorbisPCMValue** pcm = 0;
|
||||
int32_t frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm);
|
||||
// If the first packet of audio in the media produces no data, we
|
||||
// still need to produce an AudioData for it so that the correct media
|
||||
// start time is calculated. Otherwise we'd end up with a media start
|
||||
// time derived from the timecode of the first packet that produced
|
||||
// data.
|
||||
if (frames == 0 && mAudioFrames == 0) {
|
||||
AudioQueue().Push(new AudioData(aOffset, aTstampUsecs, 0, 0, nullptr,
|
||||
mInfo.mAudio.mChannels,
|
||||
mInfo.mAudio.mRate));
|
||||
}
|
||||
while (frames > 0) {
|
||||
uint32_t channels = mInfo.mAudio.mChannels;
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames*channels]);
|
||||
for (uint32_t j = 0; j < channels; ++j) {
|
||||
VorbisPCMValue* channel = pcm[j];
|
||||
for (uint32_t i = 0; i < uint32_t(frames); ++i) {
|
||||
buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]);
|
||||
}
|
||||
}
|
||||
|
||||
CheckedInt64 duration = FramesToUsecs(frames, mInfo.mAudio.mRate);
|
||||
if (!duration.isValid()) {
|
||||
NS_WARNING("Int overflow converting WebM audio duration");
|
||||
return false;
|
||||
}
|
||||
CheckedInt64 total_duration = FramesToUsecs(*aTotalFrames,
|
||||
mInfo.mAudio.mRate);
|
||||
if (!total_duration.isValid()) {
|
||||
NS_WARNING("Int overflow converting WebM audio total_duration");
|
||||
return false;
|
||||
}
|
||||
|
||||
CheckedInt64 time = total_duration + aTstampUsecs;
|
||||
if (!time.isValid()) {
|
||||
NS_WARNING("Int overflow adding total_duration and aTstampUsecs");
|
||||
return false;
|
||||
};
|
||||
|
||||
*aTotalFrames += frames;
|
||||
AudioQueue().Push(new AudioData(aOffset,
|
||||
time.value(),
|
||||
duration.value(),
|
||||
frames,
|
||||
buffer.forget(),
|
||||
mInfo.mAudio.mChannels,
|
||||
mInfo.mAudio.mRate));
|
||||
mAudioFrames += frames;
|
||||
if (vorbis_synthesis_read(&mVorbisDsp, frames) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebMReader::DecodeOpus(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
nestegg_packet* aPacket)
|
||||
{
|
||||
uint32_t channels = mOpusParser->mChannels;
|
||||
// No channel mapping for more than 8 channels.
|
||||
if (channels > 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mPaddingDiscarded) {
|
||||
// Discard padding should be used only on the final packet, so
|
||||
// decoding after a padding discard is invalid.
|
||||
LOG(LogLevel::Debug, ("Opus error, discard padding on interstitial packet"));
|
||||
mHitAudioDecodeError = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Maximum value is 63*2880, so there's no chance of overflow.
|
||||
int32_t frames_number = opus_packet_get_nb_frames(aData, aLength);
|
||||
if (frames_number <= 0) {
|
||||
return false; // Invalid packet header.
|
||||
}
|
||||
|
||||
int32_t samples =
|
||||
opus_packet_get_samples_per_frame(aData, opus_int32(mInfo.mAudio.mRate));
|
||||
|
||||
// A valid Opus packet must be between 2.5 and 120 ms long (48kHz).
|
||||
int32_t frames = frames_number*samples;
|
||||
if (frames < 120 || frames > 5760)
|
||||
return false;
|
||||
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]);
|
||||
|
||||
// Decode to the appropriate sample type.
|
||||
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
|
||||
int ret = opus_multistream_decode_float(mOpusDecoder,
|
||||
aData, aLength,
|
||||
buffer, frames, false);
|
||||
#else
|
||||
int ret = opus_multistream_decode(mOpusDecoder,
|
||||
aData, aLength,
|
||||
buffer, frames, false);
|
||||
#endif
|
||||
if (ret < 0)
|
||||
return false;
|
||||
NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
|
||||
CheckedInt64 startTime = aTstampUsecs;
|
||||
|
||||
// Trim the initial frames while the decoder is settling.
|
||||
if (mSkip > 0) {
|
||||
int32_t skipFrames = std::min<int32_t>(mSkip, frames);
|
||||
int32_t keepFrames = frames - skipFrames;
|
||||
LOG(LogLevel::Debug, ("Opus decoder skipping %d of %d frames",
|
||||
skipFrames, frames));
|
||||
PodMove(buffer.get(),
|
||||
buffer.get() + skipFrames * channels,
|
||||
keepFrames * channels);
|
||||
startTime = startTime + FramesToUsecs(skipFrames, mInfo.mAudio.mRate);
|
||||
frames = keepFrames;
|
||||
mSkip -= skipFrames;
|
||||
}
|
||||
|
||||
int64_t discardPadding = 0;
|
||||
(void) nestegg_packet_discard_padding(aPacket, &discardPadding);
|
||||
if (discardPadding < 0) {
|
||||
// Negative discard padding is invalid.
|
||||
LOG(LogLevel::Debug, ("Opus error, negative discard padding"));
|
||||
mHitAudioDecodeError = true;
|
||||
}
|
||||
if (discardPadding > 0) {
|
||||
CheckedInt64 discardFrames = UsecsToFrames(discardPadding / NS_PER_USEC,
|
||||
mInfo.mAudio.mRate);
|
||||
if (!discardFrames.isValid()) {
|
||||
NS_WARNING("Int overflow in DiscardPadding");
|
||||
return false;
|
||||
}
|
||||
if (discardFrames.value() > frames) {
|
||||
// Discarding more than the entire packet is invalid.
|
||||
LOG(LogLevel::Debug, ("Opus error, discard padding larger than packet"));
|
||||
if (!mAudioDecoder->Decode(data, length, aHolder->Offset(), tstamp, discardPadding, &total_frames)) {
|
||||
mHitAudioDecodeError = true;
|
||||
return false;
|
||||
}
|
||||
LOG(LogLevel::Debug, ("Opus decoder discarding %d of %d frames",
|
||||
int32_t(discardFrames.value()), frames));
|
||||
// Padding discard is only supposed to happen on the final packet.
|
||||
// Record the discard so we can return an error if another packet is
|
||||
// decoded.
|
||||
mPaddingDiscarded = true;
|
||||
int32_t keepFrames = frames - discardFrames.value();
|
||||
frames = keepFrames;
|
||||
}
|
||||
|
||||
// Apply the header gain if one was specified.
|
||||
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
|
||||
if (mOpusParser->mGain != 1.0f) {
|
||||
float gain = mOpusParser->mGain;
|
||||
int samples = frames * channels;
|
||||
for (int i = 0; i < samples; i++) {
|
||||
buffer[i] *= gain;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (mOpusParser->mGain_Q16 != 65536) {
|
||||
int64_t gain_Q16 = mOpusParser->mGain_Q16;
|
||||
int samples = frames * channels;
|
||||
for (int i = 0; i < samples; i++) {
|
||||
int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16);
|
||||
buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CheckedInt64 duration = FramesToUsecs(frames, mInfo.mAudio.mRate);
|
||||
if (!duration.isValid()) {
|
||||
NS_WARNING("Int overflow converting WebM audio duration");
|
||||
return false;
|
||||
}
|
||||
CheckedInt64 time = startTime - mCodecDelay;
|
||||
if (!time.isValid()) {
|
||||
NS_WARNING("Int overflow shifting tstamp by codec delay");
|
||||
return false;
|
||||
};
|
||||
AudioQueue().Push(new AudioData(aOffset,
|
||||
time.value(),
|
||||
duration.value(),
|
||||
frames,
|
||||
buffer.forget(),
|
||||
mInfo.mAudio.mChannels,
|
||||
mInfo.mAudio.mRate));
|
||||
|
||||
mAudioFrames += frames;
|
||||
mAudioFrames += total_frames;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -17,14 +17,6 @@
|
||||
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
|
||||
#ifdef MOZ_TREMOR
|
||||
#include "tremor/ivorbiscodec.h"
|
||||
#else
|
||||
#include "vorbis/codec.h"
|
||||
#endif
|
||||
|
||||
#include "OpusParser.h"
|
||||
|
||||
namespace mozilla {
|
||||
static const unsigned NS_PER_USEC = 1000;
|
||||
static const double NS_PER_S = 1e9;
|
||||
@ -138,6 +130,21 @@ public:
|
||||
virtual ~WebMVideoDecoder() {}
|
||||
};
|
||||
|
||||
// Class to handle various audio decode paths
|
||||
class WebMAudioDecoder
|
||||
{
|
||||
public:
|
||||
virtual nsresult Init() = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
virtual nsresult ResetDecode() = 0;
|
||||
virtual nsresult DecodeHeader(const unsigned char* aData, size_t aLength) = 0;
|
||||
virtual nsresult FinishInit(AudioInfo& aInfo) = 0;
|
||||
virtual bool Decode(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
int64_t aDiscardPadding, int32_t* aTotalFrames) = 0;
|
||||
virtual ~WebMAudioDecoder() {}
|
||||
};
|
||||
|
||||
class WebMReader : public MediaDecoderReader
|
||||
{
|
||||
public:
|
||||
@ -201,10 +208,9 @@ public:
|
||||
void SetLastVideoFrameTime(int64_t aFrameTime);
|
||||
layers::LayersBackend GetLayersBackendType() { return mLayersBackendType; }
|
||||
FlushableMediaTaskQueue* GetVideoTaskQueue() { return mVideoTaskQueue; }
|
||||
uint64_t GetCodecDelay() { return mCodecDelay; }
|
||||
|
||||
protected:
|
||||
// Setup opus decoder
|
||||
bool InitOpusDecoder();
|
||||
|
||||
// Decode a nestegg packet of audio data. Push the audio data on the
|
||||
// audio queue. Returns true when there's more audio to decode,
|
||||
@ -213,12 +219,6 @@ protected:
|
||||
// must be held during this call. The caller is responsible for freeing
|
||||
// aPacket.
|
||||
bool DecodeAudioPacket(NesteggPacketHolder* aHolder);
|
||||
bool DecodeVorbis(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
int32_t* aTotalFrames);
|
||||
bool DecodeOpus(const unsigned char* aData, size_t aLength,
|
||||
int64_t aOffset, uint64_t aTstampUsecs,
|
||||
nestegg_packet* aPacket);
|
||||
|
||||
// Release context and set to null. Called when an error occurs during
|
||||
// reading metadata or destruction of the reader itself.
|
||||
@ -246,22 +246,9 @@ private:
|
||||
// or decoder thread only.
|
||||
nestegg* mContext;
|
||||
|
||||
// The video decoder
|
||||
nsAutoPtr<WebMAudioDecoder> mAudioDecoder;
|
||||
nsAutoPtr<WebMVideoDecoder> mVideoDecoder;
|
||||
|
||||
// Vorbis decoder state
|
||||
vorbis_info mVorbisInfo;
|
||||
vorbis_comment mVorbisComment;
|
||||
vorbis_dsp_state mVorbisDsp;
|
||||
vorbis_block mVorbisBlock;
|
||||
int64_t mPacketCount;
|
||||
|
||||
// Opus decoder state
|
||||
nsAutoPtr<OpusParser> mOpusParser;
|
||||
OpusMSDecoder *mOpusDecoder;
|
||||
uint16_t mSkip; // Samples left to trim before playback.
|
||||
uint64_t mSeekPreroll; // Nanoseconds to discard after seeking.
|
||||
|
||||
// Queue of video and audio packets that have been read but not decoded. These
|
||||
// must only be accessed from the decode thread.
|
||||
WebMPacketQueue mVideoPackets;
|
||||
@ -280,6 +267,9 @@ private:
|
||||
// Number of microseconds that must be discarded from the start of the Stream.
|
||||
uint64_t mCodecDelay;
|
||||
|
||||
// Nanoseconds to discard after seeking.
|
||||
uint64_t mSeekPreroll;
|
||||
|
||||
// Calculate the frame duration from the last decodeable frame using the
|
||||
// previous frame's timestamp. In NS.
|
||||
int64_t mLastVideoFrameTime;
|
||||
@ -309,10 +299,6 @@ private:
|
||||
bool mHasVideo;
|
||||
bool mHasAudio;
|
||||
|
||||
// Opus padding should only be discarded on the final packet. Once this
|
||||
// is set to true, if the reader attempts to decode any further packets it
|
||||
// will raise an error so we can indicate that the file is invalid.
|
||||
bool mPaddingDiscarded;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -13,6 +13,7 @@ EXPORTS += [
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AudioDecoder.cpp',
|
||||
'SoftwareWebMVideoDecoder.cpp',
|
||||
'WebMBufferedParser.cpp',
|
||||
'WebMDecoder.cpp',
|
||||
|
@ -26,7 +26,7 @@ enum nsPluginTagType {
|
||||
|
||||
// Do not make this interface scriptable, because the virtual functions in C++
|
||||
// blocks will make script call the wrong functions.
|
||||
[uuid(c4e26e5d-7a9b-4900-b567-e128c4be6e31)]
|
||||
[uuid(8ff5f46e-96fa-4905-a75c-35aac30bdcee)]
|
||||
interface nsIPluginInstanceOwner : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -69,15 +69,6 @@ interface nsIPluginInstanceOwner : nsISupports
|
||||
void *aHeadersData, uint32_t aHeadersDataLen) = 0;
|
||||
%}
|
||||
|
||||
/**
|
||||
* Show a status message in the host environment.
|
||||
*/
|
||||
void showStatus(in string aStatusMsg);
|
||||
|
||||
%{C++
|
||||
NS_IMETHOD ShowStatus(const char16_t *aStatusMsg) = 0;
|
||||
%}
|
||||
|
||||
/**
|
||||
* Get the associated document.
|
||||
*/
|
||||
@ -104,10 +95,9 @@ interface nsIPluginInstanceOwner : nsISupports
|
||||
void getNetscapeWindow(in voidPtr aValue);
|
||||
|
||||
/**
|
||||
* Show native context menu
|
||||
* Convert between plugin, window, and screen coordinate spaces.
|
||||
*/
|
||||
%{C++
|
||||
virtual NPError ShowNativeContextMenu(NPMenu* menu, void* event) = 0;
|
||||
virtual NPBool ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
|
||||
double *destX, double *destY, NPCoordinateSpace destSpace) = 0;
|
||||
%}
|
||||
|
@ -360,10 +360,11 @@ UnregisterGCCallbacks()
|
||||
JS_RemoveExtraGCRootsTracer(jsRuntime, TraceJSObjWrappers, nullptr);
|
||||
|
||||
// Remove delayed destruction callback.
|
||||
sCallbackRuntime->UnregisterGCCallback(DelayedReleaseGCCallback);
|
||||
|
||||
// Unset runtime pointer to indicate callbacks are no longer registered.
|
||||
sCallbackRuntime = nullptr;
|
||||
if (sCallbackRuntime) {
|
||||
sCallbackRuntime->UnregisterGCCallback(DelayedReleaseGCCallback);
|
||||
// Unset runtime pointer to indicate callbacks are no longer registered.
|
||||
sCallbackRuntime = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -1066,7 +1066,7 @@ _destroystream(NPP npp, NPStream *pstream, NPError reason)
|
||||
// the reference until it is to be deleted here. Deleting the wrapper will
|
||||
// release the wrapped nsIOutputStream.
|
||||
//
|
||||
// The NPStream the plugin references should always be a sub-object of it's own
|
||||
// The NPStream the plugin references should always be a sub-object of its own
|
||||
// 'ndata', which is our nsNPAPIStramWrapper. See bug 548441.
|
||||
NS_ASSERTION((char*)streamWrapper <= (char*)pstream &&
|
||||
((char*)pstream) + sizeof(*pstream)
|
||||
@ -1084,23 +1084,7 @@ _destroystream(NPP npp, NPStream *pstream, NPError reason)
|
||||
void
|
||||
_status(NPP npp, const char *message)
|
||||
{
|
||||
if (!NS_IsMainThread()) {
|
||||
NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_status called from the wrong thread\n"));
|
||||
return;
|
||||
}
|
||||
NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_Status: npp=%p, message=%s\n",
|
||||
(void*)npp, message));
|
||||
|
||||
if (!npp || !npp->ndata) {
|
||||
NS_WARNING("_status: npp or npp->ndata == 0");
|
||||
return;
|
||||
}
|
||||
|
||||
nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata;
|
||||
|
||||
PluginDestructionGuard guard(inst);
|
||||
|
||||
inst->ShowStatus(message);
|
||||
// NPN_Status is no longer supported.
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1605,15 +1605,6 @@ nsNPAPIPluginInstance::SetOwner(nsPluginInstanceOwner *aOwner)
|
||||
mOwner = aOwner;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsNPAPIPluginInstance::ShowStatus(const char* message)
|
||||
{
|
||||
if (mOwner)
|
||||
return mOwner->ShowStatus(message);
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsNPAPIPluginInstance::AsyncSetWindow(NPWindow& window)
|
||||
{
|
||||
|
@ -116,7 +116,6 @@ public:
|
||||
nsresult GetJSContext(JSContext* *outContext);
|
||||
nsPluginInstanceOwner* GetOwner();
|
||||
void SetOwner(nsPluginInstanceOwner *aOwner);
|
||||
nsresult ShowStatus(const char* message);
|
||||
|
||||
nsNPAPIPlugin* GetPlugin();
|
||||
|
||||
|
@ -547,43 +547,6 @@ NS_IMETHODIMP nsPluginInstanceOwner::GetURL(const char *aURL,
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsPluginInstanceOwner::ShowStatus(const char *aStatusMsg)
|
||||
{
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
|
||||
rv = this->ShowStatus(NS_ConvertUTF8toUTF16(aStatusMsg).get());
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsPluginInstanceOwner::ShowStatus(const char16_t *aStatusMsg)
|
||||
{
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
|
||||
if (!mPluginFrame) {
|
||||
return rv;
|
||||
}
|
||||
nsCOMPtr<nsIDocShellTreeItem> docShellItem = mPluginFrame->PresContext()->GetDocShell();
|
||||
if (NS_FAILED(rv) || !docShellItem) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
|
||||
rv = docShellItem->GetTreeOwner(getter_AddRefs(treeOwner));
|
||||
if (NS_FAILED(rv) || !treeOwner) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(treeOwner, &rv));
|
||||
if (NS_FAILED(rv) || !browserChrome) {
|
||||
return rv;
|
||||
}
|
||||
rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_SCRIPT,
|
||||
aStatusMsg);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsPluginInstanceOwner::GetDocument(nsIDocument* *aDocument)
|
||||
{
|
||||
nsCOMPtr<nsIContent> content = do_QueryReferent(mContent);
|
||||
@ -747,13 +710,6 @@ NS_IMETHODIMP nsPluginInstanceOwner::SetEventModel(int32_t eventModel)
|
||||
#endif
|
||||
}
|
||||
|
||||
// This is no longer used, just leaving it here so we don't have to change
|
||||
// the nsIPluginInstanceOwner interface.
|
||||
NPError nsPluginInstanceOwner::ShowNativeContextMenu(NPMenu* menu, void* event)
|
||||
{
|
||||
return NPERR_GENERIC_ERROR;
|
||||
}
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
NPBool nsPluginInstanceOwner::ConvertPointPuppet(PuppetWidget *widget,
|
||||
nsPluginFrame* pluginFrame,
|
||||
|
@ -63,11 +63,6 @@ public:
|
||||
nsIInputStream *aPostStream,
|
||||
void *aHeadersData, uint32_t aHeadersDataLen) override;
|
||||
|
||||
NS_IMETHOD ShowStatus(const char16_t *aStatusMsg) override;
|
||||
|
||||
// This can go away, just leaving it here to avoid changing the interface.
|
||||
NPError ShowNativeContextMenu(NPMenu* menu, void* event) override;
|
||||
|
||||
NPBool ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
|
||||
double *destX, double *destY, NPCoordinateSpace destSpace) override;
|
||||
|
||||
|
@ -1326,9 +1326,7 @@ void
|
||||
_status(NPP aNPP,
|
||||
const char* aMessage)
|
||||
{
|
||||
PLUGIN_LOG_DEBUG_FUNCTION;
|
||||
ENSURE_PLUGIN_THREAD_VOID();
|
||||
NS_WARNING("Not yet implemented!");
|
||||
// NPN_Status is no longer supported.
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1,3 +1,19 @@
|
||||
onfetch = function(e) {
|
||||
if (e.request.url.indexOf("Referer") >= 0) {
|
||||
// Silently rewrite the referrer so the referrer test passes since the
|
||||
// document/worker isn't aware of this service worker.
|
||||
var url = e.request.url.substring(0, e.request.url.indexOf('?'));
|
||||
url += '?headers=' + ({ 'Referer': self.location.href }).toSource();
|
||||
|
||||
e.respondWith(fetch(url, {
|
||||
method: e.request.method,
|
||||
headers: e.request.headers,
|
||||
body: e.request.body,
|
||||
mode: e.request.mode,
|
||||
credentials: e.request.credentials,
|
||||
cache: e.request.cache,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
e.respondWith(fetch(e.request));
|
||||
};
|
||||
|
@ -1250,6 +1250,25 @@ function testRedirects() {
|
||||
return Promise.all(fetches);
|
||||
}
|
||||
|
||||
function testReferrer() {
|
||||
var referrer;
|
||||
if (self && self.location) {
|
||||
referrer = self.location.href;
|
||||
} else {
|
||||
referrer = document.documentURI;
|
||||
}
|
||||
|
||||
var dict = {
|
||||
'Referer': referrer
|
||||
};
|
||||
return fetch(corsServerPath + "headers=" + dict.toSource()).then(function(res) {
|
||||
is(res.status, 200, "expected correct referrer header to be sent");
|
||||
dump(res.statusText);
|
||||
}, function(e) {
|
||||
ok(false, "expected correct referrer header to be sent");
|
||||
});
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
testNoCorsCtor();
|
||||
|
||||
@ -1260,5 +1279,6 @@ function runTest() {
|
||||
.then(testSameOriginCredentials)
|
||||
.then(testCrossOriginCredentials)
|
||||
.then(testRedirects)
|
||||
.then(testReferrer)
|
||||
// Put more promise based tests here.
|
||||
}
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "mozilla/LoadContext.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/dom/CacheBinding.h"
|
||||
#include "mozilla/dom/cache/CacheTypes.h"
|
||||
#include "mozilla/dom/cache/Cache.h"
|
||||
#include "mozilla/dom/cache/CacheStorage.h"
|
||||
#include "mozilla/dom/Exceptions.h"
|
||||
@ -190,6 +191,14 @@ ChannelFromScriptURL(nsIPrincipal* principal,
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
|
||||
rv = nsContentUtils::SetFetchReferrerURIWithPolicy(principal, parentDoc,
|
||||
httpChannel);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
channel.forget(aChannel);
|
||||
return rv;
|
||||
}
|
||||
@ -422,6 +431,7 @@ private:
|
||||
nsCOMPtr<nsIInputStreamPump> mPump;
|
||||
nsCOMPtr<nsIURI> mBaseURI;
|
||||
ChannelInfo mChannelInfo;
|
||||
UniquePtr<PrincipalInfo> mPrincipalInfo;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver)
|
||||
@ -592,6 +602,27 @@ private:
|
||||
// saved in the cache.
|
||||
ir->InitChannelInfo(channel);
|
||||
|
||||
// Save the principal of the channel since its URI encodes the script URI
|
||||
// rather than the ServiceWorkerRegistrationInfo URI.
|
||||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||||
NS_ASSERTION(ssm, "Should never be null!");
|
||||
|
||||
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
||||
nsresult rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
channel->Cancel(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
|
||||
rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
channel->Cancel(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
ir->SetPrincipalInfo(Move(principalInfo));
|
||||
|
||||
nsRefPtr<Response> response = new Response(mCacheCreator->Global(), ir);
|
||||
|
||||
RequestOrUSVString request;
|
||||
@ -1030,7 +1061,8 @@ private:
|
||||
void
|
||||
DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
|
||||
uint32_t aStringLen,
|
||||
const ChannelInfo& aChannelInfo)
|
||||
const ChannelInfo& aChannelInfo,
|
||||
UniquePtr<PrincipalInfo> aPrincipalInfo)
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
MOZ_ASSERT(aIndex < mLoadInfos.Length());
|
||||
@ -1054,14 +1086,19 @@ private:
|
||||
mWorkerPrivate->SetBaseURI(finalURI);
|
||||
}
|
||||
|
||||
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
|
||||
DebugOnly<nsIPrincipal*> principal = mWorkerPrivate->GetPrincipal();
|
||||
MOZ_ASSERT(principal);
|
||||
nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
|
||||
MOZ_ASSERT(loadGroup);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> responsePrincipal =
|
||||
PrincipalInfoToPrincipal(*aPrincipalInfo);
|
||||
DebugOnly<bool> equal = false;
|
||||
MOZ_ASSERT(responsePrincipal && NS_SUCCEEDED(responsePrincipal->Equals(principal, &equal)));
|
||||
MOZ_ASSERT(equal);
|
||||
|
||||
mWorkerPrivate->InitChannelInfo(aChannelInfo);
|
||||
// Needed to initialize the principal info. This is fine because
|
||||
// the cache principal cannot change, unlike the channel principal.
|
||||
mWorkerPrivate->SetPrincipal(principal, loadGroup);
|
||||
mWorkerPrivate->SetPrincipal(responsePrincipal, loadGroup);
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
@ -1413,10 +1450,16 @@ CacheScriptLoader::ResolvedCallback(JSContext* aCx,
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
response->GetBody(getter_AddRefs(inputStream));
|
||||
mChannelInfo = response->GetChannelInfo();
|
||||
const UniquePtr<mozilla::ipc::PrincipalInfo>& pInfo =
|
||||
response->GetPrincipalInfo();
|
||||
if (pInfo) {
|
||||
mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*pInfo);
|
||||
}
|
||||
|
||||
if (!inputStream) {
|
||||
mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
|
||||
mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mChannelInfo);
|
||||
mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mChannelInfo,
|
||||
Move(mPrincipalInfo));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1472,7 +1515,9 @@ CacheScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aCont
|
||||
|
||||
mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
|
||||
|
||||
mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mChannelInfo);
|
||||
MOZ_ASSERT(mPrincipalInfo);
|
||||
mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mChannelInfo,
|
||||
Move(mPrincipalInfo));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -3303,6 +3303,7 @@ class FetchEventRunnable : public WorkerRunnable
|
||||
RequestCredentials mRequestCredentials;
|
||||
nsContentPolicyType mContentPolicyType;
|
||||
nsCOMPtr<nsIInputStream> mUploadStream;
|
||||
nsCString mReferrer;
|
||||
public:
|
||||
FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
|
||||
nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||
@ -3319,6 +3320,7 @@ public:
|
||||
// send credentials to same-origin websites unless explicitly forbidden.
|
||||
, mRequestCredentials(RequestCredentials::Same_origin)
|
||||
, mContentPolicyType(nsIContentPolicy::TYPE_INVALID)
|
||||
, mReferrer(kFETCH_CLIENT_REFERRER_STR)
|
||||
{
|
||||
MOZ_ASSERT(aWorkerPrivate);
|
||||
}
|
||||
@ -3358,6 +3360,15 @@ public:
|
||||
|
||||
mContentPolicyType = loadInfo->InternalContentPolicyType();
|
||||
|
||||
nsCOMPtr<nsIURI> referrerURI;
|
||||
rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrerURI));
|
||||
// We can't bail on failure since certain non-http channels like JAR
|
||||
// channels are intercepted but don't have referrers.
|
||||
if (NS_SUCCEEDED(rv) && referrerURI) {
|
||||
rv = referrerURI->GetSpec(mReferrer);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
|
||||
if (httpChannel) {
|
||||
rv = httpChannel->GetRequestMethod(mMethod);
|
||||
@ -3487,6 +3498,7 @@ private:
|
||||
internalReq->SetCreatedByFetchEvent();
|
||||
|
||||
internalReq->SetBody(mUploadStream);
|
||||
internalReq->SetReferrer(NS_ConvertUTF8toUTF16(mReferrer));
|
||||
|
||||
request->SetContentPolicyType(mContentPolicyType);
|
||||
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include "mozilla/dom/CacheBinding.h"
|
||||
#include "mozilla/dom/cache/CacheStorage.h"
|
||||
#include "mozilla/dom/cache/Cache.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
||||
#include "nsIThreadRetargetableRequest.h"
|
||||
|
||||
#include "nsIPrincipal.h"
|
||||
@ -88,11 +90,17 @@ public:
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsILoadGroup> loadGroup;
|
||||
rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = NS_NewChannel(getter_AddRefs(mChannel),
|
||||
uri, aPrincipal,
|
||||
nsILoadInfo::SEC_NORMAL,
|
||||
nsIContentPolicy::TYPE_SCRIPT, // FIXME(nsm): TYPE_SERVICEWORKER
|
||||
aLoadGroup);
|
||||
loadGroup);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
@ -451,6 +459,28 @@ public:
|
||||
mChannelInfo.InitFromChannel(aChannel);
|
||||
}
|
||||
|
||||
nsresult
|
||||
SetPrincipalInfo(nsIChannel* aChannel)
|
||||
{
|
||||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||||
NS_ASSERTION(ssm, "Should never be null!");
|
||||
|
||||
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
||||
nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo(new mozilla::ipc::PrincipalInfo());
|
||||
rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mPrincipalInfo = Move(principalInfo);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~CompareManager()
|
||||
{
|
||||
@ -546,6 +576,9 @@ private:
|
||||
ir->SetBody(body);
|
||||
|
||||
ir->InitChannelInfo(mChannelInfo);
|
||||
if (mPrincipalInfo) {
|
||||
ir->SetPrincipalInfo(Move(mPrincipalInfo));
|
||||
}
|
||||
|
||||
nsRefPtr<Response> response = new Response(aCache->GetGlobalObject(), ir);
|
||||
|
||||
@ -579,6 +612,8 @@ private:
|
||||
|
||||
ChannelInfo mChannelInfo;
|
||||
|
||||
UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
|
||||
|
||||
nsCString mMaxScope;
|
||||
|
||||
enum {
|
||||
@ -607,6 +642,10 @@ CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||
#endif
|
||||
|
||||
mManager->InitChannelInfo(mChannel);
|
||||
nsresult rv = mManager->SetPrincipalInfo(mChannel);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ support-files =
|
||||
empty.html
|
||||
worker_performance_user_timing.js
|
||||
sharedworker_performance_user_timing.js
|
||||
referrer.sjs
|
||||
|
||||
[test_404.html]
|
||||
[test_atob.html]
|
||||
@ -209,3 +210,4 @@ skip-if = buildapp == 'b2g' || e10s
|
||||
[test_xhr_timeout.html]
|
||||
skip-if = (os == "win") || (os == "mac") || toolkit == 'android' || e10s #bug 798220
|
||||
[test_xhrAbort.html]
|
||||
[test_referrer.html]
|
||||
|
11
dom/workers/test/referrer.sjs
Normal file
11
dom/workers/test/referrer.sjs
Normal file
@ -0,0 +1,11 @@
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
if (request.queryString == "result") {
|
||||
response.write(getState("referer"));
|
||||
} else {
|
||||
response.setHeader("Content-Type", "text/javascript", false);
|
||||
response.write("onmessage = function() { postMessage(42); }");
|
||||
setState("referer", request.getHeader("referer"));
|
||||
}
|
||||
}
|
||||
|
35
dom/workers/test/test_referrer.html
Normal file
35
dom/workers/test/test_referrer.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test the referrer of workers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var worker = new Worker("referrer.sjs");
|
||||
worker.onmessage = function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'referrer.sjs?result', true);
|
||||
xhr.onload = function() {
|
||||
is(xhr.responseText, location.href, "The referrer has been sent.");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
xhr.send();
|
||||
}
|
||||
worker.postMessage(42);
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -1069,6 +1069,11 @@ nsEditorEventListener::Focus(nsIDOMEvent* aEvent)
|
||||
|
||||
// Spell check a textarea the first time that it is focused.
|
||||
SpellCheckIfNeeded();
|
||||
if (!mEditor) {
|
||||
// In e10s, this can cause us to flush notifications, which can destroy
|
||||
// the node we're about to focus.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMEventTarget> target;
|
||||
aEvent->GetTarget(getter_AddRefs(target));
|
||||
|
@ -192,6 +192,13 @@ struct RectCornerRadii {
|
||||
return radii[aCorner];
|
||||
}
|
||||
|
||||
bool operator==(const RectCornerRadii& aOther) const {
|
||||
for (size_t i = 0; i < RectCorner::Count; i++) {
|
||||
if (radii[i] != aOther.radii[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Scale(Float aXScale, Float aYScale) {
|
||||
for (int i = 0; i < RectCorner::Count; i++) {
|
||||
radii[i].Scale(aXScale, aYScale);
|
||||
|
@ -31,6 +31,19 @@ function nativeHorizontalWheelEventMsg() {
|
||||
throw "Native wheel events not supported on platform " + getPlatform();
|
||||
}
|
||||
|
||||
// Given a pixel scrolling delta, converts it to the platform's native units.
|
||||
function nativeScrollUnits(aElement, aDimen) {
|
||||
switch (getPlatform()) {
|
||||
case "linux": {
|
||||
// GTK deltas are treated as line height divided by 3 by gecko.
|
||||
var targetWindow = aElement.ownerDocument.defaultView;
|
||||
var lineHeight = targetWindow.getComputedStyle(aElement)["font-size"];
|
||||
return aDimen / (parseInt(lineHeight) * 3);
|
||||
}
|
||||
}
|
||||
return aDimen;
|
||||
}
|
||||
|
||||
function nativeMouseMoveEventMsg() {
|
||||
switch (getPlatform()) {
|
||||
case "windows": return 1; // MOUSEEVENTF_MOVE
|
||||
@ -40,6 +53,18 @@ function nativeMouseMoveEventMsg() {
|
||||
throw "Native wheel events not supported on platform " + getPlatform();
|
||||
}
|
||||
|
||||
// Convert (aX, aY), in CSS pixels relative to aElement's bounding rect,
|
||||
// to device pixels relative to aElement's containing window.
|
||||
function coordinatesRelativeToWindow(aX, aY, aElement) {
|
||||
var targetWindow = aElement.ownerDocument.defaultView;
|
||||
var scale = targetWindow.devicePixelRatio;
|
||||
var rect = aElement.getBoundingClientRect();
|
||||
return {
|
||||
x: targetWindow.mozInnerScreenX + ((rect.left + aX) * scale),
|
||||
y: targetWindow.mozInnerScreenY + ((rect.top + aY) * scale)
|
||||
};
|
||||
}
|
||||
|
||||
// Synthesizes a native mousewheel event and returns immediately. This does not
|
||||
// guarantee anything; you probably want to use one of the other functions below
|
||||
// which actually wait for results.
|
||||
@ -47,14 +72,15 @@ function nativeMouseMoveEventMsg() {
|
||||
// aDeltaX and aDeltaY are pixel deltas, and aObserver can be left undefined
|
||||
// if not needed.
|
||||
function synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, aObserver) {
|
||||
var targetWindow = aElement.ownerDocument.defaultView;
|
||||
aX += targetWindow.mozInnerScreenX;
|
||||
aY += targetWindow.mozInnerScreenY;
|
||||
var pt = coordinatesRelativeToWindow(aX, aY, aElement);
|
||||
if (aDeltaX && aDeltaY) {
|
||||
throw "Simultaneous wheeling of horizontal and vertical is not supported on all platforms.";
|
||||
}
|
||||
aDeltaX = nativeScrollUnits(aElement, aDeltaX);
|
||||
aDeltaY = nativeScrollUnits(aElement, aDeltaY);
|
||||
var msg = aDeltaX ? nativeHorizontalWheelEventMsg() : nativeVerticalWheelEventMsg();
|
||||
_getDOMWindowUtils().sendNativeMouseScrollEvent(aX, aY, msg, aDeltaX, aDeltaY, 0, 0, 0, aElement, aObserver);
|
||||
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
|
||||
utils.sendNativeMouseScrollEvent(pt.x, pt.y, msg, aDeltaX, aDeltaY, 0, 0, 0, aElement, aObserver);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -104,10 +130,9 @@ function synthesizeNativeWheelAndWaitForScrollEvent(aElement, aX, aY, aDeltaX, a
|
||||
// Synthesizes a native mouse move event and returns immediately.
|
||||
// aX and aY are relative to the top-left of |aElement|'s containing window.
|
||||
function synthesizeNativeMouseMove(aElement, aX, aY) {
|
||||
var targetWindow = aElement.ownerDocument.defaultView;
|
||||
aX += targetWindow.mozInnerScreenX;
|
||||
aY += targetWindow.mozInnerScreenY;
|
||||
_getDOMWindowUtils().sendNativeMouseEvent(aX, aY, nativeMouseMoveEventMsg(), 0, aElement);
|
||||
var pt = coordinatesRelativeToWindow(aX, aY, aElement);
|
||||
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
|
||||
utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseMoveEventMsg(), 0, aElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -128,15 +153,9 @@ function synthesizeNativeMouseMoveAndWaitForMoveEvent(aElement, aX, aY, aCallbac
|
||||
// Synthesizes a native touch event and dispatches it. aX and aY in CSS pixels
|
||||
// relative to the top-left of |aElement|'s bounding rect.
|
||||
function synthesizeNativeTouch(aElement, aX, aY, aType, aObserver = null, aTouchId = 0) {
|
||||
var targetWindow = aElement.ownerDocument.defaultView;
|
||||
|
||||
var scale = targetWindow.devicePixelRatio;
|
||||
var rect = aElement.getBoundingClientRect();
|
||||
var x = targetWindow.mozInnerScreenX + ((rect.left + aX) * scale);
|
||||
var y = targetWindow.mozInnerScreenY + ((rect.top + aY) * scale);
|
||||
|
||||
var utils = SpecialPowers.getDOMWindowUtils(targetWindow);
|
||||
utils.sendNativeTouchPoint(aTouchId, aType, x, y, 1, 90, aObserver);
|
||||
var pt = coordinatesRelativeToWindow(aX, aY, aElement);
|
||||
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
|
||||
utils.sendNativeTouchPoint(aTouchId, aType, pt.x, pt.y, 1, 90, aObserver);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -44,11 +44,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1151667
|
||||
|
||||
function startTest() {
|
||||
var subframe = document.getElementById('subframe');
|
||||
var scale = window.devicePixelRatio;
|
||||
var rect = subframe.getBoundingClientRect();
|
||||
var x = (rect.left + 100) * scale;
|
||||
var y = (rect.top + 150) * scale;
|
||||
synthesizeNativeWheelAndWaitForScrollEvent(subframe, x, y, 0, -10, continueTest);
|
||||
synthesizeNativeWheelAndWaitForScrollEvent(subframe, 100, 150, 0, -10, continueTest);
|
||||
}
|
||||
|
||||
function continueTest() {
|
||||
|
@ -52,10 +52,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1173580
|
||||
|
||||
// Scroll the mouse wheel over |element|.
|
||||
function scrollWheelOver(element) {
|
||||
var scale = window.devicePixelRatio;
|
||||
var rect = element.getBoundingClientRect();
|
||||
var x = (rect.left + 10) * scale;
|
||||
var y = (rect.top + 10) * scale;
|
||||
var x = 10;
|
||||
var y = 10;
|
||||
// Move the mouse to the desired wheel location.
|
||||
// Not doing so can result in the wheel events from two consecutive
|
||||
// scrollWheelOver() calls on different elements being incorrectly considered
|
||||
|
@ -32,7 +32,7 @@
|
||||
// Scroll diff
|
||||
var dx = 0;
|
||||
var dy = -10; // Negative to scroll down
|
||||
synthesizeNativeWheelAndWaitForEvent(scrollDiv, x, y, dx, dy);
|
||||
synthesizeNativeWheelAndWaitForWheelEvent(scrollDiv, x, y, dx, dy);
|
||||
window.requestAnimationFrame(sendScrollEvent);
|
||||
} else {
|
||||
// Locally, with silk and apz + e10s, retina 15" mbp usually get ~1.0 - 1.5
|
||||
|
@ -447,9 +447,12 @@ TextureClientD3D11::AllocateForSurface(gfx::IntSize aSize, TextureAllocationFlag
|
||||
return false;
|
||||
}
|
||||
|
||||
ID3D11Device* d3d11device = gfxWindowsPlatform::GetPlatform()->GetD3D11ContentDevice();
|
||||
gfxWindowsPlatform* windowsPlatform = gfxWindowsPlatform::GetPlatform();
|
||||
ID3D11Device* d3d11device = windowsPlatform->GetD3D11ContentDevice();
|
||||
bool haveD3d11Backend = windowsPlatform->GetContentBackend() == BackendType::DIRECT2D1_1;
|
||||
|
||||
if (gfxPrefs::Direct2DUse1_1() && d3d11device) {
|
||||
if (haveD3d11Backend) {
|
||||
MOZ_ASSERT(d3d11device != nullptr);
|
||||
|
||||
CD3D11_TEXTURE2D_DESC newDesc(mFormat == SurfaceFormat::A8 ? DXGI_FORMAT_R8_UNORM : DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
aSize.width, aSize.height, 1, 1,
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsExpirationTracker.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "gfxUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
@ -164,44 +165,61 @@ struct BlurCacheKey : public PLDHashEntryHdr {
|
||||
typedef const BlurCacheKey* KeyTypePointer;
|
||||
enum { ALLOW_MEMMOVE = true };
|
||||
|
||||
gfxRect mRect;
|
||||
IntSize mMinSize;
|
||||
IntSize mBlurRadius;
|
||||
gfxRect mSkipRect;
|
||||
gfxRGBA mShadowColor;
|
||||
BackendType mBackend;
|
||||
RectCornerRadii mCornerRadii;
|
||||
|
||||
BlurCacheKey(const gfxRect& aRect, const IntSize &aBlurRadius, const gfxRect& aSkipRect, BackendType aBackend)
|
||||
: mRect(aRect)
|
||||
BlurCacheKey(IntSize aMinimumSize, gfxIntSize aBlurRadius,
|
||||
RectCornerRadii* aCornerRadii, gfxRGBA aShadowColor,
|
||||
BackendType aBackend)
|
||||
: mMinSize(aMinimumSize)
|
||||
, mBlurRadius(aBlurRadius)
|
||||
, mSkipRect(aSkipRect)
|
||||
, mShadowColor(aShadowColor)
|
||||
, mBackend(aBackend)
|
||||
, mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
|
||||
{ }
|
||||
|
||||
explicit BlurCacheKey(const BlurCacheKey* aOther)
|
||||
: mRect(aOther->mRect)
|
||||
: mMinSize(aOther->mMinSize)
|
||||
, mBlurRadius(aOther->mBlurRadius)
|
||||
, mSkipRect(aOther->mSkipRect)
|
||||
, mShadowColor(aOther->mShadowColor)
|
||||
, mBackend(aOther->mBackend)
|
||||
, mCornerRadii(aOther->mCornerRadii)
|
||||
{ }
|
||||
|
||||
static PLDHashNumber
|
||||
HashKey(const KeyTypePointer aKey)
|
||||
{
|
||||
PLDHashNumber hash = HashBytes(&aKey->mRect.x, 4 * sizeof(gfxFloat));
|
||||
PLDHashNumber hash = 0;
|
||||
hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height);
|
||||
hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height);
|
||||
hash = AddToHash(hash, HashBytes(&aKey->mSkipRect.x, 4 * sizeof(gfxFloat)));
|
||||
|
||||
hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.r, sizeof(gfxFloat)));
|
||||
hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.g, sizeof(gfxFloat)));
|
||||
hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.b, sizeof(gfxFloat)));
|
||||
hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.a, sizeof(gfxFloat)));
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
hash = AddToHash(hash, aKey->mCornerRadii[i].width, aKey->mCornerRadii[i].height);
|
||||
}
|
||||
|
||||
hash = AddToHash(hash, (uint32_t)aKey->mBackend);
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool KeyEquals(KeyTypePointer aKey) const
|
||||
{
|
||||
if (aKey->mRect.IsEqualInterior(mRect) &&
|
||||
if (aKey->mMinSize == mMinSize &&
|
||||
aKey->mBlurRadius == mBlurRadius &&
|
||||
aKey->mSkipRect.IsEqualInterior(mSkipRect) &&
|
||||
aKey->mCornerRadii == mCornerRadii &&
|
||||
aKey->mShadowColor == mShadowColor &&
|
||||
aKey->mBackend == mBackend) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
static KeyTypePointer KeyToPointer(KeyType aKey)
|
||||
{
|
||||
@ -214,17 +232,15 @@ struct BlurCacheKey : public PLDHashEntryHdr {
|
||||
* to the cache entry to be able to be tracked by the nsExpirationTracker.
|
||||
* */
|
||||
struct BlurCacheData {
|
||||
BlurCacheData(SourceSurface* aBlur, const IntPoint& aTopLeft, const gfxRect& aDirtyRect, const BlurCacheKey& aKey)
|
||||
BlurCacheData(SourceSurface* aBlur, IntMargin aExtendDestBy, const BlurCacheKey& aKey)
|
||||
: mBlur(aBlur)
|
||||
, mTopLeft(aTopLeft)
|
||||
, mDirtyRect(aDirtyRect)
|
||||
, mExtendDest(aExtendDestBy)
|
||||
, mKey(aKey)
|
||||
{}
|
||||
|
||||
BlurCacheData(const BlurCacheData& aOther)
|
||||
: mBlur(aOther.mBlur)
|
||||
, mTopLeft(aOther.mTopLeft)
|
||||
, mDirtyRect(aOther.mDirtyRect)
|
||||
, mExtendDest(aOther.mExtendDest)
|
||||
, mKey(aOther.mKey)
|
||||
{ }
|
||||
|
||||
@ -234,8 +250,7 @@ struct BlurCacheData {
|
||||
|
||||
nsExpirationState mExpirationState;
|
||||
RefPtr<SourceSurface> mBlur;
|
||||
IntPoint mTopLeft;
|
||||
gfxRect mDirtyRect;
|
||||
IntMargin mExtendDest;
|
||||
BlurCacheKey mKey;
|
||||
};
|
||||
|
||||
@ -259,19 +274,17 @@ class BlurCache final : public nsExpirationTracker<BlurCacheData,4>
|
||||
mHashEntries.Remove(aObject->mKey);
|
||||
}
|
||||
|
||||
BlurCacheData* Lookup(const gfxRect& aRect,
|
||||
const IntSize& aBlurRadius,
|
||||
const gfxRect& aSkipRect,
|
||||
BackendType aBackendType,
|
||||
const gfxRect* aDirtyRect)
|
||||
BlurCacheData* Lookup(const IntSize aMinSize,
|
||||
const gfxIntSize& aBlurRadius,
|
||||
RectCornerRadii* aCornerRadii,
|
||||
const gfxRGBA& aShadowColor,
|
||||
BackendType aBackendType)
|
||||
{
|
||||
BlurCacheData* blur =
|
||||
mHashEntries.Get(BlurCacheKey(aRect, aBlurRadius, aSkipRect, aBackendType));
|
||||
|
||||
mHashEntries.Get(BlurCacheKey(aMinSize, aBlurRadius,
|
||||
aCornerRadii, aShadowColor,
|
||||
aBackendType));
|
||||
if (blur) {
|
||||
if (aDirtyRect && !blur->mDirtyRect.Contains(*aDirtyRect)) {
|
||||
return nullptr;
|
||||
}
|
||||
MarkUsed(blur);
|
||||
}
|
||||
|
||||
@ -306,52 +319,164 @@ class BlurCache final : public nsExpirationTracker<BlurCacheData,4>
|
||||
|
||||
static BlurCache* gBlurCache = nullptr;
|
||||
|
||||
static IntSize
|
||||
ComputeMinSizeForShadowShape(RectCornerRadii* aCornerRadii,
|
||||
gfxIntSize aBlurRadius,
|
||||
IntMargin& aSlice,
|
||||
const IntSize& aRectSize)
|
||||
{
|
||||
float cornerWidth = 0;
|
||||
float cornerHeight = 0;
|
||||
if (aCornerRadii) {
|
||||
RectCornerRadii corners = *aCornerRadii;
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
cornerWidth = std::max(cornerWidth, corners[i].width);
|
||||
cornerHeight = std::max(cornerHeight, corners[i].height);
|
||||
}
|
||||
}
|
||||
|
||||
aSlice = IntMargin(ceil(cornerHeight) + aBlurRadius.height,
|
||||
ceil(cornerWidth) + aBlurRadius.width,
|
||||
ceil(cornerHeight) + aBlurRadius.height,
|
||||
ceil(cornerWidth) + aBlurRadius.width);
|
||||
|
||||
IntSize minSize(aSlice.LeftRight() + 1,
|
||||
aSlice.TopBottom() + 1);
|
||||
|
||||
// If aRectSize is smaller than minSize, the border-image approach won't
|
||||
// work; there's no way to squeeze parts of the min box-shadow source
|
||||
// image such that the result looks correct. So we need to adjust minSize
|
||||
// in such a way that we can later draw it without stretching in the affected
|
||||
// dimension. We also need to adjust "slice" to ensure that we're not trying
|
||||
// to slice away more than we have.
|
||||
if (aRectSize.width < minSize.width) {
|
||||
minSize.width = aRectSize.width;
|
||||
aSlice.left = 0;
|
||||
aSlice.right = 0;
|
||||
}
|
||||
if (aRectSize.height < minSize.height) {
|
||||
minSize.height = aRectSize.height;
|
||||
aSlice.top = 0;
|
||||
aSlice.bottom = 0;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aSlice.LeftRight() <= minSize.width);
|
||||
MOZ_ASSERT(aSlice.TopBottom() <= minSize.height);
|
||||
return minSize;
|
||||
}
|
||||
|
||||
void
|
||||
CacheBlur(DrawTarget& aDT,
|
||||
const IntSize& aMinSize,
|
||||
const gfxIntSize& aBlurRadius,
|
||||
RectCornerRadii* aCornerRadii,
|
||||
const gfxRGBA& aShadowColor,
|
||||
IntMargin aExtendDest,
|
||||
SourceSurface* aBoxShadow)
|
||||
{
|
||||
BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aDT.GetBackendType());
|
||||
BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendDest, key);
|
||||
if (!gBlurCache->RegisterEntry(data)) {
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
|
||||
// Blurs a small surface and creates the mask.
|
||||
static TemporaryRef<SourceSurface>
|
||||
CreateBlurMask(const IntSize& aRectSize,
|
||||
RectCornerRadii* aCornerRadii,
|
||||
gfxIntSize aBlurRadius,
|
||||
IntMargin& aExtendDestBy,
|
||||
IntMargin& aSliceBorder,
|
||||
DrawTarget& aDestDrawTarget)
|
||||
{
|
||||
IntMargin slice;
|
||||
gfxAlphaBoxBlur blur;
|
||||
IntSize minSize =
|
||||
ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, slice, aRectSize);
|
||||
IntRect minRect(IntPoint(), minSize);
|
||||
|
||||
gfxContext* blurCtx = blur.Init(ThebesRect(Rect(minRect)), gfxIntSize(),
|
||||
aBlurRadius, nullptr, nullptr);
|
||||
|
||||
if (!blurCtx) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DrawTarget* blurDT = blurCtx->GetDrawTarget();
|
||||
ColorPattern black(Color(0.f, 0.f, 0.f, 1.f));
|
||||
|
||||
if (aCornerRadii) {
|
||||
RefPtr<Path> roundedRect =
|
||||
MakePathForRoundedRect(*blurDT, Rect(minRect), *aCornerRadii);
|
||||
blurDT->Fill(roundedRect, black);
|
||||
} else {
|
||||
blurDT->FillRect(Rect(minRect), black);
|
||||
}
|
||||
|
||||
IntPoint topLeft;
|
||||
RefPtr<SourceSurface> result = blur.DoBlur(&aDestDrawTarget, &topLeft);
|
||||
|
||||
IntRect expandedMinRect(topLeft, result->GetSize());
|
||||
aExtendDestBy = expandedMinRect - minRect;
|
||||
aSliceBorder = slice + aExtendDestBy;
|
||||
|
||||
MOZ_ASSERT(aSliceBorder.LeftRight() <= expandedMinRect.width);
|
||||
MOZ_ASSERT(aSliceBorder.TopBottom() <= expandedMinRect.height);
|
||||
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
static TemporaryRef<SourceSurface>
|
||||
CreateBoxShadow(DrawTarget& aDT, SourceSurface* aBlurMask, const gfxRGBA& aShadowColor)
|
||||
{
|
||||
IntSize blurredSize = aBlurMask->GetSize();
|
||||
gfxPlatform* platform = gfxPlatform::GetPlatform();
|
||||
RefPtr<DrawTarget> boxShadowDT =
|
||||
platform->CreateOffscreenContentDrawTarget(blurredSize, SurfaceFormat::B8G8R8A8);
|
||||
MOZ_ASSERT(boxShadowDT->GetType() == aDT.GetType());
|
||||
|
||||
ColorPattern shadowColor(ToDeviceColor(aShadowColor));
|
||||
boxShadowDT->MaskSurface(shadowColor, aBlurMask, Point(0, 0));
|
||||
return boxShadowDT->Snapshot();
|
||||
}
|
||||
|
||||
SourceSurface*
|
||||
GetCachedBlur(DrawTarget *aDT,
|
||||
const gfxRect& aRect,
|
||||
const IntSize& aBlurRadius,
|
||||
const gfxRect& aSkipRect,
|
||||
const gfxRect& aDirtyRect,
|
||||
IntPoint* aTopLeft)
|
||||
GetBlur(DrawTarget& aDT,
|
||||
const IntSize& aRectSize,
|
||||
const gfxIntSize& aBlurRadius,
|
||||
RectCornerRadii* aCornerRadii,
|
||||
const gfxRGBA& aShadowColor,
|
||||
IntMargin& aExtendDestBy,
|
||||
IntMargin& aSlice)
|
||||
{
|
||||
if (!gBlurCache) {
|
||||
gBlurCache = new BlurCache();
|
||||
}
|
||||
BlurCacheData* cached = gBlurCache->Lookup(aRect, aBlurRadius, aSkipRect,
|
||||
aDT->GetBackendType(),
|
||||
&aDirtyRect);
|
||||
|
||||
IntSize minSize =
|
||||
ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aSlice, aRectSize);
|
||||
|
||||
BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius,
|
||||
aCornerRadii, aShadowColor,
|
||||
aDT.GetBackendType());
|
||||
if (cached) {
|
||||
*aTopLeft = cached->mTopLeft;
|
||||
// See CreateBlurMask() for these values
|
||||
aExtendDestBy = cached->mExtendDest;
|
||||
aSlice = aSlice + aExtendDestBy;
|
||||
return cached->mBlur;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
CacheBlur(DrawTarget *aDT,
|
||||
const gfxRect& aRect,
|
||||
const IntSize& aBlurRadius,
|
||||
const gfxRect& aSkipRect,
|
||||
SourceSurface* aBlur,
|
||||
const IntPoint& aTopLeft,
|
||||
const gfxRect& aDirtyRect)
|
||||
{
|
||||
// If we already had a cached value with this key, but an incorrect dirty region then just update
|
||||
// the existing entry
|
||||
if (BlurCacheData* cached = gBlurCache->Lookup(aRect, aBlurRadius, aSkipRect,
|
||||
aDT->GetBackendType(),
|
||||
nullptr)) {
|
||||
cached->mBlur = aBlur;
|
||||
cached->mTopLeft = aTopLeft;
|
||||
cached->mDirtyRect = aDirtyRect;
|
||||
return;
|
||||
RefPtr<SourceSurface> blurMask =
|
||||
CreateBlurMask(aRectSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, aDT);
|
||||
|
||||
if (!blurMask) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BlurCacheKey key(aRect, aBlurRadius, aSkipRect, aDT->GetBackendType());
|
||||
BlurCacheData* data = new BlurCacheData(aBlur, aTopLeft, aDirtyRect, key);
|
||||
if (!gBlurCache->RegisterEntry(data)) {
|
||||
delete data;
|
||||
}
|
||||
RefPtr<SourceSurface> boxShadow = CreateBoxShadow(aDT, blurMask, aShadowColor);
|
||||
CacheBlur(aDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, aExtendDestBy, boxShadow);
|
||||
return boxShadow;
|
||||
}
|
||||
|
||||
void
|
||||
@ -361,8 +486,65 @@ gfxAlphaBoxBlur::ShutdownBlurCache()
|
||||
gBlurCache = nullptr;
|
||||
}
|
||||
|
||||
static Rect
|
||||
RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom, Float aLeft)
|
||||
{
|
||||
return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop);
|
||||
}
|
||||
|
||||
static void
|
||||
RepeatOrStretchSurface(DrawTarget& aDT, SourceSurface* aSurface,
|
||||
const Rect& aDest, const Rect& aSrc, Rect& aSkipRect)
|
||||
{
|
||||
if (aSkipRect.Contains(aDest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!aDT.GetTransform().IsRectilinear() &&
|
||||
aDT.GetBackendType() != BackendType::CAIRO) ||
|
||||
(aDT.GetBackendType() == BackendType::DIRECT2D)) {
|
||||
// Use stretching if possible, since it leads to less seams when the
|
||||
// destination is transformed. However, don't do this if we're using cairo,
|
||||
// because if cairo is using pixman it won't render anything for large
|
||||
// stretch factors because pixman's internal fixed point precision is not
|
||||
// high enough to handle those scale factors.
|
||||
// Calling FillRect on a D2D backend with a repeating pattern is much slower
|
||||
// than DrawSurface, so special case the D2D backend here.
|
||||
aDT.DrawSurface(aSurface, aDest, aSrc);
|
||||
return;
|
||||
}
|
||||
|
||||
SurfacePattern pattern(aSurface, ExtendMode::REPEAT,
|
||||
Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()),
|
||||
Filter::GOOD, RoundedToInt(aSrc));
|
||||
aDT.FillRect(aDest, pattern);
|
||||
}
|
||||
|
||||
static void
|
||||
DrawCorner(DrawTarget& aDT, SourceSurface* aSurface,
|
||||
const Rect& aDest, const Rect& aSrc, Rect& aSkipRect)
|
||||
{
|
||||
if (aSkipRect.Contains(aDest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
aDT.DrawSurface(aSurface, aDest, aSrc);
|
||||
}
|
||||
|
||||
/***
|
||||
* We draw a blurred a rectangle by only blurring a smaller rectangle and
|
||||
* splitting the rectangle into 9 parts.
|
||||
* First, a small minimum source rect is calculated and used to create a blur
|
||||
* mask since the actual blurring itself is expensive. Next, we use the mask
|
||||
* with the given shadow color to create a minimally-sized box shadow of the
|
||||
* right color. Finally, we cut out the 9 parts from the box-shadow source and
|
||||
* paint each part in the right place, stretching the non-corner parts to fill
|
||||
* the space between the corners.
|
||||
*/
|
||||
|
||||
|
||||
/* static */ void
|
||||
gfxAlphaBoxBlur::BlurRectangle(gfxContext *aDestinationCtx,
|
||||
gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
|
||||
const gfxRect& aRect,
|
||||
RectCornerRadii* aCornerRadii,
|
||||
const gfxPoint& aBlurStdDev,
|
||||
@ -370,43 +552,123 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContext *aDestinationCtx,
|
||||
const gfxRect& aDirtyRect,
|
||||
const gfxRect& aSkipRect)
|
||||
{
|
||||
DrawTarget& aDrawTarget = *aDestinationCtx->GetDrawTarget();
|
||||
|
||||
DrawTarget& destDrawTarget = *aDestinationCtx->GetDrawTarget();
|
||||
IntSize blurRadius = CalculateBlurRadius(aBlurStdDev);
|
||||
|
||||
IntPoint topLeft;
|
||||
RefPtr<SourceSurface> surface = GetCachedBlur(&aDrawTarget, aRect, blurRadius, aSkipRect, aDirtyRect, &topLeft);
|
||||
if (!surface) {
|
||||
// Create the temporary surface for blurring
|
||||
gfxAlphaBoxBlur blur;
|
||||
gfxContext* blurCtx = blur.Init(aRect, IntSize(), blurRadius, &aDirtyRect, &aSkipRect);
|
||||
if (!blurCtx) {
|
||||
return;
|
||||
}
|
||||
DrawTarget* blurDT = blurCtx->GetDrawTarget();
|
||||
IntRect rect = RoundedToInt(ToRect(aRect));
|
||||
IntMargin extendDestBy;
|
||||
IntMargin slice;
|
||||
|
||||
Rect shadowGfxRect = ToRect(aRect);
|
||||
shadowGfxRect.Round();
|
||||
|
||||
ColorPattern black(Color(0.f, 0.f, 0.f, 1.f)); // For masking, so no ToDeviceColor!
|
||||
if (aCornerRadii) {
|
||||
RefPtr<Path> roundedRect = MakePathForRoundedRect(*blurDT,
|
||||
shadowGfxRect,
|
||||
*aCornerRadii);
|
||||
blurDT->Fill(roundedRect, black);
|
||||
} else {
|
||||
blurDT->FillRect(shadowGfxRect, black);
|
||||
}
|
||||
|
||||
surface = blur.DoBlur(&aDrawTarget, &topLeft);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
CacheBlur(&aDrawTarget, aRect, blurRadius, aSkipRect, surface, topLeft, aDirtyRect);
|
||||
RefPtr<SourceSurface> boxShadow = GetBlur(destDrawTarget,
|
||||
rect.Size(), blurRadius,
|
||||
aCornerRadii, aShadowColor,
|
||||
extendDestBy, slice);
|
||||
if (!boxShadow) {
|
||||
return;
|
||||
}
|
||||
|
||||
aDestinationCtx->SetColor(aShadowColor);
|
||||
Rect dirtyRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
|
||||
DrawBlur(aDestinationCtx, surface, topLeft, &dirtyRect);
|
||||
destDrawTarget.PushClipRect(ToRect(aDirtyRect));
|
||||
|
||||
// Copy the right parts from boxShadow into destDrawTarget. The middle parts
|
||||
// will be stretched, border-image style.
|
||||
|
||||
Rect srcOuter(Point(), Size(boxShadow->GetSize()));
|
||||
Rect srcInner = srcOuter;
|
||||
srcInner.Deflate(Margin(slice));
|
||||
|
||||
rect.Inflate(extendDestBy);
|
||||
Rect dstOuter(rect);
|
||||
Rect dstInner(rect);
|
||||
dstInner.Deflate(Margin(slice));
|
||||
|
||||
Rect skipRect = ToRect(aSkipRect);
|
||||
|
||||
if (srcInner.IsEqualInterior(srcOuter)) {
|
||||
MOZ_ASSERT(dstInner.IsEqualInterior(dstOuter));
|
||||
// The target rect is smaller than the minimal size so just draw the surface
|
||||
destDrawTarget.DrawSurface(boxShadow, dstInner, srcInner);
|
||||
} else {
|
||||
// Corners: top left, top right, bottom left, bottom right
|
||||
DrawCorner(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstOuter.Y(), dstInner.X(),
|
||||
dstInner.Y(), dstOuter.X()),
|
||||
RectWithEdgesTRBL(srcOuter.Y(), srcInner.X(),
|
||||
srcInner.Y(), srcOuter.X()),
|
||||
skipRect);
|
||||
|
||||
DrawCorner(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstOuter.Y(), dstOuter.XMost(),
|
||||
dstInner.Y(), dstInner.XMost()),
|
||||
RectWithEdgesTRBL(srcOuter.Y(), srcOuter.XMost(),
|
||||
srcInner.Y(), srcInner.XMost()),
|
||||
skipRect);
|
||||
|
||||
DrawCorner(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstInner.YMost(), dstInner.X(),
|
||||
dstOuter.YMost(), dstOuter.X()),
|
||||
RectWithEdgesTRBL(srcInner.YMost(), srcInner.X(),
|
||||
srcOuter.YMost(), srcOuter.X()),
|
||||
skipRect);
|
||||
|
||||
DrawCorner(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstInner.YMost(), dstOuter.XMost(),
|
||||
dstOuter.YMost(), dstInner.XMost()),
|
||||
RectWithEdgesTRBL(srcInner.YMost(), srcOuter.XMost(),
|
||||
srcOuter.YMost(), srcInner.XMost()),
|
||||
skipRect);
|
||||
|
||||
// Edges: top, left, right, bottom
|
||||
RepeatOrStretchSurface(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstOuter.Y(), dstInner.XMost(),
|
||||
dstInner.Y(), dstInner.X()),
|
||||
RectWithEdgesTRBL(srcOuter.Y(), srcInner.XMost(),
|
||||
srcInner.Y(), srcInner.X()),
|
||||
skipRect);
|
||||
RepeatOrStretchSurface(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstInner.Y(), dstInner.X(),
|
||||
dstInner.YMost(), dstOuter.X()),
|
||||
RectWithEdgesTRBL(srcInner.Y(), srcInner.X(),
|
||||
srcInner.YMost(), srcOuter.X()),
|
||||
skipRect);
|
||||
RepeatOrStretchSurface(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstInner.Y(), dstOuter.XMost(),
|
||||
dstInner.YMost(), dstInner.XMost()),
|
||||
RectWithEdgesTRBL(srcInner.Y(), srcOuter.XMost(),
|
||||
srcInner.YMost(), srcInner.XMost()),
|
||||
skipRect);
|
||||
RepeatOrStretchSurface(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstInner.YMost(), dstInner.XMost(),
|
||||
dstOuter.YMost(), dstInner.X()),
|
||||
RectWithEdgesTRBL(srcInner.YMost(), srcInner.XMost(),
|
||||
srcOuter.YMost(), srcInner.X()),
|
||||
skipRect);
|
||||
|
||||
// Middle part
|
||||
RepeatOrStretchSurface(destDrawTarget, boxShadow,
|
||||
RectWithEdgesTRBL(dstInner.Y(), dstInner.XMost(),
|
||||
dstInner.YMost(), dstInner.X()),
|
||||
RectWithEdgesTRBL(srcInner.Y(), srcInner.XMost(),
|
||||
srcInner.YMost(), srcInner.X()),
|
||||
skipRect);
|
||||
}
|
||||
|
||||
// A note about anti-aliasing and seems between adjacent parts:
|
||||
// We don't explicitly disable anti-aliasing in the DrawSurface calls above,
|
||||
// so if there's a transform on destDrawTarget that is not pixel-aligned,
|
||||
// there will be seams between adjacent parts of the box-shadow. It's hard to
|
||||
// avoid those without the use of an intermediate surface.
|
||||
// You might think that we could avoid those by just turning of AA, but there
|
||||
// is a problem with that: Box-shadow rendering needs to clip out the
|
||||
// element's border box, and we'd like that clip to have anti-aliasing -
|
||||
// especially if the element has rounded corners! So we can't do that unless
|
||||
// we have a way to say "Please anti-alias the clip, but don't antialias the
|
||||
// destination rect of the DrawSurface call".
|
||||
// On OS X there is an additional problem with turning off AA: CoreGraphics
|
||||
// will not just fill the pixels that have their pixel center inside the
|
||||
// filled shape. Instead, it will fill all the pixels which are partially
|
||||
// covered by the shape. So for pixels on the edge between two adjacent parts,
|
||||
// all those pixels will be painted to by both parts, which looks very bad.
|
||||
|
||||
destDrawTarget.PopClip();
|
||||
}
|
||||
|
||||
|
@ -5629,6 +5629,41 @@ class CheckSimdVectorScalarArgs
|
||||
}
|
||||
};
|
||||
|
||||
class CheckSimdReplaceLaneArgs
|
||||
{
|
||||
AsmJSSimdType formalSimdType_;
|
||||
|
||||
public:
|
||||
explicit CheckSimdReplaceLaneArgs(AsmJSSimdType t) : formalSimdType_(t) {}
|
||||
|
||||
bool operator()(FunctionCompiler& f, ParseNode* arg, unsigned argIndex, Type actualType,
|
||||
MDefinition** def) const
|
||||
{
|
||||
MOZ_ASSERT(argIndex < 3);
|
||||
uint32_t u32;
|
||||
switch (argIndex) {
|
||||
case 0:
|
||||
// First argument is the vector
|
||||
if (!(actualType <= Type(formalSimdType_))) {
|
||||
return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
|
||||
Type(formalSimdType_).toChars());
|
||||
}
|
||||
return true;
|
||||
case 1:
|
||||
// Second argument is the lane < 4
|
||||
if (!IsLiteralOrConstInt(f, arg, &u32))
|
||||
return f.failf(arg, "lane selector should be a constant integer literal");
|
||||
if (u32 >= SimdTypeToLength(formalSimdType_))
|
||||
return f.failf(arg, "lane selector should be in bounds");
|
||||
return true;
|
||||
case 2:
|
||||
// Third argument is the scalar
|
||||
return CheckSimdScalarArgs(formalSimdType_)(f, arg, argIndex, actualType, def);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
static inline bool
|
||||
@ -5684,14 +5719,17 @@ CheckSimdBinary<MSimdShift::Operation>(FunctionCompiler& f, ParseNode* call, Asm
|
||||
}
|
||||
|
||||
static bool
|
||||
CheckSimdWith(FunctionCompiler& f, ParseNode* call, AsmJSSimdType opType, SimdLane lane,
|
||||
MDefinition** def, Type* type)
|
||||
CheckSimdReplaceLane(FunctionCompiler& f, ParseNode* call, AsmJSSimdType opType,
|
||||
MDefinition** def, Type* type)
|
||||
{
|
||||
DefinitionVector defs;
|
||||
if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(opType), &defs))
|
||||
if (!CheckSimdCallArgs(f, call, 3, CheckSimdReplaceLaneArgs(opType), &defs))
|
||||
return false;
|
||||
ParseNode* laneArg = NextNode(CallArgList(call));
|
||||
uint32_t lane;
|
||||
JS_ALWAYS_TRUE(IsLiteralInt(f.m(), laneArg, &lane));
|
||||
*type = opType;
|
||||
*def = f.insertElementSimd(defs[0], defs[1], lane, type->toMIRType());
|
||||
*def = f.insertElementSimd(defs[0], defs[2], SimdLane(lane), type->toMIRType());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -5950,14 +5988,8 @@ CheckSimdOperationCall(FunctionCompiler& f, ParseNode* call, const ModuleCompile
|
||||
case AsmJSSimdOperation_xor:
|
||||
return CheckSimdBinary(f, call, opType, MSimdBinaryBitwise::xor_, def, type);
|
||||
|
||||
case AsmJSSimdOperation_withX:
|
||||
return CheckSimdWith(f, call, opType, SimdLane::LaneX, def, type);
|
||||
case AsmJSSimdOperation_withY:
|
||||
return CheckSimdWith(f, call, opType, SimdLane::LaneY, def, type);
|
||||
case AsmJSSimdOperation_withZ:
|
||||
return CheckSimdWith(f, call, opType, SimdLane::LaneZ, def, type);
|
||||
case AsmJSSimdOperation_withW:
|
||||
return CheckSimdWith(f, call, opType, SimdLane::LaneW, def, type);
|
||||
case AsmJSSimdOperation_replaceLane:
|
||||
return CheckSimdReplaceLane(f, call, opType, def, type);
|
||||
|
||||
case AsmJSSimdOperation_fromInt32x4:
|
||||
return CheckSimdCast<MSimdConvert>(f, call, AsmJSSimdType_int32x4, opType, def, type);
|
||||
|
@ -614,22 +614,6 @@ template<typename T>
|
||||
struct Or {
|
||||
static T apply(T l, T r) { return l | r; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithX {
|
||||
static T apply(int32_t lane, T scalar, T x) { return lane == 0 ? scalar : x; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithY {
|
||||
static T apply(int32_t lane, T scalar, T x) { return lane == 1 ? scalar : x; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithZ {
|
||||
static T apply(int32_t lane, T scalar, T x) { return lane == 2 ? scalar : x; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithW {
|
||||
static T apply(int32_t lane, T scalar, T x) { return lane == 3 ? scalar : x; }
|
||||
};
|
||||
// For the following three operators, if the value v we're trying to shift is
|
||||
// such that v << bits can't fit in the int32 range, then we have undefined
|
||||
// behavior, according to C++11 [expr.shift]p2.
|
||||
@ -717,26 +701,33 @@ BinaryFunc(JSContext* cx, unsigned argc, Value* vp)
|
||||
return CoercedBinaryFunc<In, Out, Op, Out>(cx, argc, vp);
|
||||
}
|
||||
|
||||
template<typename V, template<typename T> class OpWith>
|
||||
template<typename V>
|
||||
static bool
|
||||
FuncWith(JSContext* cx, unsigned argc, Value* vp)
|
||||
ReplaceLane(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
typedef typename V::Elem Elem;
|
||||
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
// Only the first argument is mandatory
|
||||
if (args.length() < 1 || !IsVectorObject<V>(args[0]))
|
||||
// Only the first and second arguments are mandatory
|
||||
if (args.length() < 2 || !IsVectorObject<V>(args[0]))
|
||||
return ErrorBadArgs(cx);
|
||||
|
||||
Elem* vec = TypedObjectMemory<Elem*>(args[0]);
|
||||
Elem result[V::lanes];
|
||||
|
||||
if (!args[1].isInt32())
|
||||
return ErrorBadArgs(cx);
|
||||
int32_t lanearg = args[1].toInt32();
|
||||
if (lanearg < 0 || uint32_t(lanearg) >= V::lanes)
|
||||
return ErrorBadArgs(cx);
|
||||
uint32_t lane = uint32_t(lanearg);
|
||||
|
||||
Elem value;
|
||||
if (!V::toType(cx, args.get(1), &value))
|
||||
if (!V::toType(cx, args.get(2), &value))
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < V::lanes; i++)
|
||||
result[i] = OpWith<Elem>::apply(i, value, vec[i]);
|
||||
result[i] = i == lane ? value : vec[i];
|
||||
return StoreResult<V>(cx, args, result);
|
||||
}
|
||||
|
||||
@ -1170,4 +1161,3 @@ js::simd_int32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
|
||||
}
|
||||
INT32X4_FUNCTION_LIST(DEFINE_SIMD_INT32X4_FUNCTION)
|
||||
#undef DEFINE_SIMD_INT32X4_FUNCTION
|
||||
|
||||
|
@ -59,15 +59,12 @@
|
||||
V(store2, (Store<Float32x4, 2>), 3) \
|
||||
V(store1, (Store<Float32x4, 1>), 3) \
|
||||
V(sub, (BinaryFunc<Float32x4, Sub, Float32x4>), 2) \
|
||||
V(withX, (FuncWith<Float32x4, WithX>), 2) \
|
||||
V(withY, (FuncWith<Float32x4, WithY>), 2) \
|
||||
V(withZ, (FuncWith<Float32x4, WithZ>), 2) \
|
||||
V(withW, (FuncWith<Float32x4, WithW>), 2) \
|
||||
V(xor, (CoercedBinaryFunc<Float32x4, Int32x4, Xor, Float32x4>), 2)
|
||||
|
||||
#define FLOAT32X4_TERNARY_FUNCTION_LIST(V) \
|
||||
V(bitselect, BitSelect<Float32x4>, 3) \
|
||||
V(clamp, Clamp<Float32x4>, 3) \
|
||||
V(replaceLane, (ReplaceLane<Float32x4>), 3) \
|
||||
V(select, Select<Float32x4>, 3)
|
||||
|
||||
#define FLOAT32X4_SHUFFLE_FUNCTION_LIST(V) \
|
||||
@ -111,13 +108,12 @@
|
||||
V(notEqual, (CompareFunc<Float64x2, NotEqual>), 2) \
|
||||
V(store, (Store<Float64x2, 2>), 3) \
|
||||
V(store1, (Store<Float64x2, 1>), 3) \
|
||||
V(sub, (BinaryFunc<Float64x2, Sub, Float64x2>), 2) \
|
||||
V(withX, (FuncWith<Float64x2, WithX>), 2) \
|
||||
V(withY, (FuncWith<Float64x2, WithY>), 2)
|
||||
V(sub, (BinaryFunc<Float64x2, Sub, Float64x2>), 2)
|
||||
|
||||
#define FLOAT64X2_TERNARY_FUNCTION_LIST(V) \
|
||||
V(bitselect, BitSelect<Float64x2>, 3) \
|
||||
V(clamp, Clamp<Float64x2>, 3) \
|
||||
V(replaceLane, (ReplaceLane<Float64x2>), 3) \
|
||||
V(select, Select<Float64x2>, 3)
|
||||
|
||||
#define FLOAT64X2_SHUFFLE_FUNCTION_LIST(V) \
|
||||
@ -163,14 +159,11 @@
|
||||
V(store3, (Store<Int32x4, 3>), 3) \
|
||||
V(store2, (Store<Int32x4, 2>), 3) \
|
||||
V(store1, (Store<Int32x4, 1>), 3) \
|
||||
V(withX, (FuncWith<Int32x4, WithX>), 2) \
|
||||
V(withY, (FuncWith<Int32x4, WithY>), 2) \
|
||||
V(withZ, (FuncWith<Int32x4, WithZ>), 2) \
|
||||
V(withW, (FuncWith<Int32x4, WithW>), 2) \
|
||||
V(xor, (BinaryFunc<Int32x4, Xor, Int32x4>), 2)
|
||||
|
||||
#define INT32X4_TERNARY_FUNCTION_LIST(V) \
|
||||
V(bitselect, BitSelect<Int32x4>, 3) \
|
||||
V(replaceLane, (ReplaceLane<Int32x4>), 3) \
|
||||
V(select, Select<Int32x4>, 3)
|
||||
|
||||
#define INT32X4_QUARTERNARY_FUNCTION_LIST(V) \
|
||||
@ -226,16 +219,11 @@
|
||||
_(notEqual) \
|
||||
_(greaterThan) \
|
||||
_(greaterThanOrEqual)
|
||||
#define WITH_COMMONX4_SIMD_OP(_) \
|
||||
_(withX) \
|
||||
_(withY) \
|
||||
_(withZ) \
|
||||
_(withW)
|
||||
// TODO: remove when all SIMD calls are inlined (bug 1112155)
|
||||
#define ION_COMMONX4_SIMD_OP(_) \
|
||||
ARITH_COMMONX4_SIMD_OP(_) \
|
||||
BITWISE_COMMONX4_SIMD_OP(_) \
|
||||
WITH_COMMONX4_SIMD_OP(_) \
|
||||
_(replaceLane) \
|
||||
_(bitselect) \
|
||||
_(select) \
|
||||
_(splat) \
|
||||
|
@ -1973,14 +1973,18 @@ js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind
|
||||
}
|
||||
}
|
||||
|
||||
if (src->is<InlineTypedObject>()) {
|
||||
InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
|
||||
} else if (src->is<UnboxedArrayObject>()) {
|
||||
tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
|
||||
} else {
|
||||
// Objects with JSCLASS_SKIP_NURSERY_FINALIZE need to be handled above
|
||||
// to ensure any additional nursery buffers they hold are moved.
|
||||
MOZ_ASSERT(!(src->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE));
|
||||
if (src->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE) {
|
||||
if (src->is<InlineTypedObject>()) {
|
||||
InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
|
||||
} else if (src->is<UnboxedArrayObject>()) {
|
||||
tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
|
||||
} else if (src->is<ArgumentsObject>()) {
|
||||
tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
|
||||
} else {
|
||||
// Objects with JSCLASS_SKIP_NURSERY_FINALIZE need to be handled above
|
||||
// to ensure any additional nursery buffers they hold are moved.
|
||||
MOZ_CRASH("Unhandled JSCLASS_SKIP_NURSERY_FINALIZE Class");
|
||||
}
|
||||
}
|
||||
|
||||
return tenuredSize;
|
||||
|
102
js/src/jit-test/tests/SIMD/replacelane.js
Normal file
102
js/src/jit-test/tests/SIMD/replacelane.js
Normal file
@ -0,0 +1,102 @@
|
||||
load(libdir + 'simd.js');
|
||||
|
||||
setJitCompilerOption("ion.warmup.trigger", 50);
|
||||
|
||||
function f() {
|
||||
var f4 = SIMD.float32x4(1, 2, 3, 4);
|
||||
var i4 = SIMD.int32x4(1, 2, 3, 4);
|
||||
|
||||
for (var i = 0; i < 150; i++) {
|
||||
assertEqX4(SIMD.int32x4.replaceLane(i4, 0, 42), [42, 2, 3, 4]);
|
||||
assertEqX4(SIMD.int32x4.replaceLane(i4, 1, 42), [1, 42, 3, 4]);
|
||||
assertEqX4(SIMD.int32x4.replaceLane(i4, 2, 42), [1, 2, 42, 4]);
|
||||
assertEqX4(SIMD.int32x4.replaceLane(i4, 3, 42), [1, 2, 3, 42]);
|
||||
|
||||
assertEqX4(SIMD.float32x4.replaceLane(f4, 0, 42), [42, 2, 3, 4]);
|
||||
assertEqX4(SIMD.float32x4.replaceLane(f4, 1, 42), [1, 42, 3, 4]);
|
||||
assertEqX4(SIMD.float32x4.replaceLane(f4, 2, 42), [1, 2, 42, 4]);
|
||||
assertEqX4(SIMD.float32x4.replaceLane(f4, 3, 42), [1, 2, 3, 42]);
|
||||
}
|
||||
}
|
||||
|
||||
f();
|
||||
|
||||
|
||||
function e() {
|
||||
var f4 = SIMD.float32x4(1, 2, 3, 4);
|
||||
var i4 = SIMD.int32x4(1, 2, 3, 4);
|
||||
|
||||
for (let i = 0; i < 150; i++) {
|
||||
let caught = false;
|
||||
try {
|
||||
let x = SIMD.int32x4.replaceLane(i < 149 ? i4 : f4, 0, 42);
|
||||
} catch(e) {
|
||||
assertEq(e instanceof TypeError, true);
|
||||
assertEq(i, 149);
|
||||
caught = true;
|
||||
}
|
||||
assertEq(i < 149 || caught, true);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 150; i++) {
|
||||
let caught = false;
|
||||
try {
|
||||
let x = SIMD.int32x4.replaceLane(i4, i < 149 ? 0 : 4, 42);
|
||||
} catch(e) {
|
||||
assertEq(e instanceof TypeError, true);
|
||||
assertEq(i, 149);
|
||||
caught = true;
|
||||
}
|
||||
assertEq(i < 149 || caught, true);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 150; i++) {
|
||||
let caught = false;
|
||||
try {
|
||||
let x = SIMD.int32x4.replaceLane(i4, i < 149 ? 0 : 1.1, 42);
|
||||
} catch(e) {
|
||||
assertEq(e instanceof TypeError, true);
|
||||
assertEq(i, 149);
|
||||
caught = true;
|
||||
}
|
||||
assertEq(i < 149 || caught, true);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 150; i++) {
|
||||
let caught = false;
|
||||
try {
|
||||
let x = SIMD.float32x4.replaceLane(i < 149 ? f4 : i4, 0, 42);
|
||||
} catch(e) {
|
||||
assertEq(e instanceof TypeError, true);
|
||||
assertEq(i, 149);
|
||||
caught = true;
|
||||
}
|
||||
assertEq(i < 149 || caught, true);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 150; i++) {
|
||||
let caught = false;
|
||||
try {
|
||||
let x = SIMD.float32x4.replaceLane(f4, i < 149 ? 0 : 4, 42);
|
||||
} catch(e) {
|
||||
assertEq(e instanceof TypeError, true);
|
||||
assertEq(i, 149);
|
||||
caught = true;
|
||||
}
|
||||
assertEq(i < 149 || caught, true);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 150; i++) {
|
||||
let caught = false;
|
||||
try {
|
||||
let x = SIMD.float32x4.replaceLane(f4, i < 149 ? 0 : 1.1, 42);
|
||||
} catch(e) {
|
||||
assertEq(e instanceof TypeError, true);
|
||||
assertEq(i, 149);
|
||||
caught = true;
|
||||
}
|
||||
assertEq(i < 149 || caught, true);
|
||||
}
|
||||
}
|
||||
|
||||
e();
|
@ -1,23 +0,0 @@
|
||||
load(libdir + 'simd.js');
|
||||
|
||||
setJitCompilerOption("ion.warmup.trigger", 50);
|
||||
|
||||
function f() {
|
||||
var f4 = SIMD.float32x4(1, 2, 3, 4);
|
||||
var i4 = SIMD.int32x4(1, 2, 3, 4);
|
||||
|
||||
for (var i = 0; i < 150; i++) {
|
||||
assertEqX4(SIMD.int32x4.withX(i4, 42), [42, 2, 3, 4]);
|
||||
assertEqX4(SIMD.int32x4.withY(i4, 42), [1, 42, 3, 4]);
|
||||
assertEqX4(SIMD.int32x4.withZ(i4, 42), [1, 2, 42, 4]);
|
||||
assertEqX4(SIMD.int32x4.withW(i4, 42), [1, 2, 3, 42]);
|
||||
|
||||
assertEqX4(SIMD.float32x4.withX(f4, 42), [42, 2, 3, 4]);
|
||||
assertEqX4(SIMD.float32x4.withY(f4, 42), [1, 42, 3, 4]);
|
||||
assertEqX4(SIMD.float32x4.withZ(f4, 42), [1, 2, 42, 4]);
|
||||
assertEqX4(SIMD.float32x4.withW(f4, 42), [1, 2, 3, 42]);
|
||||
}
|
||||
}
|
||||
|
||||
f();
|
||||
|
@ -609,37 +609,34 @@ CheckF4(F32MAXNUM, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=max(x,y)', [0,0,
|
||||
CheckF4(F32MAXNUM + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=max(x,y)', [NaN, 0, 0, 0]);
|
||||
|
||||
// With
|
||||
const WXF = 'var w = f4.withX;';
|
||||
const WYF = 'var w = f4.withY;';
|
||||
const WZF = 'var w = f4.withZ;';
|
||||
const WWF = 'var w = f4.withW;';
|
||||
const RLF = 'var r = f4.replaceLane;';
|
||||
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, 1);} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, x);} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(1, f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(1., f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(f32(1), f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + I32 + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); var y = i4(1,2,3,4); x = w(y, f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + RLF + "function f() {var x = f4(1,2,3,4); x = r(x, 0, 1);} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + RLF + "function f() {var x = f4(1,2,3,4); x = r(x, 0, x);} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(x, 4, f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(x, f32(0), f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(1, 0, f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(1, 0., f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(f32(1), 0, f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); var l = 0; x = r(x, l, f32(1));} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + I32 + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); var y = i4(1,2,3,4); x = r(y, 0, f32(1));} return f");
|
||||
|
||||
CheckF4(WXF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [Math.fround(13.37), 2, 3, 4]);
|
||||
CheckF4(WYF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, Math.fround(13.37), 3, 4]);
|
||||
CheckF4(WZF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, 2, Math.fround(13.37), 4]);
|
||||
CheckF4(WWF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, 2, 3, Math.fround(13.37)]);
|
||||
CheckF4(WWF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37) + f32(6.63));', [1, 2, 3, Math.fround(Math.fround(13.37) + Math.fround(6.63))]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 0, f32(13.37));', [Math.fround(13.37), 2, 3, 4]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 1, f32(13.37));', [1, Math.fround(13.37), 3, 4]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 2, f32(13.37));', [1, 2, Math.fround(13.37), 4]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 3, f32(13.37));', [1, 2, 3, Math.fround(13.37)]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 3, f32(13.37) + f32(6.63));', [1, 2, 3, Math.fround(Math.fround(13.37) + Math.fround(6.63))]);
|
||||
|
||||
CheckF4(WXF + FROUND, 'var x = f4(1,2,3,4); x = w(x, 13.37);', [Math.fround(13.37), 2, 3, 4]);
|
||||
CheckF4(WYF + FROUND, 'var x = f4(1,2,3,4); x = w(x, 13.37);', [1, Math.fround(13.37), 3, 4]);
|
||||
CheckF4(WZF + FROUND, 'var x = f4(1,2,3,4); x = w(x, 13.37);', [1, 2, Math.fround(13.37), 4]);
|
||||
CheckF4(WWF + FROUND, 'var x = f4(1,2,3,4); x = w(x, 13.37);', [1, 2, 3, Math.fround(13.37)]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 0, 13.37);', [Math.fround(13.37), 2, 3, 4]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 1, 13.37);', [1, Math.fround(13.37), 3, 4]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 2, 13.37);', [1, 2, Math.fround(13.37), 4]);
|
||||
CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 3, 13.37);', [1, 2, 3, Math.fround(13.37)]);
|
||||
|
||||
const WXI = 'var w = i4.withX;';
|
||||
const WYI = 'var w = i4.withY;';
|
||||
const WZI = 'var w = i4.withZ;';
|
||||
const WWI = 'var w = i4.withW;';
|
||||
CheckI4(WXI, 'var x = i4(1,2,3,4); x = w(x, 42);', [42, 2, 3, 4]);
|
||||
CheckI4(WYI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 42, 3, 4]);
|
||||
CheckI4(WZI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 2, 42, 4]);
|
||||
CheckI4(WWI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 2, 3, 42]);
|
||||
const RLI = 'var r = i4.replaceLane;';
|
||||
CheckI4(RLI, 'var x = i4(1,2,3,4); x = r(x, 0, 42);', [42, 2, 3, 4]);
|
||||
CheckI4(RLI, 'var x = i4(1,2,3,4); x = r(x, 1, 42);', [1, 42, 3, 4]);
|
||||
CheckI4(RLI, 'var x = i4(1,2,3,4); x = r(x, 2, 42);', [1, 2, 42, 4]);
|
||||
CheckI4(RLI, 'var x = i4(1,2,3,4); x = r(x, 3, 42);', [1, 2, 3, 42]);
|
||||
|
||||
// Comparisons
|
||||
// True yields all bits set to 1 (i.e as an int32, 0xFFFFFFFF === -1), false
|
||||
@ -1319,4 +1316,3 @@ asmLink(asmCompile('glob', 'ffi', code), this, assertEqFFI)();
|
||||
print('Error:', e)
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
@ -1,43 +0,0 @@
|
||||
// |jit-test| test-join=--no-unboxed-objects
|
||||
//
|
||||
// Unboxed object optimization might not trigger in all cases, thus we ensure
|
||||
// that Scalar Replacement optimization is working well independently of the
|
||||
// object representation.
|
||||
|
||||
// Ion eager fails the test below because we have not yet created any
|
||||
// template object in baseline before running the content of the top-level
|
||||
// function.
|
||||
if (getJitCompilerOptions()["ion.warmup.trigger"] <= 90)
|
||||
setJitCompilerOption("ion.warmup.trigger", 90);
|
||||
|
||||
// This test checks that we are able to remove the getprop & setprop with scalar
|
||||
// replacement, so we should not force inline caches, as this would skip the
|
||||
// generation of getprop & setprop instructions.
|
||||
if (getJitCompilerOptions()["ion.forceinlineCaches"])
|
||||
setJitCompilerOption("ion.forceinlineCaches", 0);
|
||||
|
||||
// Frequent GCs can interfere with the tests being performed here.
|
||||
if (typeof gczeal != "undefined")
|
||||
gczeal(0);
|
||||
|
||||
var arr = new Array();
|
||||
var max = 2000;
|
||||
for (var i=0; i < max; i++)
|
||||
arr[i] = i;
|
||||
|
||||
function f() {
|
||||
var res = 0;
|
||||
var nextObj;
|
||||
var itr = arr[Symbol.iterator]();
|
||||
do {
|
||||
nextObj = itr.next();
|
||||
if (nextObj.done)
|
||||
break;
|
||||
res += nextObj.value;
|
||||
assertRecoveredOnBailout(nextObj, true);
|
||||
} while (true);
|
||||
return res;
|
||||
}
|
||||
|
||||
for (var j = 0; j < 10; j++)
|
||||
assertEq(f(), max * (max - 1) / 2);
|
@ -28,8 +28,8 @@ function f(j) {
|
||||
i: i,
|
||||
v: i + i
|
||||
};
|
||||
assertRecoveredOnBailout(obj, true);
|
||||
assertRecoveredOnBailout(obj.v, true);
|
||||
assertRecoveredOnBailout(obj, false); // :TODO: Fixed by Bug 1165348
|
||||
assertRecoveredOnBailout(obj.v, false); // :TODO: Fixed by Bug 1165348
|
||||
if (uceFault(j) || uceFault(j)) {
|
||||
// MObjectState::recover should neither fail,
|
||||
// nor coerce its result to an int32.
|
||||
|
@ -50,9 +50,9 @@ function notSoEmpty1() {
|
||||
assertRecoveredOnBailout(c, true);
|
||||
assertRecoveredOnBailout(d, true);
|
||||
assertRecoveredOnBailout(unused, true);
|
||||
// The ucefault branch is not taken yet, and GVN removes it. Scalar
|
||||
// Replacement thus removes the creation of the object.
|
||||
assertRecoveredOnBailout(res, true);
|
||||
// Scalar Replacement is coming after the branch removal made by GVN, and
|
||||
// the ucefault branch is not taken yet.
|
||||
assertRecoveredOnBailout(res, false);
|
||||
}
|
||||
|
||||
// Check that we can recover objects with their content.
|
||||
@ -75,9 +75,9 @@ function notSoEmpty2(i) {
|
||||
assertRecoveredOnBailout(c, true);
|
||||
assertRecoveredOnBailout(d, true);
|
||||
assertRecoveredOnBailout(unused, true);
|
||||
// The ucefault branch is not taken yet, and GVN removes it. Scalar
|
||||
// Replacement thus removes the creation of the object.
|
||||
assertRecoveredOnBailout(res, true);
|
||||
// Scalar Replacement is coming after the branch removal made by GVN, and
|
||||
// the ucefault branch is not taken yet.
|
||||
assertRecoveredOnBailout(res, false);
|
||||
}
|
||||
|
||||
// Check that we can recover objects with their content.
|
||||
|
@ -1352,14 +1352,16 @@ OptimizeMIR(MIRGenerator* mir)
|
||||
return false;
|
||||
}
|
||||
|
||||
ValueNumberer gvn(mir, graph);
|
||||
if (!gvn.init())
|
||||
return false;
|
||||
if (mir->optimizationInfo().scalarReplacementEnabled()) {
|
||||
AutoTraceLog log(logger, TraceLogger_ScalarReplacement);
|
||||
if (!ScalarReplacement(mir, graph))
|
||||
return false;
|
||||
gs.spewPass("Scalar Replacement");
|
||||
AssertGraphCoherency(graph);
|
||||
|
||||
size_t doRepeatOptimizations = 0;
|
||||
repeatOptimizations:
|
||||
doRepeatOptimizations++;
|
||||
MOZ_ASSERT(doRepeatOptimizations <= 2);
|
||||
if (mir->shouldCancel("Scalar Replacement"))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mir->compilingAsmJS()) {
|
||||
AutoTraceLog log(logger, TraceLogger_ApplyTypes);
|
||||
@ -1395,6 +1397,10 @@ OptimizeMIR(MIRGenerator* mir)
|
||||
return false;
|
||||
}
|
||||
|
||||
ValueNumberer gvn(mir, graph);
|
||||
if (!gvn.init())
|
||||
return false;
|
||||
|
||||
// Alias analysis is required for LICM and GVN so that we don't move
|
||||
// loads across stores.
|
||||
if (mir->optimizationInfo().licmEnabled() ||
|
||||
@ -1410,50 +1416,7 @@ OptimizeMIR(MIRGenerator* mir)
|
||||
if (mir->shouldCancel("Alias analysis"))
|
||||
return false;
|
||||
|
||||
// We only eliminate dead resume point operands in the first pass
|
||||
// because it is currently unsound to do so after GVN.
|
||||
//
|
||||
// Consider the following example, where def1 dominates, and is
|
||||
// congruent with def4, and use3 dominates, and is congruent with,
|
||||
// use6.
|
||||
//
|
||||
// def1
|
||||
// nop2
|
||||
// resumepoint def1
|
||||
// use3 def1
|
||||
// def4
|
||||
// nop5
|
||||
// resumepoint def4
|
||||
// use6 def4
|
||||
// use7 use3 use6
|
||||
//
|
||||
// Assume that use3, use6, and use7 can cause OSI and are
|
||||
// non-effectful. That is, use3 will resume at nop2, and use6 and use7
|
||||
// will resume at nop5.
|
||||
//
|
||||
// After GVN, since def1 =~ def4, we have:
|
||||
//
|
||||
// def4 - replaced with def1 and pruned
|
||||
// use6 - replaced with use3 and pruned
|
||||
// use7 - renumbered to use5
|
||||
//
|
||||
// def1
|
||||
// nop2
|
||||
// resumepoint def1
|
||||
// use3 def1
|
||||
// nop4
|
||||
// resumepoint def1
|
||||
// use5 use3 use3
|
||||
//
|
||||
// nop4's resumepoint's operand of def1 is considered dead, because it
|
||||
// is dominated by the last use of def1, use3.
|
||||
//
|
||||
// However, if use5 causes OSI, we will resume at nop4's resume
|
||||
// point. The baseline frame at that point expects the now-pruned def4
|
||||
// to exist. However, since it was replaced with def1 by GVN, and def1
|
||||
// is dead at the point of nop4, the baseline frame incorrectly gets
|
||||
// an optimized out value.
|
||||
if (!mir->compilingAsmJS() && doRepeatOptimizations == 1) {
|
||||
if (!mir->compilingAsmJS()) {
|
||||
// Eliminating dead resume point operands requires basic block
|
||||
// instructions to be numbered. Reuse the numbering computed during
|
||||
// alias analysis.
|
||||
@ -1493,26 +1456,6 @@ OptimizeMIR(MIRGenerator* mir)
|
||||
}
|
||||
}
|
||||
|
||||
if (mir->optimizationInfo().scalarReplacementEnabled() && doRepeatOptimizations <= 1) {
|
||||
AutoTraceLog log(logger, TraceLogger_ScalarReplacement);
|
||||
bool success = false;
|
||||
if (!ScalarReplacement(mir, graph, &success))
|
||||
return false;
|
||||
gs.spewPass("Scalar Replacement");
|
||||
AssertGraphCoherency(graph);
|
||||
|
||||
if (mir->shouldCancel("Scalar Replacement"))
|
||||
return false;
|
||||
|
||||
// We got some success at removing objects allocation and removing the
|
||||
// loads and stores, unfortunately, this phase is terrible at keeping
|
||||
// the type consistency, so we re-run the Apply Type phase. As this
|
||||
// optimization folds loads and stores, it might also introduce new
|
||||
// opportunities for GVN and LICM, so re-run them as well.
|
||||
if (success)
|
||||
goto repeatOptimizations;
|
||||
}
|
||||
|
||||
if (mir->optimizationInfo().rangeAnalysisEnabled()) {
|
||||
AutoTraceLog log(logger, TraceLogger_RangeAnalysis);
|
||||
RangeAnalysis r(mir, graph);
|
||||
|
@ -817,8 +817,8 @@ class IonBuilder
|
||||
MSimdBinaryComp::Operation op, SimdTypeDescr::Type compType);
|
||||
InliningStatus inlineUnarySimd(CallInfo& callInfo, JSNative native,
|
||||
MSimdUnaryArith::Operation op, SimdTypeDescr::Type type);
|
||||
InliningStatus inlineSimdWith(CallInfo& callInfo, JSNative native, SimdLane lane,
|
||||
SimdTypeDescr::Type type);
|
||||
InliningStatus inlineSimdReplaceLane(CallInfo& callInfo, JSNative native,
|
||||
SimdTypeDescr::Type type);
|
||||
InliningStatus inlineSimdSplat(CallInfo& callInfo, JSNative native, SimdTypeDescr::Type type);
|
||||
InliningStatus inlineSimdShuffle(CallInfo& callInfo, JSNative native, SimdTypeDescr::Type type,
|
||||
unsigned numVectors, unsigned numLanes);
|
||||
|
@ -313,17 +313,10 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
|
||||
COMP_COMMONX4_TO_INT32X4_SIMD_OP(INLINE_SIMD_COMPARISON_)
|
||||
#undef INLINE_SIMD_COMPARISON_
|
||||
|
||||
#define INLINE_SIMD_SETTER_(LANE) \
|
||||
if (native == js::simd_int32x4_with##LANE) \
|
||||
return inlineSimdWith(callInfo, native, SimdLane::Lane##LANE, SimdTypeDescr::Int32x4); \
|
||||
if (native == js::simd_float32x4_with##LANE) \
|
||||
return inlineSimdWith(callInfo, native, SimdLane::Lane##LANE, SimdTypeDescr::Float32x4);
|
||||
|
||||
INLINE_SIMD_SETTER_(X)
|
||||
INLINE_SIMD_SETTER_(Y)
|
||||
INLINE_SIMD_SETTER_(Z)
|
||||
INLINE_SIMD_SETTER_(W)
|
||||
#undef INLINE_SIMD_SETTER_
|
||||
if (native == js::simd_int32x4_replaceLane)
|
||||
return inlineSimdReplaceLane(callInfo, native, SimdTypeDescr::Int32x4);
|
||||
if (native == js::simd_float32x4_replaceLane)
|
||||
return inlineSimdReplaceLane(callInfo, native, SimdTypeDescr::Float32x4);
|
||||
|
||||
if (native == js::simd_int32x4_not)
|
||||
return inlineUnarySimd(callInfo, native, MSimdUnaryArith::not_, SimdTypeDescr::Int32x4);
|
||||
@ -3216,17 +3209,22 @@ IonBuilder::inlineSimdSplat(CallInfo& callInfo, JSNative native, SimdTypeDescr::
|
||||
}
|
||||
|
||||
IonBuilder::InliningStatus
|
||||
IonBuilder::inlineSimdWith(CallInfo& callInfo, JSNative native, SimdLane lane,
|
||||
SimdTypeDescr::Type type)
|
||||
IonBuilder::inlineSimdReplaceLane(CallInfo& callInfo, JSNative native, SimdTypeDescr::Type type)
|
||||
{
|
||||
InlineTypedObject* templateObj = nullptr;
|
||||
if (!checkInlineSimd(callInfo, native, type, 2, &templateObj))
|
||||
if (!checkInlineSimd(callInfo, native, type, 3, &templateObj))
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
MDefinition* arg = callInfo.getArg(1);
|
||||
if (!arg->isConstantValue() || arg->type() != MIRType_Int32)
|
||||
return InliningStatus_NotInlined;
|
||||
int32_t lane = arg->constantValue().toInt32();
|
||||
if (lane < 0 || lane >= 4)
|
||||
return InliningStatus_NotInlined;
|
||||
// See comment in inlineBinarySimd
|
||||
MIRType mirType = SimdTypeDescrToMIRType(type);
|
||||
MSimdInsertElement* ins = MSimdInsertElement::New(alloc(), callInfo.getArg(0),
|
||||
callInfo.getArg(1), mirType, lane);
|
||||
callInfo.getArg(2), mirType, SimdLane(lane));
|
||||
return boxSimd(callInfo, ins, templateObj);
|
||||
}
|
||||
|
||||
|
@ -1389,14 +1389,6 @@ RObjectState::recover(JSContext* cx, SnapshotIterator& iter) const
|
||||
if (val.isUndefined())
|
||||
continue;
|
||||
|
||||
// In order to simplify the code, we do not have a
|
||||
// MStoreUnboxedBoolean, but we reuse the MStoreUnboxedScalar code.
|
||||
// This has a nasty side-effect of add a MTruncate which coerce the
|
||||
// boolean into an Int32. The following code check that if the
|
||||
// property was expected to be a boolean, then we coerce it here.
|
||||
if (properties[i].type == JSVAL_TYPE_BOOLEAN)
|
||||
val.setBoolean(val.toInt32() != 0);
|
||||
|
||||
id = NameToId(properties[i].name);
|
||||
ObjectOpResult result;
|
||||
|
||||
|
@ -1240,12 +1240,11 @@ ArrayMemoryView::visitArrayLength(MArrayLength* ins)
|
||||
}
|
||||
|
||||
bool
|
||||
ScalarReplacement(MIRGenerator* mir, MIRGraph& graph, bool* success)
|
||||
ScalarReplacement(MIRGenerator* mir, MIRGraph& graph)
|
||||
{
|
||||
EmulateStateOf<ObjectMemoryView> replaceObject(mir, graph);
|
||||
EmulateStateOf<ArrayMemoryView> replaceArray(mir, graph);
|
||||
bool addedPhi = false;
|
||||
*success = false;
|
||||
|
||||
for (ReversePostorderIterator block = graph.rpoBegin(); block != graph.rpoEnd(); block++) {
|
||||
if (mir->shouldCancel("Scalar Replacement (main loop)"))
|
||||
@ -1259,7 +1258,6 @@ ScalarReplacement(MIRGenerator* mir, MIRGraph& graph, bool* success)
|
||||
if (!replaceObject.run(view))
|
||||
return false;
|
||||
view.assertSuccess();
|
||||
*success = true;
|
||||
addedPhi = true;
|
||||
continue;
|
||||
}
|
||||
@ -1269,7 +1267,6 @@ ScalarReplacement(MIRGenerator* mir, MIRGraph& graph, bool* success)
|
||||
if (!replaceArray.run(view))
|
||||
return false;
|
||||
view.assertSuccess();
|
||||
*success = true;
|
||||
addedPhi = true;
|
||||
continue;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user