diff --git a/accessible/base/ARIAMap.cpp b/accessible/base/ARIAMap.cpp index 5a970c699374..8f348e30363e 100644 --- a/accessible/base/ARIAMap.cpp +++ b/accessible/base/ARIAMap.cpp @@ -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, diff --git a/accessible/base/Filters.cpp b/accessible/base/Filters.cpp index 57e7bd3a6bde..3bf36f143cf6 100644 --- a/accessible/base/Filters.cpp +++ b/accessible/base/Filters.cpp @@ -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 diff --git a/accessible/generic/ARIAGridAccessible.cpp b/accessible/generic/ARIAGridAccessible.cpp index 1aed0e6b9953..37b49312dfaf 100644 --- a/accessible/generic/ARIAGridAccessible.cpp +++ b/accessible/generic/ARIAGridAccessible.cpp @@ -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* aCells) { + if (IsARIARole(nsGkAtoms::table)) + return; + AccIterator rowIter(this, filters::GetRow); Accessible* row = nullptr; @@ -251,6 +272,9 @@ ARIAGridAccessible::SelectedCells(nsTArray* aCells) void ARIAGridAccessible::SelectedCellIndices(nsTArray* aCells) { + if (IsARIARole(nsGkAtoms::table)) + return; + uint32_t colCount = ColCount(); AccIterator rowIter(this, filters::GetRow); @@ -275,6 +299,9 @@ ARIAGridAccessible::SelectedCellIndices(nsTArray* aCells) void ARIAGridAccessible::SelectedColIndices(nsTArray* aCols) { + if (IsARIARole(nsGkAtoms::table)) + return; + uint32_t colCount = ColCount(); if (!colCount) return; @@ -309,6 +336,9 @@ ARIAGridAccessible::SelectedColIndices(nsTArray* aCols) void ARIAGridAccessible::SelectedRowIndices(nsTArray* 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* 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++; } diff --git a/accessible/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp index 857a2999f802..04ed5656d026 100644 --- a/accessible/generic/HyperTextAccessible.cpp +++ b/accessible/generic/HyperTextAccessible.cpp @@ -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 . + 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 children in the order index, base. + rel.AppendTarget(index); + rel.AppendTarget(base); + } + } + break; + default: + break; + } + + return rel; +} + void HyperTextAccessible::CacheChildren() { diff --git a/accessible/generic/HyperTextAccessible.h b/accessible/generic/HyperTextAccessible.h index 53a7aaee8f88..be31404f945e 100644 --- a/accessible/generic/HyperTextAccessible.h +++ b/accessible/generic/HyperTextAccessible.h @@ -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) diff --git a/accessible/mac/mozAccessible.h b/accessible/mac/mozAccessible.h index fff242ce4a63..dbe47afff391 100644 --- a/accessible/mac/mozAccessible.h +++ b/accessible/mac/mozAccessible.h @@ -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. diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm index 66adb2b2bbda..88a8b8cca012 100644 --- a/accessible/mac/mozAccessible.mm +++ b/accessible/mac/mozAccessible.mm @@ -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 + // 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); diff --git a/accessible/tests/mochitest/elm/test_MathMLSpec.html b/accessible/tests/mochitest/elm/test_MathMLSpec.html index acbf7ba8d06a..80f1e9e70daf 100644 --- a/accessible/tests/mochitest/elm/test_MathMLSpec.html +++ b/accessible/tests/mochitest/elm/test_MathMLSpec.html @@ -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 @@ 2 - x - 5 + x + 5 diff --git a/accessible/tests/mochitest/table/a11y.ini b/accessible/tests/mochitest/table/a11y.ini index bfd6995a766c..a37c1affa130 100644 --- a/accessible/tests/mochitest/table/a11y.ini +++ b/accessible/tests/mochitest/table/a11y.ini @@ -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] diff --git a/accessible/tests/mochitest/table/test_headers_ariatable.html b/accessible/tests/mochitest/table/test_headers_ariatable.html new file mode 100644 index 000000000000..d6d3b1a97251 --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_ariatable.html @@ -0,0 +1,96 @@ + + + + Table header information cells for ARIA table + + + + + + + + + + + + + Bug 1173364 + +

+ +
+  
+ +
+
+ col_1 + col_2 + col_3 +
+
+ row_1 + cell1 + cell2 +
+
+ row_2 + cell3 + cell4 +
+
+ + + diff --git a/accessible/tests/mochitest/tree/a11y.ini b/accessible/tests/mochitest/tree/a11y.ini index cafa06dd2916..62e499ac9c30 100644 --- a/accessible/tests/mochitest/tree/a11y.ini +++ b/accessible/tests/mochitest/tree/a11y.ini @@ -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] diff --git a/accessible/tests/mochitest/tree/test_aria_table.html b/accessible/tests/mochitest/tree/test_aria_table.html new file mode 100644 index 000000000000..64d3bd8916c5 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_table.html @@ -0,0 +1,63 @@ + + + + ARIA table tests + + + + + + + + + + + + + Bug 1173364 + +

+ +
+  
+ +
+
+
+
cell
+
+
+
+ + + diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 1676aba9ea13..7b41240169b1 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -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); diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp index 600054a3e00c..4cfee0c155dd 100644 --- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -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 usp = new URLSearchParams(nullptr); + UniquePtr 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 usp = new URLSearchParams(nullptr); - usp->ParseInput(Substring(aStr, 1, aStr.Length() - 1)); + UniquePtr 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 diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index feb4aa9db127..425b94a29c1c 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -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); }; /* diff --git a/dom/base/URLSearchParams.cpp b/dom/base/URLSearchParams.cpp index b5f3609baa64..84e0d7e0f695 100644 --- a/dom/base/URLSearchParams.cpp +++ b/dom/base/URLSearchParams.cpp @@ -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 aGivenProto) -{ - return URLSearchParamsBinding::Wrap(aCx, this, aGivenProto); -} - -/* static */ already_AddRefed -URLSearchParams::Constructor(const GlobalObject& aGlobal, - const nsAString& aInit, - ErrorResult& aRv) -{ - nsRefPtr sp = new URLSearchParams(nullptr); - sp->ParseInput(NS_ConvertUTF16toUTF8(aInit)); - return sp.forget(); -} - -/* static */ already_AddRefed -URLSearchParams::Constructor(const GlobalObject& aGlobal, - URLSearchParams& aInit, - ErrorResult& aRv) -{ - nsRefPtr 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& 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& 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 aGivenProto) +{ + return URLSearchParamsBinding::Wrap(aCx, this, aGivenProto); +} + +/* static */ already_AddRefed +URLSearchParams::Constructor(const GlobalObject& aGlobal, + const nsAString& aInit, + ErrorResult& aRv) +{ + nsRefPtr sp = new URLSearchParams(nullptr); + sp->ParseInput(NS_ConvertUTF16toUTF8(aInit)); + return sp.forget(); +} + +/* static */ already_AddRefed +URLSearchParams::Constructor(const GlobalObject& aGlobal, + URLSearchParams& aInit, + ErrorResult& aRv) +{ + nsRefPtr 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& 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() { diff --git a/dom/base/URLSearchParams.h b/dom/base/URLSearchParams.h index ce54707e7dba..f32a02d471fe 100644 --- a/dom/base/URLSearchParams.h +++ b/dom/base/URLSearchParams.h @@ -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& 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 mParams; + nsCOMPtr 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& aRetval); + void GetAll(const nsAString& aName, nsTArray& 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 mSearchParams; - + UniquePtr mParams; nsRefPtr mObserver; - nsCOMPtr mDecoder; }; } // namespace dom diff --git a/dom/base/nsContentSink.cpp b/dom/base/nsContentSink.cpp index 2360bcbf8d05..5caa9ffbc8c5 100644 --- a/dom/base/nsContentSink.cpp +++ b/dom/base/nsContentSink.cpp @@ -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)); } } diff --git a/dom/base/nsContentSink.h b/dom/base/nsContentSink.h index fbc83ff2025e..21b63e29b4fa 100644 --- a/dom/base/nsContentSink.h +++ b/dom/base/nsContentSink.h @@ -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 diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 23abc0a53433..c5f50a269af3 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -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 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 docCurURI = aDoc->GetDocumentURI(); + nsCOMPtr docOrigURI = aDoc->GetOriginalURI(); + + nsCOMPtr 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); +} \ No newline at end of file diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index e2e676e9ed39..c653af3bb1a8 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -2406,6 +2406,25 @@ public: static already_AddRefed 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(); diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index b479677b8a20..1d71d694c6d7 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -9763,8 +9763,26 @@ nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr, } void -nsDocument::MaybePreconnect(nsIURI* uri) +nsDocument::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) { + nsCOMPtr 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 diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 61d1910806d2..9b65fae68484 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -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, diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index dc5d8682578e..64513d9cf33b 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -31,6 +31,7 @@ #include "nsClassHashtable.h" #include "prclist.h" #include "mozilla/UniquePtr.h" +#include "mozilla/CORSMode.h" #include // 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 diff --git a/dom/base/nsXMLHttpRequest.cpp b/dom/base/nsXMLHttpRequest.cpp index cd39cd533966..c9b3cd52bd79 100644 --- a/dom/base/nsXMLHttpRequest.cpp +++ b/dom/base/nsXMLHttpRequest.cpp @@ -2682,50 +2682,10 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable& 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 principalURI; - mPrincipal->GetURI(getter_AddRefs(principalURI)); - - nsIScriptContext* sc = GetContextForEventHandlers(&rv); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr doc = - nsContentUtils::GetDocumentFromScriptContext(sc); - - nsCOMPtr docCurURI; - nsCOMPtr docOrigURI; - net::ReferrerPolicy referrerPolicy = net::RP_Default; - - if (doc) { - docCurURI = doc->GetDocumentURI(); - docOrigURI = doc->GetOriginalURI(); - referrerPolicy = doc->GetReferrerPolicy(); - } - - nsCOMPtr 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 owner = GetOwner(); + nsCOMPtr doc = owner ? owner->GetExtantDoc() : nullptr; + nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal, doc, + httpChannel); } // Some extensions override the http protocol handler and provide their own diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 829f6b8a9be5..4e8e09774443 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -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}) diff --git a/dom/cache/CacheTypes.ipdlh b/dom/cache/CacheTypes.ipdlh index bea27022f195..bbff425d080c 100644 --- a/dom/cache/CacheTypes.ipdlh +++ b/dom/cache/CacheTypes.ipdlh @@ -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 diff --git a/dom/cache/Context.cpp b/dom/cache/Context.cpp index e9caf3a4b952..d6dccf5ace07 100644 --- a/dom/cache/Context.cpp +++ b/dom/cache/Context.cpp @@ -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::CreateThreadsafeHandle() { diff --git a/dom/cache/Context.h b/dom/cache/Context.h index 4dfe7c9fd1ea..19856a7e1688 100644 --- a/dom/cache/Context.h +++ b/dom/cache/Context.h @@ -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 mTarget; nsRefPtr mData; State mState; + bool mOrphanedData; QuotaInfo mQuotaInfo; nsRefPtr mInitRunnable; nsTArray mPendingActions; diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp index ea84b90335f6..4dd444ed16c3 100644 --- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -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& aOrphanedListOut) +{ + nsCOMPtr 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& aBodyIdListOut) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConn); + + nsCOMPtr 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( diff --git a/dom/cache/DBSchema.h b/dom/cache/DBSchema.h index 6f94deb362f2..86fa097bf21e 100644 --- a/dom/cache/DBSchema.h +++ b/dom/cache/DBSchema.h @@ -48,6 +48,13 @@ nsresult IsCacheOrphaned(mozIStorageConnection* aConn, CacheId aCacheId, bool* aOrphanedOut); +nsresult +FindOrphanedCacheIds(mozIStorageConnection* aConn, + nsTArray& aOrphanedListOut); + +nsresult +GetKnownBodyIds(mozIStorageConnection* aConn, nsTArray& aBodyIdListOut); + nsresult CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId, const CacheRequest& aRequest, const CacheQueryParams& aParams, diff --git a/dom/cache/FileUtils.cpp b/dom/cache/FileUtils.cpp index 665cd0d1c06d..82b9c14c5ffa 100644 --- a/dom/cache/FileUtils.cpp +++ b/dom/cache/FileUtils.cpp @@ -319,6 +319,182 @@ BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType, } // anonymous namespace +nsresult +BodyDeleteOrphanedFiles(nsIFile* aBaseDir, nsTArray& 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 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 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 entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr 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 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 subEntry; + rv = subEntries->GetNext(getter_AddRefs(subEntry)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr 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 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 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 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 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 diff --git a/dom/cache/FileUtils.h b/dom/cache/FileUtils.h index f6d7ab6c3dfb..e7389abeaed0 100644 --- a/dom/cache/FileUtils.h +++ b/dom/cache/FileUtils.h @@ -50,6 +50,18 @@ BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId, nsresult BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray& aIdList); +nsresult +BodyDeleteOrphanedFiles(nsIFile* aBaseDir, nsTArray& aKnownBodyIdList); + +nsresult +CreateMarkerFile(const QuotaInfo& aQuotaInfo); + +nsresult +DeleteMarkerFile(const QuotaInfo& aQuotaInfo); + +bool +MarkerFileExists(const QuotaInfo& aQuotaInfo); + } // namespace cache } // namespace dom } // namespace mozilla diff --git a/dom/cache/Manager.cpp b/dom/cache/Manager.cpp index a4f81f7e1715..5878dd106bb7 100644 --- a/dom/cache/Manager.cpp +++ b/dom/cache/Manager.cpp @@ -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 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 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 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 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 = 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 = 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 = mContext; - if (orphaned && context && !context->IsCanceled()) { - context->CancelForCacheId(aCacheId); - nsRefPtr 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 = 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 = mContext; - if (orphaned && context && !context->IsCanceled()) { - nsRefPtr 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 = new DeleteOrphanedBodyAction(aBodyId); + context->Dispatch(action); + } } } MaybeAllowContextToClose(); diff --git a/dom/cache/QuotaClient.cpp b/dom/cache/QuotaClient.cpp index 8eb7689cabd3..ed571b1ace4a 100644 --- a/dom/cache/QuotaClient.cpp +++ b/dom/cache/QuotaClient.cpp @@ -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; } diff --git a/dom/cache/TypeUtils.cpp b/dom/cache/TypeUtils.cpp index a9fecca15f92..eb5f5f45c8c8 100644 --- a/dom/cache/TypeUtils.cpp +++ b/dom/cache/TypeUtils.cpp @@ -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 info(new mozilla::ipc::PrincipalInfo(aIn.principalInfo().get_PrincipalInfo())); + ir->SetPrincipalInfo(Move(info)); + } nsCOMPtr stream = ReadStream::Create(aIn.body()); ir->SetBody(stream); diff --git a/dom/cache/test/mochitest/mochitest.ini b/dom/cache/test/mochitest/mochitest.ini index 11775f8b35c0..eaa9157874fc 100644 --- a/dom/cache/test/mochitest/mochitest.ini +++ b/dom/cache/test/mochitest/mochitest.ini @@ -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] diff --git a/dom/cache/test/mochitest/test_cache_orphaned_body.html b/dom/cache/test/mochitest/test_cache_orphaned_body.html new file mode 100644 index 000000000000..f072c20c64cf --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_orphaned_body.html @@ -0,0 +1,178 @@ + + + + + Test Cache with QuotaManager Restart + + + + + + + + diff --git a/dom/cache/test/mochitest/test_cache_orphaned_cache.html b/dom/cache/test/mochitest/test_cache_orphaned_cache.html new file mode 100644 index 000000000000..2a4ed5c013aa --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_orphaned_cache.html @@ -0,0 +1,171 @@ + + + + + Test Cache with QuotaManager Restart + + + + + + + + diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp index 7c7046f01e39..041906297312 100644 --- a/dom/events/Event.cpp +++ b/dom/events/Event.cpp @@ -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, diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index e7b542f4d52c..e61d0339593d 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -217,11 +217,6 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, nsRefPtr r = request->GetInternalRequest(); - aRv = UpdateRequestReferrer(aGlobal, r); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(aGlobal); nsCOMPtr 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 window = do_QueryInterface(aGlobal); - if (window) { - nsCOMPtr 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 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; diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index 44f5dc3ad449..9315b178c865 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -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 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 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(); } diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp index ae67917dd807..4ff6d8d5c962 100644 --- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -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::Clone() { @@ -73,5 +79,41 @@ InternalResponse::CORSResponse() return cors.forget(); } +void +InternalResponse::SetPrincipalInfo(UniquePtr aPrincipalInfo) +{ + mPrincipalInfo = Move(aPrincipalInfo); +} + +already_AddRefed +InternalResponse::OpaqueResponse() +{ + MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response"); + nsRefPtr response = new InternalResponse(0, EmptyCString()); + response->mType = ResponseType::Opaque; + response->mTerminationReason = mTerminationReason; + response->mURL = mURL; + response->mChannelInfo = mChannelInfo; + if (mPrincipalInfo) { + response->mPrincipalInfo = MakeUnique(*mPrincipalInfo); + } + response->mWrappedResponse = this; + return response.forget(); +} + +already_AddRefed +InternalResponse::CreateIncompleteCopy() +{ + nsRefPtr copy = new InternalResponse(mStatus, mStatusText); + copy->mType = mType; + copy->mTerminationReason = mTerminationReason; + copy->mURL = mURL; + copy->mChannelInfo = mChannelInfo; + if (mPrincipalInfo) { + copy->mPrincipalInfo = MakeUnique(*mPrincipalInfo); + } + return copy.forget(); +} + } // namespace dom } // namespace mozilla diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h index 5262e04f53a9..769a560d9c65 100644 --- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -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 - OpaqueResponse() - { - MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response"); - nsRefPtr 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 BasicResponse(); @@ -174,9 +169,18 @@ public: return mChannelInfo; } + const UniquePtr& + GetPrincipalInfo() const + { + return mPrincipalInfo; + } + + // Takes ownership of the principal info. + void + SetPrincipalInfo(UniquePtr 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 CreateIncompleteCopy() - { - nsRefPtr copy = new InternalResponse(mStatus, mStatusText); - copy->mType = mType; - copy->mTerminationReason = mTerminationReason; - copy->mURL = mURL; - copy->mChannelInfo = mChannelInfo; - return copy.forget(); - } + already_AddRefed CreateIncompleteCopy(); ResponseType mType; nsCString mTerminationReason; @@ -202,6 +198,7 @@ private: nsRefPtr mHeaders; nsCOMPtr mBody; ChannelInfo mChannelInfo; + UniquePtr mPrincipalInfo; // For filtered responses. // Cache, and SW interception should always serialize/access the underlying diff --git a/dom/fetch/Response.h b/dom/fetch/Response.h index 5692d70eae7e..81fb6fcf44c9 100644 --- a/dom/fetch/Response.h +++ b/dom/fetch/Response.h @@ -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& + GetPrincipalInfo() const + { + return mInternalResponse->GetPrincipalInfo(); + } + Headers* Headers_(); void diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp index 0f2884d45e52..9583c3d0bdcf 100644 --- a/dom/html/HTMLLinkElement.cpp +++ b/dom/html/HTMLLinkElement.cpp @@ -316,7 +316,7 @@ HTMLLinkElement::UpdatePreconnect() if (owner) { nsCOMPtr uri = GetHrefURI(); if (uri) { - owner->MaybePreconnect(uri); + owner->MaybePreconnect(uri, GetCORSMode()); } } } diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 456a3078cd28..8d4e8f3b24aa 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -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 played(Played()); + if (played->Length() == 0 + && !IsAutoplayEnabled() + && !EventStateManager::IsHandlingUserInput() + && !nsContentUtils::IsCallerChrome()) { LOG(LogLevel::Debug, ("%p Blocked attempt to autoplay media.", this)); return; } diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index f6661c7bb93f..c7554a30b23a 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -896,70 +896,104 @@ IDBDatabase::AbortTransactions(bool aShouldWarn) class MOZ_STACK_CLASS Helper final { + typedef nsAutoTArray, 20> StrongTransactionArray; + typedef nsAutoTArray WeakTransactionArray; + public: static void - AbortTransactions(nsTHashtable>& aTable, - nsTArray>& aAbortedTransactions) + AbortTransactions(IDBDatabase* aDatabase, const bool aShouldWarn) { - const uint32_t count = aTable.Count(); - if (!count) { + MOZ_ASSERT(aDatabase); + aDatabase->AssertIsOnOwningThread(); + + nsTHashtable>& transactionTable = + aDatabase->mTransactions; + + if (!transactionTable.Count()) { return; } - nsAutoTArray, 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 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& 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* aTransaction, void* aClosure) + Collect(nsPtrHashKey* aTransactionKey, void* aClosure) { - MOZ_ASSERT(aTransaction); - aTransaction->GetKey()->AssertIsOnOwningThread(); + MOZ_ASSERT(aTransactionKey); MOZ_ASSERT(aClosure); - auto* array = static_cast>*>(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(aClosure); + array->AppendElement(transaction); + } return PL_DHASH_NEXT; } }; - nsAutoTArray, 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& transaction = abortedTransactions[index]; - MOZ_ASSERT(transaction); - - nsString filename; - uint32_t lineNo; - transaction->GetCallerLocation(filename, &lineNo); - - LogWarning(kWarningMessage, filename, lineNo); - } - } + Helper::AbortTransactions(this, aShouldWarn); } PBackgroundIDBDatabaseFileChild* diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 248bef6e2ab3..91bff962552f 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -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; } diff --git a/dom/indexedDB/IDBTransaction.cpp b/dom/indexedDB/IDBTransaction.cpp index 90d6f2820a55..bf7238231b70 100644 --- a/dom/indexedDB/IDBTransaction.cpp +++ b/dom/indexedDB/IDBTransaction.cpp @@ -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 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 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 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 transaction = mTransaction; - mTransaction = nullptr; + nsRefPtr 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; diff --git a/dom/indexedDB/IDBTransaction.h b/dom/indexedDB/IDBTransaction.h index 498dd95ccf67..749c6474060f 100644 --- a/dom/indexedDB/IDBTransaction.h +++ b/dom/indexedDB/IDBTransaction.h @@ -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 diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index 52266a3b2e57..f757475b0c6e 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -469,7 +469,7 @@ protected: // IME static TabParent *mIMETabParent; - ContentCache mContentCache; + ContentCacheInParent mContentCache; nsIntRect mRect; ScreenIntSize mDimensions; diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js index a5588ed8132f..1187bdb8e3c2 100644 --- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -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 => { diff --git a/dom/media/tests/mochitest/test_peerConnection_bug825703.html b/dom/media/tests/mochitest/test_peerConnection_bug825703.html index b1aca12dea84..a11dd143617c 100644 --- a/dom/media/tests/mochitest/test_peerConnection_bug825703.html +++ b/dom/media/tests/mochitest/test_peerConnection_bug825703.html @@ -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); }); diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html index 5c00abe7a3eb..df306066acfb 100644 --- a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html +++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html @@ -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; }); diff --git a/dom/media/webm/AudioDecoder.cpp b/dom/media/webm/AudioDecoder.cpp new file mode 100644 index 000000000000..c629728c168c --- /dev/null +++ b/dom/media/webm/AudioDecoder.cpp @@ -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(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 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 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 mReader; + + // Opus decoder state + nsAutoPtr 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(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 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(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((gain_Q16*buffer[i] + 32768)>>16); + buffer[i] = static_cast(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 diff --git a/dom/media/webm/IntelWebMVideoDecoder.cpp b/dom/media/webm/IntelWebMVideoDecoder.cpp index 7f17f120da9c..448c238ef48c 100644 --- a/dom/media/webm/IntelWebMVideoDecoder.cpp +++ b/dom/media/webm/IntelWebMVideoDecoder.cpp @@ -200,7 +200,7 @@ IntelWebMVideoDecoder::Demux(nsRefPtr& 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); diff --git a/dom/media/webm/SoftwareWebMVideoDecoder.cpp b/dom/media/webm/SoftwareWebMVideoDecoder.cpp index fa395a51354c..ce89cd948aa0 100644 --- a/dom/media/webm/SoftwareWebMVideoDecoder.cpp +++ b/dom/media/webm/SoftwareWebMVideoDecoder.cpp @@ -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); diff --git a/dom/media/webm/WebMReader.cpp b/dom/media/webm/WebMReader.cpp index 638e0f6a2f42..d4c955e331be 100644 --- a/dom/media/webm/WebMReader.cpp +++ b/dom/media/webm/WebMReader.cpp @@ -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(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 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 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(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((gain_Q16*buffer[i] + 32768)>>16); - buffer[i] = static_cast(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; } diff --git a/dom/media/webm/WebMReader.h b/dom/media/webm/WebMReader.h index 27da0842cfec..f296dacf4d06 100644 --- a/dom/media/webm/WebMReader.h +++ b/dom/media/webm/WebMReader.h @@ -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 mAudioDecoder; nsAutoPtr mVideoDecoder; - // Vorbis decoder state - vorbis_info mVorbisInfo; - vorbis_comment mVorbisComment; - vorbis_dsp_state mVorbisDsp; - vorbis_block mVorbisBlock; - int64_t mPacketCount; - - // Opus decoder state - nsAutoPtr 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 diff --git a/dom/media/webm/moz.build b/dom/media/webm/moz.build index 159752721c26..2dd1c888ad3b 100644 --- a/dom/media/webm/moz.build +++ b/dom/media/webm/moz.build @@ -13,6 +13,7 @@ EXPORTS += [ ] UNIFIED_SOURCES += [ + 'AudioDecoder.cpp', 'SoftwareWebMVideoDecoder.cpp', 'WebMBufferedParser.cpp', 'WebMDecoder.cpp', diff --git a/dom/plugins/base/nsIPluginInstanceOwner.idl b/dom/plugins/base/nsIPluginInstanceOwner.idl index be62e6a719d6..0a1d53d230a1 100644 --- a/dom/plugins/base/nsIPluginInstanceOwner.idl +++ b/dom/plugins/base/nsIPluginInstanceOwner.idl @@ -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; %} diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp index c2782e70fffe..f9791401bd31 100644 --- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -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 diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index 795316b2bbe3..771a5a7e8812 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -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 diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp index be2b0d865afd..3b5442991c8a 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.cpp +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -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) { diff --git a/dom/plugins/base/nsNPAPIPluginInstance.h b/dom/plugins/base/nsNPAPIPluginInstance.h index f00b28d52f0f..bba7463b68b0 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.h +++ b/dom/plugins/base/nsNPAPIPluginInstance.h @@ -116,7 +116,6 @@ public: nsresult GetJSContext(JSContext* *outContext); nsPluginInstanceOwner* GetOwner(); void SetOwner(nsPluginInstanceOwner *aOwner); - nsresult ShowStatus(const char* message); nsNPAPIPlugin* GetPlugin(); diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index 932ba4186473..07261e20710b 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -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 docShellItem = mPluginFrame->PresContext()->GetDocShell(); - if (NS_FAILED(rv) || !docShellItem) { - return rv; - } - - nsCOMPtr treeOwner; - rv = docShellItem->GetTreeOwner(getter_AddRefs(treeOwner)); - if (NS_FAILED(rv) || !treeOwner) { - return rv; - } - - nsCOMPtr 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 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, diff --git a/dom/plugins/base/nsPluginInstanceOwner.h b/dom/plugins/base/nsPluginInstanceOwner.h index 5ed489b57f81..3c105eaf3c60 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.h +++ b/dom/plugins/base/nsPluginInstanceOwner.h @@ -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; diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index fe0cef069f99..e43a0e55808f 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -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 diff --git a/dom/tests/mochitest/fetch/reroute.js b/dom/tests/mochitest/fetch/reroute.js index a37d5d81c80a..e117c12e99f6 100644 --- a/dom/tests/mochitest/fetch/reroute.js +++ b/dom/tests/mochitest/fetch/reroute.js @@ -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)); }; diff --git a/dom/tests/mochitest/fetch/test_fetch_cors.js b/dom/tests/mochitest/fetch/test_fetch_cors.js index eec910bc64cb..d9b74912ba31 100644 --- a/dom/tests/mochitest/fetch/test_fetch_cors.js +++ b/dom/tests/mochitest/fetch/test_fetch_cors.js @@ -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. } diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp index aaefcba69a05..fac6a9b7c736 100644 --- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -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 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 mPump; nsCOMPtr mBaseURI; ChannelInfo mChannelInfo; + UniquePtr 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 channelPrincipal; + nsresult rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + channel->Cancel(rv); + return rv; + } + + UniquePtr 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 = 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 aPrincipalInfo) { AssertIsOnMainThread(); MOZ_ASSERT(aIndex < mLoadInfos.Length()); @@ -1054,14 +1086,19 @@ private: mWorkerPrivate->SetBaseURI(finalURI); } - nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); + DebugOnly principal = mWorkerPrivate->GetPrincipal(); MOZ_ASSERT(principal); nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup(); MOZ_ASSERT(loadGroup); + + nsCOMPtr responsePrincipal = + PrincipalInfoToPrincipal(*aPrincipalInfo); + DebugOnly 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 inputStream; response->GetBody(getter_AddRefs(inputStream)); mChannelInfo = response->GetChannelInfo(); + const UniquePtr& pInfo = + response->GetPrincipalInfo(); + if (pInfo) { + mPrincipalInfo = MakeUnique(*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; } diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index e8e82489922d..222f1e61958f 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -3303,6 +3303,7 @@ class FetchEventRunnable : public WorkerRunnable RequestCredentials mRequestCredentials; nsContentPolicyType mContentPolicyType; nsCOMPtr mUploadStream; + nsCString mReferrer; public: FetchEventRunnable(WorkerPrivate* aWorkerPrivate, nsMainThreadPtrHandle& 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 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 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); diff --git a/dom/workers/ServiceWorkerScriptCache.cpp b/dom/workers/ServiceWorkerScriptCache.cpp index 168d80c92a73..bc3269bfaf95 100644 --- a/dom/workers/ServiceWorkerScriptCache.cpp +++ b/dom/workers/ServiceWorkerScriptCache.cpp @@ -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 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 channelPrincipal; + nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + UniquePtr 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 = new Response(aCache->GetGlobalObject(), ir); @@ -579,6 +612,8 @@ private: ChannelInfo mChannelInfo; + UniquePtr 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; } diff --git a/dom/workers/test/mochitest.ini b/dom/workers/test/mochitest.ini index 3c262588da26..737dcf168dcf 100644 --- a/dom/workers/test/mochitest.ini +++ b/dom/workers/test/mochitest.ini @@ -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] diff --git a/dom/workers/test/referrer.sjs b/dom/workers/test/referrer.sjs new file mode 100644 index 000000000000..0497bb729654 --- /dev/null +++ b/dom/workers/test/referrer.sjs @@ -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")); + } +} + diff --git a/dom/workers/test/test_referrer.html b/dom/workers/test/test_referrer.html new file mode 100644 index 000000000000..9f7dacc36e25 --- /dev/null +++ b/dom/workers/test/test_referrer.html @@ -0,0 +1,35 @@ + + + + + Test the referrer of workers + + + + +

+ +

+
+
+
+
diff --git a/editor/libeditor/nsEditorEventListener.cpp b/editor/libeditor/nsEditorEventListener.cpp
index a0816dec5a59..7594f83a920e 100644
--- a/editor/libeditor/nsEditorEventListener.cpp
+++ b/editor/libeditor/nsEditorEventListener.cpp
@@ -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 target;
   aEvent->GetTarget(getter_AddRefs(target));
diff --git a/gfx/2d/PathHelpers.h b/gfx/2d/PathHelpers.h
index d540a7793fde..9fe882984065 100644
--- a/gfx/2d/PathHelpers.h
+++ b/gfx/2d/PathHelpers.h
@@ -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);
diff --git a/gfx/layers/apz/test/apz_test_native_event_utils.js b/gfx/layers/apz/test/apz_test_native_event_utils.js
index efe887b53cfd..4ff622449163 100644
--- a/gfx/layers/apz/test/apz_test_native_event_utils.js
+++ b/gfx/layers/apz/test/apz_test_native_event_utils.js
@@ -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;
 }
 
diff --git a/gfx/layers/apz/test/test_bug1151667.html b/gfx/layers/apz/test/test_bug1151667.html
index 7437a5220cb4..4f0dcea9349e 100644
--- a/gfx/layers/apz/test/test_bug1151667.html
+++ b/gfx/layers/apz/test/test_bug1151667.html
@@ -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() {
diff --git a/gfx/layers/apz/test/test_layerization.html b/gfx/layers/apz/test/test_layerization.html
index 63d455d0e2cc..b258920130eb 100644
--- a/gfx/layers/apz/test/test_layerization.html
+++ b/gfx/layers/apz/test/test_layerization.html
@@ -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
diff --git a/gfx/layers/apz/test/test_smoothness.html b/gfx/layers/apz/test/test_smoothness.html
index a1c5bfe5b8db..ec0bd5785098 100644
--- a/gfx/layers/apz/test/test_smoothness.html
+++ b/gfx/layers/apz/test/test_smoothness.html
@@ -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
diff --git a/gfx/layers/d3d11/TextureD3D11.cpp b/gfx/layers/d3d11/TextureD3D11.cpp
index 42ec2f5e587f..76266ef116a1 100644
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -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,
diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp
index cd27ee6c6420..3b20059bf3bc 100644
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -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 mBlur;
-  IntPoint mTopLeft;
-  gfxRect mDirtyRect;
+  IntMargin mExtendDest;
   BlurCacheKey mKey;
 };
 
@@ -259,19 +274,17 @@ class BlurCache final : public nsExpirationTracker
       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
 
 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
+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 roundedRect =
+      MakePathForRoundedRect(*blurDT, Rect(minRect), *aCornerRadii);
+    blurDT->Fill(roundedRect, black);
+  } else {
+    blurDT->FillRect(Rect(minRect), black);
+  }
+
+  IntPoint topLeft;
+  RefPtr 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
+CreateBoxShadow(DrawTarget& aDT, SourceSurface* aBlurMask, const gfxRGBA& aShadowColor)
+{
+  IntSize blurredSize = aBlurMask->GetSize();
+  gfxPlatform* platform = gfxPlatform::GetPlatform();
+  RefPtr 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 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 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 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 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 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();
 }
 
diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp
index d86f4ca41fa1..a531ec421432 100644
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -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(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(f, call, AsmJSSimdType_int32x4, opType, def, type);
diff --git a/js/src/builtin/SIMD.cpp b/js/src/builtin/SIMD.cpp
index a7508d6e52e9..acb7e07ba3aa 100644
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -614,22 +614,6 @@ template
 struct Or {
     static T apply(T l, T r) { return l | r; }
 };
-template
-struct WithX {
-    static T apply(int32_t lane, T scalar, T x) { return lane == 0 ? scalar : x; }
-};
-template
-struct WithY {
-    static T apply(int32_t lane, T scalar, T x) { return lane == 1 ? scalar : x; }
-};
-template
-struct WithZ {
-    static T apply(int32_t lane, T scalar, T x) { return lane == 2 ? scalar : x; }
-};
-template
-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(cx, argc, vp);
 }
 
-template class OpWith>
+template
 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(args[0]))
+    // Only the first and second arguments are mandatory
+    if (args.length() < 2 || !IsVectorObject(args[0]))
         return ErrorBadArgs(cx);
 
     Elem* vec = TypedObjectMemory(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::apply(i, value, vec[i]);
+        result[i] = i == lane ? value : vec[i];
     return StoreResult(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
-
diff --git a/js/src/builtin/SIMD.h b/js/src/builtin/SIMD.h
index 1baf3d8b9504..fd1722f4d4e2 100644
--- a/js/src/builtin/SIMD.h
+++ b/js/src/builtin/SIMD.h
@@ -59,15 +59,12 @@
   V(store2, (Store), 3)                                                 \
   V(store1, (Store), 3)                                                 \
   V(sub, (BinaryFunc), 2)                                  \
-  V(withX, (FuncWith), 2)                                           \
-  V(withY, (FuncWith), 2)                                           \
-  V(withZ, (FuncWith), 2)                                           \
-  V(withW, (FuncWith), 2)                                           \
   V(xor, (CoercedBinaryFunc), 2)
 
 #define FLOAT32X4_TERNARY_FUNCTION_LIST(V)                                            \
   V(bitselect, BitSelect, 3)                                               \
   V(clamp, Clamp, 3)                                                       \
+  V(replaceLane, (ReplaceLane), 3)                                         \
   V(select, Select, 3)
 
 #define FLOAT32X4_SHUFFLE_FUNCTION_LIST(V)                                            \
@@ -111,13 +108,12 @@
   V(notEqual, (CompareFunc), 2)                                  \
   V(store,  (Store), 3)                                                 \
   V(store1, (Store), 3)                                                 \
-  V(sub, (BinaryFunc), 2)                                  \
-  V(withX, (FuncWith), 2)                                           \
-  V(withY, (FuncWith), 2)
+  V(sub, (BinaryFunc), 2)
 
 #define FLOAT64X2_TERNARY_FUNCTION_LIST(V)                                            \
   V(bitselect, BitSelect, 3)                                               \
   V(clamp, Clamp, 3)                                                       \
+  V(replaceLane, (ReplaceLane), 3)                                         \
   V(select, Select, 3)
 
 #define FLOAT64X2_SHUFFLE_FUNCTION_LIST(V)                                            \
@@ -163,14 +159,11 @@
   V(store3, (Store), 3)                                                   \
   V(store2, (Store), 3)                                                   \
   V(store1, (Store), 3)                                                   \
-  V(withX, (FuncWith), 2)                                             \
-  V(withY, (FuncWith), 2)                                             \
-  V(withZ, (FuncWith), 2)                                             \
-  V(withW, (FuncWith), 2)                                             \
   V(xor, (BinaryFunc), 2)
 
 #define INT32X4_TERNARY_FUNCTION_LIST(V)                                              \
   V(bitselect, BitSelect, 3)                                                 \
+  V(replaceLane, (ReplaceLane), 3)                                           \
   V(select, Select, 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)                         \
diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp
index 1239671bbd29..96e1d1507c5b 100644
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1973,14 +1973,18 @@ js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind
         }
     }
 
-    if (src->is()) {
-        InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
-    } else if (src->is()) {
-        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::objectMovedDuringMinorGC(this, dst, src);
+        } else if (src->is()) {
+            tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
+        } else if (src->is()) {
+            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;
diff --git a/js/src/jit-test/tests/SIMD/replacelane.js b/js/src/jit-test/tests/SIMD/replacelane.js
new file mode 100644
index 000000000000..178508eddeb4
--- /dev/null
+++ b/js/src/jit-test/tests/SIMD/replacelane.js
@@ -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();
diff --git a/js/src/jit-test/tests/SIMD/with.js b/js/src/jit-test/tests/SIMD/with.js
deleted file mode 100644
index 30c53e23ed4a..000000000000
--- a/js/src/jit-test/tests/SIMD/with.js
+++ /dev/null
@@ -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();
-
diff --git a/js/src/jit-test/tests/asm.js/testSIMD.js b/js/src/jit-test/tests/asm.js/testSIMD.js
index 294fa14aeed4..301ca294a83e 100644
--- a/js/src/jit-test/tests/asm.js/testSIMD.js
+++ b/js/src/jit-test/tests/asm.js/testSIMD.js
@@ -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;
 }
-
diff --git a/js/src/jit-test/tests/ion/recover-iterator-next.js b/js/src/jit-test/tests/ion/recover-iterator-next.js
deleted file mode 100644
index 56e4f94149c1..000000000000
--- a/js/src/jit-test/tests/ion/recover-iterator-next.js
+++ /dev/null
@@ -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);
diff --git a/js/src/jit-test/tests/ion/recover-object-bug1175233.js b/js/src/jit-test/tests/ion/recover-object-bug1175233.js
index d82c284fdf62..4c0fb809599e 100644
--- a/js/src/jit-test/tests/ion/recover-object-bug1175233.js
+++ b/js/src/jit-test/tests/ion/recover-object-bug1175233.js
@@ -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.
diff --git a/js/src/jit-test/tests/ion/recover-objects.js b/js/src/jit-test/tests/ion/recover-objects.js
index 30274f7efa65..0504d09cd042 100644
--- a/js/src/jit-test/tests/ion/recover-objects.js
+++ b/js/src/jit-test/tests/ion/recover-objects.js
@@ -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.
diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp
index fdbeed6cc919..beaedc71ab7a 100644
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -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);
diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h
index a8d4c01dd8ac..a6c5ea6fdae5 100644
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -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);
diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp
index 2117bd4f8d1f..bf647952df6d 100644
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -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);
 }
 
diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp
index 7f7c1c66cdd6..eb048adf4e04 100644
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -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;
 
diff --git a/js/src/jit/ScalarReplacement.cpp b/js/src/jit/ScalarReplacement.cpp
index d708e7a028d9..78fba67471f9 100644
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -1240,12 +1240,11 @@ ArrayMemoryView::visitArrayLength(MArrayLength* ins)
 }
 
 bool
-ScalarReplacement(MIRGenerator* mir, MIRGraph& graph, bool* success)
+ScalarReplacement(MIRGenerator* mir, MIRGraph& graph)
 {
     EmulateStateOf replaceObject(mir, graph);
     EmulateStateOf 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;
             }
diff --git a/js/src/jit/ScalarReplacement.h b/js/src/jit/ScalarReplacement.h
index 1869471a3147..836367f0f0f3 100644
--- a/js/src/jit/ScalarReplacement.h
+++ b/js/src/jit/ScalarReplacement.h
@@ -15,7 +15,7 @@ class MIRGenerator;
 class MIRGraph;
 
 bool
-ScalarReplacement(MIRGenerator* mir, MIRGraph& graph, bool* success);
+ScalarReplacement(MIRGenerator* mir, MIRGraph& graph);
 
 } // namespace jit
 } // namespace js
diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp
index cb51cd4e818f..f41e40fc59d7 100644
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3607,6 +3607,9 @@ JSObject::sizeOfIncludingThisInNursery() const
             if (!elements.isCopyOnWrite() || elements.ownerObject() == this)
                 size += elements.capacity * sizeof(HeapSlot);
         }
+
+        if (is())
+            size += as().sizeOfData();
     }
 
     return size;
diff --git a/js/src/tests/ecma_7/SIMD/replaceLane.js b/js/src/tests/ecma_7/SIMD/replaceLane.js
new file mode 100644
index 000000000000..4ef5d22b8b06
--- /dev/null
+++ b/js/src/tests/ecma_7/SIMD/replaceLane.js
@@ -0,0 +1,94 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+var float64x2 = SIMD.float64x2;
+
+
+function replaceLane0(arr, x) {
+    if (arr.length == 2)
+        return [x, arr[1]];
+    return [x, arr[1], arr[2], arr[3]];
+}
+function replaceLane1(arr, x) {
+    if (arr.length == 2)
+        return [arr[0], x];
+    return [arr[0], x, arr[2], arr[3]];
+}
+function replaceLane2(arr, x) {
+    return [arr[0], arr[1], x, arr[3]];
+}
+function replaceLane3(arr, x) {
+    return [arr[0], arr[1], arr[2], x];
+}
+
+function testReplaceLane(vec, scalar, simdFunc, func) {
+    var varr = simdToArray(vec);
+    var observed = simdToArray(simdFunc(vec, scalar));
+    var expected = func(varr, scalar);
+    for (var i = 0; i < observed.length; i++)
+        assertEq(observed[i], expected[i]);
+}
+
+function test() {
+  function testType(type, inputs) {
+      var length = simdToArray(inputs[0][0]).length;
+      for (var [vec, s] of inputs) {
+          testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 0, y), replaceLane0);
+          testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 1, y), replaceLane1);
+          if (length <= 2)
+              continue;
+          testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 2, y), replaceLane2);
+          testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 3, y), replaceLane3);
+      }
+  }
+
+  function TestError(){};
+  var good = {valueOf: () => 42};
+  var bad = {valueOf: () => {throw new TestError(); }};
+
+  var float32x4inputs = [
+      [float32x4(1, 2, 3, 4), 5],
+      [float32x4(1.87, 2.08, 3.84, 4.17), Math.fround(13.37)],
+      [float32x4(NaN, -0, Infinity, -Infinity), 0]
+  ];
+  testType('float32x4', float32x4inputs);
+
+  var v = float32x4inputs[1][0];
+  assertEqX4(float32x4.replaceLane(v, 0), replaceLane0(simdToArray(v), NaN));
+  assertEqX4(float32x4.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0));
+  assertThrowsInstanceOf(() => float32x4.replaceLane(v, 0, bad), TestError);
+  assertThrowsInstanceOf(() => float32x4.replaceLane(v, 4, good), TypeError);
+  assertThrowsInstanceOf(() => float32x4.replaceLane(v, 1.1, good), TypeError);
+
+  var float64x2inputs = [
+      [float64x2(1, 2), 5],
+      [float64x2(1.87, 2.08), Math.fround(13.37)],
+      [float64x2(NaN, -0), 0]
+  ];
+  testType('float64x2', float64x2inputs);
+
+  var v = float64x2inputs[1][0];
+  assertEqX4(float64x2.replaceLane(v, 0), replaceLane0(simdToArray(v), NaN));
+  assertEqX4(float64x2.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0));
+  assertThrowsInstanceOf(() => float64x2.replaceLane(v, 0, bad), TestError);
+  assertThrowsInstanceOf(() => float64x2.replaceLane(v, 2, good), TypeError);
+  assertThrowsInstanceOf(() => float64x2.replaceLane(v, 1.1, good), TypeError);
+
+  var int32x4inputs = [
+      [int32x4(1, 2, 3, 4), 5],
+      [int32x4(INT32_MIN, INT32_MAX, 3, 4), INT32_MIN],
+  ];
+  testType('int32x4', int32x4inputs);
+
+  var v = int32x4inputs[1][0];
+  assertEqX4(int32x4.replaceLane(v, 0), replaceLane0(simdToArray(v), 0));
+  assertEqX4(int32x4.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0));
+  assertThrowsInstanceOf(() => int32x4.replaceLane(v, 0, bad), TestError);
+  assertThrowsInstanceOf(() => int32x4.replaceLane(v, 4, good), TypeError);
+  assertThrowsInstanceOf(() => int32x4.replaceLane(v, 1.1, good), TypeError);
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
diff --git a/js/src/tests/ecma_7/SIMD/with.js b/js/src/tests/ecma_7/SIMD/with.js
deleted file mode 100644
index b9f53adc30ed..000000000000
--- a/js/src/tests/ecma_7/SIMD/with.js
+++ /dev/null
@@ -1,93 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
-var BUGNUMBER = 946042;
-var float32x4 = SIMD.float32x4;
-var int32x4 = SIMD.int32x4;
-var float64x2 = SIMD.float64x2;
-
-var summary = 'with{X,Y,Z,W}';
-
-function withX(arr, x) {
-    if (arr.length == 2)
-        return [x, arr[1]];
-    return [x, arr[1], arr[2], arr[3]];
-}
-function withY(arr, x) {
-    if (arr.length == 2)
-        return [arr[0], x];
-    return [arr[0], x, arr[2], arr[3]];
-}
-function withZ(arr, x) {
-    return [arr[0], arr[1], x, arr[3]];
-}
-function withW(arr, x) {
-    return [arr[0], arr[1], arr[2], x];
-}
-
-function testWith(vec, scalar, simdFunc, func) {
-    var varr = simdToArray(vec);
-    var observed = simdToArray(simdFunc(vec, scalar));
-    var expected = func(varr, scalar);
-    for (var i = 0; i < observed.length; i++)
-        assertEq(observed[i], expected[i]);
-}
-
-function test() {
-  print(BUGNUMBER + ": " + summary);
-
-  function testType(type, inputs) {
-      var length = simdToArray(inputs[0][0]).length;
-      for (var [vec, s] of inputs) {
-          testWith(vec, s, SIMD[type].withX, withX);
-          testWith(vec, s, SIMD[type].withY, withY);
-          if (length <= 2)
-              continue;
-          testWith(vec, s, SIMD[type].withZ, withZ);
-          testWith(vec, s, SIMD[type].withW, withW);
-      }
-  }
-
-  function TestError(){};
-  var good = {valueOf: () => 42};
-  var bad = {valueOf: () => {throw new TestError(); }};
-
-  var float32x4inputs = [
-      [float32x4(1, 2, 3, 4), 5],
-      [float32x4(1.87, 2.08, 3.84, 4.17), Math.fround(13.37)],
-      [float32x4(NaN, -0, Infinity, -Infinity), 0]
-  ];
-  testType('float32x4', float32x4inputs);
-
-  var v = float32x4inputs[1][0];
-  assertEqX4(float32x4.withX(v), withX(simdToArray(v), NaN));
-  assertEqX4(float32x4.withX(v, good), withX(simdToArray(v), good | 0));
-  assertThrowsInstanceOf(() => float32x4.withX(v, bad), TestError);
-
-  var float64x2inputs = [
-      [float64x2(1, 2), 5],
-      [float64x2(1.87, 2.08), Math.fround(13.37)],
-      [float64x2(NaN, -0), 0]
-  ];
-  testType('float64x2', float64x2inputs);
-
-  var v = float64x2inputs[1][0];
-  assertEqX4(float64x2.withX(v), withX(simdToArray(v), NaN));
-  assertEqX4(float64x2.withX(v, good), withX(simdToArray(v), good | 0));
-  assertThrowsInstanceOf(() => float64x2.withX(v, bad), TestError);
-
-  var int32x4inputs = [
-      [int32x4(1, 2, 3, 4), 5],
-      [int32x4(INT32_MIN, INT32_MAX, 3, 4), INT32_MIN],
-  ];
-  testType('int32x4', int32x4inputs);
-
-  var v = int32x4inputs[1][0];
-  assertEqX4(int32x4.withX(v), withX(simdToArray(v), 0));
-  assertEqX4(int32x4.withX(v, good), withX(simdToArray(v), good | 0));
-  assertThrowsInstanceOf(() => int32x4.withX(v, bad), TestError);
-
-  if (typeof reportCompare === "function")
-    reportCompare(true, true);
-}
-
-test();
-
diff --git a/js/src/vm/ArgumentsObject.cpp b/js/src/vm/ArgumentsObject.cpp
index 80d850d2d131..ac04817f19d8 100644
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -6,12 +6,15 @@
 
 #include "vm/ArgumentsObject-inl.h"
 
+#include "mozilla/PodOperations.h"
+
 #include "jit/JitFrames.h"
 #include "vm/GlobalObject.h"
 #include "vm/Stack.h"
 
 #include "jsobjinlines.h"
 
+#include "gc/Nursery-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
@@ -182,31 +185,33 @@ ArgumentsObject::create(JSContext* cx, HandleScript script, HandleFunction calle
                         numDeletedWords * sizeof(size_t) +
                         numArgs * sizeof(Value);
 
-    // Allocate zeroed memory to make the object GC-safe for early attachment.
-    ArgumentsData* data = reinterpret_cast(
-            cx->zone()->pod_calloc(numBytes));
-    if (!data)
-        return nullptr;
-
     Rooted obj(cx);
     JSObject* base = JSObject::create(cx, FINALIZE_KIND,
                                       GetInitialHeap(GenericObject, clasp),
                                       shape, group);
-    if (!base) {
-        js_free(data);
+    if (!base)
         return nullptr;
-    }
     obj = &base->as();
 
+    ArgumentsData* data =
+        reinterpret_cast(AllocateObjectBuffer(cx, obj, numBytes));
+    if (!data) {
+        // Make the object safe for GC.
+        obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr));
+        return nullptr;
+    }
+
     data->numArgs = numArgs;
+    data->dataBytes = numBytes;
     data->callee.init(ObjectValue(*callee.get()));
     data->script = script;
 
-    // Attach the argument object.
-    // Because the argument object was zeroed by pod_calloc(), each Value in
-    // ArgumentsData is DoubleValue(0) and therefore safe for GC tracing.
+    // Zero the argument Values. This sets each value to DoubleValue(0), which
+    // is safe for GC tracing.
+    memset(data->args, 0, numArgs * sizeof(Value));
     MOZ_ASSERT(DoubleValue(0).asRawBits() == 0x0);
     MOZ_ASSERT_IF(numArgs > 0, data->args[0].asRawBits() == 0x0);
+
     obj->initFixedSlot(DATA_SLOT, PrivateValue(data));
 
     /* Copy [0, numArgs) into data->slots. */
@@ -531,6 +536,7 @@ strictargs_enumerate(JSContext* cx, HandleObject obj)
 void
 ArgumentsObject::finalize(FreeOp* fop, JSObject* obj)
 {
+    MOZ_ASSERT(!IsInsideNursery(obj));
     fop->free_(reinterpret_cast(obj->as().data()));
 }
 
@@ -544,6 +550,34 @@ ArgumentsObject::trace(JSTracer* trc, JSObject* obj)
     TraceManuallyBarrieredEdge(trc, &data->script, "script");
 }
 
+/* static */ size_t
+ArgumentsObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src)
+{
+    ArgumentsObject* ndst = &dst->as();
+    ArgumentsObject* nsrc = &src->as();
+    MOZ_ASSERT(ndst->data() == nsrc->data());
+
+    Nursery& nursery = trc->runtime()->gc.nursery;
+
+    if (!nursery.isInside(nsrc->data())) {
+        nursery.removeMallocedBuffer(nsrc->data());
+        return 0;
+    }
+
+    uint32_t nbytes = nsrc->data()->dataBytes;
+    uint8_t* data = nsrc->zone()->pod_malloc(nbytes);
+    if (!data)
+        CrashAtUnhandlableOOM("Failed to allocate ArgumentsObject data while tenuring.");
+    ndst->initFixedSlot(DATA_SLOT, PrivateValue(data));
+
+    mozilla::PodCopy(data, reinterpret_cast(nsrc->data()), nbytes);
+
+    ArgumentsData* dstData = ndst->data();
+    dstData->deletedBits = reinterpret_cast(dstData->args + dstData->numArgs);
+
+    return nbytes;
+}
+
 /*
  * The classes below collaborate to lazily reflect and synchronize actual
  * argument values, argument count, and callee function object stored in a
@@ -554,7 +588,9 @@ const Class NormalArgumentsObject::class_ = {
     "Arguments",
     JSCLASS_IMPLEMENTS_BARRIERS |
     JSCLASS_HAS_RESERVED_SLOTS(NormalArgumentsObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_BACKGROUND_FINALIZE,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
+    JSCLASS_SKIP_NURSERY_FINALIZE |
+    JSCLASS_BACKGROUND_FINALIZE,
     nullptr,                 /* addProperty */
     args_delProperty,
     nullptr,                 /* getProperty */
@@ -579,7 +615,9 @@ const Class StrictArgumentsObject::class_ = {
     "Arguments",
     JSCLASS_IMPLEMENTS_BARRIERS |
     JSCLASS_HAS_RESERVED_SLOTS(StrictArgumentsObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_BACKGROUND_FINALIZE,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
+    JSCLASS_SKIP_NURSERY_FINALIZE |
+    JSCLASS_BACKGROUND_FINALIZE,
     nullptr,                 /* addProperty */
     args_delProperty,
     nullptr,                 /* getProperty */
diff --git a/js/src/vm/ArgumentsObject.h b/js/src/vm/ArgumentsObject.h
index 9069ab26eeaf..7dab519fc364 100644
--- a/js/src/vm/ArgumentsObject.h
+++ b/js/src/vm/ArgumentsObject.h
@@ -33,7 +33,10 @@ struct ArgumentsData
      * numArgs = Max(numFormalArgs, numActualArgs)
      * The array 'args' has numArgs elements.
      */
-    unsigned    numArgs;
+    uint32_t    numArgs;
+
+    /* Size of ArgumentsData and data allocated after it. */
+    uint32_t    dataBytes;
 
     /*
      * arguments.callee, or MagicValue(JS_OVERWRITTEN_CALLEE) if
@@ -264,9 +267,13 @@ class ArgumentsObject : public NativeObject
     size_t sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const {
         return mallocSizeOf(data());
     }
+    size_t sizeOfData() const {
+        return data()->dataBytes;
+    }
 
     static void finalize(FreeOp* fop, JSObject* obj);
     static void trace(JSTracer* trc, JSObject* obj);
+    static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src);
 
     /* For jit use: */
     static size_t getDataSlotOffset() {
diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp
index 35624832d838..a5662e7ce95a 100644
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -2298,22 +2298,6 @@ nsDocumentViewer::CreateStyleSet(nsIDocument* aDocument,
       styleSet->PrependStyleSheet(nsStyleSet::eAgentSheet, sheet);
     }
 
-    // Make sure to clone the quirk sheet so that it can be usefully
-    // enabled/disabled as needed.
-    nsRefPtr quirkClone;
-    CSSStyleSheet* quirkSheet;
-    if (!nsLayoutStylesheetCache::UASheet() ||
-        !(quirkSheet = nsLayoutStylesheetCache::QuirkSheet()) ||
-        !(quirkClone = quirkSheet->Clone(nullptr, nullptr, nullptr, nullptr)) ||
-        !sheet) {
-      delete styleSet;
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    // quirk.css needs to come after the regular UA sheet (or more precisely,
-    // after the html.css and so forth that the UA sheet imports).
-    styleSet->PrependStyleSheet(nsStyleSet::eAgentSheet, quirkClone);
-    styleSet->SetQuirkStyleSheet(quirkClone);
-
     if (aDocument->LoadsFullXULStyleSheetUpFront()) {
       // nsXULElement::BindToTree loads xul.css on-demand if we don't load it
       // up-front here.
@@ -2349,6 +2333,9 @@ nsDocumentViewer::CreateStyleSet(nsIDocument* aDocument,
       }
     }
 
+    // We don't add quirk.css here; nsPresContext::CompatibilityModeChanged will
+    // append it if needed.
+
     sheet = nsLayoutStylesheetCache::HTMLSheet();
     if (sheet) {
       styleSet->PrependStyleSheet(nsStyleSet::eAgentSheet, sheet);
diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp
index b6f5fefaedba..cf7f001798c3 100644
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1354,12 +1354,40 @@ nsPresContext::GetDisplayRootPresContext()
 void
 nsPresContext::CompatibilityModeChanged()
 {
-  if (!mShell)
+  if (!mShell) {
     return;
+  }
 
-  // enable/disable the QuirkSheet
-  mShell->StyleSet()->
-    EnableQuirkStyleSheet(CompatibilityMode() == eCompatibility_NavQuirks);
+  nsIDocument* doc = mShell->GetDocument();
+  if (!doc) {
+    return;
+  }
+
+  if (doc->IsSVGDocument()) {
+    // SVG documents never load quirk.css.
+    return;
+  }
+
+  bool needsQuirkSheet = CompatibilityMode() == eCompatibility_NavQuirks;
+  if (mQuirkSheetAdded == needsQuirkSheet) {
+    return;
+  }
+
+  nsStyleSet* styleSet = mShell->StyleSet();
+  CSSStyleSheet* sheet = nsLayoutStylesheetCache::QuirkSheet();
+
+  if (needsQuirkSheet) {
+    // quirk.css needs to come after html.css; we just keep it at the end.
+    DebugOnly rv =
+      styleSet->AppendStyleSheet(nsStyleSet::eAgentSheet, sheet);
+    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to insert quirk.css");
+  } else {
+    DebugOnly rv =
+      styleSet->RemoveStyleSheet(nsStyleSet::eAgentSheet, sheet);
+    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to remove quirk.css");
+  }
+
+  mQuirkSheetAdded = needsQuirkSheet;
 }
 
 // Helper function for setting Anim Mode on image
@@ -2304,14 +2332,11 @@ nsPresContext::NotifyMissingFonts()
 void
 nsPresContext::EnsureSafeToHandOutCSSRules()
 {
-  CSSStyleSheet::EnsureUniqueInnerResult res =
-    mShell->StyleSet()->EnsureUniqueInnerOnCSSSheets();
-  if (res == CSSStyleSheet::eUniqueInner_AlreadyUnique) {
+  if (!mShell->StyleSet()->EnsureUniqueInnerOnCSSSheets()) {
     // Nothing to do.
     return;
   }
 
-  MOZ_ASSERT(res == CSSStyleSheet::eUniqueInner_ClonedInner);
   RebuildAllStyleData(nsChangeHint(0), eRestyle_Subtree);
 }
 
diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h
index 2c807cac665c..9e9b808d77b1 100644
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -1399,6 +1399,9 @@ protected:
 
   unsigned mHasWarnedAboutPositionedTableParts : 1;
 
+  // Have we added quirk.css to the style set?
+  unsigned              mQuirkSheetAdded : 1;
+
 #ifdef RESTYLE_LOGGING
   // Should we output debug information about restyling for this document?
   bool                  mRestyleLoggingEnabled;
diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp
index e993d95cfebf..aeefa16b2f0d 100644
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -30,6 +30,7 @@
 #include "nsGkAtoms.h"
 #include "nsImageFrame.h"
 #include "nsLayoutStylesheetCache.h"
+#include "mozilla/RuleProcessorCache.h"
 #include "nsPrincipal.h"
 #include "nsRange.h"
 #include "nsRegion.h"
@@ -376,6 +377,7 @@ nsLayoutStatics::Shutdown()
   nsAttrValue::Shutdown();
   nsContentUtils::Shutdown();
   nsLayoutStylesheetCache::Shutdown();
+  RuleProcessorCache::Shutdown();
 
   ShutdownJSEnvironment();
   nsGlobalWindow::ShutDown();
diff --git a/layout/reftests/bugs/1155828-1-ref.html b/layout/reftests/bugs/1155828-1-ref.html
new file mode 100644
index 000000000000..66bfea56c44a
--- /dev/null
+++ b/layout/reftests/bugs/1155828-1-ref.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+ + diff --git a/layout/reftests/bugs/1155828-1.html b/layout/reftests/bugs/1155828-1.html new file mode 100644 index 000000000000..8eaefd2b2d61 --- /dev/null +++ b/layout/reftests/bugs/1155828-1.html @@ -0,0 +1,26 @@ + + + +Scrolling shouldn't cause gaps in the shadow + + +
+ + diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index 45b183ff3402..7a912fa91f76 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1924,6 +1924,7 @@ skip-if(B2G||Mulet) == 1150021-1.xul 1150021-1-ref.xul == 1151145-1.html 1151145-1-ref.html == 1151306-1.html 1151306-1-ref.html == 1153845-1.html 1153845-1-ref.html +== 1155828-1.html 1155828-1-ref.html == 1156129-1.html 1156129-1-ref.html == 1169331-1.html 1169331-1-ref.html fuzzy(1,74) == 1174332-1.html 1174332-1-ref.html diff --git a/layout/style/CSSStyleSheet.cpp b/layout/style/CSSStyleSheet.cpp index ec7dea848741..0cab86918f51 100644 --- a/layout/style/CSSStyleSheet.cpp +++ b/layout/style/CSSStyleSheet.cpp @@ -17,6 +17,7 @@ #include "mozilla/css/NameSpaceRule.h" #include "mozilla/css/GroupRule.h" #include "mozilla/css/ImportRule.h" +#include "nsCSSRules.h" #include "nsIMediaList.h" #include "nsIDocument.h" #include "nsPresContext.h" @@ -45,6 +46,7 @@ #include "mozilla/dom/CSSStyleSheetBinding.h" #include "nsComponentManagerUtils.h" #include "nsNullPrincipal.h" +#include "mozilla/RuleProcessorCache.h" using namespace mozilla; using namespace mozilla::dom; @@ -332,6 +334,96 @@ nsMediaQueryResultCacheKey::Matches(nsPresContext* aPresContext) const return true; } +bool +nsDocumentRuleResultCacheKey::AddMatchingRule(css::DocumentRule* aRule) +{ + MOZ_ASSERT(!mFinalized); + return mMatchingRules.AppendElement(aRule); +} + +void +nsDocumentRuleResultCacheKey::Finalize() +{ + mMatchingRules.Sort(); +#ifdef DEBUG + mFinalized = true; +#endif +} + +bool +nsDocumentRuleResultCacheKey::Matches( + nsPresContext* aPresContext, + const nsTArray& aRules) const +{ + MOZ_ASSERT(mFinalized); + + // First check that aPresContext matches all the rules listed in + // mMatchingRules. + for (css::DocumentRule* rule : mMatchingRules) { + if (!rule->UseForPresentation(aPresContext)) { + return false; + } + } + + // Then check that all the rules in aRules that aren't also in + // mMatchingRules do not match. + + // pointer to matching rules + auto pm = mMatchingRules.begin(); + auto pm_end = mMatchingRules.end(); + + // pointer to all rules + auto pr = aRules.begin(); + auto pr_end = aRules.end(); + + // mMatchingRules and aRules are both sorted by their pointer values, + // so we can iterate over the two lists simultaneously. + while (pr < pr_end) { + while (pm < pm_end && *pm < *pr) { + ++pm; + MOZ_ASSERT(pm >= pm_end || *pm == *pr, + "shouldn't find rule in mMatchingRules that is not in aRules"); + } + if (pm >= pm_end || *pm != *pr) { + if ((*pr)->UseForPresentation(aPresContext)) { + return false; + } + } + ++pr; + } + return true; +} + +#ifdef DEBUG +void +nsDocumentRuleResultCacheKey::List(FILE* aOut, int32_t aIndent) const +{ + for (css::DocumentRule* rule : mMatchingRules) { + nsCString str; + + for (int32_t i = 0; i < aIndent; i++) { + str.AppendLiteral(" "); + } + str.AppendLiteral("{ "); + + nsString condition; + rule->GetConditionText(condition); + AppendUTF16toUTF8(condition, str); + + str.AppendLiteral(" }\n"); + fprintf_stderr(aOut, "%s", str.get()); + } +} +#endif + +size_t +nsDocumentRuleResultCacheKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = 0; + n += mMatchingRules.SizeOfExcludingThis(aMallocSizeOf); + return n; +} + void nsMediaQuery::AppendToString(nsAString& aString) const { @@ -984,6 +1076,7 @@ CSSStyleSheet::CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy) mOwningNode(nullptr), mDisabled(false), mDirty(false), + mInRuleProcessorCache(false), mScopeElement(nullptr), mRuleProcessors(nullptr) { @@ -1002,6 +1095,7 @@ CSSStyleSheet::CSSStyleSheet(const CSSStyleSheet& aCopy, mOwningNode(aOwningNodeToUse), mDisabled(aCopy.mDisabled), mDirty(aCopy.mDirty), + mInRuleProcessorCache(false), mScopeElement(nullptr), mInner(aCopy.mInner), mRuleProcessors(nullptr) @@ -1044,6 +1138,9 @@ CSSStyleSheet::~CSSStyleSheet() NS_ASSERTION(mRuleProcessors->Length() == 0, "destructing sheet with rule processor reference"); delete mRuleProcessors; // weak refs, should be empty here anyway } + if (mInRuleProcessorCache) { + RuleProcessorCache::RemoveSheet(this); + } } void @@ -1186,6 +1283,20 @@ CSSStyleSheet::DropRuleProcessor(nsCSSRuleProcessor* aProcessor) : NS_ERROR_FAILURE; } +void +CSSStyleSheet::AddStyleSet(nsStyleSet* aStyleSet) +{ + NS_ASSERTION(!mStyleSets.Contains(aStyleSet), + "style set already registered"); + mStyleSets.AppendElement(aStyleSet); +} + +void +CSSStyleSheet::DropStyleSet(nsStyleSet* aStyleSet) +{ + DebugOnly found = mStyleSets.RemoveElement(aStyleSet); + NS_ASSERTION(found, "didn't find style set"); +} void CSSStyleSheet::SetURIs(nsIURI* aSheetURI, nsIURI* aOriginalSheetURI, @@ -1487,7 +1598,7 @@ CSSStyleSheet::StyleSheetCount() const return count; } -CSSStyleSheet::EnsureUniqueInnerResult +void CSSStyleSheet::EnsureUniqueInner() { mDirty = true; @@ -1495,7 +1606,8 @@ CSSStyleSheet::EnsureUniqueInner() MOZ_ASSERT(mInner->mSheets.Length() != 0, "unexpected number of outers"); if (mInner->mSheets.Length() == 1) { - return eUniqueInner_AlreadyUnique; + // already unique + return; } CSSStyleSheetInner* clone = mInner->CloneFor(this); MOZ_ASSERT(clone); @@ -1505,7 +1617,12 @@ CSSStyleSheet::EnsureUniqueInner() // otherwise the rule processor has pointers to the old rules ClearRuleCascades(); - return eUniqueInner_ClonedInner; + // let our containing style sets know that if we call + // nsPresContext::EnsureSafeToHandOutCSSRules we will need to restyle the + // document + for (nsStyleSet* styleSet : mStyleSets) { + styleSet->SetNeedsRestyleAfterEnsureUniqueInner(); + } } void @@ -1592,10 +1709,18 @@ CSSStyleSheet::List(FILE* out, int32_t aIndent) const void CSSStyleSheet::ClearRuleCascades() { + bool removedSheetFromRuleProcessorCache = false; if (mRuleProcessors) { nsCSSRuleProcessor **iter = mRuleProcessors->Elements(), **end = iter + mRuleProcessors->Length(); for(; iter != end; ++iter) { + if (!removedSheetFromRuleProcessorCache && (*iter)->IsShared()) { + // Since the sheet has been modified, we need to remove all + // RuleProcessorCache entries that contain this sheet, as the + // list of @-moz-document rules might have changed. + RuleProcessorCache::RemoveSheet(this); + removedSheetFromRuleProcessorCache = true; + } (*iter)->ClearRuleCascades(); } } diff --git a/layout/style/CSSStyleSheet.h b/layout/style/CSSStyleSheet.h index aa0681791378..243cce05c7fc 100644 --- a/layout/style/CSSStyleSheet.h +++ b/layout/style/CSSStyleSheet.h @@ -212,6 +212,9 @@ public: nsresult AddRuleProcessor(nsCSSRuleProcessor* aProcessor); nsresult DropRuleProcessor(nsCSSRuleProcessor* aProcessor); + void AddStyleSet(nsStyleSet* aStyleSet); + void DropStyleSet(nsStyleSet* aStyleSet); + /** * Like the DOM insertRule() method, but doesn't do any security checks */ @@ -226,14 +229,7 @@ public: NS_IMETHOD StyleSheetLoaded(CSSStyleSheet* aSheet, bool aWasAlternate, nsresult aStatus) override; - enum EnsureUniqueInnerResult { - // No work was needed to ensure a unique inner. - eUniqueInner_AlreadyUnique, - // A clone was done to ensure a unique inner (which means the style - // rules in this sheet have changed). - eUniqueInner_ClonedInner - }; - EnsureUniqueInnerResult EnsureUniqueInner(); + void EnsureUniqueInner(); // Append all of this sheet's child sheets to aArray. void AppendAllChildSheets(nsTArray& aArray); @@ -243,6 +239,8 @@ public: nsresult ParseSheet(const nsAString& aInput); + void SetInRuleProcessorCache() { mInRuleProcessorCache = true; } + // nsIDOMStyleSheet interface NS_DECL_NSIDOMSTYLESHEET @@ -361,11 +359,13 @@ protected: nsINode* mOwningNode; // weak ref bool mDisabled; bool mDirty; // has been modified + bool mInRuleProcessorCache; nsRefPtr mScopeElement; CSSStyleSheetInner* mInner; nsAutoTArray* mRuleProcessors; + nsTArray mStyleSets; friend class ::nsMediaList; friend class ::nsCSSRuleProcessor; diff --git a/layout/style/RuleProcessorCache.cpp b/layout/style/RuleProcessorCache.cpp new file mode 100644 index 000000000000..576b974d8290 --- /dev/null +++ b/layout/style/RuleProcessorCache.cpp @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * cache of re-usable nsCSSRuleProcessors for given sets of style sheets + */ + +#include "RuleProcessorCache.h" + +#include +#include "nsCSSRuleProcessor.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(RuleProcessorCache, nsIMemoryReporter) + +MOZ_DEFINE_MALLOC_SIZE_OF(RuleProcessorCacheMallocSizeOf) + +NS_IMETHODIMP +RuleProcessorCache::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) +{ + return MOZ_COLLECT_REPORT( + "explicit/layout/rule-processor-cache", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(RuleProcessorCacheMallocSizeOf), + "Memory used for cached rule processors."); +} + +RuleProcessorCache::~RuleProcessorCache() +{ + UnregisterWeakMemoryReporter(this); + + for (Entry& e : mEntries) { + for (DocumentEntry& de : e.mDocumentEntries) { + if (de.mRuleProcessor->GetExpirationState()->IsTracked()) { + mExpirationTracker.RemoveObject(de.mRuleProcessor); + } + de.mRuleProcessor->SetInRuleProcessorCache(false); + } + } +} + +void +RuleProcessorCache::InitMemoryReporter() +{ + RegisterWeakMemoryReporter(this); +} + +/* static */ bool +RuleProcessorCache::EnsureGlobal() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (gShutdown) { + return false; + } + + if (!gRuleProcessorCache) { + gRuleProcessorCache = new RuleProcessorCache; + gRuleProcessorCache->InitMemoryReporter(); + } + return true; +} + +/* static */ void +RuleProcessorCache::RemoveSheet(CSSStyleSheet* aSheet) +{ + if (!EnsureGlobal()) { + return; + } + gRuleProcessorCache->DoRemoveSheet(aSheet); +} + +#ifdef DEBUG +/* static */ bool +RuleProcessorCache::HasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return false; + } + return gRuleProcessorCache->DoHasRuleProcessor(aRuleProcessor); +} +#endif + +/* static */ void +RuleProcessorCache::RemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return; + } + gRuleProcessorCache->DoRemoveRuleProcessor(aRuleProcessor); +} + +/* static */ nsCSSRuleProcessor* +RuleProcessorCache::GetRuleProcessor(const nsTArray& aSheets, + nsPresContext* aPresContext) +{ + if (!EnsureGlobal()) { + return nullptr; + } + return gRuleProcessorCache->DoGetRuleProcessor(aSheets, aPresContext); +} + +/* static */ void +RuleProcessorCache::PutRuleProcessor( + const nsTArray& aSheets, + nsTArray&& aDocumentRulesInSheets, + const nsDocumentRuleResultCacheKey& aCacheKey, + nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return; + } + gRuleProcessorCache->DoPutRuleProcessor(aSheets, Move(aDocumentRulesInSheets), + aCacheKey, aRuleProcessor); +} + +/* static */ void +RuleProcessorCache::StartTracking(nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return; + } + return gRuleProcessorCache->DoStartTracking(aRuleProcessor); +} + +/* static */ void +RuleProcessorCache::StopTracking(nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return; + } + return gRuleProcessorCache->DoStopTracking(aRuleProcessor); +} + +void +RuleProcessorCache::DoRemoveSheet(CSSStyleSheet* aSheet) +{ + Entry* last = std::remove_if(mEntries.begin(), mEntries.end(), + HasSheet_ThenRemoveRuleProcessors(this, aSheet)); + mEntries.TruncateLength(last - mEntries.begin()); +} + +nsCSSRuleProcessor* +RuleProcessorCache::DoGetRuleProcessor(const nsTArray& aSheets, + nsPresContext* aPresContext) +{ + for (Entry& e : mEntries) { + if (e.mSheets == aSheets) { + for (DocumentEntry& de : e.mDocumentEntries) { + if (de.mCacheKey.Matches(aPresContext, e.mDocumentRulesInSheets)) { + return de.mRuleProcessor; + } + } + // Entry::mSheets is unique; if we matched aSheets but didn't + // find a matching DocumentEntry, we won't find one later in + // mEntries. + return nullptr; + } + } + return nullptr; +} + +void +RuleProcessorCache::DoPutRuleProcessor( + const nsTArray& aSheets, + nsTArray&& aDocumentRulesInSheets, + const nsDocumentRuleResultCacheKey& aCacheKey, + nsCSSRuleProcessor* aRuleProcessor) +{ + MOZ_ASSERT(!aRuleProcessor->IsInRuleProcessorCache()); + + Entry* entry = nullptr; + for (Entry& e : mEntries) { + if (e.mSheets == aSheets) { + entry = &e; + break; + } + } + + if (!entry) { + entry = mEntries.AppendElement(); + entry->mSheets = aSheets; + entry->mDocumentRulesInSheets = aDocumentRulesInSheets; + for (CSSStyleSheet* sheet : aSheets) { + sheet->SetInRuleProcessorCache(); + } + } else { + MOZ_ASSERT(entry->mDocumentRulesInSheets == aDocumentRulesInSheets, + "DocumentRule array shouldn't have changed"); + } + +#ifdef DEBUG + for (DocumentEntry& de : entry->mDocumentEntries) { + MOZ_ASSERT(de.mCacheKey != aCacheKey, + "should not have duplicate document cache keys"); + } +#endif + + DocumentEntry* documentEntry = entry->mDocumentEntries.AppendElement(); + documentEntry->mCacheKey = aCacheKey; + documentEntry->mRuleProcessor = aRuleProcessor; + aRuleProcessor->SetInRuleProcessorCache(true); +} + +#ifdef DEBUG +bool +RuleProcessorCache::DoHasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor) +{ + for (Entry& e : mEntries) { + for (DocumentEntry& de : e.mDocumentEntries) { + if (de.mRuleProcessor == aRuleProcessor) { + return true; + } + } + } + return false; +} +#endif + +void +RuleProcessorCache::DoRemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor) +{ + MOZ_ASSERT(aRuleProcessor->IsInRuleProcessorCache()); + + aRuleProcessor->SetInRuleProcessorCache(false); + mExpirationTracker.RemoveObjectIfTracked(aRuleProcessor); + for (Entry& e : mEntries) { + for (size_t i = 0; i < e.mDocumentEntries.Length(); i++) { + if (e.mDocumentEntries[i].mRuleProcessor == aRuleProcessor) { + e.mDocumentEntries.RemoveElementAt(i); + return; + } + } + } + + MOZ_ASSERT_UNREACHABLE("should have found rule processor"); +} + +void +RuleProcessorCache::DoStartTracking(nsCSSRuleProcessor* aRuleProcessor) +{ + mExpirationTracker.AddObject(aRuleProcessor); +} + +void +RuleProcessorCache::DoStopTracking(nsCSSRuleProcessor* aRuleProcessor) +{ + mExpirationTracker.RemoveObjectIfTracked(aRuleProcessor); +} + +size_t +RuleProcessorCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + + int count = 0; + n += mEntries.SizeOfExcludingThis(aMallocSizeOf); + for (Entry& e : mEntries) { + n += e.mDocumentEntries.SizeOfExcludingThis(aMallocSizeOf); + for (DocumentEntry& de : e.mDocumentEntries) { + count++; + n += de.mRuleProcessor->SizeOfIncludingThis(aMallocSizeOf); + } + } + + return n; +} + +void +RuleProcessorCache::ExpirationTracker::RemoveObjectIfTracked( + nsCSSRuleProcessor* aRuleProcessor) +{ + if (aRuleProcessor->GetExpirationState()->IsTracked()) { + RemoveObject(aRuleProcessor); + } +} + +bool RuleProcessorCache::gShutdown = false; +mozilla::StaticRefPtr RuleProcessorCache::gRuleProcessorCache; diff --git a/layout/style/RuleProcessorCache.h b/layout/style/RuleProcessorCache.h new file mode 100644 index 000000000000..2ec9058b1b9f --- /dev/null +++ b/layout/style/RuleProcessorCache.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * cache of re-usable nsCSSRuleProcessors for given sets of style sheets + */ + +#ifndef mozilla_RuleProcessorCache_h +#define mozilla_RuleProcessorCache_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/StaticPtr.h" +#include "nsCSSRuleProcessor.h" +#include "nsExpirationTracker.h" +#include "nsIMediaList.h" +#include "nsIMemoryReporter.h" +#include "nsTArray.h" + +class nsCSSRuleProcessor; +namespace mozilla { +class CSSStyleSheet; +namespace css { +class DocumentRule; +} +} + +namespace mozilla { + +/** + * The RuleProcessorCache is a singleton object that caches + * nsCSSRuleProcessors keyed off a list of style sheets and the result of + * evaluating all @-moz-documents in the style sheets. nsStyleSet gets and + * puts nsCSSRuleProcessors from/to the RuleProcessorCache. + * + * State bits on CSSStyleSheet and nsCSSRuleProcessor track whether they are in + * the RuleProcessorCache. This lets us remove them from the RuleProcessorCache + * when they're going away. + */ +class RuleProcessorCache final : public nsIMemoryReporter +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + +public: + static nsCSSRuleProcessor* GetRuleProcessor( + const nsTArray& aSheets, + nsPresContext* aPresContext); + static void PutRuleProcessor( + const nsTArray& aSheets, + nsTArray&& aDocumentRulesInSheets, + const nsDocumentRuleResultCacheKey& aCacheKey, + nsCSSRuleProcessor* aRuleProcessor); + static void StartTracking(nsCSSRuleProcessor* aRuleProcessor); + static void StopTracking(nsCSSRuleProcessor* aRuleProcessor); + +#ifdef DEBUG + static bool HasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor); +#endif + static void RemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor); + static void RemoveSheet(CSSStyleSheet* aSheet); + + static void Shutdown() { gShutdown = true; gRuleProcessorCache = nullptr; } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +private: + class ExpirationTracker : public nsExpirationTracker + { + public: + explicit ExpirationTracker(RuleProcessorCache* aCache) + : nsExpirationTracker(10000) + , mCache(aCache) {} + + void RemoveObjectIfTracked(nsCSSRuleProcessor* aRuleProcessor); + + virtual void NotifyExpired(nsCSSRuleProcessor* aRuleProcessor) override { + mCache->RemoveRuleProcessor(aRuleProcessor); + } + + private: + RuleProcessorCache* mCache; + }; + + RuleProcessorCache() : mExpirationTracker(this) {} + ~RuleProcessorCache(); + + void InitMemoryReporter(); + + static bool EnsureGlobal(); + static StaticRefPtr gRuleProcessorCache; + static bool gShutdown; + + void DoRemoveSheet(CSSStyleSheet* aSheet); + nsCSSRuleProcessor* DoGetRuleProcessor( + const nsTArray& aSheets, + nsPresContext* aPresContext); + void DoPutRuleProcessor(const nsTArray& aSheets, + nsTArray&& aDocumentRulesInSheets, + const nsDocumentRuleResultCacheKey& aCacheKey, + nsCSSRuleProcessor* aRuleProcessor); +#ifdef DEBUG + bool DoHasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor); +#endif + void DoRemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor); + void DoStartTracking(nsCSSRuleProcessor* aRuleProcessor); + void DoStopTracking(nsCSSRuleProcessor* aRuleProcessor); + + struct DocumentEntry { + nsDocumentRuleResultCacheKey mCacheKey; + nsRefPtr mRuleProcessor; + }; + + struct Entry { + nsTArray mSheets; + nsTArray mDocumentRulesInSheets; + nsTArray mDocumentEntries; + }; + + // Function object to test whether an Entry object has a given sheet + // in its mSheets array. If it does, removes all of its rule processors + // before returning true. + struct HasSheet_ThenRemoveRuleProcessors { + HasSheet_ThenRemoveRuleProcessors(RuleProcessorCache* aCache, + CSSStyleSheet* aSheet) + : mCache(aCache), mSheet(aSheet) {} + bool operator()(Entry& aEntry) { + if (aEntry.mSheets.Contains(mSheet)) { + for (DocumentEntry& de : aEntry.mDocumentEntries) { + de.mRuleProcessor->SetInRuleProcessorCache(false); + mCache->mExpirationTracker.RemoveObjectIfTracked(de.mRuleProcessor); + } + return true; + } + return false; + } + RuleProcessorCache* mCache; + CSSStyleSheet* mSheet; + }; + + ExpirationTracker mExpirationTracker; + nsTArray mEntries; +}; + +} // namespace mozilla + +#endif // mozilla_RuleProcessorCache_h diff --git a/layout/style/moz.build b/layout/style/moz.build index 4cb634d3d617..ba03113f9c87 100644 --- a/layout/style/moz.build +++ b/layout/style/moz.build @@ -86,6 +86,7 @@ EXPORTS.mozilla += [ 'CSSVariableValues.h', 'IncrementalClearCOMRuleArray.h', 'RuleNodeCacheConditions.h', + 'RuleProcessorCache.h', 'StyleAnimationValue.h', ] @@ -165,6 +166,7 @@ UNIFIED_SOURCES += [ 'nsStyleUtil.cpp', 'nsTransitionManager.cpp', 'RuleNodeCacheConditions.cpp', + 'RuleProcessorCache.cpp', 'StyleAnimationValue.cpp', 'StyleRule.cpp', 'SVGAttrAnimationRuleProcessor.cpp', diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index 0057638f59d7..4aa07c027f12 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -54,6 +54,7 @@ #include "mozilla/LookAndFeel.h" #include "mozilla/Likely.h" #include "mozilla/TypedEnumBits.h" +#include "RuleProcessorCache.h" using namespace mozilla; using namespace mozilla::dom; @@ -1005,7 +1006,8 @@ nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets, uint8_t aSheetType, Element* aScopeElement, nsCSSRuleProcessor* - aPreviousCSSRuleProcessor) + aPreviousCSSRuleProcessor, + bool aIsShared) : mSheets(aSheets) , mRuleCascades(nullptr) , mPreviousCacheKey(aPreviousCSSRuleProcessor @@ -1013,7 +1015,14 @@ nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets, : UniquePtr()) , mLastPresContext(nullptr) , mScopeElement(aScopeElement) + , mStyleSetRefCnt(0) , mSheetType(aSheetType) + , mIsShared(aIsShared) + , mMustGatherDocumentRules(aIsShared) + , mInRuleProcessorCache(false) +#ifdef DEBUG + , mDocumentRulesAndCacheKeyValid(false) +#endif { NS_ASSERTION(!!mScopeElement == (aSheetType == nsStyleSet::eScopedDocSheet), "aScopeElement must be specified iff aSheetType is " @@ -1025,6 +1034,11 @@ nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets, nsCSSRuleProcessor::~nsCSSRuleProcessor() { + if (mInRuleProcessorCache) { + RuleProcessorCache::RemoveRuleProcessor(this); + } + MOZ_ASSERT(!mExpirationState.IsTracked()); + MOZ_ASSERT(mStyleSetRefCnt == 0); ClearSheets(); ClearRuleCascades(); } @@ -2990,6 +3004,23 @@ nsCSSRuleProcessor::ClearRuleCascades() mPreviousCacheKey = CloneMQCacheKey(); } + // No need to remove the rule processor from the RuleProcessorCache here, + // since CSSStyleSheet::ClearRuleCascades will have called + // RuleProcessorCache::RemoveSheet() passing itself, which will catch + // this rule processor (and any others for different @-moz-document + // cache key results). + MOZ_ASSERT(!RuleProcessorCache::HasRuleProcessor(this)); + +#ifdef DEBUG + // For shared rule processors, if we've already gathered document + // rules, then they will now be out of date. We don't actually need + // them to be up-to-date (see the comment in RefreshRuleCascade), so + // record their invalidity so we can assert if we try to use them. + if (!mMustGatherDocumentRules) { + mDocumentRulesAndCacheKeyValid = false; + } +#endif + // We rely on our caller (perhaps indirectly) to do something that // will rebuild style data and the user font set (either // nsIPresShell::ReconstructStyleData or @@ -3287,17 +3318,23 @@ struct CascadeEnumData { nsTArray& aFontFeatureValuesRules, nsTArray& aPageRules, nsTArray& aCounterStyleRules, + nsTArray& aDocumentRules, nsMediaQueryResultCacheKey& aKey, - uint8_t aSheetType) + nsDocumentRuleResultCacheKey& aDocumentKey, + uint8_t aSheetType, + bool aMustGatherDocumentRules) : mPresContext(aPresContext), mFontFaceRules(aFontFaceRules), mKeyframesRules(aKeyframesRules), mFontFeatureValuesRules(aFontFeatureValuesRules), mPageRules(aPageRules), mCounterStyleRules(aCounterStyleRules), + mDocumentRules(aDocumentRules), mCacheKey(aKey), + mDocumentCacheKey(aDocumentKey), mRulesByWeight(&gRulesByWeightOps, sizeof(RuleByWeightEntry), 32), - mSheetType(aSheetType) + mSheetType(aSheetType), + mMustGatherDocumentRules(aMustGatherDocumentRules) { // Initialize our arena PL_INIT_ARENA_POOL(&mArena, "CascadeEnumDataArena", @@ -3315,14 +3352,61 @@ struct CascadeEnumData { nsTArray& mFontFeatureValuesRules; nsTArray& mPageRules; nsTArray& mCounterStyleRules; + nsTArray& mDocumentRules; nsMediaQueryResultCacheKey& mCacheKey; + nsDocumentRuleResultCacheKey& mDocumentCacheKey; PLArenaPool mArena; // Hooray, a manual PLDHashTable since nsClassHashtable doesn't // provide a getter that gives me a *reference* to the value. PLDHashTable mRulesByWeight; // of PerWeightDataListItem linked lists uint8_t mSheetType; + bool mMustGatherDocumentRules; }; +/** + * Recursively traverses rules in order to: + * (1) add any @-moz-document rules into data->mDocumentRules. + * (2) record any @-moz-document rules whose conditions evaluate to true + * on data->mDocumentCacheKey. + * + * See also CascadeRuleEnumFunc below, which calls us via + * EnumerateRulesForwards. If modifying this function you may need to + * update CascadeRuleEnumFunc too. + */ +static bool +GatherDocRuleEnumFunc(css::Rule* aRule, void* aData) +{ + CascadeEnumData* data = (CascadeEnumData*)aData; + int32_t type = aRule->GetType(); + + MOZ_ASSERT(data->mMustGatherDocumentRules, + "should only call GatherDocRuleEnumFunc if " + "mMustGatherDocumentRules is true"); + + if (css::Rule::MEDIA_RULE == type || + css::Rule::SUPPORTS_RULE == type) { + css::GroupRule* groupRule = static_cast(aRule); + if (!groupRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData)) { + return false; + } + } + else if (css::Rule::DOCUMENT_RULE == type) { + css::DocumentRule* docRule = static_cast(aRule); + if (!data->mDocumentRules.AppendElement(docRule)) { + return false; + } + if (docRule->UseForPresentation(data->mPresContext)) { + if (!data->mDocumentCacheKey.AddMatchingRule(docRule)) { + return false; + } + } + if (!docRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData)) { + return false; + } + } + return true; +} + /* * This enumerates style rules in a sheet (and recursively into any * grouping rules) in order to: @@ -3335,6 +3419,21 @@ struct CascadeEnumData { * into data->mFontFeatureValuesRules. * (5) add any @page rules, in order, into data->mPageRules. * (6) add any @counter-style rules, in order, into data->mCounterStyleRules. + * (7) add any @-moz-document rules into data->mDocumentRules. + * (8) record any @-moz-document rules whose conditions evaluate to true + * on data->mDocumentCacheKey. + * + * See also GatherDocRuleEnumFunc above, which we call to traverse into + * @-moz-document rules even if their (or an ancestor's) condition + * fails. This means we might look at the result of some @-moz-document + * rules that don't actually affect whether a RuleProcessorCache lookup + * is a hit or a miss. The presence of @-moz-document rules inside + * @media etc. rules should be rare, and looking at all of them in the + * sheets lets us avoid the complication of having different document + * cache key results for different media. + * + * If modifying this function you may need to update + * GatherDocRuleEnumFunc too. */ static bool CascadeRuleEnumFunc(css::Rule* aRule, void* aData) @@ -3365,12 +3464,38 @@ CascadeRuleEnumFunc(css::Rule* aRule, void* aData) } } else if (css::Rule::MEDIA_RULE == type || - css::Rule::DOCUMENT_RULE == type || css::Rule::SUPPORTS_RULE == type) { css::GroupRule* groupRule = static_cast(aRule); - if (groupRule->UseForPresentation(data->mPresContext, data->mCacheKey)) - if (!groupRule->EnumerateRulesForwards(CascadeRuleEnumFunc, aData)) + const bool use = + groupRule->UseForPresentation(data->mPresContext, data->mCacheKey); + if (use || data->mMustGatherDocumentRules) { + if (!groupRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc : + GatherDocRuleEnumFunc, + aData)) { return false; + } + } + } + else if (css::Rule::DOCUMENT_RULE == type) { + css::DocumentRule* docRule = static_cast(aRule); + if (data->mMustGatherDocumentRules) { + if (!data->mDocumentRules.AppendElement(docRule)) { + return false; + } + } + const bool use = docRule->UseForPresentation(data->mPresContext); + if (use && data->mMustGatherDocumentRules) { + if (!data->mDocumentCacheKey.AddMatchingRule(docRule)) { + return false; + } + } + if (use || data->mMustGatherDocumentRules) { + if (!docRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc + : GatherDocRuleEnumFunc, + aData)) { + return false; + } + } } else if (css::Rule::FONT_FACE_RULE == type) { nsCSSFontFaceRule *fontFaceRule = static_cast(aRule); @@ -3493,8 +3618,12 @@ nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext) newCascade->mFontFeatureValuesRules, newCascade->mPageRules, newCascade->mCounterStyleRules, + mDocumentRules, newCascade->mCacheKey, - mSheetType); + mDocumentCacheKey, + mSheetType, + mMustGatherDocumentRules); + for (uint32_t i = 0; i < mSheets.Length(); ++i) { if (!CascadeSheet(mSheets.ElementAt(i), &data)) return; /* out of memory */ @@ -3538,6 +3667,40 @@ nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext) newCascade->mCounterStyleRuleTable.Put(rule->GetName(), rule); } + // mMustGatherDocumentRules controls whether we build mDocumentRules + // and mDocumentCacheKey so that they can be used as keys by the + // RuleProcessorCache, as obtained by TakeDocumentRulesAndCacheKey + // later. We set it to false just below so that we only do this + // the first time we build a RuleProcessorCache for a shared rule + // processor. + // + // An up-to-date mDocumentCacheKey is only needed if we + // are still in the RuleProcessorCache (as we store a copy of the + // cache key in the RuleProcessorCache), and an up-to-date + // mDocumentRules is only needed at the time TakeDocumentRulesAndCacheKey + // is called, which is immediately after the rule processor is created + // (by nsStyleSet). + // + // Note that when nsCSSRuleProcessor::ClearRuleCascades is called, + // by CSSStyleSheet::ClearRuleCascades, we will have called + // RuleProcessorCache::RemoveSheet, which will remove the rule + // processor from the cache. (This is because the list of document + // rules now may not match the one used as they key in the + // RuleProcessorCache.) + // + // Thus, as we'll no longer be in the RuleProcessorCache, and we won't + // have TakeDocumentRulesAndCacheKey called on us, we don't need to ensure + // mDocumentCacheKey and mDocumentRules are up-to-date after the + // first time GetRuleCascade is called. + if (mMustGatherDocumentRules) { + mDocumentRules.Sort(); + mDocumentCacheKey.Finalize(); + mMustGatherDocumentRules = false; +#ifdef DEBUG + mDocumentRulesAndCacheKeyValid = true; +#endif + } + // Ensure that the current one is always mRuleCascades. newCascade->mNext = mRuleCascades; mRuleCascades = newCascade.forget(); @@ -3575,6 +3738,45 @@ nsCSSRuleProcessor::SelectorListMatches(Element* aElement, return false; } +void +nsCSSRuleProcessor::TakeDocumentRulesAndCacheKey( + nsPresContext* aPresContext, + nsTArray& aDocumentRules, + nsDocumentRuleResultCacheKey& aCacheKey) +{ + MOZ_ASSERT(mIsShared); + + GetRuleCascade(aPresContext); + MOZ_ASSERT(mDocumentRulesAndCacheKeyValid); + + aDocumentRules.Clear(); + aDocumentRules.SwapElements(mDocumentRules); + aCacheKey.Swap(mDocumentCacheKey); + +#ifdef DEBUG + mDocumentRulesAndCacheKeyValid = false; +#endif +} + +void +nsCSSRuleProcessor::AddStyleSetRef() +{ + MOZ_ASSERT(mIsShared); + if (++mStyleSetRefCnt == 1) { + RuleProcessorCache::StopTracking(this); + } +} + +void +nsCSSRuleProcessor::ReleaseStyleSetRef() +{ + MOZ_ASSERT(mIsShared); + MOZ_ASSERT(mStyleSetRefCnt > 0); + if (--mStyleSetRefCnt == 0 && mInRuleProcessorCache) { + RuleProcessorCache::StartTracking(this); + } +} + // TreeMatchContext and AncestorFilter out of line methods void TreeMatchContext::InitAncestors(Element *aElement) diff --git a/layout/style/nsCSSRuleProcessor.h b/layout/style/nsCSSRuleProcessor.h index 5ccbda211fc1..a0f1936d5190 100644 --- a/layout/style/nsCSSRuleProcessor.h +++ b/layout/style/nsCSSRuleProcessor.h @@ -16,9 +16,12 @@ #include "mozilla/EventStates.h" #include "mozilla/MemoryReporting.h" #include "nsIStyleRuleProcessor.h" +#include "nsIMediaList.h" #include "nsTArray.h" #include "nsAutoPtr.h" +#include "nsExpirationTracker.h" #include "nsRuleWalker.h" +#include "mozilla/RefCountType.h" #include "mozilla/UniquePtr.h" struct CascadeEnumData; @@ -35,6 +38,9 @@ class nsCSSCounterStyleRule; namespace mozilla { class CSSStyleSheet; +namespace css { +class DocumentRule; +} // namespace css } // namespace mozilla /** @@ -59,7 +65,8 @@ public: nsCSSRuleProcessor(const sheet_array_type& aSheets, uint8_t aSheetType, mozilla::dom::Element* aScopeElement, - nsCSSRuleProcessor* aPreviousCSSRuleProcessor); + nsCSSRuleProcessor* aPreviousCSSRuleProcessor, + bool aIsShared = false); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(nsCSSRuleProcessor) @@ -161,12 +168,22 @@ public: */ mozilla::dom::Element* GetScopeElement() const { return mScopeElement; } -#ifdef DEBUG - void AssertQuirksChangeOK() { - NS_ASSERTION(!mRuleCascades, "can't toggle quirks style sheet without " - "clearing rule cascades"); + void TakeDocumentRulesAndCacheKey( + nsPresContext* aPresContext, + nsTArray& aDocumentRules, + nsDocumentRuleResultCacheKey& aDocumentRuleResultCacheKey); + + bool IsShared() const { return mIsShared; } + + nsExpirationState* GetExpirationState() { return &mExpirationState; } + void AddStyleSetRef(); + void ReleaseStyleSetRef(); + void SetInRuleProcessorCache(bool aVal) { + MOZ_ASSERT(mIsShared); + printf("%p SetInRuleProcessorCache %d\n", this, aVal); + mInRuleProcessorCache = aVal; } -#endif + bool IsInRuleProcessorCache() const { return mInRuleProcessorCache; } #ifdef XP_WIN // Cached theme identifier for the moz-windows-theme media query. @@ -221,9 +238,29 @@ private: // Only used if mSheetType == nsStyleSet::eScopedDocSheet. nsRefPtr mScopeElement; + nsTArray mDocumentRules; + nsDocumentRuleResultCacheKey mDocumentCacheKey; + + nsExpirationState mExpirationState; + MozRefCountType mStyleSetRefCnt; + // type of stylesheet using this processor uint8_t mSheetType; // == nsStyleSet::sheetType + const bool mIsShared; + + // Whether we need to build up mDocumentCacheKey and mDocumentRules as + // we build a RuleCascadeData. Is true only for shared rule processors + // and only before we build the first RuleCascadeData. See comment in + // RefreshRuleCascade for why. + bool mMustGatherDocumentRules; + + bool mInRuleProcessorCache; + +#ifdef DEBUG + bool mDocumentRulesAndCacheKeyValid; +#endif + #ifdef XP_WIN static uint8_t sWinThemeId; #endif diff --git a/layout/style/nsCSSRules.cpp b/layout/style/nsCSSRules.cpp index 3be8b5373752..d36fb1a3faf7 100644 --- a/layout/style/nsCSSRules.cpp +++ b/layout/style/nsCSSRules.cpp @@ -1022,6 +1022,12 @@ DocumentRule::SetConditionText(const nsAString& aConditionText) /* virtual */ bool DocumentRule::UseForPresentation(nsPresContext* aPresContext, nsMediaQueryResultCacheKey& aKey) +{ + return UseForPresentation(aPresContext); +} + +bool +DocumentRule::UseForPresentation(nsPresContext* aPresContext) { nsIDocument *doc = aPresContext->Document(); nsIURI *docURI = doc->GetDocumentURI(); diff --git a/layout/style/nsCSSRules.h b/layout/style/nsCSSRules.h index 4ff5542218ea..d3a425023316 100644 --- a/layout/style/nsCSSRules.h +++ b/layout/style/nsCSSRules.h @@ -146,6 +146,8 @@ public: virtual bool UseForPresentation(nsPresContext* aPresContext, nsMediaQueryResultCacheKey& aKey) override; + bool UseForPresentation(nsPresContext* aPresContext); + enum Function { eURL, eURLPrefix, diff --git a/layout/style/nsIMediaList.h b/layout/style/nsIMediaList.h index ac9c686f4871..bb61cbce6faa 100644 --- a/layout/style/nsIMediaList.h +++ b/layout/style/nsIMediaList.h @@ -26,6 +26,9 @@ struct nsMediaFeature; namespace mozilla { class CSSStyleSheet; +namespace css { +class DocumentRule; +} // namespace css } // namespace mozilla struct nsMediaExpression { @@ -129,6 +132,64 @@ private: nsTArray mFeatureCache; }; +/** + * nsDocumentRuleResultCacheKey is analagous to nsMediaQueryResultCacheKey + * and stores the result of matching the @-moz-document rules from a set + * of style sheets. nsCSSRuleProcessor builds up an + * nsDocumentRuleResultCacheKey as it visits the @-moz-document rules + * while building its RuleCascadeData. + * + * Rather than represent the result using a list of both the matching and + * non-matching rules, we just store the matched rules. The assumption is + * that in situations where we have a large number of rules -- such as the + * thousands added by AdBlock Plus -- that only a small number will be + * matched. Thus to check if the nsDocumentRuleResultCacheKey matches a + * given nsPresContext, we also need the entire list of @-moz-document + * rules to know which rules must not match. + */ +class nsDocumentRuleResultCacheKey +{ +public: +#ifdef DEBUG + nsDocumentRuleResultCacheKey() + : mFinalized(false) {} +#endif + + bool AddMatchingRule(mozilla::css::DocumentRule* aRule); + bool Matches(nsPresContext* aPresContext, + const nsTArray& aRules) const; + + bool operator==(const nsDocumentRuleResultCacheKey& aOther) const { + MOZ_ASSERT(mFinalized); + MOZ_ASSERT(aOther.mFinalized); + return mMatchingRules == aOther.mMatchingRules; + } + bool operator!=(const nsDocumentRuleResultCacheKey& aOther) const { + return !(*this == aOther); + } + + void Swap(nsDocumentRuleResultCacheKey& aOther) { + mMatchingRules.SwapElements(aOther.mMatchingRules); +#ifdef DEBUG + std::swap(mFinalized, aOther.mFinalized); +#endif + } + + void Finalize(); + +#ifdef DEBUG + void List(FILE* aOut = stdout, int32_t aIndex = 0) const; +#endif + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + +private: + nsTArray mMatchingRules; +#ifdef DEBUG + bool mFinalized; +#endif +}; + class nsMediaQuery { public: nsMediaQuery() diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 8f1721f60f6c..b9834bfec2d6 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -49,6 +49,7 @@ #include "CounterStyleManager.h" #include "nsCSSPropertySet.h" #include "mozilla/RuleNodeCacheConditions.h" +#include "nsDeviceContext.h" #if defined(_MSC_VER) || defined(__MINGW32__) #include diff --git a/layout/style/nsStyleSet.cpp b/layout/style/nsStyleSet.cpp index 9ba05def8d30..633cc4adccb6 100644 --- a/layout/style/nsStyleSet.cpp +++ b/layout/style/nsStyleSet.cpp @@ -15,6 +15,7 @@ #include "mozilla/CSSStyleSheet.h" #include "mozilla/EventStates.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/RuleProcessorCache.h" #include "nsIDocumentInlines.h" #include "nsRuleWalker.h" #include "nsStyleContext.h" @@ -151,6 +152,16 @@ static const nsStyleSet::sheetType gCSSSheetTypes[] = { nsStyleSet::eOverrideSheet }; +static bool IsCSSSheetType(nsStyleSet::sheetType aSheetType) +{ + for (nsStyleSet::sheetType type : gCSSSheetTypes) { + if (type == aSheetType) { + return true; + } + } + return false; +} + nsStyleSet::nsStyleSet() : mRuleTree(nullptr), mBatching(0), @@ -158,11 +169,34 @@ nsStyleSet::nsStyleSet() mAuthorStyleDisabled(false), mInReconstruct(false), mInitFontFeatureValuesLookup(true), + mNeedsRestyleAfterEnsureUniqueInner(false), mDirty(0), mUnusedRuleNodeCount(0) { } +nsStyleSet::~nsStyleSet() +{ + for (sheetType type : gCSSSheetTypes) { + for (uint32_t i = 0, n = mSheets[type].Length(); i < n; i++) { + static_cast(mSheets[type][i])->DropStyleSet(this); + } + } + + // drop reference to cached rule processors + nsCSSRuleProcessor* rp; + rp = static_cast(mRuleProcessors[eAgentSheet].get()); + if (rp) { + MOZ_ASSERT(rp->IsShared()); + rp->ReleaseStyleSetRef(); + } + rp = static_cast(mRuleProcessors[eUserSheet].get()); + if (rp) { + MOZ_ASSERT(rp->IsShared()); + rp->ReleaseStyleSetRef(); + } +} + size_t nsStyleSet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { @@ -170,7 +204,16 @@ nsStyleSet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const for (int i = 0; i < eSheetTypeCount; i++) { if (mRuleProcessors[i]) { - n += mRuleProcessors[i]->SizeOfIncludingThis(aMallocSizeOf); + bool shared = false; + if (i == eAgentSheet || i == eUserSheet) { + // The only two origins we consider caching rule processors for. + nsCSSRuleProcessor* rp = + static_cast(mRuleProcessors[i].get()); + shared = rp->IsShared(); + } + if (!shared) { + n += mRuleProcessors[i]->SizeOfIncludingThis(aMallocSizeOf); + } } // mSheets is a C-style array of nsCOMArrays. We do not own the sheets in // the nsCOMArrays (either the nsLayoutStyleSheetCache singleton or our @@ -260,16 +303,6 @@ nsStyleSet::EndReconstruct() GCRuleTrees(); } -void -nsStyleSet::SetQuirkStyleSheet(nsIStyleSheet* aQuirkStyleSheet) -{ - NS_ASSERTION(aQuirkStyleSheet, "Must have quirk sheet if this is called"); - NS_ASSERTION(!mQuirkStyleSheet, "Multiple calls to SetQuirkStyleSheet?"); - NS_ASSERTION(mSheets[eAgentSheet].IndexOf(aQuirkStyleSheet) != -1, - "Quirk style sheet not one of our agent sheets?"); - mQuirkStyleSheet = aQuirkStyleSheet; -} - typedef nsDataHashtable, uint32_t> ScopeDepthCache; // Returns the depth of a style scope element, with 1 being the depth of @@ -354,7 +387,15 @@ nsStyleSet::GatherRuleProcessors(sheetType aType) { nsCOMPtr oldRuleProcessor(mRuleProcessors[aType]); nsTArray> oldScopedDocRuleProcessors; - + if (aType == eAgentSheet || aType == eUserSheet) { + // drop reference to cached rule processor + nsCSSRuleProcessor* rp = + static_cast(mRuleProcessors[aType].get()); + if (rp) { + MOZ_ASSERT(rp->IsShared()); + rp->ReleaseStyleSetRef(); + } + } mRuleProcessors[aType] = nullptr; if (aType == eScopedDocSheet) { for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) { @@ -467,10 +508,43 @@ nsStyleSet::GatherRuleProcessors(sheetType aType) if (mSheets[aType].Count()) { switch (aType) { case eAgentSheet: - case eUserSheet: + case eUserSheet: { + // levels containing non-scoped CSS style sheets whose rule processors + // we want to re-use + nsCOMArray& sheets = mSheets[aType]; + nsTArray> cssSheets(sheets.Count()); + for (int32_t i = 0, i_end = sheets.Count(); i < i_end; ++i) { + nsRefPtr cssSheet = do_QueryObject(sheets[i]); + NS_ASSERTION(cssSheet, "not a CSS sheet"); + cssSheets.AppendElement(cssSheet); + } + nsTArray cssSheetsRaw(cssSheets.Length()); + for (int32_t i = 0, i_end = cssSheets.Length(); i < i_end; ++i) { + cssSheetsRaw.AppendElement(cssSheets[i]); + } + nsCSSRuleProcessor* rp = + RuleProcessorCache::GetRuleProcessor(cssSheetsRaw, PresContext()); + if (!rp) { + rp = new nsCSSRuleProcessor(cssSheets, uint8_t(aType), nullptr, + static_cast( + oldRuleProcessor.get()), + true /* aIsShared */); + nsTArray documentRules; + nsDocumentRuleResultCacheKey cacheKey; + rp->TakeDocumentRulesAndCacheKey(PresContext(), + documentRules, cacheKey); + RuleProcessorCache::PutRuleProcessor(cssSheetsRaw, + Move(documentRules), + cacheKey, rp); + } + mRuleProcessors[aType] = rp; + rp->AddStyleSetRef(); + break; + } case eDocSheet: case eOverrideSheet: { - // levels containing CSS stylesheets (apart from eScopedDocSheet) + // levels containing non-scoped CSS stylesheets whose rule processors + // we don't want to re-use nsCOMArray& sheets = mSheets[aType]; nsTArray> cssSheets(sheets.Count()); for (int32_t i = 0, i_end = sheets.Count(); i < i_end; ++i) { @@ -510,10 +584,14 @@ nsStyleSet::AppendStyleSheet(sheetType aType, nsIStyleSheet *aSheet) NS_PRECONDITION(aSheet, "null arg"); NS_ASSERTION(aSheet->IsApplicable(), "Inapplicable sheet being placed in style set"); - mSheets[aType].RemoveObject(aSheet); + bool present = mSheets[aType].RemoveObject(aSheet); if (!mSheets[aType].AppendObject(aSheet)) return NS_ERROR_OUT_OF_MEMORY; + if (!present && IsCSSSheetType(aType)) { + static_cast(aSheet)->AddStyleSet(this); + } + return DirtyRuleProcessors(aType); } @@ -523,10 +601,14 @@ nsStyleSet::PrependStyleSheet(sheetType aType, nsIStyleSheet *aSheet) NS_PRECONDITION(aSheet, "null arg"); NS_ASSERTION(aSheet->IsApplicable(), "Inapplicable sheet being placed in style set"); - mSheets[aType].RemoveObject(aSheet); + bool present = mSheets[aType].RemoveObject(aSheet); if (!mSheets[aType].InsertObjectAt(aSheet, 0)) return NS_ERROR_OUT_OF_MEMORY; + if (!present && IsCSSSheetType(aType)) { + static_cast(aSheet)->AddStyleSet(this); + } + return DirtyRuleProcessors(aType); } @@ -536,7 +618,11 @@ nsStyleSet::RemoveStyleSheet(sheetType aType, nsIStyleSheet *aSheet) NS_PRECONDITION(aSheet, "null arg"); NS_ASSERTION(aSheet->IsComplete(), "Incomplete sheet being removed from style set"); - mSheets[aType].RemoveObject(aSheet); + if (mSheets[aType].RemoveObject(aSheet)) { + if (IsCSSSheetType(aType)) { + static_cast(aSheet)->DropStyleSet(this); + } + } return DirtyRuleProcessors(aType); } @@ -545,10 +631,23 @@ nsresult nsStyleSet::ReplaceSheets(sheetType aType, const nsCOMArray &aNewSheets) { + bool cssSheetType = IsCSSSheetType(aType); + if (cssSheetType) { + for (uint32_t i = 0, n = mSheets[aType].Length(); i < n; i++) { + static_cast(mSheets[aType][i])->DropStyleSet(this); + } + } + mSheets[aType].Clear(); if (!mSheets[aType].AppendObjects(aNewSheets)) return NS_ERROR_OUT_OF_MEMORY; + if (cssSheetType) { + for (uint32_t i = 0, n = mSheets[aType].Length(); i < n; i++) { + static_cast(mSheets[aType][i])->AddStyleSet(this); + } + } + return DirtyRuleProcessors(aType); } @@ -560,7 +659,7 @@ nsStyleSet::InsertStyleSheetBefore(sheetType aType, nsIStyleSheet *aNewSheet, NS_ASSERTION(aNewSheet->IsApplicable(), "Inapplicable sheet being placed in style set"); - mSheets[aType].RemoveObject(aNewSheet); + bool present = mSheets[aType].RemoveObject(aNewSheet); int32_t idx = mSheets[aType].IndexOf(aReferenceSheet); if (idx < 0) return NS_ERROR_INVALID_ARG; @@ -568,6 +667,10 @@ nsStyleSet::InsertStyleSheetBefore(sheetType aType, nsIStyleSheet *aNewSheet, if (!mSheets[aType].InsertObjectAt(aNewSheet, idx)) return NS_ERROR_OUT_OF_MEMORY; + if (!present && IsCSSSheetType(aType)) { + static_cast(aNewSheet)->AddStyleSet(this); + } + return DirtyRuleProcessors(aType); } @@ -615,7 +718,7 @@ nsStyleSet::AddDocStyleSheet(nsIStyleSheet* aSheet, nsIDocument* aDocument) eDocSheet; nsCOMArray& sheets = mSheets[type]; - sheets.RemoveObject(aSheet); + bool present = sheets.RemoveObject(aSheet); nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance(); // lowest index first @@ -642,6 +745,10 @@ nsStyleSet::AddDocStyleSheet(nsIStyleSheet* aSheet, nsIDocument* aDocument) if (!sheets.InsertObjectAt(aSheet, index)) return NS_ERROR_OUT_OF_MEMORY; + if (!present) { + static_cast(aSheet)->AddStyleSet(this); + } + return DirtyRuleProcessors(type); } @@ -680,35 +787,6 @@ nsStyleSet::EndUpdate() return NS_OK; } -void -nsStyleSet::EnableQuirkStyleSheet(bool aEnable) -{ - if (!mQuirkStyleSheet) { - // SVG-as-an-image doesn't load this sheet - return; - } -#ifdef DEBUG - bool oldEnabled; - { - nsCOMPtr domSheet = - do_QueryInterface(mQuirkStyleSheet); - domSheet->GetDisabled(&oldEnabled); - oldEnabled = !oldEnabled; - } -#endif - mQuirkStyleSheet->SetEnabled(aEnable); -#ifdef DEBUG - // This should always be OK, since SetEnabled should call - // ClearRuleCascades. - // Note that we can hit this codepath multiple times when document.open() - // (potentially implied) happens multiple times. - if (mRuleProcessors[eAgentSheet] && aEnable != oldEnabled) { - static_cast(static_cast( - mRuleProcessors[eAgentSheet]))->AssertQuirksChangeOK(); - } -#endif -} - template static bool EnumRulesMatching(nsIStyleRuleProcessor* aProcessor, void* aData) @@ -2349,7 +2427,7 @@ nsStyleSet::MediumFeaturesChanged() return stylesChanged; } -CSSStyleSheet::EnsureUniqueInnerResult +bool nsStyleSet::EnsureUniqueInnerOnCSSSheets() { nsAutoTArray queue; @@ -2365,22 +2443,19 @@ nsStyleSet::EnsureUniqueInnerOnCSSSheets() mBindingManager->AppendAllSheets(queue); } - CSSStyleSheet::EnsureUniqueInnerResult res = - CSSStyleSheet::eUniqueInner_AlreadyUnique; while (!queue.IsEmpty()) { uint32_t idx = queue.Length() - 1; CSSStyleSheet* sheet = queue[idx]; queue.RemoveElementAt(idx); - CSSStyleSheet::EnsureUniqueInnerResult sheetRes = - sheet->EnsureUniqueInner(); - if (sheetRes == CSSStyleSheet::eUniqueInner_ClonedInner) { - res = sheetRes; - } + sheet->EnsureUniqueInner(); // Enqueue all the sheet's children. sheet->AppendAllChildSheets(queue); } + + bool res = mNeedsRestyleAfterEnsureUniqueInner; + mNeedsRestyleAfterEnsureUniqueInner = false; return res; } diff --git a/layout/style/nsStyleSet.h b/layout/style/nsStyleSet.h index 53924d3e3d90..40c290d68bdb 100644 --- a/layout/style/nsStyleSet.h +++ b/layout/style/nsStyleSet.h @@ -83,10 +83,11 @@ public: // then handed off to the PresShell. Only the PresShell should delete a // style set. -class nsStyleSet +class nsStyleSet final { public: nsStyleSet(); + ~nsStyleSet(); size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; @@ -94,9 +95,6 @@ class nsStyleSet nsRuleNode* GetRuleTree() { return mRuleTree; } - // enable / disable the Quirk style sheet - void EnableQuirkStyleSheet(bool aEnable); - // get a style context for a non-pseudo frame. already_AddRefed ResolveStyleFor(mozilla::dom::Element* aElement, @@ -363,11 +361,6 @@ class nsStyleSet return mInReconstruct; } - // Let the style set know that a particular sheet is the quirks sheet. This - // sheet must already have been added to the UA sheets. The pointer must not - // be null. This should only be called once for a given style set. - void SetQuirkStyleSheet(nsIStyleSheet* aQuirkStyleSheet); - // Return whether the rule tree has cached data such that we need to // do dynamic change handling for changes that change the results of // media queries or require rebuilding all style data. @@ -389,7 +382,15 @@ class nsStyleSet --mUnusedRuleNodeCount; } - mozilla::CSSStyleSheet::EnsureUniqueInnerResult EnsureUniqueInnerOnCSSSheets(); + // Returns true if a restyle of the document is needed due to cloning + // sheet inners. + bool EnsureUniqueInnerOnCSSSheets(); + + // Called by CSSStyleSheet::EnsureUniqueInner to let us know it cloned + // its inner. + void SetNeedsRestyleAfterEnsureUniqueInner() { + mNeedsRestyleAfterEnsureUniqueInner = true; + } nsIStyleRule* InitialStyleRule(); @@ -467,8 +468,8 @@ class nsStyleSet // The sheets in each array in mSheets are stored with the most significant // sheet last. // The arrays for ePresHintSheet, eStyleAttrSheet, eTransitionSheet, - // and eAnimationSheet are always empty. (FIXME: We should reduce - // the storage needed for them.) + // eAnimationSheet and eSVGAttrAnimationSheet are always empty. + // (FIXME: We should reduce the storage needed for them.) nsCOMArray mSheets[eSheetTypeCount]; // mRuleProcessors[eScopedDocSheet] is always null; rule processors @@ -478,9 +479,6 @@ class nsStyleSet // Rule processors for HTML5 scoped style sheets, one per scope. nsTArray > mScopedDocSheetRuleProcessors; - // cached instance for enabling/disabling - nsCOMPtr mQuirkStyleSheet; - nsRefPtr mBindingManager; nsRuleNode* mRuleTree; // This is the root of our rule tree. It is a @@ -493,6 +491,7 @@ class nsStyleSet unsigned mAuthorStyleDisabled: 1; unsigned mInReconstruct : 1; unsigned mInitFontFeatureValuesLookup : 1; + unsigned mNeedsRestyleAfterEnsureUniqueInner : 1; unsigned mDirty : 10; // one dirty bit is used per sheet type uint32_t mUnusedRuleNodeCount; // used to batch rule node GC diff --git a/media/libstagefright/gtest/TestMP4Rust.cpp b/media/libstagefright/gtest/TestMP4Rust.cpp index d1983c3882ec..5f2803385028 100644 --- a/media/libstagefright/gtest/TestMP4Rust.cpp +++ b/media/libstagefright/gtest/TestMP4Rust.cpp @@ -6,7 +6,7 @@ #include "gtest/gtest.h" #include "mp4_demuxer/MP4Metadata.h" -#include +#include #include #include diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index 6471e2ee4196..d20ff4be092c 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -182,6 +182,7 @@ nsHttpHandler::nsHttpHandler() , mParentalControlEnabled(false) , mTelemetryEnabled(false) , mAllowExperiments(true) + , mDebugObservations(false) , mHandlerActive(false) , mEnableSpdy(false) , mSpdyV31(true) @@ -1439,6 +1440,15 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) } } + // network.http.debug-observations + if (PREF_CHANGED("network.http.debug-observations")) { + cVar = false; + rv = prefs->GetBoolPref("network.http.debug-observations", &cVar); + if (NS_SUCCEEDED(rv)) { + mDebugObservations = cVar; + } + } + // // Test HTTP Pipelining (bug796192) // If experiments are allowed and pipelining is Off, @@ -2054,6 +2064,17 @@ nsHttpHandler::SpeculativeConnectInternal(nsIURI *aURI, if (!mHandlerActive) return NS_OK; + MOZ_ASSERT(NS_IsMainThread()); + if (mDebugObservations && mObserverService) { + // this is basically used for test coverage of an otherwise 'hintable' feature + nsAutoCString spec; + aURI->GetSpec(spec); + spec.Append(anonymous ? NS_LITERAL_CSTRING("[A]") : NS_LITERAL_CSTRING("[.]")); + mObserverService->NotifyObservers(nullptr, + "speculative-connect-request", + NS_ConvertUTF8toUTF16(spec).get()); + } + nsISiteSecurityService* sss = gHttpHandler->GetSSService(); bool isStsHost = false; if (!sss) diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 411d707907aa..9211b8d22135 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -477,6 +477,9 @@ private: // The value of network.allow-experiments uint32_t mAllowExperiments : 1; + // The value of 'hidden' network.http.debug-observations : 1; + uint32_t mDebugObservations : 1; + // true in between init and shutdown states uint32_t mHandlerActive : 1; diff --git a/netwerk/test/mochitests/rel_preconnect.sjs b/netwerk/test/mochitests/rel_preconnect.sjs index d373f2846003..7c1dc2dd90d0 100644 --- a/netwerk/test/mochitests/rel_preconnect.sjs +++ b/netwerk/test/mochitests/rel_preconnect.sjs @@ -6,7 +6,10 @@ function handleRequest(request, response) response.setHeader("Cache-Control", "no-cache", false); response.setHeader("Link", "<" + request.getHeader('X-Link') + - ">; rel=preconnect"); + ">; rel=preconnect" + ", " + + "<" + + request.getHeader('X-Link') + + ">; rel=preconnect; crossOrigin=anonymous"); response.write("check that header"); } diff --git a/netwerk/test/mochitests/test_rel_preconnect.html b/netwerk/test/mochitests/test_rel_preconnect.html index 8bd6de8255ee..c1a3ab8a6891 100644 --- a/netwerk/test/mochitests/test_rel_preconnect.html +++ b/netwerk/test/mochitests/test_rel_preconnect.html @@ -11,7 +11,7 @@ SimpleTest.waitForExplicitFinish(); const Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci, Cr = SpecialPowers.Cr; -var srv1, srv2; +var srv; function TestServer1(nextTest) { this.listener= Cc["@mozilla.org/network/server-socket;1"] @@ -32,79 +32,54 @@ TestServer1.prototype = { onSocketAccepted: function(socket, trans) { try { socket.close(); } catch(e) {} try { trans.close(); } catch(e) {} - ok(true, "received connect"); - srv1.listener.close(); - if(srv1.nextTest != null) { - setTimeout(srv1.nextTest, 0); - } - srv1.nextTest = null; }, onStopListening: function(socket) {} }; -function TestServer2(nextTest) { - this.listener= Cc["@mozilla.org/network/server-socket;1"] - .createInstance(Ci.nsIServerSocket); - this.listener.init(-1, true, -1); - this.listener.asyncListen(SpecialPowers.wrapCallbackObject(this)); - this.nextTest = nextTest; -} +var remainder = 4; +var observer; -TestServer2.prototype = { - QueryInterface: function(iid) { - iid = SpecialPowers.wrap(iid); - if (iid.equals(Ci.nsIServerSocketListener) || - iid.equals(Ci.nsISupports)) - return this; - throw Cr.NS_ERROR_NO_INTERFACE; - }, - onSocketAccepted: function(socket, trans) { - try { socket.close(); } catch(e) {} - try { trans.close(); } catch(e) {} - ok(true, "received connect"); - srv2.listener.close(); - if(srv2.nextTest != null) { - setTimeout(srv2.nextTest, 0); - } - srv2.nextTest = null; - }, - onStopListening: function(socket) {} -}; - -var originalLimit = SpecialPowers.getIntPref("network.http.speculative-parallel-limit"); - -function testElement() +function doTest() { - // test the link rel=preconnect element in the head - srv1 = new TestServer1(testHeader); - SpecialPowers.setIntPref("network.http.speculative-parallel-limit", 1); + srv = new TestServer1(); + SpecialPowers.setBoolPref("network.http.debug-observations", true); + + observer = SpecialPowers.wrapCallback(function(subject, topic, data) { + remainder--; + ok(true, "observed remainder = " + remainder); + if (!remainder) { + srv.listener.close(); + SpecialPowers.removeObserver(observer, "speculative-connect-request"); + SpecialPowers.setBoolPref("network.http.debug-observations", false); + SimpleTest.finish(); + } + }); + SpecialPowers.addObserver(observer, "speculative-connect-request", false); + + // test the link rel=preconnect element in the head for both normal + // and crossOrigin=anonymous var link = document.createElement("link"); link.rel = "preconnect"; - link.href = "//localhost:" + srv1.listener.port; + link.href = "//localhost:" + srv.listener.port; + document.head.appendChild(link); + link = document.createElement("link"); + link.rel = "preconnect"; + link.href = "//localhost:" + srv.listener.port; + link.crossOrigin = "anonymous"; document.head.appendChild(link); -} -function testHeader() -{ - // test the http link response header - srv2 = new TestServer2(testDone); + // test the http link response header - the test contains both a + // normal and anonymous preconnect link header var xhr = new XMLHttpRequest(); xhr.open("GET", 'rel_preconnect.sjs', false); - xhr.setRequestHeader("X-Link", "//localhost:" + srv2.listener.port); + xhr.setRequestHeader("X-Link", "//localhost:" + srv.listener.port); xhr.send(); is(xhr.status, 200, 'xhr cool'); } -function testDone() -{ - SpecialPowers.setIntPref("network.http.speculative-parallel-limit", - originalLimit); - SimpleTest.finish(); -} - - +

diff --git a/parser/html/nsHtml5SpeculativeLoad.cpp b/parser/html/nsHtml5SpeculativeLoad.cpp
index ee15804a9532..137ca45b1399 100644
--- a/parser/html/nsHtml5SpeculativeLoad.cpp
+++ b/parser/html/nsHtml5SpeculativeLoad.cpp
@@ -68,7 +68,7 @@ nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor)
       }
       break;
     case eSpeculativeLoadPreconnect:
-      aExecutor->Preconnect(mUrl);
+      aExecutor->Preconnect(mUrl, mCrossOrigin);
       break;
     default:
       NS_NOTREACHED("Bogus speculative load.");
diff --git a/parser/html/nsHtml5SpeculativeLoad.h b/parser/html/nsHtml5SpeculativeLoad.h
index 70ac54ebc5fd..db427199010a 100644
--- a/parser/html/nsHtml5SpeculativeLoad.h
+++ b/parser/html/nsHtml5SpeculativeLoad.h
@@ -164,12 +164,14 @@ class nsHtml5SpeculativeLoad {
       mTypeOrCharsetSource.Assign((char16_t)aCharsetSource);
     }
 
-    inline void InitPreconnect(const nsAString& aUrl)
+    inline void InitPreconnect(const nsAString& aUrl,
+                               const nsAString& aCrossOrigin)
     {
       NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
                       "Trying to reinitialize a speculative load!");
       mOpCode = eSpeculativeLoadPreconnect;
       mUrl.Assign(aUrl);
+      mCrossOrigin.Assign(aCrossOrigin);
     }
 
     void Perform(nsHtml5TreeOpExecutor* aExecutor);
@@ -193,9 +195,9 @@ class nsHtml5SpeculativeLoad {
      */
     nsString mTypeOrCharsetSource;
     /**
-     * If mOpCode is eSpeculativeLoadImage or eSpeculativeLoadScript[FromHead],
-     * this is the value of the "crossorigin" attribute.  If the
-     * attribute is not set, this will be a void string.
+     * If mOpCode is eSpeculativeLoadImage or eSpeculativeLoadScript[FromHead]
+     * or eSpeculativeLoadPreconnect this is the value of the "crossorigin"
+     * attribute.  If the attribute is not set, this will be a void string.
      */
     nsString mCrossOrigin;
     /**
diff --git a/parser/html/nsHtml5TreeBuilderCppSupplement.h b/parser/html/nsHtml5TreeBuilderCppSupplement.h
index c044b1648353..b9462886e490 100644
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -191,8 +191,10 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
             } else if (rel->LowerCaseEqualsASCII("preconnect")) {
               nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
               if (url) {
+                nsString* crossOrigin =
+                  aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
                 mSpeculativeLoadQueue.AppendElement()->
-                  InitPreconnect(*url);
+                  InitPreconnect(*url, (crossOrigin) ? *crossOrigin : NullString());
               }
             }
           }
diff --git a/python/mach/mach/decorators.py b/python/mach/mach/decorators.py
index 9b28fbd31a04..733fd42f08c0 100644
--- a/python/mach/mach/decorators.py
+++ b/python/mach/mach/decorators.py
@@ -144,7 +144,7 @@ def CommandProvider(cls):
 
         seen_commands.add(command.name)
 
-        if command.conditions is None and Registrar.require_conditions:
+        if not command.conditions and Registrar.require_conditions:
             continue
 
         msg = 'Mach command \'%s\' implemented incorrectly. ' + \
diff --git a/testing/docker/ubuntu-build/Dockerfile b/testing/docker/ubuntu-build/Dockerfile
index 809191c8e1a0..50d56f2260d2 100644
--- a/testing/docker/ubuntu-build/Dockerfile
+++ b/testing/docker/ubuntu-build/Dockerfile
@@ -11,7 +11,7 @@ RUN           bash /tmp/system-setup.sh
 # configure git and install tc-vcs
 RUN git config --global user.email "nobody@mozilla.com" && \
     git config --global user.name "mozilla"
-RUN npm install -g taskcluster-vcs@2.3.6 || true
+RUN npm install -g taskcluster-vcs@2.3.6
 
 # Ensure that build specific dependencies live in a single layer
 ADD           build-setup.sh   /tmp/build-setup.sh
diff --git a/testing/docker/ubuntu32-build/Dockerfile b/testing/docker/ubuntu32-build/Dockerfile
index 03af01b15977..b96f83848f58 100644
--- a/testing/docker/ubuntu32-build/Dockerfile
+++ b/testing/docker/ubuntu32-build/Dockerfile
@@ -11,7 +11,7 @@ RUN           bash /tmp/system-setup.sh
 # configure git and install tc-vcs
 RUN git config --global user.email "nobody@mozilla.com" && \
     git config --global user.name "mozilla"
-RUN npm install -g taskcluster-vcs@2.3.5 || true
+RUN npm install -g taskcluster-vcs@2.3.6
 
 # Ensure that build specific dependencies live in a single layer
 ADD           build-setup.sh   /tmp/build-setup.sh
diff --git a/testing/marionette/client/requirements.txt b/testing/marionette/client/requirements.txt
index 9f02a667072a..3deea5ab90bd 100644
--- a/testing/marionette/client/requirements.txt
+++ b/testing/marionette/client/requirements.txt
@@ -1,4 +1,3 @@
-marionette-transport >= 0.4
 marionette-driver >= 0.8
 browsermob-proxy >= 0.6.0
 manifestparser >= 1.1
diff --git a/testing/taskcluster/tasks/builds/dbg_linux64.yml b/testing/taskcluster/tasks/builds/dbg_linux64.yml
index 2cdccd84770d..7da97b765ac4 100644
--- a/testing/taskcluster/tasks/builds/dbg_linux64.yml
+++ b/testing/taskcluster/tasks/builds/dbg_linux64.yml
@@ -13,3 +13,10 @@ task:
   payload:
     env:
       MH_CUSTOM_BUILD_VARIANT_CFG: 'debug'
+  extra:
+    treeherder:
+      groupSymbol: tc
+      groupName: Submitted by taskcluster
+      symbol: B
+      collection:
+        debug: true
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 531c0cf537f7..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
deleted file mode 100644
index 62a15ea38564..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 14e5f5f584e7..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 33bd14806ed5..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
deleted file mode 100644
index 43953bb98dc3..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 079f2bd15451..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 7791c18cb37c..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
deleted file mode 100644
index 41bd81f260a4..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index cb2b3dc8ac10..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 3500f0a7f9e1..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
deleted file mode 100644
index 749f710af871..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index ebc3fc92abcb..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 5eacb9f041e3..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
deleted file mode 100644
index db4355144e03..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 2cb7c9abddee..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 02d6990be89d..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
deleted file mode 100644
index f72933973608..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 6d139bf2de52..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 4f7ae9dfd80d..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
deleted file mode 100644
index 1dbafb4acc43..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 011173eba47e..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index aa6722005472..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
deleted file mode 100644
index 3af4585fa642..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 37cf7c2dd477..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 49ca5c382ddf..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
deleted file mode 100644
index 59efbdfdb305..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index e8d6b4a62b34..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index d35262820c73..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
deleted file mode 100644
index a49f98202fb7..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index fc54928ee94b..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index a32eac3e3680..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
deleted file mode 100644
index da601c8f3920..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 3852ed0a1638..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[insecure-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 6d6d60400ed0..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
deleted file mode 100644
index df71adfba093..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 4e444c8e5336..000000000000
--- a/testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[upgrade-protocol.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/http-csp/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/http-csp/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
deleted file mode 100644
index d4feed70346a..000000000000
--- a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/http-csp/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[same-origin-insecure.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/http-csp/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/http-csp/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
deleted file mode 100644
index 1f34c0a421c0..000000000000
--- a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/http-csp/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[same-origin-insecure.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-csp/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-csp/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
deleted file mode 100644
index d95f0ed4c356..000000000000
--- a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-csp/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[same-origin-insecure.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-csp/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-csp/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
deleted file mode 100644
index ffe14506dd64..000000000000
--- a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-csp/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[same-origin-insecure.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
deleted file mode 100644
index faec47b11a4e..000000000000
--- a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[same-origin-insecure.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
deleted file mode 100644
index df853397bb6a..000000000000
--- a/testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[same-origin-insecure.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index e36d9264acd0..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 91926847a62d..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 77e9061c4d5b..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 3c56fc297423..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index c560bdb42957..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 2ce17cf9b9c3..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 88729b90656b..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index d25dab8160af..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 246360467136..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 378a3d07a0d5..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 2928302c7dc6..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index c22c6241a6a6..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 8f1fe3d5ad65..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 5d9bc394a4e5..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 6cb8c026a5ea..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 3f18900e7d49..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index d35a4fdf3388..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 2a44f6c540a4..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 5bbb9e450d56..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 11f9f24b5a03..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 428273544f46..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index b28c6731e03d..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 05d412385b48..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index b75745639e97..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index c44b787c90ac..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 57cd3bc2ae54..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 7ea5b2427c31..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 659c07f1e1d3..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index f7c368cf7f31..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index e3b6fa2d790a..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 1936a42f50f5..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 5bb51421b812..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 17162155db2e..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 989cfc7abaae..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 0251ece06adc..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index e926c546ad2c..000000000000
--- a/testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index e36d9264acd0..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 91926847a62d..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 77e9061c4d5b..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 3c56fc297423..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index c560bdb42957..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 2ce17cf9b9c3..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 88729b90656b..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index d25dab8160af..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 246360467136..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 378a3d07a0d5..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 2928302c7dc6..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index c22c6241a6a6..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the http-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 8f1fe3d5ad65..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 5d9bc394a4e5..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 6cb8c026a5ea..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 3f18900e7d49..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index d35a4fdf3388..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 2a44f6c540a4..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 5bbb9e450d56..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 11f9f24b5a03..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 428273544f46..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index b28c6731e03d..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 05d412385b48..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index b75745639e97..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-csp\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index c44b787c90ac..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 57cd3bc2ae54..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 7ea5b2427c31..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 659c07f1e1d3..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index f7c368cf7f31..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index e3b6fa2d790a..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is cross-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 1936a42f50f5..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 5bb51421b812..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index 17162155db2e..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an http\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
deleted file mode 100644
index 989cfc7abaae..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.keep-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with keep-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
deleted file mode 100644
index 0251ece06adc..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.no-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with no-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini b/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
deleted file mode 100644
index e926c546ad2c..000000000000
--- a/testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[generic.swap-origin-redirect.http.html]
-  type: testharness
-  [The referrer URL is stripped-referrer when a\n                                 document served over http requires an https\n                                 sub-resource via fetch-request using the meta-referrer\n                                 delivery method with swap-origin-redirect and when\n                                 the target request is same-origin.]
-    expected: FAIL
-
diff --git a/widget/ContentCache.cpp b/widget/ContentCache.cpp
index 007e82b91e85..add67fa6f5bc 100644
--- a/widget/ContentCache.cpp
+++ b/widget/ContentCache.cpp
@@ -117,58 +117,26 @@ public:
 PRLogModuleInfo* sContentCacheLog = nullptr;
 
 ContentCache::ContentCache()
-  : mCompositionStart(UINT32_MAX)
-  , mCompositionEventsDuringRequest(0)
-  , mIsComposing(false)
-  , mRequestedToCommitOrCancelComposition(false)
-  , mIsChrome(XRE_GetProcessType() == GeckoProcessType_Default)
 {
   if (!sContentCacheLog) {
     sContentCacheLog = PR_NewLogModule("ContentCacheWidgets");
   }
 }
 
-void
-ContentCache::AssignContent(const ContentCache& aOther,
-                            const IMENotification* aNotification)
+/*****************************************************************************
+ * mozilla::ContentCacheInChild
+ *****************************************************************************/
+
+ContentCacheInChild::ContentCacheInChild()
+  : ContentCache()
 {
-  if (NS_WARN_IF(!mIsChrome)) {
-    return;
-  }
-
-  mText = aOther.mText;
-  mSelection = aOther.mSelection;
-  mFirstCharRect = aOther.mFirstCharRect;
-  mCaret = aOther.mCaret;
-  mTextRectArray = aOther.mTextRectArray;
-  mEditorRect = aOther.mEditorRect;
-
-  MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) AssignContent(aNotification=%s), "
-     "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
-     "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s, mRect=%s }, "
-     "mFirstCharRect=%s, mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ "
-     "mStart=%u, mRects.Length()=%u }, mEditorRect=%s",
-     this, GetBoolName(mIsChrome), GetNotificationName(aNotification),
-     mText.Length(), mSelection.mAnchor, mSelection.mFocus,
-     GetWritingModeName(mSelection.mWritingMode).get(),
-     GetRectText(mSelection.mAnchorCharRect).get(),
-     GetRectText(mSelection.mFocusCharRect).get(),
-     GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get(),
-     mCaret.mOffset, GetRectText(mCaret.mRect).get(), mTextRectArray.mStart,
-     mTextRectArray.mRects.Length(), GetRectText(mEditorRect).get()));
 }
 
 void
-ContentCache::Clear()
+ContentCacheInChild::Clear()
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) Clear()",
-     this, GetBoolName(mIsChrome)));
-
-  if (NS_WARN_IF(mIsChrome)) {
-    return;
-  }
+    ("ContentCacheInChild: 0x%p Clear()", this));
 
   mText.Truncate();
   mSelection.Clear();
@@ -179,209 +147,13 @@ ContentCache::Clear()
 }
 
 bool
-ContentCache::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
-                                      nsIWidget* aWidget) const
-{
-  MOZ_ASSERT(aWidget);
-
-  if (NS_WARN_IF(!mIsChrome)) {
-    return false;
-  }
-
-  aEvent.mSucceeded = false;
-  aEvent.mWasAsync = false;
-  aEvent.mReply.mFocusedWidget = aWidget;
-
-  switch (aEvent.message) {
-    case NS_QUERY_SELECTED_TEXT:
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent("
-         "aEvent={ message=NS_QUERY_SELECTED_TEXT }, aWidget=0x%p)",
-         this, GetBoolName(mIsChrome), aWidget));
-      if (NS_WARN_IF(!IsSelectionValid())) {
-        // If content cache hasn't been initialized properly, make the query
-        // failed.
-        MOZ_LOG(sContentCacheLog, LogLevel::Error,
-          ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-           "FAILED because mSelection is not valid",
-           this, GetBoolName(mIsChrome)));
-        return true;
-      }
-      aEvent.mReply.mOffset = mSelection.StartOffset();
-      if (mSelection.Collapsed()) {
-        aEvent.mReply.mString.Truncate(0);
-      } else {
-        if (NS_WARN_IF(mSelection.EndOffset() > mText.Length())) {
-          MOZ_LOG(sContentCacheLog, LogLevel::Error,
-            ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-             "FAILED because mSelection.EndOffset()=%u is larger than "
-             "mText.Length()=%u",
-             this, GetBoolName(mIsChrome), mSelection.EndOffset(),
-             mText.Length()));
-          return false;
-        }
-        aEvent.mReply.mString =
-          Substring(mText, aEvent.mReply.mOffset, mSelection.Length());
-      }
-      aEvent.mReply.mReversed = mSelection.Reversed();
-      aEvent.mReply.mHasSelection = true;
-      aEvent.mReply.mWritingMode = mSelection.mWritingMode;
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-         "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
-         "mReversed=%s, mHasSelection=%s, mWritingMode=%s } }",
-         this, GetBoolName(mIsChrome), aEvent.mReply.mOffset,
-         NS_ConvertUTF16toUTF8(aEvent.mReply.mString).get(),
-         GetBoolName(aEvent.mReply.mReversed),
-         GetBoolName(aEvent.mReply.mHasSelection),
-         GetWritingModeName(aEvent.mReply.mWritingMode).get()));
-      break;
-    case NS_QUERY_TEXT_CONTENT: {
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent("
-         "aEvent={ message=NS_QUERY_TEXT_CONTENT, mInput={ mOffset=%u, "
-         "mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
-         this, GetBoolName(mIsChrome), aEvent.mInput.mOffset,
-         aEvent.mInput.mLength, aWidget, mText.Length()));
-      uint32_t inputOffset = aEvent.mInput.mOffset;
-      uint32_t inputEndOffset =
-        std::min(aEvent.mInput.EndOffset(), mText.Length());
-      if (NS_WARN_IF(inputEndOffset < inputOffset)) {
-        MOZ_LOG(sContentCacheLog, LogLevel::Error,
-          ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-           "FAILED because inputOffset=%u is larger than inputEndOffset=%u",
-           this, GetBoolName(mIsChrome), inputOffset, inputEndOffset));
-        return false;
-      }
-      aEvent.mReply.mOffset = inputOffset;
-      aEvent.mReply.mString =
-        Substring(mText, inputOffset, inputEndOffset - inputOffset);
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-         "Succeeded, aEvent={ mReply={ mOffset=%u, mString.Length()=%u } }",
-         this, GetBoolName(mIsChrome), aEvent.mReply.mOffset,
-         aEvent.mReply.mString.Length()));
-      break;
-    }
-    case NS_QUERY_TEXT_RECT:
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent("
-         "aEvent={ message=NS_QUERY_TEXT_RECT, mInput={ mOffset=%u, "
-         "mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
-         this, GetBoolName(mIsChrome), aEvent.mInput.mOffset,
-         aEvent.mInput.mLength, aWidget, mText.Length()));
-      if (NS_WARN_IF(!IsSelectionValid())) {
-        // If content cache hasn't been initialized properly, make the query
-        // failed.
-        MOZ_LOG(sContentCacheLog, LogLevel::Error,
-          ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-           "FAILED because mSelection is not valid",
-           this, GetBoolName(mIsChrome)));
-        return true;
-      }
-      if (aEvent.mInput.mLength) {
-        if (NS_WARN_IF(!GetUnionTextRects(aEvent.mInput.mOffset,
-                                          aEvent.mInput.mLength,
-                                          aEvent.mReply.mRect))) {
-          // XXX We don't have cache for this request.
-          MOZ_LOG(sContentCacheLog, LogLevel::Error,
-            ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-             "FAILED to get union rect",
-             this, GetBoolName(mIsChrome)));
-          return false;
-        }
-      } else {
-        // If the length is 0, we should return caret rect instead.
-        if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
-                                     aEvent.mReply.mRect))) {
-          MOZ_LOG(sContentCacheLog, LogLevel::Error,
-            ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-             "FAILED to get caret rect",
-             this, GetBoolName(mIsChrome)));
-          return false;
-        }
-      }
-      if (aEvent.mInput.mOffset < mText.Length()) {
-        aEvent.mReply.mString =
-          Substring(mText, aEvent.mInput.mOffset,
-                    mText.Length() >= aEvent.mInput.EndOffset() ?
-                      aEvent.mInput.mLength : UINT32_MAX);
-      } else {
-        aEvent.mReply.mString.Truncate(0);
-      }
-      aEvent.mReply.mOffset = aEvent.mInput.mOffset;
-      // XXX This may be wrong if storing range isn't in the selection range.
-      aEvent.mReply.mWritingMode = mSelection.mWritingMode;
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-         "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
-         "mWritingMode=%s, mRect=%s } }",
-         this, GetBoolName(mIsChrome), aEvent.mReply.mOffset,
-         NS_ConvertUTF16toUTF8(aEvent.mReply.mString).get(),
-         GetWritingModeName(aEvent.mReply.mWritingMode).get(),
-         GetRectText(aEvent.mReply.mRect).get()));
-      break;
-    case NS_QUERY_CARET_RECT:
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent("
-         "aEvent={ message=NS_QUERY_CARET_RECT, mInput={ mOffset=%u } }, "
-         "aWidget=0x%p), mText.Length()=%u",
-         this, GetBoolName(mIsChrome), aEvent.mInput.mOffset, aWidget,
-         mText.Length()));
-      if (NS_WARN_IF(!IsSelectionValid())) {
-        // If content cache hasn't been initialized properly, make the query
-        // failed.
-        MOZ_LOG(sContentCacheLog, LogLevel::Error,
-          ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-           "FAILED because mSelection is not valid",
-           this, GetBoolName(mIsChrome)));
-        return true;
-      }
-      if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
-                                   aEvent.mReply.mRect))) {
-        MOZ_LOG(sContentCacheLog, LogLevel::Error,
-          ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-           "FAILED to get caret rect",
-           this, GetBoolName(mIsChrome)));
-        return false;
-      }
-      aEvent.mReply.mOffset = aEvent.mInput.mOffset;
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-         "Succeeded, aEvent={ mReply={ mOffset=%u, mRect=%s } }",
-         this, GetBoolName(mIsChrome), aEvent.mReply.mOffset,
-         GetRectText(aEvent.mReply.mRect).get()));
-      break;
-    case NS_QUERY_EDITOR_RECT:
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent("
-         "aEvent={ message=NS_QUERY_EDITOR_RECT }, aWidget=0x%p)",
-         this, GetBoolName(mIsChrome), aWidget));
-      aEvent.mReply.mRect = mEditorRect;
-      MOZ_LOG(sContentCacheLog, LogLevel::Info,
-        ("ContentCache: 0x%p (mIsChrome=%s) HandleQueryContentEvent(), "
-         "Succeeded, aEvent={ mReply={ mRect=%s } }",
-         this, GetBoolName(mIsChrome), GetRectText(aEvent.mReply.mRect).get()));
-      break;
-  }
-  aEvent.mSucceeded = true;
-  return true;
-}
-
-bool
-ContentCache::CacheAll(nsIWidget* aWidget,
-                       const IMENotification* aNotification)
+ContentCacheInChild::CacheAll(nsIWidget* aWidget,
+                              const IMENotification* aNotification)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheAll(aWidget=0x%p, "
+    ("ContentCacheInChild: 0x%p CacheAll(aWidget=0x%p, "
      "aNotification=%s)",
-     this, GetBoolName(mIsChrome), aWidget,
-     GetNotificationName(aNotification)));
-
-  // CacheAll() must be called in content process.
-  if (NS_WARN_IF(mIsChrome)) {
-    return false;
-  }
+     this, aWidget, GetNotificationName(aNotification)));
 
   if (NS_WARN_IF(!CacheText(aWidget, aNotification)) ||
       NS_WARN_IF(!CacheEditorRect(aWidget, aNotification))) {
@@ -391,19 +163,13 @@ ContentCache::CacheAll(nsIWidget* aWidget,
 }
 
 bool
-ContentCache::CacheSelection(nsIWidget* aWidget,
-                             const IMENotification* aNotification)
+ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
+                                    const IMENotification* aNotification)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheSelection(aWidget=0x%p, "
+    ("ContentCacheInChild: 0x%p CacheSelection(aWidget=0x%p, "
      "aNotification=%s)",
-     this, GetBoolName(mIsChrome), aWidget,
-     GetNotificationName(aNotification)));
-
-  // CacheSelection() must be called in content process.
-  if (NS_WARN_IF(mIsChrome)) {
-    return false;
-  }
+     this, aWidget, GetNotificationName(aNotification)));
 
   mCaret.Clear();
   mSelection.Clear();
@@ -413,9 +179,8 @@ ContentCache::CacheSelection(nsIWidget* aWidget,
   aWidget->DispatchEvent(&selection, status);
   if (NS_WARN_IF(!selection.mSucceeded)) {
     MOZ_LOG(sContentCacheLog, LogLevel::Error,
-      ("ContentCache: 0x%p (mIsChrome=%s) CacheSelection(), FAILED, "
-       "couldn't retrieve the selected text",
-       this, GetBoolName(mIsChrome)));
+      ("ContentCache: 0x%p CacheSelection(), FAILED, "
+       "couldn't retrieve the selected text", this));
     return false;
   }
   if (selection.mReply.mReversed) {
@@ -434,19 +199,13 @@ ContentCache::CacheSelection(nsIWidget* aWidget,
 }
 
 bool
-ContentCache::CacheCaret(nsIWidget* aWidget,
-                         const IMENotification* aNotification)
+ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
+                                const IMENotification* aNotification)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheCaret(aWidget=0x%p, "
+    ("ContentCacheInChild: 0x%p CacheCaret(aWidget=0x%p, "
      "aNotification=%s)",
-     this, GetBoolName(mIsChrome), aWidget,
-     GetNotificationName(aNotification)));
-
-  // CacheCaret() must be called in content process.
-  if (NS_WARN_IF(mIsChrome)) {
-    return false;
-  }
+     this, aWidget, GetNotificationName(aNotification)));
 
   mCaret.Clear();
 
@@ -463,70 +222,56 @@ ContentCache::CacheCaret(nsIWidget* aWidget,
   aWidget->DispatchEvent(&caretRect, status);
   if (NS_WARN_IF(!caretRect.mSucceeded)) {
     MOZ_LOG(sContentCacheLog, LogLevel::Error,
-      ("ContentCache: 0x%p (mIsChrome=%s) CacheCaret(), FAILED, "
+      ("ContentCacheInChild: 0x%p CacheCaret(), FAILED, "
        "couldn't retrieve the caret rect at offset=%u",
-       this, GetBoolName(mIsChrome), mCaret.mOffset));
+       this, mCaret.mOffset));
     mCaret.Clear();
     return false;
   }
   mCaret.mRect = caretRect.mReply.mRect;
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheCaret(), Succeeded, "
+    ("ContentCacheInChild: 0x%p CacheCaret(), Succeeded, "
      "mSelection={ mAnchor=%u, mFocus=%u, mWritingMode=%s }, "
      "mCaret={ mOffset=%u, mRect=%s }",
-     this, GetBoolName(mIsChrome), mSelection.mAnchor, mSelection.mFocus,
+     this, mSelection.mAnchor, mSelection.mFocus,
      GetWritingModeName(mSelection.mWritingMode).get(), mCaret.mOffset,
      GetRectText(mCaret.mRect).get()));
   return true;
 }
 
 bool
-ContentCache::CacheEditorRect(nsIWidget* aWidget,
-                              const IMENotification* aNotification)
+ContentCacheInChild::CacheEditorRect(nsIWidget* aWidget,
+                                     const IMENotification* aNotification)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheEditorRect(aWidget=0x%p, "
+    ("ContentCacheInChild: 0x%p CacheEditorRect(aWidget=0x%p, "
      "aNotification=%s)",
-     this, GetBoolName(mIsChrome), aWidget,
-     GetNotificationName(aNotification)));
-
-  // CacheEditorRect() must be called in content process.
-  if (NS_WARN_IF(mIsChrome)) {
-    return false;
-  }
+     this, aWidget, GetNotificationName(aNotification)));
 
   nsEventStatus status = nsEventStatus_eIgnore;
   WidgetQueryContentEvent editorRectEvent(true, NS_QUERY_EDITOR_RECT, aWidget);
   aWidget->DispatchEvent(&editorRectEvent, status);
   if (NS_WARN_IF(!editorRectEvent.mSucceeded)) {
     MOZ_LOG(sContentCacheLog, LogLevel::Error,
-      ("ContentCache: 0x%p (mIsChrome=%s) CacheEditorRect(), FAILED, "
-       "couldn't retrieve the editor rect",
-       this, GetBoolName(mIsChrome)));
+      ("ContentCacheInChild: 0x%p CacheEditorRect(), FAILED, "
+       "couldn't retrieve the editor rect", this));
     return false;
   }
   mEditorRect = editorRectEvent.mReply.mRect;
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheEditorRect(), Succeeded, "
-     "mEditorRect=%s",
-     this, GetBoolName(mIsChrome), GetRectText(mEditorRect).get()));
+    ("ContentCacheInChild: 0x%p CacheEditorRect(), Succeeded, "
+     "mEditorRect=%s", this, GetRectText(mEditorRect).get()));
   return true;
 }
 
 bool
-ContentCache::CacheText(nsIWidget* aWidget,
-                        const IMENotification* aNotification)
+ContentCacheInChild::CacheText(nsIWidget* aWidget,
+                               const IMENotification* aNotification)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheText(aWidget=0x%p, "
+    ("ContentCacheInChild: 0x%p CacheText(aWidget=0x%p, "
      "aNotification=%s)",
-     this, GetBoolName(mIsChrome), aWidget,
-     GetNotificationName(aNotification)));
-
-  // CacheText() must be called in content process.
-  if (NS_WARN_IF(mIsChrome)) {
-    return false;
-  }
+     this, aWidget, GetNotificationName(aNotification)));
 
   nsEventStatus status = nsEventStatus_eIgnore;
   WidgetQueryContentEvent queryText(true, NS_QUERY_TEXT_CONTENT, aWidget);
@@ -534,32 +279,26 @@ ContentCache::CacheText(nsIWidget* aWidget,
   aWidget->DispatchEvent(&queryText, status);
   if (NS_WARN_IF(!queryText.mSucceeded)) {
     MOZ_LOG(sContentCacheLog, LogLevel::Error,
-      ("ContentCache: 0x%p (mIsChrome=%s) CacheText(), FAILED, "
-       "couldn't retrieve whole text",
-       this, GetBoolName(mIsChrome)));
+      ("ContentCacheInChild: 0x%p CacheText(), FAILED, "
+       "couldn't retrieve whole text", this));
     mText.Truncate();
     return false;
   }
   mText = queryText.mReply.mString;
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheText(), Succeeded, "
-     "mText.Length()=%u",
-     this, GetBoolName(mIsChrome), mText.Length()));
+    ("ContentCacheInChild: 0x%p CacheText(), Succeeded, "
+     "mText.Length()=%u", this, mText.Length()));
 
   return CacheSelection(aWidget, aNotification);
 }
 
 bool
-ContentCache::QueryCharRect(nsIWidget* aWidget,
-                            uint32_t aOffset,
-                            LayoutDeviceIntRect& aCharRect) const
+ContentCacheInChild::QueryCharRect(nsIWidget* aWidget,
+                                   uint32_t aOffset,
+                                   LayoutDeviceIntRect& aCharRect) const
 {
   aCharRect.SetEmpty();
 
-  if (NS_WARN_IF(mIsChrome)) {
-    return false;
-  }
-
   nsEventStatus status = nsEventStatus_eIgnore;
   WidgetQueryContentEvent textRect(true, NS_QUERY_TEXT_RECT, aWidget);
   textRect.InitForQueryTextRect(aOffset, 1);
@@ -580,21 +319,15 @@ ContentCache::QueryCharRect(nsIWidget* aWidget,
 }
 
 bool
-ContentCache::CacheTextRects(nsIWidget* aWidget,
-                             const IMENotification* aNotification)
+ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
+                                    const IMENotification* aNotification)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(aWidget=0x%p, "
+    ("ContentCacheInChild: 0x%p CacheTextRects(aWidget=0x%p, "
      "aNotification=%s), mCaret={ mOffset=%u, IsValid()=%s }",
-     this, GetBoolName(mIsChrome), aWidget,
-     GetNotificationName(aNotification), mCaret.mOffset,
+     this, aWidget, GetNotificationName(aNotification), mCaret.mOffset,
      GetBoolName(mCaret.IsValid())));
 
-  // CacheTextRects() must be called in content process.
-  if (NS_WARN_IF(mIsChrome)) {
-    return false;
-  }
-
   mTextRectArray.Clear();
   mSelection.mAnchorCharRect.SetEmpty();
   mSelection.mFocusCharRect.SetEmpty();
@@ -620,9 +353,8 @@ ContentCache::CacheTextRects(nsIWidget* aWidget,
       LayoutDeviceIntRect charRect;
       if (NS_WARN_IF(!QueryCharRect(aWidget, i, charRect))) {
         MOZ_LOG(sContentCacheLog, LogLevel::Error,
-          ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, "
-           "couldn't retrieve text rect at offset=%u",
-           this, GetBoolName(mIsChrome), i));
+          ("ContentCacheInChild: 0x%p CacheTextRects(), FAILED, "
+           "couldn't retrieve text rect at offset=%u", this, i));
         mTextRectArray.Clear();
         return false;
       }
@@ -636,9 +368,9 @@ ContentCache::CacheTextRects(nsIWidget* aWidget,
     LayoutDeviceIntRect charRect;
     if (NS_WARN_IF(!QueryCharRect(aWidget, mSelection.mAnchor, charRect))) {
       MOZ_LOG(sContentCacheLog, LogLevel::Error,
-        ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, "
+        ("ContentCacheInChild: 0x%p CacheTextRects(), FAILED, "
          "couldn't retrieve text rect at anchor of selection (%u)",
-         this, GetBoolName(mIsChrome), mSelection.mAnchor));
+         this, mSelection.mAnchor));
     }
     mSelection.mAnchorCharRect = charRect;
   }
@@ -651,9 +383,9 @@ ContentCache::CacheTextRects(nsIWidget* aWidget,
     LayoutDeviceIntRect charRect;
     if (NS_WARN_IF(!QueryCharRect(aWidget, mSelection.mFocus, charRect))) {
       MOZ_LOG(sContentCacheLog, LogLevel::Error,
-        ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, "
+        ("ContentCacheInChild: 0x%p CacheTextRects(), FAILED, "
          "couldn't retrieve text rect at focus of selection (%u)",
-         this, GetBoolName(mIsChrome), mSelection.mFocus));
+         this, mSelection.mFocus));
     }
     mSelection.mFocusCharRect = charRect;
   }
@@ -666,9 +398,8 @@ ContentCache::CacheTextRects(nsIWidget* aWidget,
     aWidget->DispatchEvent(&textRect, status);
     if (NS_WARN_IF(!textRect.mSucceeded)) {
       MOZ_LOG(sContentCacheLog, LogLevel::Error,
-        ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, "
-         "couldn't retrieve text rect of whole selected text",
-         this, GetBoolName(mIsChrome)));
+        ("ContentCacheInChild: 0x%p CacheTextRects(), FAILED, "
+         "couldn't retrieve text rect of whole selected text", this));
     } else {
       mSelection.mRect = textRect.mReply.mRect;
     }
@@ -684,20 +415,19 @@ ContentCache::CacheTextRects(nsIWidget* aWidget,
     LayoutDeviceIntRect charRect;
     if (NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect))) {
       MOZ_LOG(sContentCacheLog, LogLevel::Error,
-        ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, "
-         "couldn't retrieve first char rect",
-         this, GetBoolName(mIsChrome)));
+        ("ContentCacheInChild: 0x%p CacheTextRects(), FAILED, "
+         "couldn't retrieve first char rect", this));
     } else {
       mFirstCharRect = charRect;
     }
   }
 
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), Succeeded, "
+    ("ContentCacheInChild: 0x%p CacheTextRects(), Succeeded, "
      "mText.Length()=%u, mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
      "mSelection={ mAnchor=%u, mAnchorCharRect=%s, mFocus=%u, "
      "mFocusCharRect=%s, mRect=%s }, mFirstCharRect=%s",
-     this, GetBoolName(mIsChrome), mText.Length(), mTextRectArray.mStart,
+     this, mText.Length(), mTextRectArray.mStart,
      mTextRectArray.mRects.Length(), mSelection.mAnchor,
      GetRectText(mSelection.mAnchorCharRect).get(), mSelection.mFocus,
      GetRectText(mSelection.mFocusCharRect).get(),
@@ -706,22 +436,17 @@ ContentCache::CacheTextRects(nsIWidget* aWidget,
 }
 
 void
-ContentCache::SetSelection(nsIWidget* aWidget,
-                           uint32_t aStartOffset,
-                           uint32_t aLength,
-                           bool aReversed,
-                           const WritingMode& aWritingMode)
+ContentCacheInChild::SetSelection(nsIWidget* aWidget,
+                                  uint32_t aStartOffset,
+                                  uint32_t aLength,
+                                  bool aReversed,
+                                  const WritingMode& aWritingMode)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) SetSelection(aStartOffset=%u, "
+    ("ContentCacheInChild: 0x%p SetSelection(aStartOffset=%u, "
      "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
-     this, GetBoolName(mIsChrome), aStartOffset, aLength,
-     GetBoolName(aReversed), GetWritingModeName(aWritingMode).get(),
-     mText.Length()));
-
-  if (NS_WARN_IF(mIsChrome)) {
-    return;
-  }
+     this, aStartOffset, aLength, GetBoolName(aReversed),
+     GetWritingModeName(aWritingMode).get(), mText.Length()));
 
   if (!aReversed) {
     mSelection.mAnchor = aStartOffset;
@@ -738,16 +463,232 @@ ContentCache::SetSelection(nsIWidget* aWidget,
   NS_WARN_IF(!CacheTextRects(aWidget));
 }
 
+/*****************************************************************************
+ * mozilla::ContentCacheInParent
+ *****************************************************************************/
+
+ContentCacheInParent::ContentCacheInParent()
+  : ContentCache()
+  , mCompositionStart(UINT32_MAX)
+  , mCompositionEventsDuringRequest(0)
+  , mIsComposing(false)
+  , mRequestedToCommitOrCancelComposition(false)
+{
+}
+
+void
+ContentCacheInParent::AssignContent(const ContentCache& aOther,
+                                    const IMENotification* aNotification)
+{
+  mText = aOther.mText;
+  mSelection = aOther.mSelection;
+  mFirstCharRect = aOther.mFirstCharRect;
+  mCaret = aOther.mCaret;
+  mTextRectArray = aOther.mTextRectArray;
+  mEditorRect = aOther.mEditorRect;
+
+  MOZ_LOG(sContentCacheLog, LogLevel::Info,
+    ("ContentCacheInParent: 0x%p AssignContent(aNotification=%s), "
+     "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
+     "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s, mRect=%s }, "
+     "mFirstCharRect=%s, mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ "
+     "mStart=%u, mRects.Length()=%u }, mEditorRect=%s",
+     this, GetNotificationName(aNotification),
+     mText.Length(), mSelection.mAnchor, mSelection.mFocus,
+     GetWritingModeName(mSelection.mWritingMode).get(),
+     GetRectText(mSelection.mAnchorCharRect).get(),
+     GetRectText(mSelection.mFocusCharRect).get(),
+     GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get(),
+     mCaret.mOffset, GetRectText(mCaret.mRect).get(), mTextRectArray.mStart,
+     mTextRectArray.mRects.Length(), GetRectText(mEditorRect).get()));
+}
+
 bool
-ContentCache::GetTextRect(uint32_t aOffset,
-                          LayoutDeviceIntRect& aTextRect) const
+ContentCacheInParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
+                                              nsIWidget* aWidget) const
+{
+  MOZ_ASSERT(aWidget);
+
+  aEvent.mSucceeded = false;
+  aEvent.mWasAsync = false;
+  aEvent.mReply.mFocusedWidget = aWidget;
+
+  switch (aEvent.message) {
+    case NS_QUERY_SELECTED_TEXT:
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent("
+         "aEvent={ message=NS_QUERY_SELECTED_TEXT }, aWidget=0x%p)",
+         this, aWidget));
+      if (NS_WARN_IF(!IsSelectionValid())) {
+        // If content cache hasn't been initialized properly, make the query
+        // failed.
+        MOZ_LOG(sContentCacheLog, LogLevel::Error,
+          ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+           "FAILED because mSelection is not valid", this));
+        return true;
+      }
+      aEvent.mReply.mOffset = mSelection.StartOffset();
+      if (mSelection.Collapsed()) {
+        aEvent.mReply.mString.Truncate(0);
+      } else {
+        if (NS_WARN_IF(mSelection.EndOffset() > mText.Length())) {
+          MOZ_LOG(sContentCacheLog, LogLevel::Error,
+            ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+             "FAILED because mSelection.EndOffset()=%u is larger than "
+             "mText.Length()=%u",
+             this, mSelection.EndOffset(), mText.Length()));
+          return false;
+        }
+        aEvent.mReply.mString =
+          Substring(mText, aEvent.mReply.mOffset, mSelection.Length());
+      }
+      aEvent.mReply.mReversed = mSelection.Reversed();
+      aEvent.mReply.mHasSelection = true;
+      aEvent.mReply.mWritingMode = mSelection.mWritingMode;
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+         "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
+         "mReversed=%s, mHasSelection=%s, mWritingMode=%s } }",
+         this, aEvent.mReply.mOffset,
+         NS_ConvertUTF16toUTF8(aEvent.mReply.mString).get(),
+         GetBoolName(aEvent.mReply.mReversed),
+         GetBoolName(aEvent.mReply.mHasSelection),
+         GetWritingModeName(aEvent.mReply.mWritingMode).get()));
+      break;
+    case NS_QUERY_TEXT_CONTENT: {
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent("
+         "aEvent={ message=NS_QUERY_TEXT_CONTENT, mInput={ mOffset=%u, "
+         "mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
+         this, aEvent.mInput.mOffset,
+         aEvent.mInput.mLength, aWidget, mText.Length()));
+      uint32_t inputOffset = aEvent.mInput.mOffset;
+      uint32_t inputEndOffset =
+        std::min(aEvent.mInput.EndOffset(), mText.Length());
+      if (NS_WARN_IF(inputEndOffset < inputOffset)) {
+        MOZ_LOG(sContentCacheLog, LogLevel::Error,
+          ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+           "FAILED because inputOffset=%u is larger than inputEndOffset=%u",
+           this, inputOffset, inputEndOffset));
+        return false;
+      }
+      aEvent.mReply.mOffset = inputOffset;
+      aEvent.mReply.mString =
+        Substring(mText, inputOffset, inputEndOffset - inputOffset);
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+         "Succeeded, aEvent={ mReply={ mOffset=%u, mString.Length()=%u } }",
+         this, aEvent.mReply.mOffset, aEvent.mReply.mString.Length()));
+      break;
+    }
+    case NS_QUERY_TEXT_RECT:
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent("
+         "aEvent={ message=NS_QUERY_TEXT_RECT, mInput={ mOffset=%u, "
+         "mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
+         this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
+         mText.Length()));
+      if (NS_WARN_IF(!IsSelectionValid())) {
+        // If content cache hasn't been initialized properly, make the query
+        // failed.
+        MOZ_LOG(sContentCacheLog, LogLevel::Error,
+          ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+           "FAILED because mSelection is not valid", this));
+        return true;
+      }
+      if (aEvent.mInput.mLength) {
+        if (NS_WARN_IF(!GetUnionTextRects(aEvent.mInput.mOffset,
+                                          aEvent.mInput.mLength,
+                                          aEvent.mReply.mRect))) {
+          // XXX We don't have cache for this request.
+          MOZ_LOG(sContentCacheLog, LogLevel::Error,
+            ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+             "FAILED to get union rect", this));
+          return false;
+        }
+      } else {
+        // If the length is 0, we should return caret rect instead.
+        if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
+                                     aEvent.mReply.mRect))) {
+          MOZ_LOG(sContentCacheLog, LogLevel::Error,
+            ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+             "FAILED to get caret rect", this));
+          return false;
+        }
+      }
+      if (aEvent.mInput.mOffset < mText.Length()) {
+        aEvent.mReply.mString =
+          Substring(mText, aEvent.mInput.mOffset,
+                    mText.Length() >= aEvent.mInput.EndOffset() ?
+                      aEvent.mInput.mLength : UINT32_MAX);
+      } else {
+        aEvent.mReply.mString.Truncate(0);
+      }
+      aEvent.mReply.mOffset = aEvent.mInput.mOffset;
+      // XXX This may be wrong if storing range isn't in the selection range.
+      aEvent.mReply.mWritingMode = mSelection.mWritingMode;
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+         "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
+         "mWritingMode=%s, mRect=%s } }",
+         this, aEvent.mReply.mOffset,
+         NS_ConvertUTF16toUTF8(aEvent.mReply.mString).get(),
+         GetWritingModeName(aEvent.mReply.mWritingMode).get(),
+         GetRectText(aEvent.mReply.mRect).get()));
+      break;
+    case NS_QUERY_CARET_RECT:
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent("
+         "aEvent={ message=NS_QUERY_CARET_RECT, mInput={ mOffset=%u } }, "
+         "aWidget=0x%p), mText.Length()=%u",
+         this, aEvent.mInput.mOffset, aWidget, mText.Length()));
+      if (NS_WARN_IF(!IsSelectionValid())) {
+        // If content cache hasn't been initialized properly, make the query
+        // failed.
+        MOZ_LOG(sContentCacheLog, LogLevel::Error,
+          ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+           "FAILED because mSelection is not valid", this));
+        return true;
+      }
+      if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
+                                   aEvent.mReply.mRect))) {
+        MOZ_LOG(sContentCacheLog, LogLevel::Error,
+          ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+           "FAILED to get caret rect", this));
+        return false;
+      }
+      aEvent.mReply.mOffset = aEvent.mInput.mOffset;
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+         "Succeeded, aEvent={ mReply={ mOffset=%u, mRect=%s } }",
+         this, aEvent.mReply.mOffset, GetRectText(aEvent.mReply.mRect).get()));
+      break;
+    case NS_QUERY_EDITOR_RECT:
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent("
+         "aEvent={ message=NS_QUERY_EDITOR_RECT }, aWidget=0x%p)",
+         this, aWidget));
+      aEvent.mReply.mRect = mEditorRect;
+      MOZ_LOG(sContentCacheLog, LogLevel::Info,
+        ("ContentCacheInParent: 0x%p HandleQueryContentEvent(), "
+         "Succeeded, aEvent={ mReply={ mRect=%s } }",
+         this, GetRectText(aEvent.mReply.mRect).get()));
+      break;
+  }
+  aEvent.mSucceeded = true;
+  return true;
+}
+
+bool
+ContentCacheInParent::GetTextRect(uint32_t aOffset,
+                                  LayoutDeviceIntRect& aTextRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) GetTextRect(aOffset=%u), "
+    ("ContentCacheInParent: 0x%p GetTextRect(aOffset=%u), "
      "mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
      "mSelection={ mAnchor=%u, mFocus=%u }",
-     this, GetBoolName(mIsChrome), aOffset, mTextRectArray.mStart,
-     mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus));
+     this, aOffset, mTextRectArray.mStart, mTextRectArray.mRects.Length(),
+     mSelection.mAnchor, mSelection.mFocus));
 
   if (!aOffset) {
     NS_WARN_IF(mFirstCharRect.IsEmpty());
@@ -774,17 +715,17 @@ ContentCache::GetTextRect(uint32_t aOffset,
 }
 
 bool
-ContentCache::GetUnionTextRects(uint32_t aOffset,
-                                uint32_t aLength,
-                                LayoutDeviceIntRect& aUnionTextRect) const
+ContentCacheInParent::GetUnionTextRects(
+                        uint32_t aOffset,
+                        uint32_t aLength,
+                        LayoutDeviceIntRect& aUnionTextRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) GetUnionTextRects(aOffset=%u, "
+    ("ContentCacheInParent: 0x%p GetUnionTextRects(aOffset=%u, "
      "aLength=%u), mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
      "mSelection={ mAnchor=%u, mFocus=%u }",
-     this, GetBoolName(mIsChrome), aOffset, aLength,
-     mTextRectArray.mStart, mTextRectArray.mRects.Length(),
-     mSelection.mAnchor, mSelection.mFocus));
+     this, aOffset, aLength, mTextRectArray.mStart,
+     mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus));
 
   CheckedInt endOffset =
     CheckedInt(aOffset) + aLength;
@@ -848,19 +789,18 @@ ContentCache::GetUnionTextRects(uint32_t aOffset,
 }
 
 bool
-ContentCache::GetCaretRect(uint32_t aOffset,
-                           LayoutDeviceIntRect& aCaretRect) const
+ContentCacheInParent::GetCaretRect(uint32_t aOffset,
+                                   LayoutDeviceIntRect& aCaretRect) const
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) GetCaretRect(aOffset=%u), "
+    ("ContentCacheInParent: 0x%p GetCaretRect(aOffset=%u), "
      "mCaret={ mOffset=%u, mRect=%s, IsValid()=%s }, mTextRectArray={ "
      "mStart=%u, mRects.Length()=%u }, mSelection={ mAnchor=%u, mFocus=%u, "
      "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s }, "
      "mFirstCharRect=%s",
-     this, GetBoolName(mIsChrome), aOffset, mCaret.mOffset,
-     GetRectText(mCaret.mRect).get(), GetBoolName(mCaret.IsValid()),
-     mTextRectArray.mStart, mTextRectArray.mRects.Length(),
-     mSelection.mAnchor, mSelection.mFocus,
+     this, aOffset, mCaret.mOffset, GetRectText(mCaret.mRect).get(),
+     GetBoolName(mCaret.IsValid()), mTextRectArray.mStart,
+     mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus,
      GetWritingModeName(mSelection.mWritingMode).get(),
      GetRectText(mSelection.mAnchorCharRect).get(),
      GetRectText(mSelection.mFocusCharRect).get(),
@@ -900,21 +840,17 @@ ContentCache::GetCaretRect(uint32_t aOffset,
 }
 
 bool
-ContentCache::OnCompositionEvent(const WidgetCompositionEvent& aEvent)
+ContentCacheInParent::OnCompositionEvent(const WidgetCompositionEvent& aEvent)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) OnCompositionEvent(aEvent={ "
+    ("ContentCacheInParent: 0x%p OnCompositionEvent(aEvent={ "
      "message=%s, mData=\"%s\" (Length()=%u), mRanges->Length()=%u }), "
      "mIsComposing=%s, mRequestedToCommitOrCancelComposition=%s",
-     this, GetBoolName(mIsChrome), GetEventMessageName(aEvent.message),
+     this, GetEventMessageName(aEvent.message),
      NS_ConvertUTF16toUTF8(aEvent.mData).get(), aEvent.mData.Length(),
      aEvent.mRanges ? aEvent.mRanges->Length() : 0, GetBoolName(mIsComposing),
      GetBoolName(mRequestedToCommitOrCancelComposition)));
 
-  if (NS_WARN_IF(!mIsChrome)) {
-    return false;
-  }
-
   if (!aEvent.CausesDOMTextEvent()) {
     MOZ_ASSERT(aEvent.message == NS_COMPOSITION_START);
     mIsComposing = !aEvent.CausesDOMCompositionEndEvent();
@@ -953,23 +889,18 @@ ContentCache::OnCompositionEvent(const WidgetCompositionEvent& aEvent)
 }
 
 uint32_t
-ContentCache::RequestToCommitComposition(nsIWidget* aWidget,
-                                         bool aCancel,
-                                         nsAString& aLastString)
+ContentCacheInParent::RequestToCommitComposition(nsIWidget* aWidget,
+                                                 bool aCancel,
+                                                 nsAString& aLastString)
 {
   MOZ_LOG(sContentCacheLog, LogLevel::Info,
-    ("ContentCache: 0x%p (mIsChrome=%s) RequestToCommitComposition(aWidget=%p, "
+    ("ContentCacheInParent: 0x%p RequestToCommitComposition(aWidget=%p, "
      "aCancel=%s), mIsComposing=%s, mRequestedToCommitOrCancelComposition=%s, "
      "mCompositionEventsDuringRequest=%u",
-     this, GetBoolName(mIsChrome), aWidget, GetBoolName(aCancel),
-     GetBoolName(mIsComposing),
+     this, aWidget, GetBoolName(aCancel), GetBoolName(mIsComposing),
      GetBoolName(mRequestedToCommitOrCancelComposition),
      mCompositionEventsDuringRequest));
 
-  if (NS_WARN_IF(!mIsChrome)) {
-    return 0;
-  }
-
   mRequestedToCommitOrCancelComposition = true;
   mCompositionEventsDuringRequest = 0;
 
@@ -983,7 +914,7 @@ ContentCache::RequestToCommitComposition(nsIWidget* aWidget,
 }
 
 void
-ContentCache::InitNotification(IMENotification& aNotification) const
+ContentCacheInParent::InitNotification(IMENotification& aNotification) const
 {
   if (NS_WARN_IF(aNotification.mMessage != NOTIFY_IME_OF_SELECTION_CHANGE)) {
     return;
diff --git a/widget/ContentCache.h b/widget/ContentCache.h
index d96e5514d7f4..1071c9dcfcf3 100644
--- a/widget/ContentCache.h
+++ b/widget/ContentCache.h
@@ -26,17 +26,15 @@ namespace widget {
 struct IMENotification;
 }
 
+class ContentCacheInParent;
+
 /**
- * ContentCache stores various information of the child content both on
- * PuppetWidget (child process) and TabParent (chrome process).
- * When PuppetWidget receives some notifications of content state change,
- * Cache*() are called.  Then, stored data is modified for the latest content
- * in PuppetWidget.  After that, PuppetWidget sends the ContentCache to
- * TabParent.  In this time, TabParent stores the latest content data with
- * AssignContent().
+ * ContentCache stores various information of the child content.
+ * This class has members which are necessary both in parent process and
+ * content process.
  */
 
-class ContentCache final
+class ContentCache
 {
 public:
   typedef InfallibleTArray RectArray;
@@ -44,119 +42,9 @@ public:
 
   ContentCache();
 
-  /**
-   * When IME loses focus, this should be called and making this forget the
-   * content for reducing footprint.
-   * This must be called in content process.
-   */
-  void Clear();
-
-  /**
-   * AssignContent() is called when TabParent receives ContentCache from
-   * the content process.  This doesn't copy composition information because
-   * it's managed by TabParent itself.
-   * This must be called in chrome process.
-   */
-  void AssignContent(const ContentCache& aOther,
-                     const IMENotification* aNotification = nullptr);
-
-  /**
-   * HandleQueryContentEvent() sets content data to aEvent.mReply.
-   * This must be called in chrome process.
-   *
-   * For NS_QUERY_SELECTED_TEXT, fail if the cache doesn't contain the whole
-   *  selected range. (This shouldn't happen because PuppetWidget should have
-   *  already sent the whole selection.)
-   *
-   * For NS_QUERY_TEXT_CONTENT, fail only if the cache doesn't overlap with
-   *  the queried range. Note the difference from above. We use
-   *  this behavior because a normal NS_QUERY_TEXT_CONTENT event is allowed to
-   *  have out-of-bounds offsets, so that widget can request content without
-   *  knowing the exact length of text. It's up to widget to handle cases when
-   *  the returned offset/length are different from the queried offset/length.
-   *
-   * For NS_QUERY_TEXT_RECT, fail if cached offset/length aren't equals to input.
-   *   Cocoa widget always queries selected offset, so it works on it.
-   *
-   * For NS_QUERY_CARET_RECT, fail if cached offset isn't equals to input
-   *
-   * For NS_QUERY_EDITOR_RECT, always success
-   */
-  bool HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
-                               nsIWidget* aWidget) const;
-
-  /**
-   * Cache*() retrieves the latest content information and store them.
-   * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
-   * calls CacheSelection().  So, related data is also retrieved automatically.
-   * These methods must be called in content process.
-   */
-  bool CacheEditorRect(nsIWidget* aWidget,
-                       const IMENotification* aNotification = nullptr);
-  bool CacheSelection(nsIWidget* aWidget,
-                      const IMENotification* aNotification = nullptr);
-  bool CacheText(nsIWidget* aWidget,
-                 const IMENotification* aNotification = nullptr);
-
-  bool CacheAll(nsIWidget* aWidget,
-                const IMENotification* aNotification = nullptr);
-
-  /**
-   * OnCompositionEvent() should be called before sending composition string.
-   * This returns true if the event should be sent.  Otherwise, false.
-   * This must be called in chrome process.
-   */
-  bool OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
-  /**
-   * RequestToCommitComposition() requests to commit or cancel composition to
-   * the widget.  If it's handled synchronously, this returns the number of
-   * composition events after that.
-   * This must be called in chrome process.
-   *
-   * @param aWidget     The widget to be requested to commit or cancel
-   *                    the composition.
-   * @param aCancel     When the caller tries to cancel the composition, true.
-   *                    Otherwise, i.e., tries to commit the composition, false.
-   * @param aLastString The last composition string before requesting to
-   *                    commit or cancel composition.
-   * @return            The count of composition events ignored after a call of
-   *                    WillRequestToCommitOrCancelComposition().
-   */
-  uint32_t RequestToCommitComposition(nsIWidget* aWidget,
-                                      bool aCancel,
-                                      nsAString& aLastString);
-
-  /**
-   * InitNotification() initializes aNotification with stored data.
-   *
-   * @param aNotification       Must be NOTIFY_IME_OF_SELECTION_CHANGE.
-   */
-  void InitNotification(IMENotification& aNotification) const;
-
-  /**
-   * SetSelection() modifies selection with specified raw data. And also this
-   * tries to retrieve text rects too.
-   * This must be called in content process.
-   */
-  void SetSelection(nsIWidget* aWidget,
-                    uint32_t aStartOffset,
-                    uint32_t aLength,
-                    bool aReversed,
-                    const WritingMode& aWritingMode);
-
-private:
+protected:
   // Whole text in the target
   nsString mText;
-  // This is commit string which is caused by our request.
-  // This value is valid only in chrome process.
-  nsString mCommitStringByRequest;
-  // Start offset of the composition string.
-  // This value is valid only in chrome process.
-  uint32_t mCompositionStart;
-  // Count of composition events during requesting commit or cancel the
-  // composition.
-  // This value is valid only in chrome process.
-  uint32_t mCompositionEventsDuringRequest;
 
   struct Selection final
   {
@@ -333,12 +221,47 @@ private:
 
   LayoutDeviceIntRect mEditorRect;
 
-  // mIsComposing is valid only in chrome process.
-  bool mIsComposing;
-  // mRequestedToCommitOrCancelComposition is valid only in chrome process.
-  bool mRequestedToCommitOrCancelComposition;
-  bool mIsChrome;
+  friend class ContentCacheInParent;
+  friend struct IPC::ParamTraits;
+};
 
+class ContentCacheInChild final : public ContentCache
+{
+public:
+  ContentCacheInChild();
+
+  /**
+   * When IME loses focus, this should be called and making this forget the
+   * content for reducing footprint.
+   */
+  void Clear();
+
+  /**
+   * Cache*() retrieves the latest content information and store them.
+   * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
+   * calls CacheSelection().  So, related data is also retrieved automatically.
+   */
+  bool CacheEditorRect(nsIWidget* aWidget,
+                       const IMENotification* aNotification = nullptr);
+  bool CacheSelection(nsIWidget* aWidget,
+                      const IMENotification* aNotification = nullptr);
+  bool CacheText(nsIWidget* aWidget,
+                 const IMENotification* aNotification = nullptr);
+
+  bool CacheAll(nsIWidget* aWidget,
+                const IMENotification* aNotification = nullptr);
+
+  /**
+   * SetSelection() modifies selection with specified raw data. And also this
+   * tries to retrieve text rects too.
+   */
+  void SetSelection(nsIWidget* aWidget,
+                    uint32_t aStartOffset,
+                    uint32_t aLength,
+                    bool aReversed,
+                    const WritingMode& aWritingMode);
+
+private:
   bool QueryCharRect(nsIWidget* aWidget,
                      uint32_t aOffset,
                      LayoutDeviceIntRect& aCharRect) const;
@@ -346,6 +269,87 @@ private:
                   const IMENotification* aNotification = nullptr);
   bool CacheTextRects(nsIWidget* aWidget,
                       const IMENotification* aNotification = nullptr);
+};
+
+class ContentCacheInParent final : public ContentCache
+{
+public:
+  ContentCacheInParent();
+
+  /**
+   * AssignContent() is called when TabParent receives ContentCache from
+   * the content process.  This doesn't copy composition information because
+   * it's managed by TabParent itself.
+   */
+  void AssignContent(const ContentCache& aOther,
+                     const IMENotification* aNotification = nullptr);
+
+  /**
+   * HandleQueryContentEvent() sets content data to aEvent.mReply.
+   *
+   * For NS_QUERY_SELECTED_TEXT, fail if the cache doesn't contain the whole
+   *  selected range. (This shouldn't happen because PuppetWidget should have
+   *  already sent the whole selection.)
+   *
+   * For NS_QUERY_TEXT_CONTENT, fail only if the cache doesn't overlap with
+   *  the queried range. Note the difference from above. We use
+   *  this behavior because a normal NS_QUERY_TEXT_CONTENT event is allowed to
+   *  have out-of-bounds offsets, so that widget can request content without
+   *  knowing the exact length of text. It's up to widget to handle cases when
+   *  the returned offset/length are different from the queried offset/length.
+   *
+   * For NS_QUERY_TEXT_RECT, fail if cached offset/length aren't equals to input.
+   *   Cocoa widget always queries selected offset, so it works on it.
+   *
+   * For NS_QUERY_CARET_RECT, fail if cached offset isn't equals to input
+   *
+   * For NS_QUERY_EDITOR_RECT, always success
+   */
+  bool HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
+                               nsIWidget* aWidget) const;
+
+  /**
+   * OnCompositionEvent() should be called before sending composition string.
+   * This returns true if the event should be sent.  Otherwise, false.
+   */
+  bool OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
+
+  /**
+   * RequestToCommitComposition() requests to commit or cancel composition to
+   * the widget.  If it's handled synchronously, this returns the number of
+   * composition events after that.
+   *
+   * @param aWidget     The widget to be requested to commit or cancel
+   *                    the composition.
+   * @param aCancel     When the caller tries to cancel the composition, true.
+   *                    Otherwise, i.e., tries to commit the composition, false.
+   * @param aLastString The last composition string before requesting to
+   *                    commit or cancel composition.
+   * @return            The count of composition events ignored after a call of
+   *                    WillRequestToCommitOrCancelComposition().
+   */
+  uint32_t RequestToCommitComposition(nsIWidget* aWidget,
+                                      bool aCancel,
+                                      nsAString& aLastString);
+
+  /**
+   * InitNotification() initializes aNotification with stored data.
+   *
+   * @param aNotification       Must be NOTIFY_IME_OF_SELECTION_CHANGE.
+   */
+  void InitNotification(IMENotification& aNotification) const;
+
+private:
+  // This is commit string which is caused by our request.
+  nsString mCommitStringByRequest;
+  // Start offset of the composition string.
+  uint32_t mCompositionStart;
+  // Count of composition events during requesting commit or cancel the
+  // composition.
+  uint32_t mCompositionEventsDuringRequest;
+
+  bool mIsComposing;
+  bool mRequestedToCommitOrCancelComposition;
 
   bool GetCaretRect(uint32_t aOffset, LayoutDeviceIntRect& aCaretRect) const;
   bool GetTextRect(uint32_t aOffset,
@@ -354,7 +358,6 @@ private:
                          uint32_t aLength,
                          LayoutDeviceIntRect& aUnionTextRect) const;
 
-  friend struct IPC::ParamTraits;
 };
 
 } // namespace mozilla
diff --git a/widget/PuppetWidget.h b/widget/PuppetWidget.h
index 3c3f32d716b5..e2ed64613ca1 100644
--- a/widget/PuppetWidget.h
+++ b/widget/PuppetWidget.h
@@ -313,7 +313,7 @@ private:
   mozilla::RefPtr mDrawTarget;
   // IME
   nsIMEUpdatePreference mIMEPreferenceOfParent;
-  ContentCache mContentCache;
+  ContentCacheInChild mContentCache;
   bool mNeedIMEStateInit;
 
   // The DPI of the screen corresponding to this widget
diff --git a/widget/gtk/gtk3drawing.c b/widget/gtk/gtk3drawing.c
index c203a6498772..2f770ed89913 100644
--- a/widget/gtk/gtk3drawing.c
+++ b/widget/gtk/gtk3drawing.c
@@ -1568,7 +1568,7 @@ moz_gtk_treeview_expander_paint(cairo_t *cr, GdkRectangle* rect,
 
     /* Because the frame we get is of the entire treeview, we can't get the precise
      * event state of one expander, thus rendering hover and active feedback useless. */
-    state_flags = state->disabled ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
+    state_flags = state->disabled ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
 
     /* GTK_STATE_FLAG_ACTIVE controls expanded/colapsed state rendering
      * in gtk_render_expander()
diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp
index 5b624c52f16d..1b4a50387037 100644
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -725,7 +725,7 @@ ThemeRenderer::DrawWithGDK(GdkDrawable * drawable, gint offsetX,
 #else
 class SystemCairoClipper : public ClipExporter {
 public:
-  SystemCairoClipper(cairo_t* aContext) : mContext(aContext)
+  explicit SystemCairoClipper(cairo_t* aContext) : mContext(aContext)
   {
   }
 
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
index 4056bf287f8f..c26fac7300a0 100644
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -1924,7 +1924,7 @@ nsWindow::HasPendingInputEvent()
     // without blocking or removing.  To prevent event reordering, peek
     // anything except expose events.  Reordering expose and others should be
     // ok, hopefully.
-    bool haveEvent;
+    bool haveEvent = false;
 #ifdef MOZ_X11
     XEvent ev;
     GdkDisplay* gdkDisplay = gdk_display_get_default();
@@ -1947,8 +1947,6 @@ nsWindow::HasPendingInputEvent()
             XPutBackEvent(display, &ev);
         }
     }
-#else
-    haveEvent = false;
 #endif
     return haveEvent;
 }