From 37ebc31704010c8becfd078ed7cac50a65f7673d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Mon, 23 Apr 2018 21:28:01 +0200 Subject: [PATCH 01/44] Bug 1421807: Add an assertion that should help with catching more instances of this bug. r=me MozReview-Commit-ID: KSLF6GbKHRQ --- layout/base/RestyleManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index dd37beeed48c..3593cc5afec3 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -2592,6 +2592,8 @@ RestyleManager::ProcessPostTraversal( nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement); nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); + MOZ_ASSERT(aElement->HasServoData(), "How in the world?"); + // NOTE(emilio): This is needed because for table frames the bit is set on the // table wrapper (which is the primary frame), not on the table itself. const bool isOutOfFlow = From 29b27943c56489122b1aa2006fd707cb74bcaaec Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Fri, 20 Apr 2018 11:34:00 -0700 Subject: [PATCH 02/44] Bug 1400153 - Rename nextInPhase -> nextWithPhaseKind, r=jonco --HG-- extra : rebase_source : 0d221747096898fc24f7e26c354679afb967a537 extra : histedit_source : 28b1d08cc591ca2caf8ec5a5f091e2c4f8245bfe --- js/src/gc/Statistics.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 57dbb00f2ba5..04e08143a538 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -110,7 +110,7 @@ struct PhaseInfo Phase parent; Phase firstChild; Phase nextSibling; - Phase nextInPhase; + Phase nextWithPhaseKind; PhaseKind phaseKind; uint8_t depth; const char* name; @@ -166,7 +166,7 @@ Statistics::lookupChildPhase(PhaseKind phaseKind) const Phase phase; for (phase = phaseKinds[phaseKind].firstPhase; phase != Phase::NONE; - phase = phases[phase].nextInPhase) + phase = phases[phase].nextWithPhaseKind) { if (phases[phase].parent == currentPhase()) break; @@ -769,10 +769,10 @@ Statistics::initialize() MOZ_ASSERT(parent == phases[nextSibling].parent); MOZ_ASSERT(phases[i].depth == phases[nextSibling].depth); } - auto nextInPhase = phases[i].nextInPhase; - if (nextInPhase != Phase::NONE) { - MOZ_ASSERT(phases[i].phaseKind == phases[nextInPhase].phaseKind); - MOZ_ASSERT(parent != phases[nextInPhase].parent); + auto nextWithPhaseKind = phases[i].nextWithPhaseKind; + if (nextWithPhaseKind != Phase::NONE) { + MOZ_ASSERT(phases[i].phaseKind == phases[nextWithPhaseKind].phaseKind); + MOZ_ASSERT(parent != phases[nextWithPhaseKind].parent); } } for (auto i : AllPhaseKinds()) { @@ -825,7 +825,7 @@ SumPhase(PhaseKind phaseKind, const Statistics::PhaseTimeTable& times) TimeDuration sum = 0; for (Phase phase = phaseKinds[phaseKind].firstPhase; phase != Phase::NONE; - phase = phases[phase].nextInPhase) + phase = phases[phase].nextWithPhaseKind) { sum += times[phase]; } From d4194e400358a279eaddeb9bf08c1b6e8c71083c Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Fri, 20 Apr 2018 11:35:28 -0700 Subject: [PATCH 03/44] Bug 1400153 - Add another check for timestamp problems, and attempt to get all the intermittents routed to one bug, r=jonco --HG-- extra : rebase_source : 1731917b91ce47a3b6302fbb140ffbdd78b0dba2 extra : amend_source : 79bf89753f9cb8fca9ba66efc858dfd838288a6e extra : histedit_source : 1d796c678b8967615e434d3fb74c976d8f33f2ca --- js/src/gc/Statistics.cpp | 36 ++++++++++++++++++++++++++++++------ js/src/gc/Statistics.h | 5 +++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 04e08143a538..304ed92af9fb 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -634,7 +634,7 @@ Statistics::formatJsonDescription(uint64_t timestamp, JSONPrinter& json) const sccDurations(&sccTotal, &sccLongest); json.property("scc_sweep_total", sccTotal, JSONPrinter::MILLISECONDS); // #14 json.property("scc_sweep_max_pause", sccLongest, JSONPrinter::MILLISECONDS); // #15 - + if (nonincrementalReason_ != AbortReason::None) json.property("nonincremental_reason", ExplainAbortReason(nonincrementalReason_)); // #16 json.property("allocated_bytes", preBytes); // #17 @@ -870,7 +870,7 @@ LongestPhaseSelfTimeInMajorGC(const Statistics::PhaseTimeTable& times) // This happens very occasionally in release builds. Skip collecting // longest phase telemetry if it does. - MOZ_ASSERT(ok, "Inconsistent time data"); + MOZ_ASSERT(ok, "Inconsistent time data; see bug 1400153"); if (!ok) return PhaseKind::NONE; @@ -1099,6 +1099,9 @@ Statistics::endSlice() auto mutatorStartTime = phaseStartTimes[Phase::MUTATOR]; auto mutatorTime = phaseTimes[Phase::MUTATOR]; PodZero(&phaseStartTimes); +#ifdef DEBUG + PodZero(&phaseEndTimes); +#endif PodZero(&phaseTimes); phaseStartTimes[Phase::MUTATOR] = mutatorStartTime; phaseTimes[Phase::MUTATOR] = mutatorTime; @@ -1212,8 +1215,7 @@ Statistics::recordPhaseBegin(Phase phase) TimeStamp now = TimeStamp::Now(); if (current != Phase::NONE) { - // Sadly this happens sometimes. - MOZ_ASSERT(now >= phaseStartTimes[currentPhase()]); + MOZ_ASSERT(now >= phaseStartTimes[currentPhase()], "Inconsistent time data; see bug 1400153"); if (now < phaseStartTimes[currentPhase()]) { now = phaseStartTimes[currentPhase()]; aborted = true; @@ -1233,8 +1235,26 @@ Statistics::recordPhaseEnd(Phase phase) TimeStamp now = TimeStamp::Now(); - // Sadly this happens sometimes. - MOZ_ASSERT(now >= phaseStartTimes[phase]); + // Make sure this phase ends after it starts. + MOZ_ASSERT(now >= phaseStartTimes[phase], "Inconsistent time data; see bug 1400153"); + +#ifdef DEBUG + // Make sure this phase ends after all of its children. Note that some + // children might not have run in this instance, in which case they will + // have run in a previous instance of this parent or not at all. + for (Phase kid = phases[phase].firstChild; kid != Phase::NONE; kid = phases[kid].nextSibling) { + if (phaseEndTimes[kid].IsNull()) + continue; + if (phaseEndTimes[kid] > now) + fprintf(stderr, "Parent %s ended at %.3fms, before child %s ended at %.3fms?\n", + phases[phase].name, + t(now - TimeStamp::ProcessCreation()), + phases[kid].name, + t(phaseEndTimes[kid] - TimeStamp::ProcessCreation())); + MOZ_ASSERT(phaseEndTimes[kid] <= now, "Inconsistent time data; see bug 1400153"); + } +#endif + if (now < phaseStartTimes[phase]) { now = phaseStartTimes[phase]; aborted = true; @@ -1250,6 +1270,10 @@ Statistics::recordPhaseEnd(Phase phase) slices_.back().phaseTimes[phase] += t; phaseTimes[phase] += t; phaseStartTimes[phase] = TimeStamp(); + +#ifdef DEBUG + phaseEndTimes[phase] = now; +#endif } void diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index ddedd1618df0..7775e3e71e39 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -289,6 +289,11 @@ struct Statistics /* Most recent time when the given phase started. */ EnumeratedArray phaseStartTimes; +#ifdef DEBUG + /* Most recent time when the given phase ended. */ + EnumeratedArray phaseEndTimes; +#endif + /* Bookkeeping for GC timings when timingMutator is true */ TimeStamp timedGCStart; TimeDuration timedGCTime; From 79d20383ee21afa6290464363213177cbafc32eb Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 24 Apr 2018 01:52:51 +0200 Subject: [PATCH 04/44] Bug 1456166 - Downgrade a non-essential assertion to a warning for now because it keeps failing. r=mattwoodrow --- layout/painting/nsDisplayList.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp index 4682b1a82021..9612716cf5ba 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -2401,8 +2401,9 @@ nsDisplayList::ComputeVisibilityForSublist(nsDisplayListBuilder* aBuilder, #ifdef DEBUG nsRegion r; r.And(*aVisibleRegion, GetBounds(aBuilder)); - NS_ASSERTION(r.GetBounds().IsEqualInterior(aListVisibleBounds), - "bad aListVisibleBounds"); + // XXX this fails sometimes: + NS_WARNING_ASSERTION(r.GetBounds().IsEqualInterior(aListVisibleBounds), + "bad aListVisibleBounds"); #endif bool anyVisible = false; From 314470600fb8fbf70fffb323b590540bd2cb779b Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 24 Apr 2018 01:52:51 +0200 Subject: [PATCH 05/44] Bug 1398482 part 1 - [css-grid][css-flexbox][css-multicol] Add 'row-gap' and 'gap' properties; make 'grid-[column|row]-gap' and 'grid-gap' alias the respective unprefixed properties (Stylo part). r=emilio This also makes 'normal' the initial value for the grid-* properties, per: https://github.com/w3c/csswg-drafts/issues/2294#issuecomment-369313438 --- .../style/properties/longhand/column.mako.rs | 11 -------- .../properties/longhand/position.mako.rs | 27 ++++++++++++++----- .../properties/shorthand/position.mako.rs | 22 +++++++-------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/servo/components/style/properties/longhand/column.mako.rs b/servo/components/style/properties/longhand/column.mako.rs index 3b2a3ec38ed2..7a97c3f41e37 100644 --- a/servo/components/style/properties/longhand/column.mako.rs +++ b/servo/components/style/properties/longhand/column.mako.rs @@ -31,17 +31,6 @@ ${helpers.predefined_type( -${helpers.predefined_type( - "column-gap", - "length::NonNegativeLengthOrPercentageOrNormal", - "Either::Second(Normal)", - extra_prefixes="moz", - servo_pref="layout.columns.enabled", - animation_value_type="NonNegativeLengthOrPercentageOrNormal", - spec="https://drafts.csswg.org/css-multicol/#propdef-column-gap", - servo_restyle_damage = "reflow", -)} - ${helpers.single_keyword("column-fill", "balance auto", extra_prefixes="moz", products="gecko", animation_value_type="discrete", spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill")} diff --git a/servo/components/style/properties/longhand/position.mako.rs b/servo/components/style/properties/longhand/position.mako.rs index cbff436e4bf2..7666781ebc5d 100644 --- a/servo/components/style/properties/longhand/position.mako.rs +++ b/servo/components/style/properties/longhand/position.mako.rs @@ -302,13 +302,6 @@ ${helpers.predefined_type("object-position", animation_value_type="ComputedValue")} % for kind in ["row", "column"]: - ${helpers.predefined_type("grid-%s-gap" % kind, - "NonNegativeLengthOrPercentage", - "computed::NonNegativeLengthOrPercentage::zero()", - spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-gap" % kind, - animation_value_type="NonNegativeLengthOrPercentage", - products="gecko")} - % for range in ["start", "end"]: ${helpers.predefined_type("grid-%s-%s" % (kind, range), "GridLine", @@ -355,3 +348,23 @@ ${helpers.predefined_type("grid-template-areas", products="gecko", animation_value_type="discrete", spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-areas")} + +${helpers.predefined_type("column-gap", + "length::NonNegativeLengthOrPercentageOrNormal", + "Either::Second(Normal)", + alias="grid-column-gap", + extra_prefixes="moz", + servo_pref="layout.columns.enabled", + spec="https://drafts.csswg.org/css-align-3/#propdef-column-gap", + animation_value_type="NonNegativeLengthOrPercentageOrNormal", + servo_restyle_damage = "reflow")} + +// no need for -moz- prefixed alias for this property +${helpers.predefined_type("row-gap", + "length::NonNegativeLengthOrPercentageOrNormal", + "Either::Second(Normal)", + alias="grid-row-gap", + servo_pref="layout.columns.enabled", + spec="https://drafts.csswg.org/css-align-3/#propdef-row-gap", + animation_value_type="NonNegativeLengthOrPercentageOrNormal", + servo_restyle_damage = "reflow")} diff --git a/servo/components/style/properties/shorthand/position.mako.rs b/servo/components/style/properties/shorthand/position.mako.rs index e5b07d616e4c..19ca0cf19168 100644 --- a/servo/components/style/properties/shorthand/position.mako.rs +++ b/servo/components/style/properties/shorthand/position.mako.rs @@ -108,30 +108,30 @@ } -<%helpers:shorthand name="grid-gap" sub_properties="grid-row-gap grid-column-gap" - spec="https://drafts.csswg.org/css-grid/#propdef-grid-gap" +<%helpers:shorthand name="gap" alias="grid-gap" sub_properties="row-gap column-gap" + spec="https://drafts.csswg.org/css-align-3/#gap-shorthand" products="gecko"> - use properties::longhands::{grid_row_gap, grid_column_gap}; + use properties::longhands::{row_gap, column_gap}; pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { - let row_gap = grid_row_gap::parse(context, input)?; - let column_gap = input.try(|input| grid_column_gap::parse(context, input)).unwrap_or(row_gap.clone()); + let r_gap = row_gap::parse(context, input)?; + let c_gap = input.try(|input| column_gap::parse(context, input)).unwrap_or(r_gap.clone()); Ok(expanded! { - grid_row_gap: row_gap, - grid_column_gap: column_gap, + row_gap: r_gap, + column_gap: c_gap, }) } impl<'a> ToCss for LonghandsToSerialize<'a> { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { - if self.grid_row_gap == self.grid_column_gap { - self.grid_row_gap.to_css(dest) + if self.row_gap == self.column_gap { + self.row_gap.to_css(dest) } else { - self.grid_row_gap.to_css(dest)?; + self.row_gap.to_css(dest)?; dest.write_str(" ")?; - self.grid_column_gap.to_css(dest) + self.column_gap.to_css(dest) } } } From 8e30369644b29e0b6a4f0611905bb89f9a5323dc Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 24 Apr 2018 01:52:51 +0200 Subject: [PATCH 06/44] Bug 1398482 part 2 - [css-grid][css-flexbox][css-multicol] Add 'row-gap' and 'gap' properties; make 'grid-[column|row]-gap' and 'grid-gap' alias the respective unprefixed properties (Gecko part). r=dholbert --- layout/generic/nsColumnSetFrame.cpp | 13 ++-- layout/generic/nsGridContainerFrame.cpp | 12 ++-- layout/style/nsCSSPropAliasList.h | 15 +++++ layout/style/nsCSSPropList.h | 36 +++++------ layout/style/nsCSSProps.cpp | 6 +- layout/style/nsComputedDOMStyle.cpp | 33 +++++----- layout/style/nsComputedDOMStyle.h | 5 +- layout/style/nsComputedDOMStylePropertyList.h | 3 +- layout/style/nsStyleStruct.cpp | 15 ++--- layout/style/nsStyleStruct.h | 5 +- layout/style/test/property_database.js | 62 ++++++++++--------- .../test/test_transitions_per_property.html | 6 +- 12 files changed, 104 insertions(+), 107 deletions(-) diff --git a/layout/generic/nsColumnSetFrame.cpp b/layout/generic/nsColumnSetFrame.cpp index a8edd47216cc..d1957aa7dd37 100644 --- a/layout/generic/nsColumnSetFrame.cpp +++ b/layout/generic/nsColumnSetFrame.cpp @@ -288,11 +288,10 @@ nsColumnSetFrame::GetAvailableContentBSize(const ReflowInput& aReflowInput) } static nscoord -GetColumnGap(nsColumnSetFrame* aFrame, - const nsStyleColumn* aColStyle, - nscoord aPercentageBasis) +GetColumnGap(nsColumnSetFrame* aFrame, + nscoord aPercentageBasis) { - const auto& columnGap = aColStyle->mColumnGap; + const auto& columnGap = aFrame->StylePosition()->mColumnGap; if (columnGap.GetUnit() == eStyleUnit_Normal) { return aFrame->StyleFont()->mFont.size; } @@ -330,7 +329,7 @@ nsColumnSetFrame::ChooseColumnStrategy(const ReflowInput& aReflowInput, colBSize = std::min(colBSize, aReflowInput.ComputedMaxBSize()); } - nscoord colGap = GetColumnGap(this, colStyle, aReflowInput.ComputedISize()); + nscoord colGap = GetColumnGap(this, aReflowInput.ComputedISize()); int32_t numColumns = colStyle->mColumnCount; // If column-fill is set to 'balance', then we want to balance the columns. @@ -514,7 +513,7 @@ nsColumnSetFrame::GetMinISize(gfxContext *aRenderingContext) // include n-1 column gaps. colISize = iSize; iSize *= colStyle->mColumnCount; - nscoord colGap = GetColumnGap(this, colStyle, NS_UNCONSTRAINEDSIZE); + nscoord colGap = GetColumnGap(this, NS_UNCONSTRAINEDSIZE); iSize += colGap * (colStyle->mColumnCount - 1); // The multiplication above can make 'width' negative (integer overflow), // so use std::max to protect against that. @@ -535,7 +534,7 @@ nsColumnSetFrame::GetPrefISize(gfxContext *aRenderingContext) nscoord result = 0; DISPLAY_PREF_WIDTH(this, result); const nsStyleColumn* colStyle = StyleColumn(); - nscoord colGap = GetColumnGap(this, colStyle, NS_UNCONSTRAINEDSIZE); + nscoord colGap = GetColumnGap(this, NS_UNCONSTRAINEDSIZE); nscoord colISize; if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) { diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 6081482da433..99b98507ecca 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -2375,9 +2375,9 @@ nsGridContainerFrame::GridReflowInput::CalculateTrackSizes( const LogicalSize& aContentBox, SizingConstraint aConstraint) { - mCols.Initialize(mColFunctions, mGridStyle->mGridColumnGap, + mCols.Initialize(mColFunctions, mGridStyle->mColumnGap, aGrid.mGridColEnd, aContentBox.ISize(mWM)); - mRows.Initialize(mRowFunctions, mGridStyle->mGridRowGap, + mRows.Initialize(mRowFunctions, mGridStyle->mRowGap, aGrid.mGridRowEnd, aContentBox.BSize(mWM)); mCols.CalculateSizes(*this, mGridItems, mColFunctions, @@ -3131,7 +3131,7 @@ nsGridContainerFrame::Grid::PlaceGridItems(GridReflowInput& aState, // to a 0,0 based grid after placing definite lines. auto areas = gridStyle->mGridTemplateAreas.get(); uint32_t numRepeatCols = aState.mColFunctions.InitRepeatTracks( - gridStyle->mGridColumnGap, + gridStyle->mColumnGap, aComputedMinSize.ISize(aState.mWM), aComputedSize.ISize(aState.mWM), aComputedMaxSize.ISize(aState.mWM)); @@ -3140,7 +3140,7 @@ nsGridContainerFrame::Grid::PlaceGridItems(GridReflowInput& aState, LineNameMap colLineNameMap(gridStyle->GridTemplateColumns(), numRepeatCols); uint32_t numRepeatRows = aState.mRowFunctions.InitRepeatTracks( - gridStyle->mGridRowGap, + gridStyle->mRowGap, aComputedMinSize.BSize(aState.mWM), aComputedSize.BSize(aState.mWM), aComputedMaxSize.BSize(aState.mWM)); @@ -6370,7 +6370,7 @@ nsGridContainerFrame::IntrinsicISize(gfxContext* aRenderingContext, if (grid.mGridColEnd == 0) { return 0; } - state.mCols.Initialize(state.mColFunctions, state.mGridStyle->mGridColumnGap, + state.mCols.Initialize(state.mColFunctions, state.mGridStyle->mColumnGap, grid.mGridColEnd, NS_UNCONSTRAINEDSIZE); auto constraint = aType == nsLayoutUtils::MIN_ISIZE ? SizingConstraint::eMinContent : SizingConstraint::eMaxContent; @@ -6378,7 +6378,7 @@ nsGridContainerFrame::IntrinsicISize(gfxContext* aRenderingContext, NS_UNCONSTRAINEDSIZE, &GridArea::mCols, constraint); state.mCols.mGridGap = - nsLayoutUtils::ResolveGapToLength(state.mGridStyle->mGridColumnGap, + nsLayoutUtils::ResolveGapToLength(state.mGridStyle->mColumnGap, NS_UNCONSTRAINEDSIZE); nscoord length = 0; for (const TrackSize& sz : state.mCols.mSizes) { diff --git a/layout/style/nsCSSPropAliasList.h b/layout/style/nsCSSPropAliasList.h index bbba65d8fa75..ea58225f8392 100644 --- a/layout/style/nsCSSPropAliasList.h +++ b/layout/style/nsCSSPropAliasList.h @@ -597,6 +597,21 @@ CSS_PROP_ALIAS(-webkit-user-select, WEBKIT_PREFIX_PREF) #undef WEBKIT_PREFIX_PREF +CSS_PROP_ALIAS(grid-column-gap, + grid_column_gap, + column_gap, + GridColumnGap, + "") +CSS_PROP_ALIAS(grid-gap, + grid_gap, + gap, + GridGap, + "") +CSS_PROP_ALIAS(grid-row-gap, + grid_row_gap, + row_gap, + GridRowGap, + "") CSS_PROP_ALIAS(word-wrap, word_wrap, overflow_wrap, diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index a05b680d5a08..2b3412c338ef 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1363,6 +1363,12 @@ CSS_PROP_( "", VARIANT_HI, nullptr) // bug 58646 +CSS_PROP_SHORTHAND( + gap, + gap, + Gap, + CSS_PROPERTY_PARSE_FUNCTION, + "") CSS_PROP_SHORTHAND( grid, grid, @@ -1413,14 +1419,6 @@ CSS_PROP_( "", 0, nullptr) -CSS_PROP_( - grid-column-gap, - grid_column_gap, - GridColumnGap, - 0, - "", - VARIANT_HLP | VARIANT_CALC, - nullptr) CSS_PROP_( grid-column-start, grid_column_start, @@ -1429,12 +1427,6 @@ CSS_PROP_( "", 0, nullptr) -CSS_PROP_SHORTHAND( - grid-gap, - grid_gap, - GridGap, - CSS_PROPERTY_PARSE_FUNCTION, - "") CSS_PROP_SHORTHAND( grid-row, grid_row, @@ -1449,14 +1441,6 @@ CSS_PROP_( "", 0, nullptr) -CSS_PROP_( - grid-row-gap, - grid_row_gap, - GridRowGap, - 0, - "", - VARIANT_HLP | VARIANT_CALC, - nullptr) CSS_PROP_( grid-row-start, grid_row_start, @@ -2383,6 +2367,14 @@ CSS_PROP_( "layout.css.individual-transform.enabled", 0, nullptr) +CSS_PROP_( + row-gap, + row_gap, + RowGap, + 0, + "", + VARIANT_HLP | VARIANT_NORMAL | VARIANT_CALC, + nullptr) CSS_PROP_( ruby-align, ruby_align, diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 391f48b25b83..fecc73a97ab1 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -2672,9 +2672,9 @@ static const nsCSSPropertyID gGridAreaSubpropTable[] = { eCSSProperty_UNKNOWN }; -static const nsCSSPropertyID gGridGapSubpropTable[] = { - eCSSProperty_grid_row_gap, - eCSSProperty_grid_column_gap, +static const nsCSSPropertyID gGapSubpropTable[] = { + eCSSProperty_row_gap, + eCSSProperty_column_gap, eCSSProperty_UNKNOWN }; diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index ce26122e4c1d..23077b343263 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -1251,21 +1251,6 @@ nsComputedDOMStyle::DoGetColumnWidth() return val.forget(); } -already_AddRefed -nsComputedDOMStyle::DoGetColumnGap() -{ - RefPtr val = new nsROCSSPrimitiveValue; - - const nsStyleColumn* column = StyleColumn(); - if (column->mColumnGap.GetUnit() == eStyleUnit_Normal) { - val->SetIdent(eCSSKeyword_normal); - } else { - SetValueToCoord(val, StyleColumn()->mColumnGap, true); - } - - return val.forget(); -} - already_AddRefed nsComputedDOMStyle::DoGetColumnFill() { @@ -3429,18 +3414,28 @@ nsComputedDOMStyle::DoGetGridRowEnd() } already_AddRefed -nsComputedDOMStyle::DoGetGridColumnGap() +nsComputedDOMStyle::DoGetColumnGap() { RefPtr val = new nsROCSSPrimitiveValue; - SetValueToCoord(val, StylePosition()->mGridColumnGap, true); + const auto& columnGap = StylePosition()->mColumnGap; + if (columnGap.GetUnit() == eStyleUnit_Normal) { + val->SetIdent(eCSSKeyword_normal); + } else { + SetValueToCoord(val, columnGap, true); + } return val.forget(); } already_AddRefed -nsComputedDOMStyle::DoGetGridRowGap() +nsComputedDOMStyle::DoGetRowGap() { RefPtr val = new nsROCSSPrimitiveValue; - SetValueToCoord(val, StylePosition()->mGridRowGap, true); + const auto& rowGap = StylePosition()->mRowGap; + if (rowGap.GetUnit() == eStyleUnit_Normal) { + val->SetIdent(eCSSKeyword_normal); + } else { + SetValueToCoord(val, rowGap, true); + } return val.forget(); } diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index bac89795e41a..22bd20a5e448 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -316,8 +316,6 @@ private: already_AddRefed DoGetGridColumnEnd(); already_AddRefed DoGetGridRowStart(); already_AddRefed DoGetGridRowEnd(); - already_AddRefed DoGetGridColumnGap(); - already_AddRefed DoGetGridRowGap(); /* StyleImageLayer properties */ already_AddRefed DoGetImageLayerImage(const nsStyleImageLayers& aLayers); @@ -539,7 +537,6 @@ private: already_AddRefed DoGetColumnFill(); already_AddRefed DoGetColumnSpan(); already_AddRefed DoGetColumnWidth(); - already_AddRefed DoGetColumnGap(); already_AddRefed DoGetColumnRuleWidth(); already_AddRefed DoGetColumnRuleStyle(); already_AddRefed DoGetColumnRuleColor(); @@ -577,6 +574,8 @@ private: already_AddRefed DoGetJustifyContent(); already_AddRefed DoGetJustifyItems(); already_AddRefed DoGetJustifySelf(); + already_AddRefed DoGetColumnGap(); + already_AddRefed DoGetRowGap(); /* SVG properties */ already_AddRefed DoGetFill(); diff --git a/layout/style/nsComputedDOMStylePropertyList.h b/layout/style/nsComputedDOMStylePropertyList.h index 8e82a81e7e80..470ef866d3bd 100644 --- a/layout/style/nsComputedDOMStylePropertyList.h +++ b/layout/style/nsComputedDOMStylePropertyList.h @@ -154,10 +154,8 @@ COMPUTED_STYLE_PROP(grid_auto_columns, GridAutoColumns) COMPUTED_STYLE_PROP(grid_auto_flow, GridAutoFlow) COMPUTED_STYLE_PROP(grid_auto_rows, GridAutoRows) COMPUTED_STYLE_PROP(grid_column_end, GridColumnEnd) -COMPUTED_STYLE_PROP(grid_column_gap, GridColumnGap) COMPUTED_STYLE_PROP(grid_column_start, GridColumnStart) COMPUTED_STYLE_PROP(grid_row_end, GridRowEnd) -COMPUTED_STYLE_PROP(grid_row_gap, GridRowGap) COMPUTED_STYLE_PROP(grid_row_start, GridRowStart) COMPUTED_STYLE_PROP(grid_template_areas, GridTemplateAreas) COMPUTED_STYLE_PROP(grid_template_columns, GridTemplateColumns) @@ -224,6 +222,7 @@ COMPUTED_STYLE_PROP(quotes, Quotes) COMPUTED_STYLE_PROP(resize, Resize) COMPUTED_STYLE_PROP(right, Right) COMPUTED_STYLE_PROP(rotate, Rotate) +COMPUTED_STYLE_PROP(row_gap, RowGap) COMPUTED_STYLE_PROP(ruby_align, RubyAlign) COMPUTED_STYLE_PROP(ruby_position, RubyPosition) COMPUTED_STYLE_PROP(scale, Scale) diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 2b6ed5c8d03e..ab992c80693c 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -756,7 +756,6 @@ nsStyleXUL::CalcDifference(const nsStyleXUL& aNewData) const nsStyleColumn::nsStyleColumn(const nsPresContext* aContext) : mColumnCount(NS_STYLE_COLUMN_COUNT_AUTO) , mColumnWidth(eStyleUnit_Auto) - , mColumnGap(eStyleUnit_Normal) , mColumnRuleColor(StyleComplexColor::CurrentColor()) , mColumnRuleStyle(NS_STYLE_BORDER_STYLE_NONE) , mColumnFill(NS_STYLE_COLUMN_FILL_BALANCE) @@ -776,7 +775,6 @@ nsStyleColumn::~nsStyleColumn() nsStyleColumn::nsStyleColumn(const nsStyleColumn& aSource) : mColumnCount(aSource.mColumnCount) , mColumnWidth(aSource.mColumnWidth) - , mColumnGap(aSource.mColumnGap) , mColumnRuleColor(aSource.mColumnRuleColor) , mColumnRuleStyle(aSource.mColumnRuleStyle) , mColumnFill(aSource.mColumnFill) @@ -801,7 +799,6 @@ nsStyleColumn::CalcDifference(const nsStyleColumn& aNewData) const } if (mColumnWidth != aNewData.mColumnWidth || - mColumnGap != aNewData.mColumnGap || mColumnFill != aNewData.mColumnFill) { return NS_STYLE_HINT_REFLOW; } @@ -1523,8 +1520,8 @@ nsStylePosition::nsStylePosition(const nsPresContext* aContext) , mFlexGrow(0.0f) , mFlexShrink(1.0f) , mZIndex(eStyleUnit_Auto) - , mGridColumnGap(nscoord(0), nsStyleCoord::CoordConstructor) - , mGridRowGap(nscoord(0), nsStyleCoord::CoordConstructor) + , mColumnGap(eStyleUnit_Normal) + , mRowGap(eStyleUnit_Normal) { MOZ_COUNT_CTOR(nsStylePosition); @@ -1587,8 +1584,8 @@ nsStylePosition::nsStylePosition(const nsStylePosition& aSource) , mGridColumnEnd(aSource.mGridColumnEnd) , mGridRowStart(aSource.mGridRowStart) , mGridRowEnd(aSource.mGridRowEnd) - , mGridColumnGap(aSource.mGridColumnGap) - , mGridRowGap(aSource.mGridRowGap) + , mColumnGap(aSource.mColumnGap) + , mRowGap(aSource.mRowGap) { MOZ_COUNT_CTOR(nsStylePosition); @@ -1712,8 +1709,8 @@ nsStylePosition::CalcDifference(const nsStylePosition& aNewData, mGridColumnEnd != aNewData.mGridColumnEnd || mGridRowStart != aNewData.mGridRowStart || mGridRowEnd != aNewData.mGridRowEnd || - mGridColumnGap != aNewData.mGridColumnGap || - mGridRowGap != aNewData.mGridRowGap) { + mColumnGap != aNewData.mColumnGap || + mRowGap != aNewData.mRowGap) { return hint | nsChangeHint_AllReflowHints; } diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 328a046d2791..5e8645024be1 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1507,8 +1507,8 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePosition nsStyleGridLine mGridColumnEnd; nsStyleGridLine mGridRowStart; nsStyleGridLine mGridRowEnd; - nsStyleCoord mGridColumnGap; // [reset] coord, percent, calc - nsStyleCoord mGridRowGap; // [reset] coord, percent, calc + nsStyleCoord mColumnGap; // [reset] normal, coord, percent, calc + nsStyleCoord mRowGap; // [reset] normal, coord, percent, calc // FIXME: Logical-coordinate equivalents to these WidthDepends... and // HeightDepends... methods have been introduced (see below); we probably @@ -2931,7 +2931,6 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleColumn uint32_t mColumnCount; // [reset] see nsStyleConsts.h nsStyleCoord mColumnWidth; // [reset] coord, auto - nsStyleCoord mColumnGap; // [reset] | normal mozilla::StyleComplexColor mColumnRuleColor; // [reset] uint8_t mColumnRuleStyle; // [reset] diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 00df8269f57b..50343ec2af7c 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -1705,24 +1705,6 @@ var gCSSProperties = { alias_for: "column-fill", subproperties: [ "column-fill" ] }, - "column-gap": { - domProp: "columnGap", - inherited: false, - type: CSS_TYPE_LONGHAND, - initial_values: [ "normal" ], - other_values: [ "2px", "1em", "4em", "3%", "calc(3%)", "calc(1em - 3%)", - "calc(2px)", - "calc(-2px)", - "calc(0px)", - "calc(0pt)", - "calc(5em)", - "calc(-2em + 3em)", - "calc(3*25px)", - "calc(25px*3)", - "calc(3*25px + 5em)", - ], - invalid_values: [ "-3%", "-1px", "4" ] - }, "-moz-column-gap": { domProp: "MozColumnGap", inherited: false, @@ -7226,33 +7208,55 @@ gCSSProperties["grid-area"] = { invalid_values: gridAreaInvalidValues }; -gCSSProperties["grid-column-gap"] = { - domProp: "gridColumnGap", +gCSSProperties["column-gap"] = { + domProp: "columnGap", inherited: false, type: CSS_TYPE_LONGHAND, - initial_values: [ "0" ], + initial_values: [ "normal" ], other_values: [ "2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)", "calc(1% + 1ch)" , "calc(1px - 99%)" ], invalid_values: [ "-1px", "auto", "none", "1px 1px", "-1%", "fit-content(1px)" ], }; -gCSSProperties["grid-row-gap"] = { - domProp: "gridRowGap", +gCSSProperties["grid-column-gap"] = { + domProp: "gridColumnGap", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-gap", + subproperties: [ "column-gap" ] +}; +gCSSProperties["row-gap"] = { + domProp: "rowGap", inherited: false, type: CSS_TYPE_LONGHAND, - initial_values: [ "0" ], + initial_values: [ "normal" ], other_values: [ "2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)", "calc(1% + 1ch)" , "calc(1px - 99%)" ], invalid_values: [ "-1px", "auto", "none", "1px 1px", "-1%", "min-content" ], }; +gCSSProperties["grid-row-gap"] = { + domProp: "gridRowGap", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "row-gap", + subproperties: [ "row-gap" ] +}; +gCSSProperties["gap"] = { + domProp: "gap", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "column-gap", "row-gap" ], + initial_values: [ "normal", "normal normal" ], + other_values: [ "1ch 0", "1px 1%", "1em 1px", "calc(1px) calc(1%)", + "normal 0", "1% normal" ], + invalid_values: [ "-1px", "1px -1px", "1px 1px 1px", "inherit 1px", + "1px auto" ] +}; gCSSProperties["grid-gap"] = { domProp: "gridGap", inherited: false, type: CSS_TYPE_TRUE_SHORTHAND, - subproperties: [ "grid-column-gap", "grid-row-gap" ], - initial_values: [ "0", "0 0" ], - other_values: [ "1ch 0", "1px 1%", "1em 1px", "calc(1px) calc(1%)" ], - invalid_values: [ "-1px", "1px -1px", "1px 1px 1px", "inherit 1px", - "1px auto" ] + alias_for: "gap", + subproperties: [ "column-gap", "row-gap" ], }; if (IsCSSPropertyPrefEnabled("layout.css.contain.enabled")) { diff --git a/layout/style/test/test_transitions_per_property.html b/layout/style/test/test_transitions_per_property.html index 6226807433ee..41e7eeb91a66 100644 --- a/layout/style/test/test_transitions_per_property.html +++ b/layout/style/test/test_transitions_per_property.html @@ -73,8 +73,6 @@ var supported_properties = { "box-shadow": [ test_shadow_transition ], "column-count": [ test_pos_integer_or_auto_transition, test_integer_at_least_one_clamping ], - "column-gap": [ test_length_transition, - test_length_clamped ], "column-rule-color": [ test_color_transition, test_true_currentcolor_transition ], "column-rule-width": [ test_length_transition, @@ -169,8 +167,8 @@ var supported_properties = { /* test_float_zeroToOne_clamped */ ], "font-stretch": [ test_percent_transition, test_percent_clamped ], "font-weight": [ test_font_weight ], - "grid-column-gap": [ test_grid_gap ], - "grid-row-gap": [ test_grid_gap ], + "column-gap": [ test_grid_gap ], + "row-gap": [ test_grid_gap ], "height": [ test_length_transition, test_percent_transition, test_length_percent_calc_transition, test_length_clamped, test_percent_clamped ], From 023ce1dc342ae99f780ec1fa7f558e3175837d95 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 24 Apr 2018 01:52:51 +0200 Subject: [PATCH 07/44] Bug 1398482 part 3 - [css-grid][css-flexbox][css-multicol] Add 'row-gap' and 'gap' properties; make 'grid-[column|row]-gap' and 'grid-gap' alias the respective unprefixed properties (automated devtools update). --- .../shared/css/generated/properties-db.js | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index ef5dc6fb4fe9..92afcc65f551 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -2986,10 +2986,8 @@ exports.CSS_PROPERTIES = { "grid-auto-flow", "grid-auto-rows", "grid-column-end", - "grid-column-gap", "grid-column-start", "grid-row-end", - "grid-row-gap", "grid-row-start", "grid-template-areas", "grid-template-columns", @@ -3089,6 +3087,7 @@ exports.CSS_PROPERTIES = { "resize", "right", "rotate", + "row-gap", "ruby-align", "ruby-position", "scale", @@ -6167,6 +6166,24 @@ exports.CSS_PROPERTIES = { "unset" ] }, + "gap": { + "isInherited": false, + "subproperties": [ + "row-gap", + "column-gap" + ], + "supports": [ + 6, + 8 + ], + "values": [ + "calc", + "inherit", + "initial", + "normal", + "unset" + ] + }, "grid": { "isInherited": false, "subproperties": [ @@ -6290,7 +6307,7 @@ exports.CSS_PROPERTIES = { "grid-column-gap": { "isInherited": false, "subproperties": [ - "grid-column-gap" + "column-gap" ], "supports": [ 6, @@ -6300,6 +6317,7 @@ exports.CSS_PROPERTIES = { "calc", "inherit", "initial", + "normal", "unset" ] }, @@ -6320,8 +6338,8 @@ exports.CSS_PROPERTIES = { "grid-gap": { "isInherited": false, "subproperties": [ - "grid-row-gap", - "grid-column-gap" + "row-gap", + "column-gap" ], "supports": [ 6, @@ -6331,6 +6349,7 @@ exports.CSS_PROPERTIES = { "calc", "inherit", "initial", + "normal", "unset" ] }, @@ -6366,7 +6385,7 @@ exports.CSS_PROPERTIES = { "grid-row-gap": { "isInherited": false, "subproperties": [ - "grid-row-gap" + "row-gap" ], "supports": [ 6, @@ -6376,6 +6395,7 @@ exports.CSS_PROPERTIES = { "calc", "inherit", "initial", + "normal", "unset" ] }, @@ -8354,6 +8374,23 @@ exports.CSS_PROPERTIES = { "unset" ] }, + "row-gap": { + "isInherited": false, + "subproperties": [ + "row-gap" + ], + "supports": [ + 6, + 8 + ], + "values": [ + "calc", + "inherit", + "initial", + "normal", + "unset" + ] + }, "ruby-align": { "isInherited": true, "subproperties": [ From 850a64bef96a16aa3fe8514d82d9e77834116bdd Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 24 Apr 2018 01:52:51 +0200 Subject: [PATCH 08/44] Bug 1398482 part 4 - [css-grid][css-flexbox][css-multicol] Add 'row-gap' and 'gap' properties; make 'grid-[column|row]-gap' and 'grid-gap' alias the respective unprefixed properties (automated update of WPT results). --- .../gaps/gap-normal-computed-001.html.ini | 10 -- .../css-align/gaps/gap-parsing-001.html.ini | 79 --------- .../gaps/grid-column-gap-parsing-001.html.ini | 22 --- .../gaps/grid-gap-parsing-001.html.ini | 154 ------------------ .../gaps/grid-row-gap-parsing-001.html.ini | 100 ------------ .../gaps/row-gap-animation-001.html.ini | 4 - .../gaps/row-gap-animation-002.html.ini | 4 - .../gaps/row-gap-animation-003.html.ini | 4 - .../gaps/row-gap-parsing-001.html.ini | 52 ------ .../alignment/grid-gutters-001.html.ini | 2 - .../alignment/grid-gutters-003.html.ini | 2 - .../alignment/grid-gutters-005.html.ini | 2 - .../alignment/grid-gutters-007.html.ini | 2 - .../alignment/grid-gutters-009.html.ini | 2 - .../alignment/grid-gutters-011.html.ini | 2 - 15 files changed, 441 deletions(-) delete mode 100644 testing/web-platform/meta/css/css-align/gaps/gap-normal-computed-001.html.ini delete mode 100644 testing/web-platform/meta/css/css-align/gaps/gap-parsing-001.html.ini delete mode 100644 testing/web-platform/meta/css/css-align/gaps/grid-column-gap-parsing-001.html.ini delete mode 100644 testing/web-platform/meta/css/css-align/gaps/grid-gap-parsing-001.html.ini delete mode 100644 testing/web-platform/meta/css/css-align/gaps/grid-row-gap-parsing-001.html.ini delete mode 100644 testing/web-platform/meta/css/css-align/gaps/row-gap-animation-001.html.ini delete mode 100644 testing/web-platform/meta/css/css-align/gaps/row-gap-animation-002.html.ini delete mode 100644 testing/web-platform/meta/css/css-align/gaps/row-gap-animation-003.html.ini delete mode 100644 testing/web-platform/meta/css/css-align/gaps/row-gap-parsing-001.html.ini delete mode 100644 testing/web-platform/meta/css/css-grid/alignment/grid-gutters-001.html.ini delete mode 100644 testing/web-platform/meta/css/css-grid/alignment/grid-gutters-003.html.ini delete mode 100644 testing/web-platform/meta/css/css-grid/alignment/grid-gutters-005.html.ini delete mode 100644 testing/web-platform/meta/css/css-grid/alignment/grid-gutters-007.html.ini delete mode 100644 testing/web-platform/meta/css/css-grid/alignment/grid-gutters-009.html.ini delete mode 100644 testing/web-platform/meta/css/css-grid/alignment/grid-gutters-011.html.ini diff --git a/testing/web-platform/meta/css/css-align/gaps/gap-normal-computed-001.html.ini b/testing/web-platform/meta/css/css-align/gaps/gap-normal-computed-001.html.ini deleted file mode 100644 index 4e119392c019..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/gap-normal-computed-001.html.ini +++ /dev/null @@ -1,10 +0,0 @@ -[gap-normal-computed-001.html] - [row-gap:normal computes to normal on multicol elements] - expected: FAIL - - [row-gap:normal computes to normal on grid] - expected: FAIL - - [row-gap:normal (cross axis) computes to normal on flexbox] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-align/gaps/gap-parsing-001.html.ini b/testing/web-platform/meta/css/css-align/gaps/gap-parsing-001.html.ini deleted file mode 100644 index e15341bca23d..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/gap-parsing-001.html.ini +++ /dev/null @@ -1,79 +0,0 @@ -[gap-parsing-001.html] - [Default gap is 'normal'] - expected: FAIL - - [gap accepts pixels] - expected: FAIL - - [gap accepts pixels 2] - expected: FAIL - - [gap accepts pixels combined with percentage] - expected: FAIL - - [gap accepts em] - expected: FAIL - - [gap accepts em 2] - expected: FAIL - - [gap accepts vw] - expected: FAIL - - [gap accepts vw and vh] - expected: FAIL - - [gap accepts percentage] - expected: FAIL - - [gap accepts percentage 2] - expected: FAIL - - [gap accepts calc()] - expected: FAIL - - [gap accepts calc() 2] - expected: FAIL - - [Initial gap is 'normal'] - expected: FAIL - - [Initial gap is 'normal' 2] - expected: FAIL - - [Initial inherited gap is 'normal'] - expected: FAIL - - [gap is inheritable] - expected: FAIL - - [Negative gap is invalid] - expected: FAIL - - ['max-content' gap is invalid] - expected: FAIL - - ['none' gap is invalid] - expected: FAIL - - [Angle gap is invalid] - expected: FAIL - - [Resolution gap is invalid] - expected: FAIL - - [Time gap is invalid] - expected: FAIL - - [gap with three values is invalid] - expected: FAIL - - [gap with slash is invalid] - expected: FAIL - - [gap with one wrong value is invalid] - expected: FAIL - - [gap accepts calc() mixing fixed and percentage values] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-align/gaps/grid-column-gap-parsing-001.html.ini b/testing/web-platform/meta/css/css-align/gaps/grid-column-gap-parsing-001.html.ini deleted file mode 100644 index 458e92f74221..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/grid-column-gap-parsing-001.html.ini +++ /dev/null @@ -1,22 +0,0 @@ -[grid-column-gap-parsing-001.html] - [grid-column-gap accepts pixels] - expected: FAIL - - [grid-column-gap accepts em] - expected: FAIL - - [grid-column-gap accepts vw] - expected: FAIL - - [grid-column-gap accepts percentage] - expected: FAIL - - [grid-column-gap accepts calc()] - expected: FAIL - - [grid-column-gap accepts calc() mixing fixed and percentage values] - expected: FAIL - - [grid-column-gap is inheritable] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-align/gaps/grid-gap-parsing-001.html.ini b/testing/web-platform/meta/css/css-align/gaps/grid-gap-parsing-001.html.ini deleted file mode 100644 index 1247312b23a4..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/grid-gap-parsing-001.html.ini +++ /dev/null @@ -1,154 +0,0 @@ -[grid-gap-parsing-001.html] - [Default gap is 'normal'] - expected: FAIL - - [gap accepts pixels] - expected: FAIL - - [gap accepts pixels 2] - expected: FAIL - - [gap accepts pixels combined with percentage] - expected: FAIL - - [gap accepts em] - expected: FAIL - - [gap accepts em 2] - expected: FAIL - - [gap accepts vw] - expected: FAIL - - [gap accepts vw and vh] - expected: FAIL - - [gap accepts percentage] - expected: FAIL - - [gap accepts percentage 2] - expected: FAIL - - [gap accepts calc()] - expected: FAIL - - [gap accepts calc() 2] - expected: FAIL - - [Initial gap is 'normal'] - expected: FAIL - - [Initial gap is 'normal' 2] - expected: FAIL - - [Initial inherited gap is 'normal'] - expected: FAIL - - [gap is inheritable] - expected: FAIL - - [Negative gap is invalid] - expected: FAIL - - ['max-content' gap is invalid] - expected: FAIL - - ['none' gap is invalid] - expected: FAIL - - [Angle gap is invalid] - expected: FAIL - - [Resolution gap is invalid] - expected: FAIL - - [Time gap is invalid] - expected: FAIL - - [gap with three values is invalid] - expected: FAIL - - [gap with slash is invalid] - expected: FAIL - - [gap with one wrong value is invalid] - expected: FAIL - - [Default grid-gap is 'normal'] - expected: FAIL - - [grid-gap accepts pixels] - expected: FAIL - - [grid-gap accepts pixels 2] - expected: FAIL - - [grid-gap accepts pixels combined with percentage] - expected: FAIL - - [grid-gap accepts em] - expected: FAIL - - [grid-gap accepts em 2] - expected: FAIL - - [grid-gap accepts vw] - expected: FAIL - - [grid-gap accepts vw and vh] - expected: FAIL - - [grid-gap accepts percentage] - expected: FAIL - - [grid-gap accepts percentage 2] - expected: FAIL - - [grid-gap accepts calc()] - expected: FAIL - - [grid-gap accepts calc() mixing fixed and percentage values] - expected: FAIL - - [grid-gap accepts calc() 2] - expected: FAIL - - [Initial grid-gap is 'normal'] - expected: FAIL - - [Initial grid-gap is 'normal' 2] - expected: FAIL - - [Initial inherited grid-gap is 'normal'] - expected: FAIL - - [grid-gap is inheritable] - expected: FAIL - - [Negative grid-gap is invalid] - expected: FAIL - - ['max-content' grid-gap is invalid] - expected: FAIL - - ['none' grid-gap is invalid] - expected: FAIL - - [Angle grid-gap is invalid] - expected: FAIL - - [Resolution grid-gap is invalid] - expected: FAIL - - [Time grid-gap is invalid] - expected: FAIL - - [grid-gap with three values is invalid] - expected: FAIL - - [grid-gap with slash is invalid] - expected: FAIL - - [grid-gap with one wrong value is invalid] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-align/gaps/grid-row-gap-parsing-001.html.ini b/testing/web-platform/meta/css/css-align/gaps/grid-row-gap-parsing-001.html.ini deleted file mode 100644 index 7809319c7282..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/grid-row-gap-parsing-001.html.ini +++ /dev/null @@ -1,100 +0,0 @@ -[grid-row-gap-parsing-001.html] - [Default row-gap is 'normal'] - expected: FAIL - - [row-gap accepts pixels] - expected: FAIL - - [row-gap accepts em] - expected: FAIL - - [row-gap accepts percentage] - expected: FAIL - - [row-gap accepts calc()] - expected: FAIL - - [Initial row-gap is 'normal'] - expected: FAIL - - [Initial row-gap is 'normal' 2] - expected: FAIL - - [Initial inherited row-gap is 'normal'] - expected: FAIL - - [row-gap is inheritable] - expected: FAIL - - [Negative row-gap is invalid] - expected: FAIL - - ['max-content' row-gap is invalid] - expected: FAIL - - ['none' row-gap is invalid] - expected: FAIL - - [row-gap with multiple values is invalid] - expected: FAIL - - [Angle row-gap is invalid] - expected: FAIL - - [Resolution row-gap is invalid] - expected: FAIL - - [Time row-gap is invalid] - expected: FAIL - - [Default grid-row-gap is 'normal'] - expected: FAIL - - [grid-row-gap accepts pixels] - expected: FAIL - - [grid-row-gap accepts em] - expected: FAIL - - [grid-row-gap accepts percentage] - expected: FAIL - - [grid-row-gap accepts calc()] - expected: FAIL - - [grid-row-gap accepts calc() mixing fixed and percentage values] - expected: FAIL - - [Initial grid-row-gap is 'normal'] - expected: FAIL - - [Initial grid-row-gap is 'normal' 2] - expected: FAIL - - [Initial inherited grid-row-gap is 'normal'] - expected: FAIL - - [grid-row-gap is inheritable] - expected: FAIL - - [Negative grid-row-gap is invalid] - expected: FAIL - - ['max-content' grid-row-gap is invalid] - expected: FAIL - - ['none' grid-row-gap is invalid] - expected: FAIL - - [grid-row-gap with multiple values is invalid] - expected: FAIL - - [Angle grid-row-gap is invalid] - expected: FAIL - - [Resolution grid-row-gap is invalid] - expected: FAIL - - [Time grid-row-gap is invalid] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-001.html.ini b/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-001.html.ini deleted file mode 100644 index 8e43e8858284..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-001.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[row-gap-animation-001.html] - [row-gap is interpolable] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-002.html.ini b/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-002.html.ini deleted file mode 100644 index ba1bcddaf716..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-002.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[row-gap-animation-002.html] - [row-gap: normal is not interpolable] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-003.html.ini b/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-003.html.ini deleted file mode 100644 index 88a6bcf7e25f..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/row-gap-animation-003.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[row-gap-animation-003.html] - [Default row-gap is not interpolable] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-align/gaps/row-gap-parsing-001.html.ini b/testing/web-platform/meta/css/css-align/gaps/row-gap-parsing-001.html.ini deleted file mode 100644 index 8355628b790d..000000000000 --- a/testing/web-platform/meta/css/css-align/gaps/row-gap-parsing-001.html.ini +++ /dev/null @@ -1,52 +0,0 @@ -[row-gap-parsing-001.html] - [Default row-gap is 'normal'] - expected: FAIL - - [row-gap accepts pixels] - expected: FAIL - - [row-gap accepts em] - expected: FAIL - - [row-gap accepts percentage] - expected: FAIL - - [row-gap accepts calc()] - expected: FAIL - - [Initial row-gap is 'normal'] - expected: FAIL - - [Initial row-gap is 'normal' 2] - expected: FAIL - - [Initial inherited row-gap is 'normal'] - expected: FAIL - - [row-gap is inheritable] - expected: FAIL - - [Negative row-gap is invalid] - expected: FAIL - - ['max-content' row-gap is invalid] - expected: FAIL - - ['none' row-gap is invalid] - expected: FAIL - - [row-gap with multiple values is invalid] - expected: FAIL - - [Angle row-gap is invalid] - expected: FAIL - - [Resolution row-gap is invalid] - expected: FAIL - - [Time row-gap is invalid] - expected: FAIL - - [row-gap accepts calc() mixing fixed and percentage values] - expected: FAIL - diff --git a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-001.html.ini b/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-001.html.ini deleted file mode 100644 index 0b6903ccb957..000000000000 --- a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[grid-gutters-001.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-003.html.ini b/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-003.html.ini deleted file mode 100644 index 44bb08096a8e..000000000000 --- a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-003.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[grid-gutters-003.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-005.html.ini b/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-005.html.ini deleted file mode 100644 index c0ae0d075848..000000000000 --- a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-005.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[grid-gutters-005.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-007.html.ini b/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-007.html.ini deleted file mode 100644 index 33a4f329b0b8..000000000000 --- a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-007.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[grid-gutters-007.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-009.html.ini b/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-009.html.ini deleted file mode 100644 index 10d5a3a46408..000000000000 --- a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-009.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[grid-gutters-009.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-011.html.ini b/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-011.html.ini deleted file mode 100644 index 8e6bf1d943f9..000000000000 --- a/testing/web-platform/meta/css/css-grid/alignment/grid-gutters-011.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[grid-gutters-011.html] - expected: FAIL From 21d3390a471f89ad09c3ce42adce4f2f3d91b22c Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 24 Apr 2018 01:52:51 +0200 Subject: [PATCH 09/44] Bug 1398482 part 5 - Update devtools autocompletion expectations. r=me --- .../rules/test/browser_rules_gridline-names-autocomplete.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/devtools/client/inspector/rules/test/browser_rules_gridline-names-autocomplete.js b/devtools/client/inspector/rules/test/browser_rules_gridline-names-autocomplete.js index d29618b0f745..2c393d03a3f5 100644 --- a/devtools/client/inspector/rules/test/browser_rules_gridline-names-autocomplete.js +++ b/devtools/client/inspector/rules/test/browser_rules_gridline-names-autocomplete.js @@ -29,7 +29,8 @@ const changeTestData = [ // Creates a new CSS property value. // Checks that grid-area autocompletes column and row names. const newAreaTestData = [ - ["g", {}, "grid", OPEN, SELECTED, !CHANGE], + ["g", {}, "gap", OPEN, SELECTED, !CHANGE], + ["VK_DOWN", {}, "grid", OPEN, SELECTED, !CHANGE], ["VK_DOWN", {}, "grid-area", OPEN, SELECTED, !CHANGE], ["VK_TAB", {}, "", !OPEN, !SELECTED, !CHANGE], "grid-line-names-updated", @@ -46,7 +47,7 @@ const newAreaTestData = [ // Creates a new CSS property value. // Checks that grid-row only autocompletes row names. const newRowTestData = [ - ["g", {}, "grid", OPEN, SELECTED, !CHANGE], + ["g", {}, "gap", OPEN, SELECTED, !CHANGE], ["r", {}, "grid", OPEN, SELECTED, !CHANGE], ["i", {}, "grid", OPEN, SELECTED, !CHANGE], ["d", {}, "grid", OPEN, SELECTED, !CHANGE], From 9acb0f48fc557716926e8af130b1e889f3c42730 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 24 Apr 2018 01:52:51 +0200 Subject: [PATCH 10/44] Bug 1398482 part 6 - Update devtools with the renamed longhands. r=me --- devtools/server/actors/animation-type-longhand.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/devtools/server/actors/animation-type-longhand.js b/devtools/server/actors/animation-type-longhand.js index 31a44a32fac9..18ce68c5857f 100644 --- a/devtools/server/actors/animation-type-longhand.js +++ b/devtools/server/actors/animation-type-longhand.js @@ -283,8 +283,6 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [ "column-gap", "column-width", "flex-basis", - "grid-column-gap", - "grid-row-gap", "height", "left", "letter-spacing", @@ -307,6 +305,7 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [ "padding-top", "perspective", "right", + "row-gap", "stroke-dashoffset", "stroke-width", "-moz-tab-size", From 9203ed3c583b832597db43cb2a85815e557e1ddc Mon Sep 17 00:00:00 2001 From: Tom Prince Date: Mon, 23 Apr 2018 17:39:35 -0600 Subject: [PATCH 11/44] Bug 1456234: [mozharness] Process EXTRA_MOZHARNESS_CONFIG even if no files are specified; r=aki This will allow mozharness configs to be specified exclusively in the taskgraph. Differential Revision: https://phabricator.services.mozilla.com/D1017 --HG-- extra : rebase_source : a3a9b6cc9d1004c4bd396fccc3e4354a7316651d extra : source : 10acd193df92b7c495789dc24157b85f116ade5e --- taskcluster/scripts/builder/build-linux.sh | 2 +- testing/mozharness/mozharness/base/config.py | 62 ++++++++++---------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/taskcluster/scripts/builder/build-linux.sh b/taskcluster/scripts/builder/build-linux.sh index e506b3636b74..64224dfcd33a 100755 --- a/taskcluster/scripts/builder/build-linux.sh +++ b/taskcluster/scripts/builder/build-linux.sh @@ -53,7 +53,7 @@ fi # test required parameters are supplied if [[ -z ${MOZHARNESS_SCRIPT} ]]; then fail "MOZHARNESS_SCRIPT is not set"; fi -if [[ -z ${MOZHARNESS_CONFIG} ]]; then fail "MOZHARNESS_CONFIG is not set"; fi +if [[ -z "${MOZHARNESS_CONFIG}" && -z "${EXTRA_MOZHARNESS_CONFIG}" ]]; then fail "MOZHARNESS_CONFIG or EXTRA_MOZHARNESS_CONFIG is not set"; fi # run XVfb in the background, if necessary if $NEED_XVFB; then diff --git a/testing/mozharness/mozharness/base/config.py b/testing/mozharness/mozharness/base/config.py index f2d50737affe..03768ae98b5d 100644 --- a/testing/mozharness/mozharness/base/config.py +++ b/testing/mozharness/mozharness/base/config.py @@ -285,8 +285,9 @@ class BaseConfig(object): type="string", help="Specify additional paths to search for config files.", ) self.config_parser.add_option( - "-c", "--config-file", "--cfg", action="extend", dest="config_files", - type="string", help="Specify a config file; can be repeated" + "-c", "--config-file", "--cfg", action="extend", + dest="config_files", default=[], type="string", + help="Specify a config file; can be repeated", ) self.config_parser.add_option( "-C", "--opt-config-file", "--opt-cfg", action="extend", @@ -489,35 +490,36 @@ class BaseConfig(object): self.list_actions() print("Required config file not set! (use --config-file option)") raise SystemExit(-1) + + # this is what get_cfgs_from_files returns. It will represent each + # config file name and its assoctiated dict + # eg ('builds/branch_specifics.py', {'foo': 'bar'}) + # let's store this to self for things like --interpret-config-files + self.all_cfg_files_and_dicts.extend(self.get_cfgs_from_files( + # append opt_config to allow them to overwrite previous configs + options.config_files + options.opt_config_files, options=options + )) + config = {} + if (self.append_env_variables_from_configs + or options.append_env_variables_from_configs): + # We only append values from various configs for the 'env' entry + # For everything else we follow the standard behaviour + for i, (c_file, c_dict) in enumerate(self.all_cfg_files_and_dicts): + for v in c_dict.keys(): + if v == 'env' and v in config: + config[v].update(c_dict[v]) + else: + config[v] = c_dict[v] else: - # this is what get_cfgs_from_files returns. It will represent each - # config file name and its assoctiated dict - # eg ('builds/branch_specifics.py', {'foo': 'bar'}) - # let's store this to self for things like --interpret-config-files - self.all_cfg_files_and_dicts.extend(self.get_cfgs_from_files( - # append opt_config to allow them to overwrite previous configs - options.config_files + options.opt_config_files, options=options - )) - config = {} - if (self.append_env_variables_from_configs - or options.append_env_variables_from_configs): - # We only append values from various configs for the 'env' entry - # For everything else we follow the standard behaviour - for i, (c_file, c_dict) in enumerate(self.all_cfg_files_and_dicts): - for v in c_dict.keys(): - if v == 'env' and v in config: - config[v].update(c_dict[v]) - else: - config[v] = c_dict[v] - else: - for i, (c_file, c_dict) in enumerate(self.all_cfg_files_and_dicts): - config.update(c_dict) - # assign or update self._config depending on if it exists or not - # NOTE self._config will be passed to ReadOnlyConfig's init -- a - # dict subclass with immutable locking capabilities -- and serve - # as the keys/values that make up that instance. Ultimately, - # this becomes self.config during BaseScript's init - self.set_config(config) + for i, (c_file, c_dict) in enumerate(self.all_cfg_files_and_dicts): + config.update(c_dict) + # assign or update self._config depending on if it exists or not + # NOTE self._config will be passed to ReadOnlyConfig's init -- a + # dict subclass with immutable locking capabilities -- and serve + # as the keys/values that make up that instance. Ultimately, + # this becomes self.config during BaseScript's init + self.set_config(config) + for key in defaults.keys(): value = getattr(options, key) if value is None: From b929e4e30de5299ed947600424f313980b44579f Mon Sep 17 00:00:00 2001 From: Tom Prince Date: Mon, 23 Apr 2018 12:43:53 -0600 Subject: [PATCH 12/44] Bug 1456234: [release] Embed generate-checksums mozharness config in taskcluster config; r=aki Differential Revision: https://phabricator.services.mozilla.com/D1018 --HG-- extra : rebase_source : daa6987654bbd16858dd17dfa4b85a78bd9f77e6 extra : source : 934cccf5ed74d5024f183bedbc40ad12bdc3ae00 --- .../ci/release-generate-checksums/kind.yml | 41 ++++++++----------- .../transforms/release_generate_checksums.py | 3 +- .../configs/releases/checksums_devedition.py | 5 --- .../configs/releases/checksums_fennec.py | 5 --- .../configs/releases/checksums_firefox.py | 5 --- .../releases/dev_checksums_devedition.py | 5 --- .../configs/releases/dev_checksums_fennec.py | 5 --- .../configs/releases/dev_checksums_firefox.py | 5 --- 8 files changed, 20 insertions(+), 54 deletions(-) delete mode 100644 testing/mozharness/configs/releases/checksums_devedition.py delete mode 100644 testing/mozharness/configs/releases/checksums_fennec.py delete mode 100644 testing/mozharness/configs/releases/checksums_firefox.py delete mode 100644 testing/mozharness/configs/releases/dev_checksums_devedition.py delete mode 100644 testing/mozharness/configs/releases/dev_checksums_fennec.py delete mode 100644 testing/mozharness/configs/releases/dev_checksums_firefox.py diff --git a/taskcluster/ci/release-generate-checksums/kind.yml b/taskcluster/ci/release-generate-checksums/kind.yml index 5a402af9e7d8..fde2575885eb 100644 --- a/taskcluster/ci/release-generate-checksums/kind.yml +++ b/taskcluster/ci/release-generate-checksums/kind.yml @@ -40,6 +40,7 @@ job-defaults: type: file run: using: mozharness + config: [] # See extra-config below actions: [create-virtualenv collect-individual-checksums create-big-checksums create-summary] options: - "version={version}" @@ -57,16 +58,14 @@ jobs: build_platform: linux64 build_type: opt run: - config: + extra-config: by-project: - mozilla-release: - - releases/checksums_firefox.py - mozilla-beta: - - releases/checksums_firefox.py - maple: - - releases/dev_checksums_firefox.py + mozilla-(release|beta): + stage_product: "firefox" + bucket_name: "net-mozaws-prod-delivery-firefox" default: - - releases/dev_checksums_firefox.py + stage_product: "firefox" + bucket_name: "net-mozaws-stage-delivery-firefox" treeherder: platform: linux64/opt @@ -76,16 +75,14 @@ jobs: build_platform: android-nightly build_type: opt run: - config: + extra-config: by-project: - mozilla-release: - - releases/checksums_fennec.py - mozilla-beta: - - releases/checksums_fennec.py - maple: - - releases/dev_checksums_fennec.py + mozilla-(release|beta): + stage_product: "mobile" + bucket_name: "net-mozaws-prod-delivery-archive" default: - - releases/dev_checksums_fennec.py + stage_product: "mobile" + bucket_name: "net-mozaws-stage-delivery-archive" treeherder: platform: Android/opt @@ -95,15 +92,13 @@ jobs: build_platform: linux64-devedition build_type: opt run: - config: + extra-config: by-project: - mozilla-release: - - releases/checksums_devedition.py mozilla-beta: - - releases/checksums_devedition.py - maple: - - releases/dev_checksums_devedition.py + stage_product: "devedition" + bucket_name: "net-mozaws-prod-delivery-archive" default: - - releases/dev_checksums_devedition.py + stage_product: "devedition" + bucket_name: "net-mozaws-stage-delivery-archive" treeherder: platform: linux64-devedition/opt diff --git a/taskcluster/taskgraph/transforms/release_generate_checksums.py b/taskcluster/taskgraph/transforms/release_generate_checksums.py index 6349a7d3f115..be66e0f5ae7c 100644 --- a/taskcluster/taskgraph/transforms/release_generate_checksums.py +++ b/taskcluster/taskgraph/transforms/release_generate_checksums.py @@ -25,7 +25,8 @@ transforms = TransformSequence() def handle_keyed_by(config, jobs): """Resolve fields that can be keyed by project, etc.""" fields = [ - "run.config", + "run.config", + "run.extra-config", ] for job in jobs: job = copy.deepcopy(job) diff --git a/testing/mozharness/configs/releases/checksums_devedition.py b/testing/mozharness/configs/releases/checksums_devedition.py deleted file mode 100644 index 3d059e90aebb..000000000000 --- a/testing/mozharness/configs/releases/checksums_devedition.py +++ /dev/null @@ -1,5 +0,0 @@ -# lint_ignore=E501 -config = { - "stage_product": "devedition", - "bucket_name": "net-mozaws-prod-delivery-archive", -} diff --git a/testing/mozharness/configs/releases/checksums_fennec.py b/testing/mozharness/configs/releases/checksums_fennec.py deleted file mode 100644 index 84745ee12e12..000000000000 --- a/testing/mozharness/configs/releases/checksums_fennec.py +++ /dev/null @@ -1,5 +0,0 @@ -# lint_ignore=E501 -config = { - "stage_product": "mobile", - "bucket_name": "net-mozaws-prod-delivery-archive", -} diff --git a/testing/mozharness/configs/releases/checksums_firefox.py b/testing/mozharness/configs/releases/checksums_firefox.py deleted file mode 100644 index d471fb8c1ccd..000000000000 --- a/testing/mozharness/configs/releases/checksums_firefox.py +++ /dev/null @@ -1,5 +0,0 @@ -# lint_ignore=E501 -config = { - "stage_product": "firefox", - "bucket_name": "net-mozaws-prod-delivery-firefox", -} diff --git a/testing/mozharness/configs/releases/dev_checksums_devedition.py b/testing/mozharness/configs/releases/dev_checksums_devedition.py deleted file mode 100644 index 13f032285a78..000000000000 --- a/testing/mozharness/configs/releases/dev_checksums_devedition.py +++ /dev/null @@ -1,5 +0,0 @@ -# lint_ignore=E501 -config = { - "stage_product": "devedition", - "bucket_name": "net-mozaws-stage-delivery-archive", -} diff --git a/testing/mozharness/configs/releases/dev_checksums_fennec.py b/testing/mozharness/configs/releases/dev_checksums_fennec.py deleted file mode 100644 index bebbfe45a1db..000000000000 --- a/testing/mozharness/configs/releases/dev_checksums_fennec.py +++ /dev/null @@ -1,5 +0,0 @@ -# lint_ignore=E501 -config = { - "stage_product": "mobile", - "bucket_name": "net-mozaws-stage-delivery-archive", -} diff --git a/testing/mozharness/configs/releases/dev_checksums_firefox.py b/testing/mozharness/configs/releases/dev_checksums_firefox.py deleted file mode 100644 index 4b34e9a5eedf..000000000000 --- a/testing/mozharness/configs/releases/dev_checksums_firefox.py +++ /dev/null @@ -1,5 +0,0 @@ -# lint_ignore=E501 -config = { - "stage_product": "firefox", - "bucket_name": "net-mozaws-stage-delivery-firefox", -} From 6f962094cc4176af01ad1b383a2799d142090a2c Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Fri, 20 Apr 2018 16:31:16 -0700 Subject: [PATCH 13/44] Bug 1455599 - Redo bug 1446693, creating separate AutoTraceSessions instead of expanding one, r=jonco --HG-- extra : rebase_source : 9900a8500ddf958523cf397744fad4b1cba6cec6 --- js/src/gc/Nursery.cpp | 16 ++++++++-------- js/src/gc/Nursery.h | 4 +--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 65c8861af15c..35baeff0dedc 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -725,10 +725,8 @@ js::Nursery::collect(JS::gcreason::Reason reason) TenureCountCache tenureCounts; previousGC.reason = JS::gcreason::NO_REASON; - mozilla::Maybe session; if (!isEmpty()) { - session.emplace(rt, JS::HeapState::MinorCollecting); - doCollection(reason, session.ref(), tenureCounts); + doCollection(reason, tenureCounts); } else { previousGC.nurseryUsedBytes = 0; previousGC.nurseryCapacity = spaceToEnd(maxChunkCount()); @@ -763,9 +761,12 @@ js::Nursery::collect(JS::gcreason::Reason reason) } } } + + mozilla::Maybe session; for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) { if (shouldPretenure && zone->allocNurseryStrings && zone->tenuredStrings >= 30 * 1000) { - MOZ_ASSERT(session.isSome(), "discarding JIT code must be in an AutoTraceSession"); + if (!session.isSome()) + session.emplace(rt, JS::HeapState::MinorCollecting); CancelOffThreadIonCompile(zone); bool preserving = zone->isPreservingCode(); zone->setPreservingCode(false); @@ -781,6 +782,7 @@ js::Nursery::collect(JS::gcreason::Reason reason) } zone->tenuredStrings = 0; } + session.reset(); // End the minor GC session, if running one. endProfile(ProfileKey::Pretenure); // We ignore gcMaxBytes when allocating for minor collection. However, if we @@ -795,7 +797,6 @@ js::Nursery::collect(JS::gcreason::Reason reason) disable(); endProfile(ProfileKey::Total); - session.reset(); // End the minor GC session, if running one. rt->gc.incMinorGcNumber(); TimeDuration totalTime = profileDurations_[ProfileKey::Total]; @@ -831,11 +832,10 @@ js::Nursery::collect(JS::gcreason::Reason reason) } void -js::Nursery::doCollection(JS::gcreason::Reason reason, - AutoTraceSession& session, - TenureCountCache& tenureCounts) +js::Nursery::doCollection(JS::gcreason::Reason reason, TenureCountCache& tenureCounts) { JSRuntime* rt = runtime(); + AutoTraceSession session(rt, JS::HeapState::MinorCollecting); AutoSetThreadIsPerformingGC performingGC; AutoStopVerifyingBarriers av(rt, false); AutoDisableProxyCheck disableStrictProxyChecking; diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index a37c920ae1cc..36b97995af35 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -517,9 +517,7 @@ class Nursery /* Common internal allocator function. */ void* allocate(size_t size); - void doCollection(JS::gcreason::Reason reason, - gc::AutoTraceSession& sesssion, - gc::TenureCountCache& tenureCounts); + void doCollection(JS::gcreason::Reason reason, gc::TenureCountCache& tenureCounts); /* * Move the object at |src| in the Nursery to an already-allocated cell From 34d8c86389eeb1039dfab8aada4b1ca75751dd1f Mon Sep 17 00:00:00 2001 From: Robert Longson Date: Tue, 24 Apr 2018 01:42:41 +0100 Subject: [PATCH 14/44] Bug 1455986 - If we cannot parse an enum, set its numeric value to UNKNOWN i.e. 0 r=heycam --- dom/svg/nsSVGElement.cpp | 42 +++++++++++++------ dom/svg/nsSVGElement.h | 1 + layout/reftests/svg/filters/feBlend-1-ref.svg | 1 - 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/dom/svg/nsSVGElement.cpp b/dom/svg/nsSVGElement.cpp index 22d9f1f7a6c0..b309a76a2b6b 100644 --- a/dom/svg/nsSVGElement.cpp +++ b/dom/svg/nsSVGElement.cpp @@ -535,7 +535,7 @@ nsSVGElement::ParseAttribute(int32_t aNamespaceID, RefPtr valAtom = NS_Atomize(aValue); rv = enumInfo.mEnums[i].SetBaseValueAtom(valAtom, this); if (NS_FAILED(rv)) { - enumInfo.Reset(i); + enumInfo.SetUnknownValue(i); } else { aResult.SetTo(valAtom); didSetResult = true; @@ -1508,7 +1508,8 @@ nsSVGElement::GetLengthInfo() return LengthAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::LengthAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::LengthAttributesInfo::Reset(uint8_t aAttrEnum) { mLengths[aAttrEnum].Init(mLengthInfo[aAttrEnum].mCtxType, aAttrEnum, @@ -1858,7 +1859,8 @@ nsSVGElement::GetNumberInfo() return NumberAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::NumberAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::NumberAttributesInfo::Reset(uint8_t aAttrEnum) { mNumbers[aAttrEnum].Init(aAttrEnum, mNumberInfo[aAttrEnum].mDefaultValue); @@ -1920,7 +1922,8 @@ nsSVGElement::GetNumberPairInfo() return NumberPairAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::NumberPairAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::NumberPairAttributesInfo::Reset(uint8_t aAttrEnum) { mNumberPairs[aAttrEnum].Init(aAttrEnum, mNumberPairInfo[aAttrEnum].mDefaultValue1, @@ -1969,7 +1972,8 @@ nsSVGElement::GetIntegerInfo() return IntegerAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::IntegerAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::IntegerAttributesInfo::Reset(uint8_t aAttrEnum) { mIntegers[aAttrEnum].Init(aAttrEnum, mIntegerInfo[aAttrEnum].mDefaultValue); @@ -2031,7 +2035,8 @@ nsSVGElement::GetIntegerPairInfo() return IntegerPairAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::IntegerPairAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::IntegerPairAttributesInfo::Reset(uint8_t aAttrEnum) { mIntegerPairs[aAttrEnum].Init(aAttrEnum, mIntegerPairInfo[aAttrEnum].mDefaultValue1, @@ -2081,7 +2086,8 @@ nsSVGElement::GetAngleInfo() return AngleAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::AngleAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::AngleAttributesInfo::Reset(uint8_t aAttrEnum) { mAngles[aAttrEnum].Init(aAttrEnum, mAngleInfo[aAttrEnum].mDefaultValue, @@ -2129,7 +2135,8 @@ nsSVGElement::GetBooleanInfo() return BooleanAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::BooleanAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::BooleanAttributesInfo::Reset(uint8_t aAttrEnum) { mBooleans[aAttrEnum].Init(aAttrEnum, mBooleanInfo[aAttrEnum].mDefaultValue); @@ -2168,12 +2175,20 @@ nsSVGElement::GetEnumInfo() return EnumAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::EnumAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::EnumAttributesInfo::Reset(uint8_t aAttrEnum) { mEnums[aAttrEnum].Init(aAttrEnum, mEnumInfo[aAttrEnum].mDefaultValue); } +void +nsSVGElement::EnumAttributesInfo::SetUnknownValue(uint8_t aAttrEnum) +{ + // Fortunately in SVG every enum's unknown value is 0 + mEnums[aAttrEnum].Init(aAttrEnum, 0); +} + void nsSVGElement::DidChangeEnum(uint8_t aAttrEnum) { @@ -2332,12 +2347,14 @@ nsSVGElement::GetStringInfo() return StringAttributesInfo(nullptr, nullptr, 0); } -void nsSVGElement::StringAttributesInfo::Reset(uint8_t aAttrEnum) +void +nsSVGElement::StringAttributesInfo::Reset(uint8_t aAttrEnum) { mStrings[aAttrEnum].Init(aAttrEnum); } -void nsSVGElement::GetStringBaseValue(uint8_t aAttrEnum, nsAString& aResult) const +void +nsSVGElement::GetStringBaseValue(uint8_t aAttrEnum, nsAString& aResult) const { nsSVGElement::StringAttributesInfo info = const_cast(this)->GetStringInfo(); @@ -2350,7 +2367,8 @@ void nsSVGElement::GetStringBaseValue(uint8_t aAttrEnum, nsAString& aResult) con *info.mStringInfo[aAttrEnum].mName, aResult); } -void nsSVGElement::SetStringBaseValue(uint8_t aAttrEnum, const nsAString& aValue) +void +nsSVGElement::SetStringBaseValue(uint8_t aAttrEnum, const nsAString& aValue) { nsSVGElement::StringAttributesInfo info = GetStringInfo(); diff --git a/dom/svg/nsSVGElement.h b/dom/svg/nsSVGElement.h index afdd44d17640..a9b3f3ca583e 100644 --- a/dom/svg/nsSVGElement.h +++ b/dom/svg/nsSVGElement.h @@ -516,6 +516,7 @@ protected: {} void Reset(uint8_t aAttrEnum); + void SetUnknownValue(uint8_t aAttrEnum); }; struct NumberListInfo { diff --git a/layout/reftests/svg/filters/feBlend-1-ref.svg b/layout/reftests/svg/filters/feBlend-1-ref.svg index 24a6fdfd6a3a..f2677a8dee9b 100644 --- a/layout/reftests/svg/filters/feBlend-1-ref.svg +++ b/layout/reftests/svg/filters/feBlend-1-ref.svg @@ -16,5 +16,4 @@ - From a1a65038fe23d225744d0980d43e51047a4944dc Mon Sep 17 00:00:00 2001 From: Ted Campbell Date: Wed, 11 Apr 2018 15:49:36 -0400 Subject: [PATCH 15/44] Bug 1448563 - Part 6: Add memory reporting for off-thread WASM. r=luke MozReview-Commit-ID: 1lXRj1JUJk2 --HG-- extra : source : 97dbd15798f96a95021bc0a66c9b1dfc22414025 --- js/public/MemoryMetrics.h | 3 ++- js/src/vm/HelperThreads.cpp | 7 +++++++ js/src/wasm/WasmGenerator.cpp | 25 +++++++++++++++++++++++++ js/src/wasm/WasmGenerator.h | 10 ++++++++++ js/xpconnect/src/XPCJSRuntime.cpp | 4 ++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index ec8066797b19..a96d697a1ccf 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -509,7 +509,8 @@ struct HelperThreadStats #define FOR_EACH_SIZE(macro) \ macro(_, MallocHeap, stateData) \ macro(_, MallocHeap, parseTask) \ - macro(_, MallocHeap, ionBuilder) + macro(_, MallocHeap, ionBuilder) \ + macro(_, MallocHeap, wasmCompile) explicit HelperThreadStats() : FOR_EACH_SIZE(ZERO_SIZE) diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index b106c8402e5f..75a1b58f227c 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -23,6 +23,7 @@ #include "vm/Time.h" #include "vm/TraceLogging.h" #include "vm/Xdr.h" +#include "wasm/WasmGenerator.h" #include "gc/PrivateIterators-inl.h" #include "vm/JSCompartment-inl.h" @@ -1176,6 +1177,12 @@ GlobalHelperThreadState::addSizeOfIncludingThis(JS::GlobalStats* stats, for (auto builder : ionFreeList_) htStats.ionBuilder += builder->sizeOfIncludingThis(mallocSizeOf); + // Report wasm::CompileTasks on wait lists + for (auto task : wasmWorklist_tier1_) + htStats.wasmCompile += task->sizeOfIncludingThis(mallocSizeOf); + for (auto task : wasmWorklist_tier2_) + htStats.wasmCompile += task->sizeOfIncludingThis(mallocSizeOf); + // Report number of helper threads. MOZ_ASSERT(htStats.idleThreadCount == 0); if (threads) { diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp index 185b76483c44..536490b86936 100644 --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -1025,3 +1025,28 @@ ModuleGenerator::finishTier2(Module& module) return module.finishTier2(Move(linkDataTier_), Move(tier2), env_); } + +size_t +CompiledCode::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + size_t trapSitesSize = 0; + for (const TrapSiteVector& vec : trapSites) + trapSitesSize += vec.sizeOfExcludingThis(mallocSizeOf); + + return bytes.sizeOfExcludingThis(mallocSizeOf) + + codeRanges.sizeOfExcludingThis(mallocSizeOf) + + callSites.sizeOfExcludingThis(mallocSizeOf) + + callSiteTargets.sizeOfExcludingThis(mallocSizeOf) + + trapSitesSize + + callFarJumps.sizeOfExcludingThis(mallocSizeOf) + + symbolicAccesses.sizeOfExcludingThis(mallocSizeOf) + + codeLabels.sizeOfExcludingThis(mallocSizeOf); +} + +size_t +CompileTask::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + return lifo.sizeOfExcludingThis(mallocSizeOf) + + inputs.sizeOfExcludingThis(mallocSizeOf) + + output.sizeOfExcludingThis(mallocSizeOf); +} diff --git a/js/src/wasm/WasmGenerator.h b/js/src/wasm/WasmGenerator.h index 78d5783d5c09..c55bb8c0d3ce 100644 --- a/js/src/wasm/WasmGenerator.h +++ b/js/src/wasm/WasmGenerator.h @@ -19,6 +19,8 @@ #ifndef wasm_generator_h #define wasm_generator_h +#include "mozilla/MemoryReporting.h" + #include "jit/MacroAssembler.h" #include "wasm/WasmCompile.h" #include "wasm/WasmModule.h" @@ -93,6 +95,8 @@ struct CompiledCode symbolicAccesses.empty() && codeLabels.empty(); } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; }; // The CompileTaskState of a ModuleGenerator contains the mutable state shared @@ -128,6 +132,12 @@ struct CompileTask state(state), lifo(defaultChunkSize) {} + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const + { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } }; // A ModuleGenerator encapsulates the creation of a wasm module. During the diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 8b62c6af87e6..69ee0ec485f1 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2499,6 +2499,10 @@ JSReporter::CollectReports(WindowPaths* windowPaths, REPORT_BYTES(NS_LITERAL_CSTRING("explicit/js-non-window/helper-thread/ion-builder"), KIND_HEAP, gStats.helperThread.ionBuilder, "The memory used by IonBuilders waiting in HelperThreadState."); + + REPORT_BYTES(NS_LITERAL_CSTRING("explicit/js-non-window/helper-thread/wasm-compile"), + KIND_HEAP, gStats.helperThread.parseTask, + "The memory used by Wasm compilations waiting in HelperThreadState."); } static nsresult From 02245c0bd2d7641784e60dffd70e2a86b331196e Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Mon, 23 Apr 2018 17:35:20 -0700 Subject: [PATCH 16/44] Bug 1400153 - Stop using PodZero to initialize TimeStamps, r=nfroyd --HG-- extra : rebase_source : 9d14b89f7baaf87c813de2810956bd0ba6db0a71 --- js/src/gc/Statistics.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 304ed92af9fb..3aeffcfbed73 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -1098,9 +1098,11 @@ Statistics::endSlice() // Clear the timers at the end of a GC, preserving the data for PhaseKind::MUTATOR. auto mutatorStartTime = phaseStartTimes[Phase::MUTATOR]; auto mutatorTime = phaseTimes[Phase::MUTATOR]; - PodZero(&phaseStartTimes); + for (mozilla::TimeStamp& t : phaseStartTimes) + t = TimeStamp(); #ifdef DEBUG - PodZero(&phaseEndTimes); + for (mozilla::TimeStamp& t : phaseEndTimes) + t = TimeStamp(); #endif PodZero(&phaseTimes); phaseStartTimes[Phase::MUTATOR] = mutatorStartTime; From d8e6c45ef20346dd7f3a342f38973fc855f2bb8b Mon Sep 17 00:00:00 2001 From: Lee Salzman Date: Mon, 23 Apr 2018 21:46:55 -0400 Subject: [PATCH 17/44] Bug 1448703 - clear blob image resources from clear_namespace hook. r=jrmuizel MozReview-Commit-ID: 8DinL2bE64O --- gfx/webrender_bindings/Moz2DImageRenderer.cpp | 2 +- gfx/webrender_bindings/WebRenderAPI.cpp | 17 ----------------- gfx/webrender_bindings/src/bindings.rs | 2 +- gfx/webrender_bindings/src/moz2d_renderer.rs | 6 ++++-- 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/gfx/webrender_bindings/Moz2DImageRenderer.cpp b/gfx/webrender_bindings/Moz2DImageRenderer.cpp index 0e2fb592b141..34019636ad60 100644 --- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp +++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp @@ -94,6 +94,7 @@ static struct FontDeleteLog { } } sFontDeleteLog; +extern "C" { void ClearBlobImageResources(WrIdNamespace aNamespace) { StaticMutexAutoLock lock(sFontDataTableLock); @@ -110,7 +111,6 @@ ClearBlobImageResources(WrIdNamespace aNamespace) { } } -extern "C" { void AddFontData(WrFontKey aKey, const uint8_t *aData, size_t aSize, uint32_t aIndex, const ArcVecU8 *aVec) { StaticMutexAutoLock lock(sFontDataTableLock); diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp index 18905a1d6916..0ae9af83dd59 100644 --- a/gfx/webrender_bindings/WebRenderAPI.cpp +++ b/gfx/webrender_bindings/WebRenderAPI.cpp @@ -357,8 +357,6 @@ WebRenderAPI::GetNamespace() { return wr_api_get_namespace(mDocHandle); } -extern void ClearBlobImageResources(WrIdNamespace aNamespace); - WebRenderAPI::~WebRenderAPI() { if (!mRootDocumentApi) { @@ -376,21 +374,6 @@ WebRenderAPI::~WebRenderAPI() wr_api_shut_down(mDocHandle); } - // wr_api_get_namespace cannot be marked destructor-safe because it has a - // return value, and we can't call it if MOZ_BUILD_WEBRENDER is not defined - // because it's not destructor-safe. So let's just ifdef around it. This is - // basically a hack to get around compile-time warnings, this code never runs - // unless MOZ_BUILD_WEBRENDER is defined anyway. -#ifdef MOZ_BUILD_WEBRENDER - wr::WrIdNamespace ns = GetNamespace(); -#else - wr::WrIdNamespace ns{0}; -#endif - - // Clean up any resources the blob image renderer is holding onto that - // can no longer be used once this WR API instance goes away. - ClearBlobImageResources(ns); - wr_api_delete(mDocHandle); } diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs index 2d9c0d16cc62..811f26c23788 100644 --- a/gfx/webrender_bindings/src/bindings.rs +++ b/gfx/webrender_bindings/src/bindings.rs @@ -63,7 +63,7 @@ type WrEpoch = Epoch; /// cbindgen:derive-lt=true /// cbindgen:derive-lte=true /// cbindgen:derive-neq=true -type WrIdNamespace = IdNamespace; +pub type WrIdNamespace = IdNamespace; /// cbindgen:field-names=[mNamespace, mHandle] type WrPipelineId = PipelineId; diff --git a/gfx/webrender_bindings/src/moz2d_renderer.rs b/gfx/webrender_bindings/src/moz2d_renderer.rs index cce4f1f01dba..468fa3337cd3 100644 --- a/gfx/webrender_bindings/src/moz2d_renderer.rs +++ b/gfx/webrender_bindings/src/moz2d_renderer.rs @@ -494,17 +494,19 @@ impl BlobImageRenderer for Moz2dImageRenderer { fn delete_font_instance(&mut self, _key: FontInstanceKey) { } - fn clear_namespace(&mut self, _namespace: IdNamespace) { + fn clear_namespace(&mut self, namespace: IdNamespace) { + unsafe { ClearBlobImageResources(namespace); } } } -use bindings::WrFontKey; +use bindings::{WrFontKey, WrIdNamespace}; #[allow(improper_ctypes)] // this is needed so that rustc doesn't complain about passing the &Arc to an extern function extern "C" { fn AddFontData(key: WrFontKey, data: *const u8, size: usize, index: u32, vec: &ArcVecU8); fn AddNativeFontHandle(key: WrFontKey, handle: *mut c_void, index: u32); fn DeleteFontData(key: WrFontKey); + fn ClearBlobImageResources(namespace: WrIdNamespace); } impl Moz2dImageRenderer { From ba14455b5ffebdf33c55afbced97c144969ae1d5 Mon Sep 17 00:00:00 2001 From: Paul Bone Date: Mon, 23 Apr 2018 12:40:09 +1000 Subject: [PATCH 18/44] Bug 1443396 - Use colours that contrast well on about:memory r=jaws,snorp The about:memory page specified some colours using constants and others using variables whose values can change. If those variables changed, for example due to the desktop toolkit using a dark theme, then it could create poor contrast with the constants in these CSS files (desktop & mobile). This change ensures that only the Mozilla system colour extensions and Mozilla colour preference extensions colours are used. Creating fore/back-ground combinations that hopefully contrast well regardless of the desktop theme. --HG-- extra : rebase_source : f916aac0bb07c3a75ab45ece3fd58d1cdcf8bf46 extra : source : a78bc7ff3de5a0ea4aea060731253fed63a59d00 --- mobile/android/themes/core/aboutMemory.css | 39 ++++++++++--------- .../aboutmemory/content/aboutMemory.css | 39 ++++++++++--------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/mobile/android/themes/core/aboutMemory.css b/mobile/android/themes/core/aboutMemory.css index a4f0887da59d..d568903cb286 100644 --- a/mobile/android/themes/core/aboutMemory.css +++ b/mobile/android/themes/core/aboutMemory.css @@ -10,6 +10,7 @@ html { background: -moz-Dialog; + color: -moz-DialogText; font: message-box; } @@ -31,6 +32,7 @@ div.section { border: 1px solid ThreeDShadow; border-radius: 10px; background: -moz-Field; + color: -moz-FieldText; } div.opsRow { @@ -40,6 +42,7 @@ div.opsRow { border: 1px solid ThreeDShadow; border-radius: 10px; background: -moz-Field; + color: -moz-FieldText; display: inline-block; } @@ -65,25 +68,38 @@ h1 { } h2 { - background: #ddd; padding-left: .1em; } +.accuracyWarning, .badInputWarning, .invalid { + /* + * Technically this should be used with the default background colour, + * instead we're using the default field background colour, + * I hope this will be okay. + */ + color: -moz-activehyperlinktext; +} + .accuracyWarning { - color: #d22; } .badInputWarning { - color: #f00; } .treeline { - color: #888; + color: -moz-FieldText; + opacity: 0.5; +} + +/* + * We might like to style these but cannot find a colour that always + * contrasts with the background colour. + */ +.mrValue, .mrName, .mrNote { } .mrValue { font-weight: bold; - color: #400; } .mrPerc { @@ -92,14 +108,6 @@ h2 { .mrSep { } -.mrName { - color: #004; -} - -.mrNote { - color: #604; -} - .hasKids { cursor: pointer; } @@ -130,11 +138,6 @@ h2 { display: none; } -.invalid { - color: #fff; - background-color: #f00; -} - /* Mobile-specific parts go here. */ /* buttons are different sizes and overlapping without this */ diff --git a/toolkit/components/aboutmemory/content/aboutMemory.css b/toolkit/components/aboutmemory/content/aboutMemory.css index b63bbac13ca1..8a94d1ac171f 100644 --- a/toolkit/components/aboutmemory/content/aboutMemory.css +++ b/toolkit/components/aboutmemory/content/aboutMemory.css @@ -10,6 +10,7 @@ html { background: -moz-Dialog; + color: -moz-DialogText; font: message-box; } @@ -31,6 +32,7 @@ div.section { border: 1px solid ThreeDShadow; border-radius: 10px; background: -moz-Field; + color: -moz-FieldText; } div.opsRow { @@ -40,6 +42,7 @@ div.opsRow { border: 1px solid ThreeDShadow; border-radius: 10px; background: -moz-Field; + color: -moz-FieldText; display: inline-block; } @@ -66,7 +69,6 @@ h1 { } h2 { - background: #ddd; padding-left: .1em; } @@ -80,21 +82,35 @@ a.upDownArrow { -moz-user-select: none; /* no need to include this when cutting+pasting */ } +.accuracyWarning, .badInputWarning, .invalid { + /* + * Technically this should be used with the default background colour, + * instead we're using the default field background colour, + * I hope this will be okay. + */ + color: -moz-activehyperlinktext; +} + .accuracyWarning { - color: #d22; } .badInputWarning { - color: #f00; } .treeline { - color: #888; + color: -moz-FieldText; + opacity: 0.5; +} + +/* + * We might like to style these but cannot find a colour that always + * contrasts with the background colour. + */ +.mrValue, .mrName, .mrNote { } .mrValue { font-weight: bold; - color: #400; } .mrPerc { @@ -103,14 +119,6 @@ a.upDownArrow { .mrSep { } -.mrName { - color: #004; -} - -.mrNote { - color: #604; -} - .hasKids { cursor: pointer; } @@ -141,11 +149,6 @@ a.upDownArrow { display: none; } -.invalid { - color: #fff; - background-color: #f00; -} - /* Desktop-specific parts go here. */ .hasKids:hover { From 92c9a958e1e1986a19f3f9a9b92e2abda2bd47d4 Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Sun, 1 Apr 2018 13:53:31 +0200 Subject: [PATCH 19/44] Bug 1372694 - Stop making the default theme a heavyweight theme. r=kmag,aswan MozReview-Commit-ID: 30wMauuc9oo --HG-- rename : browser/base/content/default-theme-icon.svg => toolkit/mozapps/extensions/content/default-theme-icon.svg extra : rebase_source : 5e4cf784135f4a8e40a2ed8357ba651e7fce9728 --- browser/app/moz.build | 5 - browser/app/profile/extensions/moz.build | 7 - .../install.rdf.in | 42 ----- .../moz.build | 11 -- browser/app/profile/firefox.js | 7 +- browser/base/content/moz.build | 3 - browser/base/content/test/general/browser.ini | 3 - .../content/test/general/browser_bug592338.js | 117 ------------- browser/base/jar.mn | 5 +- .../customizableui/CustomizableUI.jsm | 3 +- .../customizableui/CustomizeMode.jsm | 18 +- ...wser_1007336_lwthemes_in_customize_mode.js | 7 +- .../browser_970511_undo_restore_default.js | 2 +- browser/installer/allowed-dupes.mn | 1 - browser/installer/package-manifest.in | 2 - .../en-US/chrome/browser/browser.properties | 3 - browser/themes/linux/jar.mn | 1 - browser/themes/osx/jar.mn | 1 - .../customizableui/customizeMode.inc.css | 6 +- browser/themes/windows/jar.mn | 1 - security/manager/ssl/tests/unit/xpcshell.ini | 1 + services/sync/tests/unit/test_prefs_store.js | 6 +- .../browser/browser_ext_management_themes.js | 1 - .../test/xpcshell/test_ext_management.js | 2 +- .../telemetry/tests/unit/xpcshell.ini | 2 - .../en-US/chrome/global/extensions.properties | 5 + toolkit/mozapps/extensions/AddonManager.jsm | 24 +++ .../extensions/LightweightThemeManager.jsm | 92 ++++------ .../content/default-theme-icon.svg | 0 .../mozapps/extensions/content/extensions.js | 3 +- .../extensions/internal/AddonTestUtils.jsm | 2 + .../extensions/internal/XPIInstall.jsm | 16 +- .../extensions/internal/XPIProvider.jsm | 122 ++++---------- .../extensions/internal/XPIProviderUtils.js | 36 +--- toolkit/mozapps/extensions/jar.mn | 1 + .../extensions/test/browser/browser.ini | 1 - .../extensions/test/xpcshell/head_addons.js | 2 +- .../xpcshell/test_AddonRepository_cache.js | 43 +++-- .../xpcshell/test_LightweightThemeManager.js | 158 +++++++++--------- .../test/xpcshell/test_addonStartup.js | 26 --- .../test/xpcshell/test_backgroundupdate.js | 2 +- .../test/xpcshell/test_badschema.js | 18 +- .../extensions/test/xpcshell/test_manifest.js | 23 --- .../test/xpcshell/test_strictcompatibility.js | 19 --- .../test/xpcshell/test_webextension_theme.js | 18 +- .../extensions/test/xpinstall/browser.ini | 1 - .../extensions/test/xpinstall/theme.xpi | Bin 4450 -> 0 bytes toolkit/themes/linux/mozapps/jar.mn | 6 - toolkit/themes/osx/global/jar.mn | 6 - toolkit/themes/osx/mozapps/jar.mn | 6 - toolkit/themes/shared/non-mac.jar.inc.mn | 5 - toolkit/themes/windows/global/jar.mn | 6 - toolkit/themes/windows/mozapps/jar.mn | 6 - 53 files changed, 240 insertions(+), 664 deletions(-) delete mode 100644 browser/app/profile/extensions/moz.build delete mode 100644 browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in delete mode 100644 browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build delete mode 100644 browser/base/content/test/general/browser_bug592338.js rename {browser/base => toolkit/mozapps/extensions}/content/default-theme-icon.svg (100%) delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/theme.xpi diff --git a/browser/app/moz.build b/browser/app/moz.build index cf390ec27860..44613bd5b5a6 100644 --- a/browser/app/moz.build +++ b/browser/app/moz.build @@ -24,16 +24,11 @@ with Files("moz.build"): with Files("Makefile.in"): BUG_COMPONENT = ("Firefox Build System", "General") -with Files("profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/**"): - BUG_COMPONENT = ("Firefox", "Theme") with Files("profile/channel-prefs.js"): BUG_COMPONENT = ("Firefox", "Installer") with Files("profile/firefox.js"): BUG_COMPONENT = ("Firefox", "General") - -DIRS += ['profile/extensions'] - GeckoProgram(CONFIG['MOZ_APP_NAME']) SOURCES += [ diff --git a/browser/app/profile/extensions/moz.build b/browser/app/profile/extensions/moz.build deleted file mode 100644 index bb02c9165d82..000000000000 --- a/browser/app/profile/extensions/moz.build +++ /dev/null @@ -1,7 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -DIRS += ['{972ce4c6-7e08-4474-a285-3208198ce6fd}'] diff --git a/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in b/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in deleted file mode 100644 index 3ed57102e632..000000000000 --- a/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in +++ /dev/null @@ -1,42 +0,0 @@ - - - - -#filter substitution - - - - - {972ce4c6-7e08-4474-a285-3208198ce6fd} - @FIREFOX_VERSION@ - - - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - @FIREFOX_VERSION@ - @FIREFOX_VERSION@ - - - - - Default - The default theme. - - - Mozilla - Mozilla Contributors - - - true - - classic/1.0 - - chrome://browser/content/default-theme-icon.svg - - - diff --git a/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build b/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build deleted file mode 100644 index 20a4c1cbe516..000000000000 --- a/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build +++ /dev/null @@ -1,11 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -FINAL_TARGET = 'dist/bin/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}' - -FINAL_TARGET_PP_FILES += [ - 'install.rdf.in', -] diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index dbf8675e4328..81ca399bfe4e 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -81,7 +81,7 @@ pref("extensions.webextensions.remote", true); #endif // Extensions that should not be flagged as legacy in about:addons -pref("extensions.legacy.exceptions", "{972ce4c6-7e08-4474-a285-3208198ce6fd},testpilot@cliqz.com,@testpilot-containers,jid1-NeEaf3sAHdKHPA@jetpack,@activity-streams,pulse@mozilla.com,@testpilot-addon,@min-vid,tabcentertest1@mozilla.com,snoozetabs@mozilla.com,speaktome@mozilla.com,hoverpad@mozilla.com"); +pref("extensions.legacy.exceptions", "testpilot@cliqz.com,@testpilot-containers,jid1-NeEaf3sAHdKHPA@jetpack,@activity-streams,pulse@mozilla.com,@testpilot-addon,@min-vid,tabcentertest1@mozilla.com,snoozetabs@mozilla.com,speaktome@mozilla.com,hoverpad@mozilla.com"); // Require signed add-ons by default pref("extensions.langpacks.signatures.required", true); @@ -190,9 +190,6 @@ pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla pref("extensions.update.interval", 86400); // Check for updates to Extensions and // Themes every day -pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name", "chrome://browser/locale/browser.properties"); -pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description", "chrome://browser/locale/browser.properties"); - pref("extensions.webextensions.themes.enabled", true); pref("extensions.webextensions.themes.icons.buttons", "back,forward,reload,stop,bookmark_star,bookmark_menu,downloads,home,app_menu,cut,copy,paste,new_window,new_private_window,save_page,print,history,full_screen,find,options,addons,developer,synced_tabs,open_file,sidebars,share_page,subscribe,text_encoding,email_link,forget,pocket"); @@ -1257,7 +1254,7 @@ pref("services.sync.syncedTabs.showRemoteIcons", true); pref("lightweightThemes.selectedThemeID", "firefox-compact-dark@mozilla.org", sticky); #else -pref("lightweightThemes.selectedThemeID", "", sticky); +pref("lightweightThemes.selectedThemeID", "default-theme@mozilla.org", sticky); #endif // Whether the character encoding menu is under the main Firefox button. This diff --git a/browser/base/content/moz.build b/browser/base/content/moz.build index dbf389b2e641..ace837528b6b 100644 --- a/browser/base/content/moz.build +++ b/browser/base/content/moz.build @@ -146,9 +146,6 @@ with Files("browser-tabPreviews.xml"): with Files("contentSearch*"): BUG_COMPONENT = ("Firefox", "Search") -with Files("*.svg"): - BUG_COMPONENT = ("Firefox", "Theme") - with Files("hiddenWindow.xul"): BUG_COMPONENT = ("Firefox", "Device Permissions") diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index 70df78580518..4868ee52bf6f 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -78,7 +78,6 @@ support-files = !/image/test/mochitest/blue.png !/toolkit/content/tests/browser/common/mockTransfer.js !/toolkit/modules/tests/browser/metadata_*.html - !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. [browser_addKeywordSearch.js] @@ -211,8 +210,6 @@ skip-if = toolkit != "cocoa" # Because of tests for supporting pasting from Serv # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. [browser_bug585830.js] # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. -[browser_bug592338.js] -# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. [browser_bug594131.js] # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. [browser_bug596687.js] diff --git a/browser/base/content/test/general/browser_bug592338.js b/browser/base/content/test/general/browser_bug592338.js deleted file mode 100644 index 027eece8891d..000000000000 --- a/browser/base/content/test/general/browser_bug592338.js +++ /dev/null @@ -1,117 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/"; - -const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", {}); - -/** - * Wait for the given PopupNotification to display - * - * @param {string} name - * The name of the notification to wait for. - * - * @returns {Promise} - * Resolves with the notification window. - */ -function promisePopupNotificationShown(name) { - return new Promise(resolve => { - function popupshown() { - let notification = PopupNotifications.getNotification(name); - if (!notification) { return; } - - ok(notification, `${name} notification shown`); - ok(PopupNotifications.isPanelOpen, "notification panel open"); - - PopupNotifications.panel.removeEventListener("popupshown", popupshown); - resolve(PopupNotifications.panel.firstChild); - } - - PopupNotifications.panel.addEventListener("popupshown", popupshown); - }); -} - - -var TESTS = [ -function test_install_http() { - is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected"); - - var pm = Services.perms; - pm.add(makeURI("http://example.org/"), "install", pm.ALLOW_ACTION); - - // NB: Not https so no installs allowed. - const URL = "http://example.org/browser/browser/base/content/test/general/bug592338.html"; - BrowserTestUtils.openNewForegroundTab({ gBrowser, url: URL }).then(async function() { - let prompted = promisePopupNotificationShown("addon-webext-permissions"); - BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser); - await prompted; - - is(LightweightThemeManager.currentTheme, null, "Should not have installed the test theme"); - - gBrowser.removeTab(gBrowser.selectedTab); - - pm.remove(makeURI("http://example.org/"), "install"); - - runNextTest(); - }); -}, - -function test_install_lwtheme() { - is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected"); - - var pm = Services.perms; - pm.add(makeURI("https://example.com/"), "install", pm.ALLOW_ACTION); - - const URL = "https://example.com/browser/browser/base/content/test/general/bug592338.html"; - BrowserTestUtils.openNewForegroundTab({ gBrowser, url: URL }).then(() => { - let promise = promisePopupNotificationShown("addon-installed"); - BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser); - promise.then(() => { - is(LightweightThemeManager.currentTheme.id, "test", "Should have installed the test theme"); - - LightweightThemeManager.currentTheme = null; - gBrowser.removeTab(gBrowser.selectedTab); - Services.perms.remove(makeURI("http://example.com/"), "install"); - - runNextTest(); - }); - }); -} -]; - -async function runNextTest() { - let aInstalls = await AddonManager.getAllInstalls(); - is(aInstalls.length, 0, "Should be no active installs"); - - if (TESTS.length == 0) { - let aAddon = await AddonManager.getAddonByID("theme-xpi@tests.mozilla.org"); - aAddon.uninstall(); - - Services.prefs.setBoolPref("extensions.logging.enabled", false); - - finish(); - return; - } - - info("Running " + TESTS[0].name); - TESTS.shift()(); -} - -async function test() { - waitForExplicitFinish(); - - Services.prefs.setBoolPref("extensions.logging.enabled", true); - - let aInstall = await AddonManager.getInstallForURL(TESTROOT + "theme.xpi", "application/x-xpinstall"); - aInstall.addListener({ - async onInstallEnded() { - let aAddon = await AddonManager.getAddonByID("theme-xpi@tests.mozilla.org"); - isnot(aAddon, null, "Should have installed the test theme."); - - runNextTest(); - } - }); - - aInstall.install(); -} diff --git a/browser/base/jar.mn b/browser/base/jar.mn index c2cb9964830b..8490e1865da7 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -76,7 +76,6 @@ browser.jar: content/browser/browser-webrender.js (content/browser-webrender.js) content/browser/tab-content.js (content/tab-content.js) content/browser/content.js (content/content.js) - content/browser/default-theme-icon.svg (content/default-theme-icon.svg) content/browser/defaultthemes/1.header.jpg (content/defaultthemes/1.header.jpg) content/browser/defaultthemes/1.icon.jpg (content/defaultthemes/1.icon.jpg) content/browser/defaultthemes/1.preview.jpg (content/defaultthemes/1.preview.jpg) @@ -92,8 +91,8 @@ browser.jar: content/browser/defaultthemes/5.header.png (content/defaultthemes/5.header.png) content/browser/defaultthemes/5.icon.jpg (content/defaultthemes/5.icon.jpg) content/browser/defaultthemes/5.preview.jpg (content/defaultthemes/5.preview.jpg) - content/browser/defaultthemes/dark.icon.svg (content/defaultthemes/dark.icon.svg) - content/browser/defaultthemes/light.icon.svg (content/defaultthemes/light.icon.svg) + content/browser/defaultthemes/dark.icon.svg (content/defaultthemes/dark.icon.svg) + content/browser/defaultthemes/light.icon.svg (content/defaultthemes/light.icon.svg) * content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul) content/browser/pageinfo/pageInfo.js (content/pageinfo/pageInfo.js) content/browser/pageinfo/pageInfo.css (content/pageinfo/pageInfo.css) diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm index bead82dd02d0..9f34799e4791 100644 --- a/browser/components/customizableui/CustomizableUI.jsm +++ b/browser/components/customizableui/CustomizableUI.jsm @@ -2905,7 +2905,8 @@ var CustomizableUIInternal = { return false; } - if (LightweightThemeManager.currentTheme) { + if (LightweightThemeManager.currentTheme && + LightweightThemeManager.currentTheme.id != "default-theme@mozilla.org") { log.debug(LightweightThemeManager.currentTheme + " theme is non-default"); return false; } diff --git a/browser/components/customizableui/CustomizeMode.jsm b/browser/components/customizableui/CustomizeMode.jsm index 69350102244b..76b8f2a60192 100644 --- a/browser/components/customizableui/CustomizeMode.jsm +++ b/browser/components/customizableui/CustomizeMode.jsm @@ -1339,8 +1339,8 @@ CustomizeMode.prototype = { this._onUIChange(); }, - async onLWThemesMenuShowing(aEvent) { - const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}"; + onLWThemesMenuShowing(aEvent) { + const DEFAULT_THEME_ID = "default-theme@mozilla.org"; const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org"; const DARK_THEME_ID = "firefox-compact-dark@mozilla.org"; const MAX_THEME_COUNT = 6; @@ -1363,21 +1363,13 @@ CustomizeMode.prototype = { panel.hidePopup(); }; - let aDefaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME_ID); let doc = this.window.document; function buildToolbarButton(aTheme) { let tbb = doc.createElement("toolbarbutton"); tbb.theme = aTheme; tbb.setAttribute("label", aTheme.name); - if (aDefaultTheme == aTheme) { - // The actual icon is set up so it looks nice in about:addons, but - // we'd like the version that's correct for the OS we're on, so we set - // an attribute that our styling will then use to display the icon. - tbb.setAttribute("defaulttheme", "true"); - } else { - tbb.setAttribute("image", aTheme.iconURL); - } + tbb.setAttribute("image", aTheme.iconURL); if (aTheme.description) tbb.setAttribute("tooltiptext", aTheme.description); tbb.setAttribute("tabindex", "0"); @@ -1396,14 +1388,14 @@ CustomizeMode.prototype = { return tbb; } - let themes = [aDefaultTheme]; + let themes = []; let lwts = LightweightThemeManager.usedThemes; let currentLwt = LightweightThemeManager.currentTheme; let activeThemeID = currentLwt ? currentLwt.id : DEFAULT_THEME_ID; // Move the current theme (if any) and the light/dark themes to the start: - let importantThemes = [LIGHT_THEME_ID, DARK_THEME_ID]; + let importantThemes = [DEFAULT_THEME_ID, LIGHT_THEME_ID, DARK_THEME_ID]; if (currentLwt && !importantThemes.includes(currentLwt.id)) { importantThemes.push(currentLwt.id); } diff --git a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js index 7c32531b4d65..48009b178e8b 100644 --- a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js +++ b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js @@ -4,7 +4,7 @@ "use strict"; -const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}"; +const DEFAULT_THEME_ID = "default-theme@mozilla.org"; const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org"; const DARK_THEME_ID = "firefox-compact-dark@mozilla.org"; const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", {}); @@ -95,7 +95,8 @@ add_task(async function() { let defaultTheme = header.nextSibling; defaultTheme.doCommand(); - is(Services.prefs.getCharPref("lightweightThemes.selectedThemeID"), "", "No lwtheme should be selected"); + is(Services.prefs.getCharPref("lightweightThemes.selectedThemeID"), + DEFAULT_THEME_ID, "Default theme should be selected"); // ensure current theme isn't set to "Default" popupShownPromise = popupShown(popup); @@ -111,7 +112,7 @@ add_task(async function() { // check that "Restore Defaults" button resets theme await gCustomizeMode.reset(); - is(LightweightThemeManager.currentTheme, null, "Current theme reset to default"); + is(LightweightThemeManager.currentTheme.id, DEFAULT_THEME_ID, "Current theme reset to default"); await endCustomizing(); Services.prefs.setCharPref("lightweightThemes.usedThemes", "[]"); diff --git a/browser/components/customizableui/test/browser_970511_undo_restore_default.js b/browser/components/customizableui/test/browser_970511_undo_restore_default.js index 8e3f7bf3a341..72b987d9d31d 100644 --- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js +++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js @@ -37,7 +37,7 @@ add_task(async function() { ok(CustomizableUI.inDefaultState, "In default state after reset"); is(undoResetButton.hidden, false, "The undo button is visible after reset"); - is(LightweightThemeManager.currentTheme, null, "Theme reset to default"); + is(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org", "Theme reset to default"); await gCustomizeMode.undoReset(); diff --git a/browser/installer/allowed-dupes.mn b/browser/installer/allowed-dupes.mn index 9a9e9ed070ae..b29c8f5d1e2c 100644 --- a/browser/installer/allowed-dupes.mn +++ b/browser/installer/allowed-dupes.mn @@ -81,7 +81,6 @@ browser/chrome/pdfjs/content/web/images/findbarButton-previous-rtl.png browser/chrome/pdfjs/content/web/images/findbarButton-previous-rtl@2x.png browser/chrome/pdfjs/content/web/images/findbarButton-previous.png browser/chrome/pdfjs/content/web/images/findbarButton-previous@2x.png -browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/icon.png browser/features/firefox@getpocket.com/chrome/skin/linux/menuPanel.png browser/features/firefox@getpocket.com/chrome/skin/linux/menuPanel@2x.png browser/features/firefox@getpocket.com/chrome/skin/windows/menuPanel.png diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 39d83c6fb9da..dd523cc60338 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -404,8 +404,6 @@ @RESPATH@/browser/chrome/browser.manifest @RESPATH@/browser/chrome/pdfjs.manifest @RESPATH@/browser/chrome/pdfjs/* -@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.manifest -@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf @RESPATH@/chrome/toolkit@JAREXT@ @RESPATH@/chrome/toolkit.manifest @RESPATH@/chrome/recording.manifest diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties index 9bcf83749263..fd4e532ebfb6 100644 --- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -625,9 +625,6 @@ ctrlTab.listAllTabs.label=;List All #1 Tabs # Used as the bookmark name when saving a keyword for a search field. addKeywordTitleAutoFill=Search %S -extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name=Default -extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description=The default theme. - # safeModeRestart safeModeRestartPromptTitle=Restart with Add-ons Disabled safeModeRestartPromptMessage=Are you sure you want to disable all add-ons and restart? diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index e977909beefe..50a85fe5971f 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -50,7 +50,6 @@ browser.jar: skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png) -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: % override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png % override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png % override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index bf3667496e00..70edbfbec20f 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -61,7 +61,6 @@ browser.jar: skin/classic/browser/tabbrowser/tabDragIndicator@2x.png (tabbrowser/tabDragIndicator@2x.png) skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png) -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: % override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png % override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png % override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png diff --git a/browser/themes/shared/customizableui/customizeMode.inc.css b/browser/themes/shared/customizableui/customizeMode.inc.css index 2ec4dafb1a96..99b0381816e9 100644 --- a/browser/themes/shared/customizableui/customizeMode.inc.css +++ b/browser/themes/shared/customizableui/customizeMode.inc.css @@ -158,7 +158,7 @@ } #customization-lwtheme-button > .box-inherit > .box-inherit > .button-icon { - background-image: url("chrome://browser/content/default-theme-icon.svg"); + background-image: url("chrome://mozapps/content/extensions/default-theme-icon.svg"); } #customization-uidensity-button > .box-inherit > .box-inherit > .button-icon { @@ -334,10 +334,6 @@ toolbarpaletteitem[place=toolbar] > toolbarspring { color: inherit; } -.customization-lwtheme-menu-theme[defaulttheme] { - list-style-image: url(chrome://browser/content/default-theme-icon.svg); -} - #customization-uidensity-menuitem-normal { list-style-image: url("chrome://browser/skin/customizableui/density-normal.svg"); } diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index 524ea5133f4e..cdea20515f59 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -61,7 +61,6 @@ browser.jar: skin/classic/browser/window-controls/restore-themes.svg (window-controls/restore-themes.svg) skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png) -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: % override chrome://browser/skin/page-livemarks.png chrome://browser/skin/feeds/feedIcon16.png % override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png % override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png diff --git a/security/manager/ssl/tests/unit/xpcshell.ini b/security/manager/ssl/tests/unit/xpcshell.ini index 8e24fc1f924a..cfb209e98bc2 100644 --- a/security/manager/ssl/tests/unit/xpcshell.ini +++ b/security/manager/ssl/tests/unit/xpcshell.ini @@ -1,6 +1,7 @@ [DEFAULT] head = head_psm.js tags = psm +firefox-appdir = browser support-files = bad_certs/** ocsp_certs/** diff --git a/services/sync/tests/unit/test_prefs_store.js b/services/sync/tests/unit/test_prefs_store.js index 8d92b6e9e37e..d17db5d68905 100644 --- a/services/sync/tests/unit/test_prefs_store.js +++ b/services/sync/tests/unit/test_prefs_store.js @@ -109,8 +109,7 @@ add_task(async function run_test() { // Ensure we don't go to the network to fetch personas and end up leaking // stuff. Services.io.offline = true; - Assert.ok(!prefs.get("lightweightThemes.selectedThemeID")); - Assert.equal(LightweightThemeManager.currentTheme, null); + Assert.equal(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org"); let persona1 = makePersona(); let persona2 = makePersona(); @@ -130,8 +129,7 @@ add_task(async function run_test() { "lightweightThemes.usedThemes": usedThemes }; await store.update(record); - Assert.ok(!prefs.get("lightweightThemes.selectedThemeID")); - Assert.equal(LightweightThemeManager.currentTheme, null); + Assert.equal(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org"); _("Only the current app's preferences are applied."); record = new PrefRec("prefs", "some-fake-app"); diff --git a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js index 51137b6bbc46..3c0cc2940ed0 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js +++ b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js @@ -124,7 +124,6 @@ add_task(async function test_management_themes() { accentcolor: Math.random().toString(), }; is(await extension.awaitMessage("onInstalled"), "Bling", "LWT installed"); - is(await extension.awaitMessage("onDisabled"), "Default", "default disabled"); is(await extension.awaitMessage("onEnabled"), "Bling", "LWT enabled"); await theme.startup(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_management.js b/toolkit/components/extensions/test/xpcshell/test_ext_management.js index 09f3b9b1ba44..43d3c197f037 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_management.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_management.js @@ -34,7 +34,7 @@ add_task(async function test_management_getAll() { }); let addons = await browser.management.getAll(); - browser.test.assertEq(addons.length, 2, "management.getAll returned two extensions."); + browser.test.assertEq(addons.length, 3, "management.getAll returned three add-ons."); browser.test.sendMessage("addons", addons); } diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini index d9c9f0699c75..cd7900603019 100644 --- a/toolkit/components/telemetry/tests/unit/xpcshell.ini +++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini @@ -11,7 +11,6 @@ support-files = engine.xml system.xpi restartless.xpi - theme.xpi testUnicodePDB32.dll testNoPDB32.dll testUnicodePDB64.dll @@ -22,7 +21,6 @@ generated-files = experiment.xpi system.xpi restartless.xpi - theme.xpi [test_MigratePendingPings.js] [test_TelemetryHistograms.js] diff --git a/toolkit/locales/en-US/chrome/global/extensions.properties b/toolkit/locales/en-US/chrome/global/extensions.properties index 7b6295288974..77e66bb49e32 100644 --- a/toolkit/locales/en-US/chrome/global/extensions.properties +++ b/toolkit/locales/en-US/chrome/global/extensions.properties @@ -37,3 +37,8 @@ newTabControlled.learnMore = Learn more #LOCALIZATION NOTE (homepageControlled.message) %S is the icon and name of the extension which updated the homepage. homepageControlled.message = An extension, %S, changed what you see when you open your homepage and new windows. homepageControlled.learnMore = Learn more + +# LOCALIZATION NOTE (defaultTheme.name): This is displayed in about:addons -> Appearance +defaultTheme.name=Default +defaultTheme.description=The default theme. + diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index a8c055af8bfe..dbd2649795d7 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -45,6 +45,8 @@ const KEY_PROFILEDIR = "ProfD"; const KEY_APPDIR = "XCurProcD"; const FILE_BLOCKLIST = "blocklist.xml"; +const DEFAULT_THEME_ID = "default-theme@mozilla.org"; + const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi; const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility"; var PREF_EM_CHECK_COMPATIBILITY = MOZ_COMPATIBILITY_NIGHTLY ? @@ -70,6 +72,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm", Extension: "resource://gre/modules/Extension.jsm", FileUtils: "resource://gre/modules/FileUtils.jsm", + LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm", PromptUtils: "resource://gre/modules/SharedPromptUtils.jsm", }); @@ -838,6 +841,27 @@ var AddonManagerInternal = { AddonManagerPrivate.recordException("AMI", "startup failed", e); } + let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); + let extensionsBundle = Services.strings.createBundle( + "chrome://global/locale/extensions.properties"); + + // When running in xpcshell tests, the default theme may already + // exist. + if (!LightweightThemeManager._builtInThemes.has(DEFAULT_THEME_ID)) { + let author = "Mozilla"; + try { + author = brandBundle.GetStringFromName("vendorShortName"); + } catch (e) {} + + LightweightThemeManager.addBuiltInTheme({ + id: DEFAULT_THEME_ID, + name: extensionsBundle.GetStringFromName("defaultTheme.name"), + description: extensionsBundle.GetStringFromName("defaultTheme.description"), + iconURL: "chrome://mozapps/content/extensions/default-theme-icon.svg", + author, + }); + } + logger.debug("Completed startup sequence"); this.callManagerListeners("onStartup"); }, diff --git a/toolkit/mozapps/extensions/LightweightThemeManager.jsm b/toolkit/mozapps/extensions/LightweightThemeManager.jsm index 689ea3ee2a2b..9d9d0f230cee 100644 --- a/toolkit/mozapps/extensions/LightweightThemeManager.jsm +++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm @@ -12,12 +12,12 @@ ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); ChromeUtils.import("resource://gre/modules/Services.jsm"); const ID_SUFFIX = "@personas.mozilla.org"; -const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect"; const ADDON_TYPE = "theme"; const ADDON_TYPE_WEBEXT = "webextension-theme"; const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; +const DEFAULT_THEME_ID = "default-theme@mozilla.org"; const DEFAULT_MAX_USED_THEMES_COUNT = 30; const MAX_PREVIEW_SECONDS = 30; @@ -120,7 +120,7 @@ var LightweightThemeManager = { }, get currentTheme() { - let selectedThemeID = _prefs.getCharPref("selectedThemeID", ""); + let selectedThemeID = _prefs.getStringPref("selectedThemeID", DEFAULT_THEME_ID); let data = null; if (selectedThemeID) { @@ -131,7 +131,7 @@ var LightweightThemeManager = { get currentThemeForDisplay() { var data = this.currentTheme; - if (!data && _fallbackThemeData) + if ((!data || data.id == DEFAULT_THEME_ID) && _fallbackThemeData) data = _fallbackThemeData; if (data && PERSIST_ENABLED) { @@ -189,7 +189,7 @@ var LightweightThemeManager = { this._builtInThemes.set(theme.id, theme); - if (_prefs.getCharPref("selectedThemeID") == theme.id) { + if (_prefs.getStringPref("selectedThemeID", DEFAULT_THEME_ID) == theme.id) { this.currentTheme = theme; } }, @@ -323,15 +323,6 @@ var LightweightThemeManager = { * necessary. */ startup() { - if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) { - let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - if (id) - this.themeChanged(this.getUsedTheme(id)); - else - this.themeChanged(null); - Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); - } - _prefs.addObserver("", _prefObserver); }, @@ -350,47 +341,29 @@ var LightweightThemeManager = { * The ID of the newly enabled add-on * @param aType * The type of the newly enabled add-on - * @param aPendingRestart - * true if the newly enabled add-on will only become enabled after a - * restart */ - addonChanged(aId, aType, aPendingRestart) { + addonChanged(aId, aType) { if (aType != ADDON_TYPE && aType != ADDON_TYPE_WEBEXT) return; let id = _getInternalID(aId); let current = this.currentTheme; - try { - let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - if (id == next && aPendingRestart) - return; - - Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); - if (next) { - AddonManagerPrivate.callAddonListeners("onOperationCancelled", - new AddonWrapper(this.getUsedTheme(next))); - } else if (id == current.id) { - AddonManagerPrivate.callAddonListeners("onOperationCancelled", - new AddonWrapper(current)); - return; - } - } catch (e) { + if (current && id == current.id) { + AddonManagerPrivate.callAddonListeners("onOperationCancelled", + new AddonWrapper(current)); + return; } if (current) { - if (current.id == id) + if (current.id == id || (!aId && current.id == DEFAULT_THEME_ID)) return; _themeIDBeingDisabled = current.id; let wrapper = new AddonWrapper(current); - if (aPendingRestart) { - Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, ""); - AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true); - } else { - AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false); - this.themeChanged(null); - AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); - } + + AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false); + this.themeChanged(null); + AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); _themeIDBeingDisabled = null; } @@ -401,17 +374,11 @@ var LightweightThemeManager = { return; _themeIDBeingEnabled = id; let wrapper = new AddonWrapper(theme); - if (aPendingRestart) { - AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true); - Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id); - // Flush the preferences to disk so they survive any crash - Services.prefs.savePrefFile(null); - } else { - AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false); - this.themeChanged(theme); - AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); - } + AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false); + this.themeChanged(theme); + AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); + _themeIDBeingEnabled = null; } }, @@ -464,7 +431,7 @@ function AddonWrapper(aTheme) { AddonWrapper.prototype = { get id() { - return themeFor(this).id + ID_SUFFIX; + return _getExternalID(themeFor(this).id); }, get type() { @@ -522,7 +489,7 @@ AddonWrapper.prototype = { permissions = AddonManager.PERM_CAN_UNINSTALL; if (this.userDisabled) permissions |= AddonManager.PERM_CAN_ENABLE; - else + else if (themeFor(this).id != DEFAULT_THEME_ID) permissions |= AddonManager.PERM_CAN_DISABLE; return permissions; }, @@ -534,13 +501,8 @@ AddonWrapper.prototype = { if (_themeIDBeingDisabled == id) return true; - try { - let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - return id != toSelect; - } catch (e) { - let current = LightweightThemeManager.currentTheme; - return !current || current.id != id; - } + let current = LightweightThemeManager.currentTheme; + return !current || current.id != id; }, set userDisabled(val) { @@ -638,12 +600,20 @@ AddonWrapper.prototype = { function _getInternalID(id) { if (!id) return null; + if (id == DEFAULT_THEME_ID) + return id; let len = id.length - ID_SUFFIX.length; if (len > 0 && id.substring(len) == ID_SUFFIX) return id.substring(0, len); return null; } +function _getExternalID(id) { + if (id == DEFAULT_THEME_ID) + return id; + return id + ID_SUFFIX; +} + function _setCurrentTheme(aData, aLocal) { aData = _sanitizeTheme(aData, null, aLocal); @@ -691,7 +661,7 @@ function _setCurrentTheme(aData, aLocal) { return null; if (notify) { - AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null, + AddonManagerPrivate.notifyAddonChanged(aData ? _getExternalID(aData.id) : null, ADDON_TYPE, false); } diff --git a/browser/base/content/default-theme-icon.svg b/toolkit/mozapps/extensions/content/default-theme-icon.svg similarity index 100% rename from browser/base/content/default-theme-icon.svg rename to toolkit/mozapps/extensions/content/default-theme-icon.svg diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index a3c08484358c..0b884233447c 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -245,7 +245,8 @@ function isLegacyExtension(addon) { // themes so explicitly check for new style themes (for which // isWebExtension is true) or lightweight themes (which have // ids that end with @personas.mozilla.org) - legacy = !(addon.isWebExtension || addon.id.endsWith("@personas.mozilla.org")); + legacy = !(addon.isWebExtension || addon.id.endsWith("@personas.mozilla.org") || + addon.id == "default-theme@mozilla.org"); } if (legacy && (addon.hidden || addon.signedState == AddonManager.SIGNEDSTATE_PRIVILEGED)) { diff --git a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm index 7eb75162e040..66a4e0ce96ed 100644 --- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm +++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm @@ -959,6 +959,8 @@ var AddonTestUtils = { var zipW = ZipWriter(zipFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | flags); for (let [path, data] of Object.entries(files)) { + if (typeof data === "object" && ChromeUtils.getClassName(data) === "Object") + data = JSON.stringify(data); if (!(data instanceof ArrayBuffer)) data = new TextEncoder("utf-8").encode(data).buffer; diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index 3fae301fd71e..046b8fc55a95 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -71,8 +71,6 @@ ChromeUtils.defineModuleGetter(this, "XPIProvider", const PREF_ALLOW_NON_RESTARTLESS = "extensions.legacy.non-restartless.enabled"; -const DEFAULT_SKIN = "classic/1.0"; - /* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPIDatabase, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */ const XPI_INTERNAL_SYMBOLS = [ "AddonInternal", @@ -158,14 +156,13 @@ const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; // Map new string type identifiers to old style nsIUpdateItem types. // Retired values: -// 8 = locale // 32 = multipackage xpi file // 8 = locale // 256 = apiextension // 128 = experiment +// theme = 4 const TYPES = { extension: 2, - theme: 4, dictionary: 64, }; @@ -801,16 +798,7 @@ async function loadManifestFromRDF(aUri, aData) { addon.targetPlatforms.push(platform); } - // A theme's userDisabled value is true if the theme is not the selected skin - // or if there is an active lightweight theme. We ignore whether softblocking - // is in effect since it would change the active theme. - if (isTheme(addon.type)) { - addon.userDisabled = !!LightweightThemeManager.currentTheme || - addon.internalName != DEFAULT_SKIN; - } else { - addon.userDisabled = false; - } - + addon.userDisabled = false; addon.softDisabled = addon.blocklistState == nsIBlocklistService.STATE_SOFTBLOCKED; addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index c7a6c7d78e33..f30229d3795d 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -87,8 +87,6 @@ const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVer const PREF_EM_LAST_APP_BUILD_ID = "extensions.lastAppBuildId"; -const DEFAULT_SKIN = "classic/1.0"; - // Specify a list of valid built-in add-ons to load. const BUILT_IN_ADDONS_URI = "chrome://browser/content/built_in_addons.json"; @@ -112,8 +110,6 @@ const FILE_RDF_MANIFEST = "install.rdf"; const FILE_WEB_MANIFEST = "manifest.json"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; -const ADDON_ID_DEFAULT_THEME = "{972ce4c6-7e08-4474-a285-3208198ce6fd}"; - const KEY_PROFILEDIR = "ProfD"; const KEY_ADDON_APP_DIR = "XREAddonAppDir"; const KEY_APP_DISTRIBUTION = "XREAppDist"; @@ -128,6 +124,8 @@ const KEY_APP_SYSTEM_SHARE = "app-system-share"; const KEY_APP_SYSTEM_USER = "app-system-user"; const KEY_APP_TEMPORARY = "app-temporary"; +const DEFAULT_THEME_ID = "default-theme@mozilla.org"; + const TEMPORARY_ADDON_SUFFIX = "@temporary-addon"; const STARTUP_MTIME_SCOPES = [KEY_APP_GLOBAL, @@ -142,7 +140,7 @@ const TOOLKIT_ID = "toolkit@mozilla.org"; const XPI_SIGNATURE_CHECK_PERIOD = 24 * 60 * 60; -XPCOMUtils.defineConstant(this, "DB_SCHEMA", 24); +XPCOMUtils.defineConstant(this, "DB_SCHEMA", 25); const NOTIFICATION_TOOLBOX_CONNECTION_CHANGE = "toolbox-connection-change"; @@ -211,7 +209,6 @@ const SIGNED_TYPES = new Set([ const LEGACY_TYPES = new Set([ "extension", - "theme", ]); const ALL_EXTERNAL_TYPES = new Set([ @@ -289,7 +286,6 @@ function loadLazyObjects() { SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA, - DEFAULT_SKIN, AddonInternal, XPIProvider, XPIStates, @@ -791,9 +787,6 @@ function isDisabledLegacy(addon) { * @return true if the add-on should not be appDisabled */ function isUsableAddon(aAddon) { - if (aAddon.type == "theme") - return aAddon.internalName == DEFAULT_SKIN; - if (mustSign(aAddon.type) && !aAddon.isCorrectlySigned) { logger.warn(`Add-on ${aAddon.id} is not correctly signed.`); if (Services.prefs.getBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, false)) { @@ -1201,14 +1194,7 @@ class XPIState { // We don't use aDBAddon.active here because it's not updated until after restart. let mustGetMod = (aDBAddon.visible && !aDBAddon.disabled && !this.enabled); - // We need to treat XUL themes specially here, since lightweight - // themes require the default theme's chrome to be registered even - // though we report it as disabled for UI purposes. - if (aDBAddon.type == "theme") { - this.enabled = aDBAddon.internalName == DEFAULT_SKIN; - } else { - this.enabled = aDBAddon.visible && !aDBAddon.disabled; - } + this.enabled = aDBAddon.visible && !aDBAddon.disabled; this.version = aDBAddon.version; this.type = aDBAddon.type; @@ -2060,9 +2046,6 @@ var XPIProvider = { if (AppConstants.MOZ_CRASHREPORTER) { // Annotate the crash report with relevant add-on information. - try { - Services.appinfo.annotateCrashReport("Theme", DEFAULT_SKIN); - } catch (e) { } try { Services.appinfo.annotateCrashReport("EMCheckCompatibility", AddonManager.checkCompatibility); @@ -2883,30 +2866,6 @@ var XPIProvider = { .filter(addon => addon.dependencies.includes(aAddon.id)); }, - /** - * Returns the add-on state data for the restartful extensions which - * should be available in safe mode. In particular, this means the - * default theme, and only the default theme. - * - * @returns {object} - */ - getSafeModeExtensions() { - let loc = XPIStates.getLocation(KEY_APP_GLOBAL); - let state = loc.get(ADDON_ID_DEFAULT_THEME); - - // Use the default state data for the default theme, but always mark - // it enabled, in case another theme is enabled in normal mode. - let addonData = state.toJSON(); - addonData.enabled = true; - - return { - [KEY_APP_GLOBAL]: { - path: loc.path, - addons: { [ADDON_ID_DEFAULT_THEME]: addonData }, - }, - }; - }, - /** * Checks for any changes that have occurred since the last time the * application was launched. @@ -3006,25 +2965,13 @@ var XPIProvider = { } } - if (Services.appinfo.inSafeMode) { - aomStartup.initializeExtensions(this.getSafeModeExtensions()); - logger.debug("Initialized safe mode add-ons"); - return false; - } - // If the application crashed before completing any pending operations then // we should perform them now. if (extensionListChanged || hasPendingChanges) { this._updateActiveAddons(); - - // Serialize and deserialize so we get the expected JSON data. - let state = JSON.parse(JSON.stringify(XPIStates)); - aomStartup.initializeExtensions(state); return true; } - aomStartup.initializeExtensions(XPIStates.initialStateData); - logger.debug("No changes found"); } catch (e) { logger.error("Error during startup file checks", e); @@ -3570,14 +3517,21 @@ var XPIProvider = { if (!isTheme(aType)) return; - let addons = XPIDatabase.getAddonsByType("theme", "webextension-theme"); + let addons = XPIDatabase.getAddonsByType("webextension-theme"); for (let theme of addons) { - if (isWebExtension(theme.type) && theme.visible && theme.id != aId) - this.updateAddonDisabledState(theme, true, undefined); + if (theme.visible && theme.id != aId) + this.updateAddonDisabledState(theme, true, undefined, true); } - let defaultTheme = XPIDatabase.getVisibleAddonForInternalName(DEFAULT_SKIN); - this.updateAddonDisabledState(defaultTheme, aId && aId != defaultTheme.id); + if (!aId && (!LightweightThemeManager.currentTheme || + LightweightThemeManager.currentTheme !== DEFAULT_THEME_ID)) { + let theme = LightweightThemeManager.getUsedTheme(DEFAULT_THEME_ID); + // This can only ever be null in tests. + // This can all go away once lightweight themes are gone. + if (theme) { + LightweightThemeManager.currentTheme = theme; + } + } }, /** @@ -3914,13 +3868,16 @@ var XPIProvider = { * @param aSoftDisabled * Value for the softDisabled property. If undefined the value will * not change. If true this will force userDisabled to be true + * @param {boolean} aBecauseSelecting + * True if we're disabling this add-on because we're selecting + * another. * @return a tri-state indicating the action taken for the add-on: * - undefined: The add-on did not change state * - true: The add-on because disabled * - false: The add-on became enabled * @throws if addon is not a DBAddonInternal */ - updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled) { + updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled, aBecauseSelecting) { if (!(aAddon.inDatabase)) throw new Error("Can only update addon states for installed addons."); if (aUserDisabled !== undefined && aSoftDisabled !== undefined) { @@ -4016,12 +3973,16 @@ var XPIProvider = { } // Notify any other providers that a new theme has been enabled - if (isTheme(aAddon.type) && !isDisabled) { - AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type); + if (isTheme(aAddon.type)) { + if (!isDisabled) { + AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type); - if (xpiState) { - xpiState.syncWithDB(aAddon); - XPIStates.save(); + if (xpiState) { + xpiState.syncWithDB(aAddon); + XPIStates.save(); + } + } else if (isDisabled && !aBecauseSelecting) { + AddonManagerPrivate.notifyAddonChanged(null, "theme"); } } @@ -4768,8 +4729,7 @@ AddonWrapper.prototype = { } } - let canUseIconURLs = this.isActive || - (addon.type == "theme" && addon.internalName == DEFAULT_SKIN); + let canUseIconURLs = this.isActive; if (canUseIconURLs && addon.iconURL) { icons[32] = addon.iconURL; icons[48] = addon.iconURL; @@ -4931,22 +4891,12 @@ AddonWrapper.prototype = { } if (addon.inDatabase) { - let theme = isTheme(addon.type); - if (theme && val) { - if (addon.internalName == DEFAULT_SKIN) - throw new Error("Cannot disable the default theme"); - - let defaultTheme = XPIDatabase.getVisibleAddonForInternalName(DEFAULT_SKIN); - XPIProvider.updateAddonDisabledState(defaultTheme, false); - } - if (!(theme && val) || isWebExtension(addon.type)) { - // hidden and system add-ons should not be user disasbled, - // as there is no UI to re-enable them. - if (this.hidden) { - throw new Error(`Cannot disable hidden add-on ${addon.id}`); - } - XPIProvider.updateAddonDisabledState(addon, val); + // hidden and system add-ons should not be user disabled, + // as there is no UI to re-enable them. + if (this.hidden) { + throw new Error(`Cannot disable hidden add-on ${addon.id}`); } + XPIProvider.updateAddonDisabledState(addon, val); } else { addon.userDisabled = val; // When enabling remove the softDisabled flag @@ -4965,8 +4915,6 @@ AddonWrapper.prototype = { if (addon.inDatabase) { // When softDisabling a theme just enable the active theme if (isTheme(addon.type) && val && !addon.userDisabled) { - if (addon.internalName == DEFAULT_SKIN) - throw new Error("Cannot disable the default theme"); if (isWebExtension(addon.type)) XPIProvider.updateAddonDisabledState(addon, undefined, val); } else { diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 34b468a76eb3..177c8ad8d82f 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -8,7 +8,7 @@ /* globals ADDON_SIGNING, SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA, AddonInternal, XPIProvider, XPIStates, syncLoadManifestFromFile, isUsableAddon, recordAddonTelemetry, - flushChromeCaches, descriptorToPath, DEFAULT_SKIN */ + flushChromeCaches, descriptorToPath */ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -48,7 +48,7 @@ const KEY_APP_TEMPORARY = "app-temporary"; // Properties to save in JSON file const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", - "internalName", "updateURL", "optionsURL", + "updateURL", "optionsURL", "optionsType", "optionsBrowserStyle", "aboutURL", "defaultLocale", "visible", "active", "userDisabled", "appDisabled", "pendingUninstall", "installDate", @@ -711,28 +711,6 @@ this.XPIDatabase = { return _filterDB(this.addonDB, aAddon => aTypes.includes(aAddon.type)); }, - /** - * Synchronously gets an add-on with a particular internalName. - * - * @param aInternalName - * The internalName of the add-on to retrieve - * @return a DBAddonInternal - */ - getVisibleAddonForInternalName(aInternalName) { - if (!this.addonDB) { - // This may be called when the DB hasn't otherwise been loaded - logger.warn(`Synchronous load of XPI database due to ` + - `getVisibleAddonForInternalName. Stack: ${Error().stack}`); - AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_forInternalName", - XPIProvider.runPhase); - this.syncLoadDB(true); - } - - return _findAddon(this.addonDB, - aAddon => aAddon.visible && - (aAddon.internalName == aInternalName)); - }, - /** * Asynchronously gets all add-ons with pending operations. * @@ -1095,10 +1073,6 @@ this.XPIDatabaseReconcile = { // appDisabled depends on whether the add-on is a foreignInstall so update aNewAddon.appDisabled = !isUsableAddon(aNewAddon); - // The default theme is never a foreign install - if (aNewAddon.type == "theme" && aNewAddon.internalName == DEFAULT_SKIN) - aNewAddon.foreignInstall = false; - if (isDetectedInstall && aNewAddon.foreignInstall) { // If the add-on is a foreign install and is in a scope where add-ons // that were dropped in should default to disabled then disable it @@ -1460,11 +1434,7 @@ this.XPIDatabaseReconcile = { // We might be recovering from a corrupt database, if so use the // list of known active add-ons to update the new add-on if (!wasStaged && XPIDatabase.activeBundles) { - // For themes we know which is active by the current skin setting - if (currentAddon.type == "theme") - isActive = currentAddon.internalName == DEFAULT_SKIN; - else - isActive = XPIDatabase.activeBundles.includes(currentAddon.path); + isActive = XPIDatabase.activeBundles.includes(currentAddon.path); if (currentAddon.type == "webextension-theme") currentAddon.userDisabled = !isActive; diff --git a/toolkit/mozapps/extensions/jar.mn b/toolkit/mozapps/extensions/jar.mn index 70303ed5dbe5..66e47cfd49d3 100644 --- a/toolkit/mozapps/extensions/jar.mn +++ b/toolkit/mozapps/extensions/jar.mn @@ -22,6 +22,7 @@ toolkit.jar: content/mozapps/extensions/pluginPrefs.xul (content/pluginPrefs.xul) content/mozapps/extensions/pluginPrefs.js (content/pluginPrefs.js) content/mozapps/extensions/OpenH264-license.txt (content/OpenH264-license.txt) + content/mozapps/extensions/default-theme-icon.svg (content/default-theme-icon.svg) #endif content/mozapps/xpinstall/xpinstallConfirm.xul (content/xpinstallConfirm.xul) content/mozapps/xpinstall/xpinstallConfirm.js (content/xpinstallConfirm.js) diff --git a/toolkit/mozapps/extensions/test/browser/browser.ini b/toolkit/mozapps/extensions/test/browser/browser.ini index 1a68cd4f8bab..5307d304d7bc 100644 --- a/toolkit/mozapps/extensions/test/browser/browser.ini +++ b/toolkit/mozapps/extensions/test/browser/browser.ini @@ -23,7 +23,6 @@ support-files = !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi - !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi !/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi !/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi !/toolkit/mozapps/extensions/test/xpinstall/amosigned-restart-required.xpi diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index a3ba29ba98e6..c800c16e528f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1110,7 +1110,7 @@ function check_test_completed(aArgs) { function ensure_test_completed() { for (let i in gExpectedEvents) { if (gExpectedEvents[i].length > 0) - do_throw("Didn't see all the expected events for " + i); + do_throw(`Didn't see all the expected events for ${i}: Still expecting ${gExpectedEvents[i].map(([k]) => k)}`); } gExpectedEvents = {}; if (gExpectedInstalls) diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index 71d4693a81fc..aaa7a5732c2d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -48,40 +48,37 @@ const ADDONS = [ }, }, { - "install.rdf": { - id: "test_AddonRepository_2@tests.mozilla.org", - type: 4, - internalName: "test2/1.0", - version: "1.2", - bootstrap: true, + "manifest.json": { + manifest_version: 2, name: "XPI Add-on 2", - - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1"}], + version: "1.2", + applications: { + gecko: { + id: "test_AddonRepository_2@tests.mozilla.org", + }, + }, + theme: {}, }, }, { - "install.rdf": { - id: "test_AddonRepository_3@tests.mozilla.org", - type: "4", - internalName: "test3/1.0", - version: "1.3", - bootstrap: true, + "manifest.json": { + manifest_version: 2, name: "XPI Add-on 3", - - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1"}], + version: "1.3", + applications: { + gecko: { + id: "test_AddonRepository_3@tests.mozilla.org", + }, + }, + theme: {}, + icons: {32: "icon.png"}, }, "icon.png": "", "preview.png": "", }, ]; -const ADDON_IDS = ADDONS.map(addon => addon["install.rdf"].id); +const ADDON_IDS = ADDONS.map(addon => addon["install.rdf"] ? addon["install.rdf"].id : addon["manifest.json"].applications.gecko.id); const ADDON_FILES = ADDONS.map(addon => AddonTestUtils.createTempXPIFile(addon)); const PREF_ADDON0_CACHE_ENABLED = "extensions." + ADDON_IDS[0] + ".getAddons.cache.enabled"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js b/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js index ae67d19e0a28..a4c082906642 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js @@ -5,6 +5,8 @@ const OPTIONAL = ["headerURL", "footerURL", "textcolor", "accentcolor", ChromeUtils.import("resource://gre/modules/Services.jsm"); +const DEFAULT_THEME_ID = "default-theme@mozilla.org"; + function dummy(id) { return { id: id || Math.random().toString(), @@ -31,39 +33,39 @@ async function run_test() { Assert.equal(typeof ltm, "object"); Assert.equal(typeof ltm.usedThemes, "object"); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); ltm.previewTheme(dummy("preview0")); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); ltm.previewTheme(dummy("preview1")); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); ltm.resetPreview(); ltm.currentTheme = dummy("x0"); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.id, "x0"); Assert.equal(ltm.usedThemes[0].id, "x0"); Assert.equal(ltm.getUsedTheme("x0").id, "x0"); ltm.previewTheme(dummy("preview0")); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.id, "x0"); ltm.resetPreview(); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.id, "x0"); ltm.currentTheme = dummy("x1"); - Assert.equal(ltm.usedThemes.length, 2); + Assert.equal(ltm.usedThemes.length, 3); Assert.equal(ltm.currentTheme.id, "x1"); Assert.equal(ltm.usedThemes[1].id, "x0"); ltm.currentTheme = dummy("x2"); - Assert.equal(ltm.usedThemes.length, 3); + Assert.equal(ltm.usedThemes.length, 4); Assert.equal(ltm.currentTheme.id, "x2"); Assert.equal(ltm.usedThemes[1].id, "x1"); Assert.equal(ltm.usedThemes[2].id, "x0"); @@ -73,25 +75,25 @@ async function run_test() { ltm.currentTheme = dummy("x5"); ltm.currentTheme = dummy("x6"); ltm.currentTheme = dummy("x7"); - Assert.equal(ltm.usedThemes.length, 8); + Assert.equal(ltm.usedThemes.length, 9); Assert.equal(ltm.currentTheme.id, "x7"); Assert.equal(ltm.usedThemes[1].id, "x6"); Assert.equal(ltm.usedThemes[7].id, "x0"); ltm.currentTheme = dummy("x8"); - Assert.equal(ltm.usedThemes.length, 8); + Assert.equal(ltm.usedThemes.length, 9); Assert.equal(ltm.currentTheme.id, "x8"); Assert.equal(ltm.usedThemes[1].id, "x7"); Assert.equal(ltm.usedThemes[7].id, "x1"); Assert.equal(ltm.getUsedTheme("x0"), null); ltm.forgetUsedTheme("nonexistent"); - Assert.equal(ltm.usedThemes.length, 8); - Assert.notEqual(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 9); + Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID); ltm.forgetUsedTheme("x8"); - Assert.equal(ltm.usedThemes.length, 7); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 8); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); Assert.equal(ltm.usedThemes[0].id, "x7"); Assert.equal(ltm.usedThemes[6].id, "x1"); @@ -100,40 +102,40 @@ async function run_test() { ltm.forgetUsedTheme("x5"); ltm.forgetUsedTheme("x4"); ltm.forgetUsedTheme("x3"); - Assert.equal(ltm.usedThemes.length, 2); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 3); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); Assert.equal(ltm.usedThemes[0].id, "x2"); Assert.equal(ltm.usedThemes[1].id, "x1"); ltm.currentTheme = dummy("x1"); - Assert.equal(ltm.usedThemes.length, 2); + Assert.equal(ltm.usedThemes.length, 3); Assert.equal(ltm.currentTheme.id, "x1"); Assert.equal(ltm.usedThemes[0].id, "x1"); Assert.equal(ltm.usedThemes[1].id, "x2"); ltm.currentTheme = dummy("x2"); - Assert.equal(ltm.usedThemes.length, 2); + Assert.equal(ltm.usedThemes.length, 3); Assert.equal(ltm.currentTheme.id, "x2"); Assert.equal(ltm.usedThemes[0].id, "x2"); Assert.equal(ltm.usedThemes[1].id, "x1"); ltm.currentTheme = ltm.getUsedTheme("x1"); - Assert.equal(ltm.usedThemes.length, 2); + Assert.equal(ltm.usedThemes.length, 3); Assert.equal(ltm.currentTheme.id, "x1"); Assert.equal(ltm.usedThemes[0].id, "x1"); Assert.equal(ltm.usedThemes[1].id, "x2"); ltm.forgetUsedTheme("x1"); ltm.forgetUsedTheme("x2"); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); // Use chinese name to test utf-8, for bug #541943 var chineseTheme = dummy("chinese0"); chineseTheme.name = "笢恅0"; chineseTheme.description = "笢恅1"; ltm.currentTheme = chineseTheme; - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.name, "笢恅0"); Assert.equal(ltm.currentTheme.description, "笢恅1"); Assert.equal(ltm.usedThemes[0].name, "笢恅0"); @@ -146,8 +148,8 @@ async function run_test() { chineseTheme1.name = "眵昜湮桵蔗坌~郔乾"; chineseTheme1.description = "眵昜湮桵蔗坌~郔乾"; ltm.currentTheme = chineseTheme1; - Assert.notEqual(ltm.currentTheme, null); - Assert.equal(ltm.usedThemes.length, 2); + Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID); + Assert.equal(ltm.usedThemes.length, 3); Assert.equal(ltm.currentTheme.name, "眵昜湮桵蔗坌~郔乾"); Assert.equal(ltm.currentTheme.description, "眵昜湮桵蔗坌~郔乾"); Assert.equal(ltm.usedThemes[1].name, "笢恅0"); @@ -156,12 +158,12 @@ async function run_test() { Assert.equal(ltm.usedThemes[0].description, "眵昜湮桵蔗坌~郔乾"); ltm.forgetUsedTheme("chinese0"); - Assert.equal(ltm.usedThemes.length, 1); - Assert.notEqual(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 2); + Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID); ltm.forgetUsedTheme("chinese1"); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); Assert.equal(ltm.parseTheme("invalid json"), null); Assert.equal(ltm.parseTheme('"json string"'), null); @@ -300,8 +302,8 @@ async function run_test() { Assert.equal(typeof after[prop], "undefined"); }); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); data = dummy(); delete data.name; @@ -316,52 +318,52 @@ async function run_test() { data = dummy(); data.headerURL = "foo"; ltm.currentTheme = data; - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.headerURL, undefined); ltm.forgetUsedTheme(ltm.currentTheme.id); - Assert.equal(ltm.usedThemes.length, 0); + Assert.equal(ltm.usedThemes.length, 1); // Sanitize themes with a non-http(s) headerURL data = dummy(); data.headerURL = "ftp://lwtest.invalid/test.png"; ltm.currentTheme = data; - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.headerURL, undefined); ltm.forgetUsedTheme(ltm.currentTheme.id); - Assert.equal(ltm.usedThemes.length, 0); + Assert.equal(ltm.usedThemes.length, 1); // Sanitize themes with a non-http(s) headerURL data = dummy(); data.headerURL = "file:///test.png"; ltm.currentTheme = data; - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.headerURL, undefined); ltm.forgetUsedTheme(ltm.currentTheme.id); - Assert.equal(ltm.usedThemes.length, 0); + Assert.equal(ltm.usedThemes.length, 1); data = dummy(); data.updateURL = "file:///test.json"; ltm.setLocalTheme(data); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.updateURL, undefined); ltm.forgetUsedTheme(ltm.currentTheme.id); - Assert.equal(ltm.usedThemes.length, 0); + Assert.equal(ltm.usedThemes.length, 1); data = dummy(); data.headerURL = "file:///test.png"; ltm.setLocalTheme(data); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.headerURL, "file:///test.png"); ltm.forgetUsedTheme(ltm.currentTheme.id); - Assert.equal(ltm.usedThemes.length, 0); + Assert.equal(ltm.usedThemes.length, 1); data = dummy(); data.headerURL = "ftp://lwtest.invalid/test.png"; ltm.setLocalTheme(data); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); Assert.equal(ltm.currentTheme.updateURL, undefined); ltm.forgetUsedTheme(ltm.currentTheme.id); - Assert.equal(ltm.usedThemes.length, 0); + Assert.equal(ltm.usedThemes.length, 1); data = dummy(); delete data.id; @@ -372,60 +374,60 @@ async function run_test() { // Expected exception } - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); // Force the theme into the prefs anyway let themes = [data]; Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes)); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); // This should silently drop the bad theme. ltm.currentTheme = dummy(); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); ltm.forgetUsedTheme(ltm.currentTheme.id); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); // Add one broken and some working. themes = [data, dummy("x1"), dummy("x2")]; Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes)); - Assert.equal(ltm.usedThemes.length, 3); + Assert.equal(ltm.usedThemes.length, 4); // Switching to an existing theme should drop the bad theme. ltm.currentTheme = ltm.getUsedTheme("x1"); - Assert.equal(ltm.usedThemes.length, 2); + Assert.equal(ltm.usedThemes.length, 3); ltm.forgetUsedTheme("x1"); ltm.forgetUsedTheme("x2"); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes)); - Assert.equal(ltm.usedThemes.length, 3); + Assert.equal(ltm.usedThemes.length, 4); // Forgetting an existing theme should drop the bad theme. ltm.forgetUsedTheme("x1"); - Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.usedThemes.length, 2); ltm.forgetUsedTheme("x2"); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); // Test whether a JSON set with setCharPref can be retrieved with usedThemes ltm.currentTheme = dummy("x0"); ltm.currentTheme = dummy("x1"); Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(ltm.usedThemes)); - Assert.equal(ltm.usedThemes.length, 2); + Assert.equal(ltm.usedThemes.length, 4); Assert.equal(ltm.currentTheme.id, "x1"); Assert.equal(ltm.usedThemes[1].id, "x0"); Assert.equal(ltm.usedThemes[0].id, "x1"); ltm.forgetUsedTheme("x0"); - Assert.equal(ltm.usedThemes.length, 1); - Assert.notEqual(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 2); + Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID); ltm.forgetUsedTheme("x1"); - Assert.equal(ltm.usedThemes.length, 0); - Assert.equal(ltm.currentTheme, null); + Assert.equal(ltm.usedThemes.length, 1); + Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID); Services.prefs.clearUserPref("lightweightThemes.maxUsedThemes"); @@ -460,16 +462,16 @@ async function run_test() { ltm.currentTheme = dummy("x29"); ltm.currentTheme = dummy("x30"); - Assert.equal(ltm.usedThemes.length, 30); + Assert.equal(ltm.usedThemes.length, 31); ltm.currentTheme = dummy("x31"); - Assert.equal(ltm.usedThemes.length, 30); + Assert.equal(ltm.usedThemes.length, 31); Assert.equal(ltm.getUsedTheme("x1"), null); Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 15); - Assert.equal(ltm.usedThemes.length, 15); + Assert.equal(ltm.usedThemes.length, 16); Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 32); @@ -492,15 +494,15 @@ async function run_test() { ltm.currentTheme = dummy("x32"); - Assert.equal(ltm.usedThemes.length, 32); + Assert.equal(ltm.usedThemes.length, 33); ltm.currentTheme = dummy("x33"); - Assert.equal(ltm.usedThemes.length, 32); + Assert.equal(ltm.usedThemes.length, 33); Services.prefs.clearUserPref("lightweightThemes.maxUsedThemes"); - Assert.equal(ltm.usedThemes.length, 30); + Assert.equal(ltm.usedThemes.length, 31); let usedThemes = ltm.usedThemes; for (let theme of usedThemes) { @@ -509,18 +511,18 @@ async function run_test() { // Check builtInTheme functionality for Bug 1094821 Assert.equal(ltm._builtInThemes.toString(), "[object Map]"); - Assert.equal([...ltm._builtInThemes.entries()].length, 0); - Assert.equal(ltm.usedThemes.length, 0); + Assert.equal([...ltm._builtInThemes.entries()].length, 1); + Assert.equal(ltm.usedThemes.length, 1); ltm.addBuiltInTheme(dummy("builtInTheme0")); - Assert.equal([...ltm._builtInThemes].length, 1); - Assert.equal(ltm.usedThemes.length, 1); - Assert.equal(ltm.usedThemes[0].id, "builtInTheme0"); - - ltm.addBuiltInTheme(dummy("builtInTheme1")); Assert.equal([...ltm._builtInThemes].length, 2); Assert.equal(ltm.usedThemes.length, 2); - Assert.equal(ltm.usedThemes[1].id, "builtInTheme1"); + Assert.equal(ltm.usedThemes[1].id, "builtInTheme0"); + + ltm.addBuiltInTheme(dummy("builtInTheme1")); + Assert.equal([...ltm._builtInThemes].length, 3); + Assert.equal(ltm.usedThemes.length, 3); + Assert.equal(ltm.usedThemes[2].id, "builtInTheme1"); // Clear all and then re-add ltm.clearBuiltInThemes(); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_addonStartup.js b/toolkit/mozapps/extensions/test/xpcshell/test_addonStartup.js index a2f84892d82a..bc434e01bfdf 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_addonStartup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_addonStartup.js @@ -5,19 +5,6 @@ add_task(async function test_XPIStates_invalid_paths() { let startupDatasets = [ { - "app-global": { - "addons": { - "{972ce4c6-7e08-4474-a285-3208198ce6fd}": { - "enabled": true, - "lastModifiedTime": 1, - "path": "{972ce4c6-7e08-4474-a285-3208198ce6fd}", - "type": "theme", - "version": "55.0a1", - } - }, - "checkStartupModifications": true, - "path": "c:\\Program Files\\Mozilla Firefox\\extensions", - }, "app-profile": { "addons": { "xpcshell-something-or-other@mozilla.org": { @@ -35,19 +22,6 @@ add_task(async function test_XPIStates_invalid_paths() { }, }, { - "app-global": { - "addons": { - "{972ce4c6-7e08-4474-a285-3208198ce6fd}": { - "enabled": true, - "lastModifiedTime": 1, - "path": "{972ce4c6-7e08-4474-a285-3208198ce6fd}", - "type": "theme", - "version": "55.0a1", - } - }, - "checkStartupModifications": true, - "path": "c:\\Program Files\\Mozilla Firefox\\extensions", - }, "app-profile": { "addons": { "xpcshell-something-or-other@mozilla.org": { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js b/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js index a9eeb0e4029b..05736d060985 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js @@ -31,7 +31,7 @@ function end_test() { // called async function run_test_1() { let aAddons = await AddonManager.getAddonsByTypes(["extension", "theme", "locale"]); - Assert.equal(aAddons.length, 0); + Assert.equal(aAddons.length, 1); Services.obs.addObserver(function observer() { Services.obs.removeObserver(observer, "addons-background-update-complete"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js index e14380dc0f62..8537026f308c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js @@ -183,16 +183,16 @@ const ADDONS = { }, "theme1@tests.mozilla.org": { - "install.rdf": { - id: "theme1@tests.mozilla.org", - version: "1.0", + manifest: { + manifest_version: 2, name: "Theme 1", - internalName: "classic/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "2", - maxVersion: "2" - }] + version: "1.0", + theme: { images: { headerURL: "example.png" } }, + applications: { + gecko: { + id: "theme1@tests.mozilla.org", + }, + }, }, desiredValues: { isActive: false, diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js b/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js index 070990ce91ad..5581c2433ae5 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js @@ -602,29 +602,6 @@ const ADDONS = [ expected: null, }, - // Theme manifests should ignore aboutURL and optionsURL. - { - "install.rdf": { - id: "bug371495@tests.mozilla.org", - version: "1.0", - type: "4", - internalName: "test/1.0", - optionsURL: "chrome://foo/content/bar.xul", - aboutURL: "chrome://foo/content/bar.xul", - name: "Test theme", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1", - }], - }, - - expected: { - aboutURL: null, - optionsURL: null, - } - }, - // Tests compatibility based on target platforms. // No targetPlatforms so should be compatible diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js b/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js index 2f5d553a4d99..7083edd530a9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js @@ -47,25 +47,6 @@ const ADDONS = { compatible: [false, true, false, true], }, - // Theme - always uses strict compatibility, so is always incompatible - "addon3@tests.mozilla.org": { - "install.rdf": { - id: "addon3@tests.mozilla.org", - version: "1.0", - name: "Test 3", - internalName: "test-theme-3", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.8", - maxVersion: "0.9" - }] - }, - expected: { - strictCompatibility: true, - }, - compatible: [false, false, false, false], - }, - // Opt-in to strict compatibility - always incompatible "addon4@tests.mozilla.org": { "install.rdf": { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js index 5a1822015c0f..fc3cff9bd83d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js @@ -8,11 +8,12 @@ * Coverage may overlap with other tests in this folder. */ -const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", {}); +ChromeUtils.defineModuleGetter(this, "LightweightThemeManager", + "resource://gre/modules/LightweightThemeManager.jsm"); const THEME_IDS = [ "theme3@tests.mozilla.org", "theme2@personas.mozilla.org", - "default@tests.mozilla.org" + "default-theme@mozilla.org", ]; const DEFAULT_THEME = THEME_IDS[2]; @@ -37,19 +38,6 @@ add_task(async function setup_to_default_browserish_state() { } } }, profileDir); - // We need a default theme for some of these things to work but we have hidden - // the one in the application directory. - writeInstallRDFForExtension({ - id: DEFAULT_THEME, - version: "1.0", - name: "Default", - internalName: "classic/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); startupManager(); diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser.ini b/toolkit/mozapps/extensions/test/xpinstall/browser.ini index d331a8ca3b4f..7b0c85acd534 100644 --- a/toolkit/mozapps/extensions/test/xpinstall/browser.ini +++ b/toolkit/mozapps/extensions/test/xpinstall/browser.ini @@ -25,7 +25,6 @@ support-files = signed.xpi slowinstall.sjs startsoftwareupdate.html - theme.xpi triggerredirect.html unsigned.xpi diff --git a/toolkit/mozapps/extensions/test/xpinstall/theme.xpi b/toolkit/mozapps/extensions/test/xpinstall/theme.xpi deleted file mode 100644 index 74e650b4a8e01d96611d73cc27a597d14ee9dc23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4450 zcma)AcQhQ{ww_^>D2XH(Eqa773?tEN!sxw>62c(Snb8K(MfBc9jdCSK^obx5(MIp1 zhKLeG&*&ca-SzIhzjxnSZ=bdQIqU3iedp|b{`fu}bz%@L001BdM8`Z)Nf?Q<1yTb5 zCNu!R)vl(JfdWKTOIgU-)z{w1$x0A~wwg7@h>xByiA8^4?w=&Yq}cYlZoJu09B1Lg zXAMri{P6PHBV@N0C%%S_(*z}8OR1@-ncm&aTJ~oNW<~n)3)} z0rP~TmOKImcq>{WMl0u?`kW2XP!L*GKoStCL`+3w#Q>0Gr~|=3y^l0Wpr_oyDAkxJ zfXI(`*cE!v98YN&sE9LF6%u~|M)Cw`z82^sRtW4gXkIGdL$R;hKxudoYcRB(Y;k6W zB}{Y&q%HmB3t0r=b3Sl1F%`&h9AP*+i0BFnYF&($;Uv#o$dPel?N_vm1Ie+B-wXu~ z!mrb`s}9Ji@@}zl(~c|jEcDQQifBkViGQx=69QAFPdML;@?ekqF(;>pDca7RyCruh zcRF^y-L^Oj1ZwfbNV7vK!gN2YAeV$#6~iBu9cq(@J)I1)448TSCG7j^OLQkjoTl>3 zahC-!eKTJH%DDJK#-GKFRgqpC9!PasgybuUa=e|QhB=2I?no7lY}F9!64#d?F%>bmBZt~Im`Xz8MnlbQzxXeuHHY1 zxocKtN;7Qht-A9v2bXRx_^*vf$&pjzmcF6WLtolgfg4ve%{s^m;Pm})O6d04d#-2SGLCsV=!8rMI zYyQD%j}O!4a<`4di-bzLBZnvY^J|hm0`m}A{>k|IB6kVf_T+mVT|h5K-sENczEWHH z_IO>n$9AZ7E1K-o+_N}qPW;c(AZYBaUt`O8tb8Vyq%uH?`P!-~Nz>k8Zj;wMAd%QDv$wL4}NE75cHE))Y|Zy!I%3fBy4RGw05#tPn! z%p>d~G=Cl$fX}59x+EK1yAuMGs`xz9$zafv<8;%_eZoi@_Z!M%kh0W*VX$5n`QDGs z0`pzH4;6_it;*--QJ$TXxyeq+r>i7?!{2>5g@DK5Tu1zF+5|gs3)4tUtfZMfq8jI> zWt1*0{l2woFm8)dokL+fP&TWJAt>FlAiLAx*^Sk?3Y$iLIw8I)4jfnM{Hvqj#~i2G zdUAiL=HNuePz#yvCyo>fDuVru6qa}B1SoimI3wImJw1$>o9OIj@lW!d_(}AdGCEtv z0xabWCGW$S79NN~__vmn>h^bw&l^LkBHr9#ASxs}tB~S=-zef`uv33Cpd?+x%DbNR zu2GFmKqI@FsW~L+AnE8u;P0_J{WuDDZTDX7!^P)Sp&#Fi#VH9l*R%Y@vufDK@t$RE z7=JT(CgE5?`r+)ggY!ZCXZvz zJjl3lSk|mpG$zF5Nq?=cLav%!*v7McatJ7Ie@^e&EiLv}q};M|wLT+6MWY=V5Qx*` zQQ7iRYzURU8+z^&1Irec1&JoFKGUMPslnU*wTaZ$#`weS-s$Rdn&Z&?0qU^p2>q8X zkm0JHGJp0ZkO1ys_&N`6g^A}&4xeo!I#rxNZTl_yA>dj)WkNno&W5E;k>q@x-%5Ja zZ86ZWjRbhkcB^0E^N$zTm6#8}eBN%If$0_!@}x!V5UzU={ujK>!*HEHdD|Cu?Wgxh zT#LG5Mt9CcG~=CGzQ#gw#G;pWwCK!KPey;|6gPRl9&R6+%~`GGGwZqV-5vTmZ%&2V zh|M}%5ux5{P?ct*Z|N3K+cxC#a~W8O>v4t>nM+AuS_7D9s}Y+66TJq zL7ETjY$Ap0mO~Y0bw}2da%XWU1KMlfxbNNJxY?C?tC55oe}twJ+2-hq;^B0gpB&yo z1g<(U_(N)b&~V2m_KQ#oL0$H9QY>zt#01 z->b+(jJg@!-@cv`5_rOQ^8K2?zzFPv;+o!}j*z1i-kMH3x+c9R7drUMhUy0Fn*C#M zM#b_2CH-G$EmyVDeg|@tf{WD)45alaX279>RubBCVyn<)wo&MJ&aLbrPr!Eaj>5l)Trx zAPA&Kq{1!{w6m?d%auP00D+E4p==z73t(E`$qs1H4{|oSwitj+j1duOVM$4_d`kn9 z94v*nXZf~Mr_o_06jRc1#|RZ_X%Vdtr6A{*sOBoL!pb9zc+E6N;Ocq1_VIunvY+0G zM0XM0(+3zUD}v&AXOcWoFo2QawTub)@!T*#p`eh3)A)tIbn4J*;FhxJI575Zqbf%7 zjWt zL2n^wGYzqR4_Mvm15fbT@0JMOO*rzpofjd6x^oJ!ntzh9w|ZHqs4{F-6}P^B7mY`>-|yvG6gs4j@^ zJ3a>(&SE?k_i2UZXPTA=q}9X0lFqzCHyCq;YuL2A69~hNPJOBpmYSXqWXbzXZ+&iT z`zRBBoMDN23vJ>VJ`mqzDjnSNXS9SwQEaL1k{&z*65W<;)ht^!?BKDmfaUs@zaO8D zbm$VI(EER7l=RFBg zpjc<_c(^>VN!>npG-$Ed<VK&402yWd_RCij*tNNW zkonSNndk=p+r|r}gDYgGO=COQts*e!^**lM!A7XJVTF^D3PjhewpW%HON(nD_nj@m zWAe~a9vlYBqB^JQM9&1w3|wqPbeVHu5_+sSXItJ6Ulee<*bk-Nn%9IqWPhVK5e%V8 zYqz#qf_?0lG!vs1?q`*cP44_0>@+F%LbAqC*We(NWyUw%o#U)DpULij82wej5Z{E5 z7F^1YFu3lj_oOl9*U#cH1N_3c$aH=lt!vc7I{r^h;IbxU1*$qn`88@@9Ms9~Cn6d{ zdiK!NK;+{>N0?oPv+L`z%BTSox7MkT+Lk3_+{L*l8m5gYPfvGDv9B9f%7x%XrRVp^ zv`_e_ej=RDDZQuk))VvX2Wqjy&uh<0Nyd!|{pY+uO=>Tmw|%E;pctyEp&yQ32j58e z6F@(OgHhZG$l&~*j(*J zn{5#*-uI5;P*Qrf;!?gddwu_PXw8jq3hi>8vRjBO>+Td4pKGRLPEn0i-yXh{NoSeB zB7$;L)0;Sl_4@bq#}V)cHNU4NVL^0K*9n;1C~9ID>;ox})W*g_vTMm`Q8&odyr(K~ z<(@b~KdzH?5AiuPQtY6W)Jw9C_SeBuh-7Yox%{L1Z>3J_@BWcrNjj~v&oH(=6Be;C zIp%q~Gwo@KBW$g?K$YW_N&qrt5Z}emX;chtjqk&(lRDp)JbGSyg6xMO0_8e$Is=-@ zFq^p2AaGRi0Ch7T+nVo#jEtxoISuyjUaiVO>RW*S?AMjVJ?)SYe|afwQ!n%1C9ai= zy`3%EL(ti7PqP=Np$-uoqE?68@9!O`(Swe{zAx^o_iEPZb@lQKs`hpd@vEu+Ky_=7 z@u7QF2ZVYAy1O9tdfT4DhU9fu}_;ibZThOo7 z*PFAax*1}4?CddfNBB9HU?mG#LoEx5R6gaGwsss-r1j?J|1xJ+yfY{=791`)V=nXd zW&ST|j<%!h*BDS2;De}*{TuIHPvJ4oZVm0r`y5LOMZWkpv#f}4^%H*gewHre<1v)Z**06Rs1FxcIij_KR(r8DgCw2SAbU$1pIOR vUn%{w>wiV~>x=(){S`v?jekY>hc$w>(@`fOy*dQCT=bXZA6~jB0Kk6%2{#>_ diff --git a/toolkit/themes/linux/mozapps/jar.mn b/toolkit/themes/linux/mozapps/jar.mn index a519ad6a71f1..39369acac6ba 100644 --- a/toolkit/themes/linux/mozapps/jar.mn +++ b/toolkit/themes/linux/mozapps/jar.mn @@ -8,9 +8,3 @@ toolkit.jar: skin/classic/mozapps/extensions/heart.png (extensions/heart.png) skin/classic/mozapps/profile/profileicon.png (profile/profileicon.png) skin/classic/mozapps/update/updates.css (update/updates.css) - -#if MOZ_BUILD_APP == browser -[browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#endif diff --git a/toolkit/themes/osx/global/jar.mn b/toolkit/themes/osx/global/jar.mn index 977bb58b038f..0b4840168cc9 100644 --- a/toolkit/themes/osx/global/jar.mn +++ b/toolkit/themes/osx/global/jar.mn @@ -82,9 +82,3 @@ toolkit.jar: skin/classic/global/tree/columnpicker.gif (tree/columnpicker.gif) skin/classic/global/tree/folder.png (tree/folder.png) skin/classic/global/tree/folder@2x.png (tree/folder@2x.png) - -#if MOZ_BUILD_APP == browser -[browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#endif diff --git a/toolkit/themes/osx/mozapps/jar.mn b/toolkit/themes/osx/mozapps/jar.mn index d77912b8c4b6..9525c7e2b5bc 100644 --- a/toolkit/themes/osx/mozapps/jar.mn +++ b/toolkit/themes/osx/mozapps/jar.mn @@ -23,9 +23,3 @@ toolkit.jar: * skin/classic/mozapps/update/updates.css (update/updates.css) skin/classic/mozapps/xpinstall/xpinstallConfirm.css (extensions/xpinstallConfirm.css) skin/classic/mozapps/handling/handling.css (handling/handling.css) - -#if MOZ_BUILD_APP == browser -[browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#endif diff --git a/toolkit/themes/shared/non-mac.jar.inc.mn b/toolkit/themes/shared/non-mac.jar.inc.mn index 670cedb6aac6..cf8018f16c20 100644 --- a/toolkit/themes/shared/non-mac.jar.inc.mn +++ b/toolkit/themes/shared/non-mac.jar.inc.mn @@ -51,9 +51,4 @@ skin/classic/mozapps/update/downloadButtons.png (../../windows/mozapps/update/downloadButtons.png) * skin/classic/mozapps/xpinstall/xpinstallConfirm.css (../../windows/mozapps/extensions/xpinstallConfirm.css) -#if MOZ_BUILD_APP == browser -[browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#endif % override chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png chrome://mozapps/skin/extensions/extensionGeneric.svg diff --git a/toolkit/themes/windows/global/jar.mn b/toolkit/themes/windows/global/jar.mn index 13d0b19f8ff6..b43b4f9bf780 100644 --- a/toolkit/themes/windows/global/jar.mn +++ b/toolkit/themes/windows/global/jar.mn @@ -61,12 +61,6 @@ toolkit.jar: skin/classic/global/tree/twisty.svg (tree/twisty.svg) skin/classic/global/tree/twisty-preWin10.svg (tree/twisty-preWin10.svg) -#if MOZ_BUILD_APP == browser -[browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#endif - % override chrome://global/skin/tree/twisty.svg#clsd chrome://global/skin/tree/twisty-preWin10.svg#clsd osversion<=6.3 % override chrome://global/skin/tree/twisty.svg#clsd-rtl chrome://global/skin/tree/twisty-preWin10.svg#clsd-rtl osversion<=6.3 % override chrome://global/skin/tree/twisty.svg#clsd-hover chrome://global/skin/tree/twisty-preWin10.svg#clsd-hover osversion<=6.3 diff --git a/toolkit/themes/windows/mozapps/jar.mn b/toolkit/themes/windows/mozapps/jar.mn index c130802a2598..d05807d67885 100644 --- a/toolkit/themes/windows/mozapps/jar.mn +++ b/toolkit/themes/windows/mozapps/jar.mn @@ -8,9 +8,3 @@ toolkit.jar: skin/classic/mozapps/extensions/heart.png (extensions/heart.png) skin/classic/mozapps/profile/profileicon.png (profile/profileicon.png) skin/classic/mozapps/update/updates.css (update/updates.css) - -#if MOZ_BUILD_APP == browser -[browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES -[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: -#endif From 86b3ec9863d00448e44de30aae81a6276bac5be4 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 20 Apr 2018 17:00:29 -0700 Subject: [PATCH 20/44] Bug 1372694: Part 2 - Remove support for registering non-bootstrapped extension chrome. r=aswan MozReview-Commit-ID: FpTfAHWpTnt --HG-- extra : rebase_source : 0a22166f56a6316957b372f1f45b20822df0d784 extra : amend_source : 0a0d30a995fc032672cf5283627c55b3c26955ac --- .../extensions/AddonManagerStartup.cpp | 80 ------------------- .../mozapps/extensions/AddonManagerStartup.h | 17 ---- .../extensions/amIAddonManagerStartup.idl | 16 ---- .../extensions/internal/AddonTestUtils.jsm | 6 -- toolkit/xre/nsXREDirProvider.cpp | 9 --- 5 files changed, 128 deletions(-) diff --git a/toolkit/mozapps/extensions/AddonManagerStartup.cpp b/toolkit/mozapps/extensions/AddonManagerStartup.cpp index 65ba9d6eb584..c78c74af0e70 100644 --- a/toolkit/mozapps/extensions/AddonManagerStartup.cpp +++ b/toolkit/mozapps/extensions/AddonManagerStartup.cpp @@ -64,7 +64,6 @@ AddonManagerStartup::GetSingleton() } AddonManagerStartup::AddonManagerStartup() - : mInitialized(false) {} @@ -413,8 +412,6 @@ public: Result, nsresult> FullPath(); - NSLocationType LocationType(); - Result UpdateLastModifiedTime(); @@ -441,16 +438,6 @@ Addon::FullPath() return Move(file); } -NSLocationType -Addon::LocationType() -{ - nsString type = GetString("type", "extension"); - if (type.LowerCaseEqualsLiteral("theme")) { - return NS_SKIN_LOCATION; - } - return NS_EXTENSION_LOCATION; -} - Result Addon::UpdateLastModifiedTime() { @@ -508,32 +495,6 @@ InstallLocation::InstallLocation(JSContext* cx, const JS::Value& value) * XPC interfacing *****************************************************************************/ -Result -AddonManagerStartup::AddInstallLocation(Addon& addon) -{ - nsCOMPtr file; - MOZ_TRY_VAR(file, addon.FullPath()); - - nsString path; - MOZ_TRY(file->GetPath(path)); - - auto type = addon.LocationType(); - - if (type == NS_SKIN_LOCATION) { - mThemePaths.AppendElement(file); - } else { - return Ok(); - } - - if (StringTail(path, 4).LowerCaseEqualsLiteral(".xpi")) { - XRE_AddJarManifestLocation(type, file); - } else { - nsCOMPtr manifest = CloneAndAppend(file, "chrome.manifest"); - XRE_AddManifestLocation(type, manifest); - } - return Ok(); -} - nsresult AddonManagerStartup::ReadStartupData(JSContext* cx, JS::MutableHandleValue locations) { @@ -581,34 +542,6 @@ AddonManagerStartup::ReadStartupData(JSContext* cx, JS::MutableHandleValue locat return NS_OK; } -nsresult -AddonManagerStartup::InitializeExtensions(JS::HandleValue locations, JSContext* cx) -{ - NS_ENSURE_FALSE(mInitialized, NS_ERROR_UNEXPECTED); - NS_ENSURE_TRUE(locations.isObject(), NS_ERROR_INVALID_ARG); - - mInitialized = true; - - if (!Preferences::GetBool("extensions.defaultProviders.enabled", true)) { - return NS_OK; - } - - JS::RootedObject locs(cx, &locations.toObject()); - for (auto e1 : PropertyIter(cx, locs)) { - InstallLocation loc(e1); - - for (auto e2 : loc.Addons()) { - Addon addon(e2); - - if (addon.Enabled() && !addon.Bootstrapped()) { - Unused << AddInstallLocation(addon); - } - } - } - - return NS_OK; -} - nsresult AddonManagerStartup::EncodeBlob(JS::HandleValue value, JSContext* cx, JS::MutableHandleValue result) { @@ -706,19 +639,6 @@ AddonManagerStartup::EnumerateZipFile(nsIFile* file, const nsACString& pattern, return NS_OK; } -nsresult -AddonManagerStartup::Reset() -{ - MOZ_RELEASE_ASSERT(xpc::IsInAutomation()); - - mInitialized = false; - - mExtensionPaths.Clear(); - mThemePaths.Clear(); - - return NS_OK; -} - nsresult AddonManagerStartup::InitializeURLPreloader() { diff --git a/toolkit/mozapps/extensions/AddonManagerStartup.h b/toolkit/mozapps/extensions/AddonManagerStartup.h index 64bc0e52a303..feb1a2015918 100644 --- a/toolkit/mozapps/extensions/AddonManagerStartup.h +++ b/toolkit/mozapps/extensions/AddonManagerStartup.h @@ -38,28 +38,11 @@ public: return inst.forget(); } - const nsCOMArray& ExtensionPaths() - { - return mExtensionPaths; - } - - const nsCOMArray& ThemePaths() - { - return mExtensionPaths; - } - private: - Result AddInstallLocation(Addon& addon); - nsIFile* ProfileDir(); nsCOMPtr mProfileDir; - nsCOMArray mExtensionPaths; - nsCOMArray mThemePaths; - - bool mInitialized; - protected: virtual ~AddonManagerStartup() = default; }; diff --git a/toolkit/mozapps/extensions/amIAddonManagerStartup.idl b/toolkit/mozapps/extensions/amIAddonManagerStartup.idl index 82d63779986d..d4717a5c60f4 100644 --- a/toolkit/mozapps/extensions/amIAddonManagerStartup.idl +++ b/toolkit/mozapps/extensions/amIAddonManagerStartup.idl @@ -21,13 +21,6 @@ interface amIAddonManagerStartup : nsISupports [implicit_jscontext] jsval readStartupData(); - /** - * Initializes the chrome registry for the enabled, non-restartless add-on - * in the given state data. - */ - [implicit_jscontext] - void initializeExtensions(in jsval locations); - /** * Registers a set of dynamic chrome registry entries, and returns an object * with a `destruct()` method which must be called in order to unregister @@ -67,15 +60,6 @@ interface amIAddonManagerStartup : nsISupports [optional] out unsigned long count, [retval, array, size_is(count)] out wstring entries); - /** - * Resets the internal state of the startup service, and allows - * initializeExtensions() to be called again. Does *not* fully unregister - * chrome registry locations for previously registered add-ons. - * - * NOT FOR USE OUTSIDE OF UNIT TESTS. - */ - void reset(); - /** * Initializes the URL Preloader. * diff --git a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm index 66a4e0ce96ed..50162510a792 100644 --- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm +++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm @@ -789,12 +789,6 @@ var AddonTestUtils = { Cu.unload("resource://gre/modules/addons/XPIProvider.jsm"); Cu.unload("resource://gre/modules/addons/XPIInstall.jsm"); - // We need to set this in order reset the startup service, which - // is only possible when running in automation. - Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true); - - aomStartup.reset(); - if (shutdownError) throw shutdownError; diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp index 54e3410b63d8..d922620d14bc 100644 --- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -6,7 +6,6 @@ #include "nsAppRunner.h" #include "nsToolkitCompsCID.h" #include "nsXREDirProvider.h" -#include "mozilla/AddonManagerStartup.h" #include "jsapi.h" #include "xpcpublic.h" @@ -867,8 +866,6 @@ nsXREDirProvider::GetFilesInternal(const char* aProperty, LoadDirsIntoArray(mAppBundleDirectories, kAppendNothing, directories); - LoadDirsIntoArray(AddonManagerStartup::GetSingleton().ExtensionPaths(), - kAppendNothing, directories); rv = NS_NewArrayEnumerator(aResult, directories); } @@ -893,9 +890,6 @@ nsXREDirProvider::GetFilesInternal(const char* aProperty, LoadDirsIntoArray(mAppBundleDirectories, kAppendChromeDir, directories); - LoadDirsIntoArray(AddonManagerStartup::GetSingleton().ExtensionPaths(), - kAppendChromeDir, - directories); rv = NS_NewArrayEnumerator(aResult, directories); } @@ -918,9 +912,6 @@ nsXREDirProvider::GetFilesInternal(const char* aProperty, LoadDirsIntoArray(mAppBundleDirectories, kAppendPlugins, directories); - LoadDirsIntoArray(AddonManagerStartup::GetSingleton().ExtensionPaths(), - kAppendPlugins, - directories); if (mProfileDir) { nsCOMArray profileDir; From e115a440b6539f12a473e62aa69f03ae506b9dfa Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 23 Apr 2018 15:03:00 -0700 Subject: [PATCH 21/44] Bug 1456291: Avoid loading the blocklist service before UI is interactive. r=Gijs The TelemetryEnvironment initialization process currently forces a load and initialization of the blocklist service only to check its isLoaded flag. This adds measurable overhead to startup, and without those checks, the service would not be initialized until after first paint. We should defer even checking whether the blocklist is loaded until after startup has finished. MozReview-Commit-ID: 73c4o5oVqze --HG-- extra : rebase_source : f97ad4e9f2c96668b4418a1c7fb47a07bc798da7 extra : amend_source : 874240858529280e88f960e40920f009ccccfcbc --- .../telemetry/TelemetryEnvironment.jsm | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/toolkit/components/telemetry/TelemetryEnvironment.jsm b/toolkit/components/telemetry/TelemetryEnvironment.jsm index 975126eba815..cd2c8864149b 100644 --- a/toolkit/components/telemetry/TelemetryEnvironment.jsm +++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm @@ -513,7 +513,7 @@ EnvironmentAddonBuilder.prototype = { this._pendingTask = (async () => { try { // Gather initial addons details - await this._updateAddons(); + await this._updateAddons(true); if (!this._environment._addonsAreFull) { // The addon database has not been loaded, so listen for the event @@ -628,11 +628,15 @@ EnvironmentAddonBuilder.prototype = { * This should only be called from _pendingTask; otherwise we risk * running this during addon manager shutdown. * + * @param {boolean} [atStartup] + * True if this is the first check we're performing at startup. In that + * situation, we defer some more expensive initialization. + * * @returns Promise This returns a Promise resolved with a status object with the following members: * changed - Whether the environment changed. * oldEnvironment - Only set if a change occured, contains the environment data before the change. */ - async _updateAddons() { + async _updateAddons(atStartup) { this._environment._log.trace("_updateAddons"); let personaId = null; let theme = LightweightThemeManager.currentTheme; @@ -643,8 +647,8 @@ EnvironmentAddonBuilder.prototype = { let addons = { activeAddons: await this._getActiveAddons(), theme: await this._getActiveTheme(), - activePlugins: this._getActivePlugins(), - activeGMPlugins: await this._getActiveGMPlugins(), + activePlugins: this._getActivePlugins(atStartup), + activeGMPlugins: await this._getActiveGMPlugins(atStartup), activeExperiment: {}, persona: personaId, }; @@ -752,12 +756,17 @@ EnvironmentAddonBuilder.prototype = { /** * Get the plugins data in object form. + * + * @param {boolean} [atStartup] + * True if this is the first check we're performing at startup. In that + * situation, we defer some more expensive initialization. + * * @return Object containing the plugins data. */ - _getActivePlugins() { + _getActivePlugins(atStartup) { // If we haven't yet loaded the blocklist, pass back dummy data for now, // and add an observer to update this data as soon as we get it. - if (!Services.blocklist.isLoaded) { + if (atStartup || !Services.blocklist.isLoaded) { if (!this._blocklistObserverAdded) { Services.obs.addObserver(this, BLOCKLIST_LOADED_TOPIC); this._blocklistObserverAdded = true; @@ -804,15 +813,20 @@ EnvironmentAddonBuilder.prototype = { /** * Get the GMPlugins data in object form. + * + * @param {boolean} [atStartup] + * True if this is the first check we're performing at startup. In that + * situation, we defer some more expensive initialization. + * * @return Object containing the GMPlugins data. * * This should only be called from _pendingTask; otherwise we risk * running this during addon manager shutdown. */ - async _getActiveGMPlugins() { + async _getActiveGMPlugins(atStartup) { // If we haven't yet loaded the blocklist, pass back dummy data for now, // and add an observer to update this data as soon as we get it. - if (!Services.blocklist.isLoaded) { + if (atStartup || !Services.blocklist.isLoaded) { if (!this._blocklistObserverAdded) { Services.obs.addObserver(this, BLOCKLIST_LOADED_TOPIC); this._blocklistObserverAdded = true; From beeee1a02314d09e617b275d2a29c6301df36f54 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 21 Apr 2018 18:04:22 -0700 Subject: [PATCH 22/44] Bug 1363925: Part 2 - Support inferring array length from typed arrays. r=mccr8 MozReview-Commit-ID: FI5ggNE68v7 --HG-- extra : rebase_source : ed20f55c5497486863b89c5b6f6f342c7d938d6a extra : histedit_source : 99c7eee864fd45fdea64f48a1d056e467ad2f56d --- js/xpconnect/src/XPCWrappedNative.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index 1ee21f30b47e..3dd33d7db3f6 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -1351,10 +1351,15 @@ CallMethodHelper::GetArraySizeFromParam(uint8_t paramIndex, : nullptr); bool isArray; - if (!JS_IsArrayObject(mCallContext, maybeArray, &isArray) || - !isArray || - !JS_GetArrayLength(mCallContext, arrayOrNull, &GetDispatchParam(paramIndex)->val.u32)) - { + bool ok = false; + if (JS_IsArrayObject(mCallContext, maybeArray, &isArray) && isArray) { + ok = JS_GetArrayLength(mCallContext, arrayOrNull, &GetDispatchParam(paramIndex)->val.u32); + } else if (JS_IsTypedArrayObject(&maybeArray.toObject())) { + GetDispatchParam(paramIndex)->val.u32 = JS_GetTypedArrayLength(&maybeArray.toObject()); + ok = true; + } + + if (!ok) { return Throw(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY, mCallContext); } } From 33ff77dabf682f9cabe3c4918a36fffdb0fb8b4e Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 21 Apr 2018 18:29:33 -0700 Subject: [PATCH 23/44] Bug 1363925: Part 3 - Move more install logic from XPIProvider to XPIInstall. r=aswan MozReview-Commit-ID: 87PXV43Lpn9 --HG-- extra : rebase_source : dfc38cfb001455243449d7fe0da7f9294e88c8c2 extra : histedit_source : 6e561d0601dcca8da34c926b72e65a126bd40572 --- .../extensions/internal/XPIInstall.jsm | 1016 +++++++++++++++- .../extensions/internal/XPIProvider.jsm | 1076 ++--------------- .../extensions/internal/XPIProviderUtils.js | 15 +- xpcom/io/nsIBinaryOutputStream.idl | 5 +- 4 files changed, 1081 insertions(+), 1031 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index 046b8fc55a95..056ca2f77e17 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -49,8 +49,13 @@ const {nsIBlocklistService} = Ci; const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); + +const BinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", "setOutputStream"); const CryptoHash = Components.Constructor("@mozilla.org/security/hash;1", "nsICryptoHash", "initWithString"); +const FileOutputStream = Components.Constructor("@mozilla.org/network/file-output-stream;1", + "nsIFileOutputStream", "init"); const ZipReader = Components.Constructor("@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open"); @@ -70,14 +75,17 @@ ChromeUtils.defineModuleGetter(this, "XPIProvider", "resource://gre/modules/addons/XPIProvider.jsm"); const PREF_ALLOW_NON_RESTARTLESS = "extensions.legacy.non-restartless.enabled"; +const PREF_DISTRO_ADDONS_PERMS = "extensions.distroAddons.promptForPermissions"; -/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPIDatabase, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */ +/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPIDatabase, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */ const XPI_INTERNAL_SYMBOLS = [ "AddonInternal", "BOOTSTRAP_REASONS", "KEY_APP_SYSTEM_ADDONS", "KEY_APP_SYSTEM_DEFAULTS", "KEY_APP_TEMPORARY", + "PREF_BRANCH_INSTALLED_ADDON", + "PREF_SYSTEM_ADDON_SET", "SIGNED_TYPES", "TEMPORARY_ADDON_SUFFIX", "TOOLKIT_ID", @@ -141,8 +149,14 @@ const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root"; const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; const FILE_WEB_MANIFEST = "manifest.json"; +const KEY_PROFILEDIR = "ProfD"; const KEY_TEMPDIR = "TmpD"; +const KEY_APP_PROFILE = "app-profile"; + +const DIR_STAGE = "staged"; +const DIR_TRASH = "trash"; + const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest"; const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; @@ -391,21 +405,22 @@ XPIPackage = class XPIPackage extends Package { } }; -/** - * Sets permissions on a file - * - * @param aFile - * The file or directory to operate on. - * @param aPermissions - * The permissions to set - */ -function setFilePermissions(aFile, aPermissions) { - try { - aFile.permissions = aPermissions; - } catch (e) { - logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " + - aFile.path, e); - } +// Behaves like Promise.all except waits for all promises to resolve/reject +// before resolving/rejecting itself +function waitForAllPromises(promises) { + return new Promise((resolve, reject) => { + let shouldReject = false; + let rejectValue = null; + + let newPromises = promises.map( + p => p.catch(value => { + shouldReject = true; + rejectValue = value; + }) + ); + Promise.all(newPromises) + .then((results) => shouldReject ? reject(rejectValue) : resolve(results)); + }); } function EM_R(aProperty) { @@ -915,6 +930,14 @@ var loadManifestFromFile = async function(aFile, aInstallLocation, aOldAddon) { } }; +/** + * A synchronous method for loading an add-on's manifest. This should only ever + * be used during startup or a sync load of the add-ons DB + */ +function syncLoadManifestFromFile(aFile, aInstallLocation, aOldAddon) { + return XPIInternal.awaitPromise(loadManifestFromFile(aFile, aInstallLocation, aOldAddon)); +} + function flushChromeCaches() { // Init this, so it will get the notification. Services.obs.notifyObservers(null, "startupcache-invalidate"); @@ -1127,6 +1150,250 @@ function recursiveRemove(aFile) { } } +/** + * Sets permissions on a file + * + * @param aFile + * The file or directory to operate on. + * @param aPermissions + * The permissions to set + */ +function setFilePermissions(aFile, aPermissions) { + try { + aFile.permissions = aPermissions; + } catch (e) { + logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " + + aFile.path, e); + } +} + +/** + * Write a given string to a file + * + * @param file + * The nsIFile instance to write into + * @param string + * The string to write + */ +function writeStringToFile(file, string) { + let fileStream = new FileOutputStream( + file, (FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | + FileUtils.MODE_TRUNCATE), + FileUtils.PERMS_FILE, 0); + + try { + let binStream = new BinaryOutputStream(fileStream); + + binStream.writeByteArray(new TextEncoder().encode(string)); + } finally { + fileStream.close(); + } +} + +/** + * A safe way to install a file or the contents of a directory to a new + * directory. The file or directory is moved or copied recursively and if + * anything fails an attempt is made to rollback the entire operation. The + * operation may also be rolled back to its original state after it has + * completed by calling the rollback method. + * + * Operations can be chained. Calling move or copy multiple times will remember + * the whole set and if one fails all of the operations will be rolled back. + */ +function SafeInstallOperation() { + this._installedFiles = []; + this._createdDirs = []; +} + +SafeInstallOperation.prototype = { + _installedFiles: null, + _createdDirs: null, + + _installFile(aFile, aTargetDirectory, aCopy) { + let oldFile = aCopy ? null : aFile.clone(); + let newFile = aFile.clone(); + try { + if (aCopy) { + newFile.copyTo(aTargetDirectory, null); + // copyTo does not update the nsIFile with the new. + newFile = getFile(aFile.leafName, aTargetDirectory); + // Windows roaming profiles won't properly sync directories if a new file + // has an older lastModifiedTime than a previous file, so update. + newFile.lastModifiedTime = Date.now(); + } else { + newFile.moveTo(aTargetDirectory, null); + } + } catch (e) { + logger.error("Failed to " + (aCopy ? "copy" : "move") + " file " + aFile.path + + " to " + aTargetDirectory.path, e); + throw e; + } + this._installedFiles.push({ oldFile, newFile }); + }, + + _installDirectory(aDirectory, aTargetDirectory, aCopy) { + if (aDirectory.contains(aTargetDirectory)) { + let err = new Error(`Not installing ${aDirectory} into its own descendent ${aTargetDirectory}`); + logger.error(err); + throw err; + } + + let newDir = getFile(aDirectory.leafName, aTargetDirectory); + try { + newDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + } catch (e) { + logger.error("Failed to create directory " + newDir.path, e); + throw e; + } + this._createdDirs.push(newDir); + + // Use a snapshot of the directory contents to avoid possible issues with + // iterating over a directory while removing files from it (the YAFFS2 + // embedded filesystem has this issue, see bug 772238), and to remove + // normal files before their resource forks on OSX (see bug 733436). + let entries = getDirectoryEntries(aDirectory, true); + for (let entry of entries) { + try { + this._installDirEntry(entry, newDir, aCopy); + } catch (e) { + logger.error("Failed to " + (aCopy ? "copy" : "move") + " entry " + + entry.path, e); + throw e; + } + } + + // If this is only a copy operation then there is nothing else to do + if (aCopy) + return; + + // The directory should be empty by this point. If it isn't this will throw + // and all of the operations will be rolled back + try { + setFilePermissions(aDirectory, FileUtils.PERMS_DIRECTORY); + aDirectory.remove(false); + } catch (e) { + logger.error("Failed to remove directory " + aDirectory.path, e); + throw e; + } + + // Note we put the directory move in after all the file moves so the + // directory is recreated before all the files are moved back + this._installedFiles.push({ oldFile: aDirectory, newFile: newDir }); + }, + + _installDirEntry(aDirEntry, aTargetDirectory, aCopy) { + let isDir = null; + + try { + isDir = aDirEntry.isDirectory() && !aDirEntry.isSymlink(); + } catch (e) { + // If the file has already gone away then don't worry about it, this can + // happen on OSX where the resource fork is automatically moved with the + // data fork for the file. See bug 733436. + if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) + return; + + logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path + + " to " + aTargetDirectory.path); + throw e; + } + + try { + if (isDir) + this._installDirectory(aDirEntry, aTargetDirectory, aCopy); + else + this._installFile(aDirEntry, aTargetDirectory, aCopy); + } catch (e) { + logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path + + " to " + aTargetDirectory.path); + throw e; + } + }, + + /** + * Moves a file or directory into a new directory. If an error occurs then all + * files that have been moved will be moved back to their original location. + * + * @param aFile + * The file or directory to be moved. + * @param aTargetDirectory + * The directory to move into, this is expected to be an empty + * directory. + */ + moveUnder(aFile, aTargetDirectory) { + try { + this._installDirEntry(aFile, aTargetDirectory, false); + } catch (e) { + this.rollback(); + throw e; + } + }, + + /** + * Renames a file to a new location. If an error occurs then all + * files that have been moved will be moved back to their original location. + * + * @param aOldLocation + * The old location of the file. + * @param aNewLocation + * The new location of the file. + */ + moveTo(aOldLocation, aNewLocation) { + try { + let oldFile = aOldLocation.clone(), newFile = aNewLocation.clone(); + oldFile.moveTo(newFile.parent, newFile.leafName); + this._installedFiles.push({ oldFile, newFile, isMoveTo: true}); + } catch (e) { + this.rollback(); + throw e; + } + }, + + /** + * Copies a file or directory into a new directory. If an error occurs then + * all new files that have been created will be removed. + * + * @param aFile + * The file or directory to be copied. + * @param aTargetDirectory + * The directory to copy into, this is expected to be an empty + * directory. + */ + copy(aFile, aTargetDirectory) { + try { + this._installDirEntry(aFile, aTargetDirectory, true); + } catch (e) { + this.rollback(); + throw e; + } + }, + + /** + * Rolls back all the moves that this operation performed. If an exception + * occurs here then both old and new directories are left in an indeterminate + * state + */ + rollback() { + while (this._installedFiles.length > 0) { + let move = this._installedFiles.pop(); + if (move.isMoveTo) { + move.newFile.moveTo(move.oldDir.parent, move.oldDir.leafName); + } else if (move.newFile.isDirectory() && !move.newFile.isSymlink()) { + let oldDir = getFile(move.oldFile.leafName, move.oldFile.parent); + oldDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + } else if (!move.oldFile) { + // No old file means this was a copied file + move.newFile.remove(true); + } else { + move.newFile.moveTo(move.oldFile.parent, null); + } + } + + while (this._createdDirs.length > 0) + recursiveRemove(this._createdDirs.pop()); + } +}; + /** * Gets a snapshot of directory entries. * @@ -2636,8 +2903,725 @@ UpdateChecker.prototype = { } }; +/** + * Creates a new AddonInstall to install an add-on from a local file. + * + * @param file + * The file to install + * @param location + * The location to install to + * @returns Promise + * A Promise that resolves with the new install object. + */ +function createLocalInstall(file, location) { + if (!location) { + location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; + } + let url = Services.io.newFileURI(file); + + try { + let install = new LocalAddonInstall(location, url); + return install.init().then(() => install); + } catch (e) { + logger.error("Error creating install", e); + XPIProvider.removeActiveInstall(this); + return Promise.resolve(null); + } +} + +// These are partial classes which contain the install logic for the +// homonymous classes in XPIProvider.jsm. Those classes forward calls to +// their install methods to these classes, with the `this` value set to +// an instance the class as defined in XPIProvider. +class DirectoryInstallLocation {} + +class MutableDirectoryInstallLocation extends DirectoryInstallLocation { + /** + * Gets the staging directory to put add-ons that are pending install and + * uninstall into. + * + * @return an nsIFile + */ + getStagingDir() { + return getFile(DIR_STAGE, this._directory); + } + + requestStagingDir() { + this._stagingDirLock++; + + if (this._stagingDirPromise) + return this._stagingDirPromise; + + OS.File.makeDir(this._directory.path); + let stagepath = OS.Path.join(this._directory.path, DIR_STAGE); + return this._stagingDirPromise = OS.File.makeDir(stagepath).catch((e) => { + if (e instanceof OS.File.Error && e.becauseExists) + return; + logger.error("Failed to create staging directory", e); + throw e; + }); + } + + releaseStagingDir() { + this._stagingDirLock--; + + if (this._stagingDirLock == 0) { + this._stagingDirPromise = null; + this.cleanStagingDir(); + } + + return Promise.resolve(); + } + + /** + * Removes the specified files or directories in the staging directory and + * then if the staging directory is empty attempts to remove it. + * + * @param aLeafNames + * An array of file or directory to remove from the directory, the + * array may be empty + */ + cleanStagingDir(aLeafNames = []) { + let dir = this.getStagingDir(); + + for (let name of aLeafNames) { + let file = getFile(name, dir); + recursiveRemove(file); + } + + if (this._stagingDirLock > 0) + return; + + let dirEntries = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); + try { + if (dirEntries.nextFile) + return; + } finally { + dirEntries.close(); + } + + try { + setFilePermissions(dir, FileUtils.PERMS_DIRECTORY); + dir.remove(false); + } catch (e) { + logger.warn("Failed to remove staging dir", e); + // Failing to remove the staging directory is ignorable + } + } + + /** + * Returns a directory that is normally on the same filesystem as the rest of + * the install location and can be used for temporarily storing files during + * safe move operations. Calling this method will delete the existing trash + * directory and its contents. + * + * @return an nsIFile + */ + getTrashDir() { + let trashDir = getFile(DIR_TRASH, this._directory); + let trashDirExists = trashDir.exists(); + try { + if (trashDirExists) + recursiveRemove(trashDir); + trashDirExists = false; + } catch (e) { + logger.warn("Failed to remove trash directory", e); + } + if (!trashDirExists) + trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + return trashDir; + } + + /** + * Installs an add-on into the install location. + * + * @param id + * The ID of the add-on to install + * @param source + * The source nsIFile to install from + * @param existingAddonID + * The ID of an existing add-on to uninstall at the same time + * @param action + * What to we do with the given source file: + * "move" + * Default action, the source files will be moved to the new + * location, + * "copy" + * The source files will be copied, + * "proxy" + * A "proxy file" is going to refer to the source file path + * @return an nsIFile indicating where the add-on was installed to + */ + installAddon({ id, source, existingAddonID, action = "move" }) { + let trashDir = this.getTrashDir(); + + let transaction = new SafeInstallOperation(); + + let moveOldAddon = aId => { + let file = getFile(aId, this._directory); + if (file.exists()) + transaction.moveUnder(file, trashDir); + + file = getFile(`${aId}.xpi`, this._directory); + if (file.exists()) { + flushJarCache(file); + transaction.moveUnder(file, trashDir); + } + }; + + // If any of these operations fails the finally block will clean up the + // temporary directory + try { + moveOldAddon(id); + if (existingAddonID && existingAddonID != id) { + moveOldAddon(existingAddonID); + + { + // Move the data directories. + /* XXX ajvincent We can't use OS.File: installAddon isn't compatible + * with Promises, nor is SafeInstallOperation. Bug 945540 has been filed + * for porting to OS.File. + */ + let oldDataDir = FileUtils.getDir( + KEY_PROFILEDIR, ["extension-data", existingAddonID], false, true + ); + + if (oldDataDir.exists()) { + let newDataDir = FileUtils.getDir( + KEY_PROFILEDIR, ["extension-data", id], false, true + ); + if (newDataDir.exists()) { + let trashData = getFile("data-directory", trashDir); + transaction.moveUnder(newDataDir, trashData); + } + + transaction.moveTo(oldDataDir, newDataDir); + } + } + } + + if (action == "copy") { + transaction.copy(source, this._directory); + } else if (action == "move") { + if (source.isFile()) + flushJarCache(source); + + transaction.moveUnder(source, this._directory); + } + // Do nothing for the proxy file as we sideload an addon permanently + } finally { + // It isn't ideal if this cleanup fails but it isn't worth rolling back + // the install because of it. + try { + recursiveRemove(trashDir); + } catch (e) { + logger.warn("Failed to remove trash directory when installing " + id, e); + } + } + + let newFile = this._directory.clone(); + + if (action == "proxy") { + // When permanently installing sideloaded addon, we just put a proxy file + // referring to the addon sources + newFile.append(id); + + writeStringToFile(newFile, source.path); + } else { + newFile.append(source.leafName); + } + + try { + newFile.lastModifiedTime = Date.now(); + } catch (e) { + logger.warn("failed to set lastModifiedTime on " + newFile.path, e); + } + this._IDToFileMap[id] = newFile; + + if (existingAddonID && existingAddonID != id && + existingAddonID in this._IDToFileMap) { + delete this._IDToFileMap[existingAddonID]; + } + + return newFile; + } + + /** + * Uninstalls an add-on from this location. + * + * @param aId + * The ID of the add-on to uninstall + * @throws if the ID does not match any of the add-ons installed + */ + uninstallAddon(aId) { + let file = this._IDToFileMap[aId]; + if (!file) { + logger.warn("Attempted to remove " + aId + " from " + + this._name + " but it was already gone"); + return; + } + + file = getFile(aId, this._directory); + if (!file.exists()) + file.leafName += ".xpi"; + + if (!file.exists()) { + logger.warn("Attempted to remove " + aId + " from " + + this._name + " but it was already gone"); + + delete this._IDToFileMap[aId]; + return; + } + + let trashDir = this.getTrashDir(); + + if (file.leafName != aId) { + logger.debug("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId); + flushJarCache(file); + } + + let transaction = new SafeInstallOperation(); + + try { + transaction.moveUnder(file, trashDir); + } finally { + // It isn't ideal if this cleanup fails, but it is probably better than + // rolling back the uninstall at this point + try { + recursiveRemove(trashDir); + } catch (e) { + logger.warn("Failed to remove trash directory when uninstalling " + aId, e); + } + } + + XPIStates.removeAddon(this.name, aId); + + delete this._IDToFileMap[aId]; + } +} + +class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { + /** + * Saves the current set of system add-ons + * + * @param {Object} aAddonSet - object containing schema, directory and set + * of system add-on IDs and versions. + */ + static _saveAddonSet(aAddonSet) { + Services.prefs.setStringPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet)); + } + + static _loadAddonSet() { + return XPIInternal.SystemAddonInstallLocation._loadAddonSet(); + } + + /** + * Gets the staging directory to put add-ons that are pending install and + * uninstall into. + * + * @return {nsIFile} - staging directory for system add-on upgrades. + */ + getStagingDir() { + this._addonSet = SystemAddonInstallLocation._loadAddonSet(); + let dir = null; + if (this._addonSet.directory) { + this._directory = getFile(this._addonSet.directory, this._baseDir); + dir = getFile(DIR_STAGE, this._directory); + } else { + logger.info("SystemAddonInstallLocation directory is missing"); + } + + return dir; + } + + requestStagingDir() { + this._addonSet = SystemAddonInstallLocation._loadAddonSet(); + if (this._addonSet.directory) { + this._directory = getFile(this._addonSet.directory, this._baseDir); + } + return super.requestStagingDir(); + } + + isValidAddon(aAddon) { + if (aAddon.appDisabled) { + logger.warn(`System add-on ${aAddon.id} isn't compatible with the application.`); + return false; + } + + return true; + } + + /** + * Tests whether the loaded add-on information matches what is expected. + */ + isValid(aAddons) { + for (let id of Object.keys(this._addonSet.addons)) { + if (!aAddons.has(id)) { + logger.warn(`Expected add-on ${id} is missing from the system add-on location.`); + return false; + } + + let addon = aAddons.get(id); + if (addon.version != this._addonSet.addons[id].version) { + logger.warn(`Expected system add-on ${id} to be version ${this._addonSet.addons[id].version} but was ${addon.version}.`); + return false; + } + + if (!this.isValidAddon(addon)) + return false; + } + + return true; + } + + /** + * Resets the add-on set so on the next startup the default set will be used. + */ + async resetAddonSet() { + logger.info("Removing all system add-on upgrades."); + + // remove everything from the pref first, if uninstall + // fails then at least they will not be re-activated on + // next restart. + this._addonSet = { schema: 1, addons: {} }; + SystemAddonInstallLocation._saveAddonSet(this._addonSet); + + // If this is running at app startup, the pref being cleared + // will cause later stages of startup to notice that the + // old updates are now gone. + // + // Updates will only be explicitly uninstalled if they are + // removed restartlessly, for instance if they are no longer + // part of the latest update set. + if (this._addonSet) { + let ids = Object.keys(this._addonSet.addons); + for (let addon of await AddonManager.getAddonsByIDs(ids)) { + if (addon) { + addon.uninstall(); + } + } + } + } + + /** + * Removes any directories not currently in use or pending use after a + * restart. Any errors that happen here don't really matter as we'll attempt + * to cleanup again next time. + */ + async cleanDirectories() { + // System add-ons directory does not exist + if (!(await OS.File.exists(this._baseDir.path))) { + return; + } + + let iterator; + try { + iterator = new OS.File.DirectoryIterator(this._baseDir.path); + } catch (e) { + logger.error("Failed to clean updated system add-ons directories.", e); + return; + } + + try { + for (;;) { + let {value: entry, done} = await iterator.next(); + if (done) { + break; + } + + // Skip the directory currently in use + if (this._directory && this._directory.path == entry.path) { + continue; + } + + // Skip the next directory + if (this._nextDir && this._nextDir.path == entry.path) { + continue; + } + + if (entry.isDir) { + await OS.File.removeDir(entry.path, { + ignoreAbsent: true, + ignorePermissions: true, + }); + } else { + await OS.File.remove(entry.path, { + ignoreAbsent: true, + }); + } + } + + } catch (e) { + logger.error("Failed to clean updated system add-ons directories.", e); + } finally { + iterator.close(); + } + } + + /** + * Installs a new set of system add-ons into the location and updates the + * add-on set in prefs. + * + * @param {Array} aAddons - An array of addons to install. + */ + async installAddonSet(aAddons) { + // Make sure the base dir exists + await OS.File.makeDir(this._baseDir.path, { ignoreExisting: true }); + + let addonSet = SystemAddonInstallLocation._loadAddonSet(); + + // Remove any add-ons that are no longer part of the set. + for (let addonID of Object.keys(addonSet.addons)) { + if (!aAddons.includes(addonID)) { + AddonManager.getAddonByID(addonID).then(a => a.uninstall()); + } + } + + let newDir = this._baseDir.clone(); + + let uuidGen = Cc["@mozilla.org/uuid-generator;1"]. + getService(Ci.nsIUUIDGenerator); + newDir.append("blank"); + + while (true) { + newDir.leafName = uuidGen.generateUUID().toString(); + + try { + await OS.File.makeDir(newDir.path, { ignoreExisting: false }); + break; + } catch (e) { + logger.debug("Could not create new system add-on updates dir, retrying", e); + } + } + + // Record the new upgrade directory. + let state = { schema: 1, directory: newDir.leafName, addons: {} }; + SystemAddonInstallLocation._saveAddonSet(state); + + this._nextDir = newDir; + let location = this; + + let installs = []; + for (let addon of aAddons) { + let install = await createLocalInstall(addon._sourceBundle, location); + installs.push(install); + } + + async function installAddon(install) { + // Make the new install own its temporary file. + install.ownsTempFile = true; + install.install(); + } + + async function postponeAddon(install) { + let resumeFn; + if (AddonManagerPrivate.hasUpgradeListener(install.addon.id)) { + logger.info(`system add-on ${install.addon.id} has an upgrade listener, postponing upgrade set until restart`); + resumeFn = () => { + logger.info(`${install.addon.id} has resumed a previously postponed addon set`); + install.installLocation.resumeAddonSet(installs); + }; + } + await install.postpone(resumeFn); + } + + let previousState; + + try { + // All add-ons in position, create the new state and store it in prefs + state = { schema: 1, directory: newDir.leafName, addons: {} }; + for (let addon of aAddons) { + state.addons[addon.id] = { + version: addon.version + }; + } + + previousState = SystemAddonInstallLocation._loadAddonSet(); + SystemAddonInstallLocation._saveAddonSet(state); + + let blockers = aAddons.filter( + addon => AddonManagerPrivate.hasUpgradeListener(addon.id) + ); + + if (blockers.length > 0) { + await waitForAllPromises(installs.map(postponeAddon)); + } else { + await waitForAllPromises(installs.map(installAddon)); + } + } catch (e) { + // Roll back to previous upgrade set (if present) on restart. + if (previousState) { + SystemAddonInstallLocation._saveAddonSet(previousState); + } + // Otherwise, roll back to built-in set on restart. + // TODO try to do these restartlessly + this.resetAddonSet(); + + try { + await OS.File.removeDir(newDir.path, { ignorePermissions: true }); + } catch (e) { + logger.warn(`Failed to remove failed system add-on directory ${newDir.path}.`, e); + } + throw e; + } + } + + /** + * Resumes upgrade of a previously-delayed add-on set. + */ + async resumeAddonSet(installs) { + async function resumeAddon(install) { + install.state = AddonManager.STATE_DOWNLOADED; + install.installLocation.releaseStagingDir(); + install.install(); + } + + let blockers = installs.filter( + install => AddonManagerPrivate.hasUpgradeListener(install.addon.id) + ); + + if (blockers.length > 1) { + logger.warn("Attempted to resume system add-on install but upgrade blockers are still present"); + } else { + await waitForAllPromises(installs.map(resumeAddon)); + } + } + + /** + * Returns a directory that is normally on the same filesystem as the rest of + * the install location and can be used for temporarily storing files during + * safe move operations. Calling this method will delete the existing trash + * directory and its contents. + * + * @return an nsIFile + */ + getTrashDir() { + let trashDir = getFile(DIR_TRASH, this._directory); + let trashDirExists = trashDir.exists(); + try { + if (trashDirExists) + recursiveRemove(trashDir); + trashDirExists = false; + } catch (e) { + logger.warn("Failed to remove trash directory", e); + } + if (!trashDirExists) + trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + return trashDir; + } + + /** + * Installs an add-on into the install location. + * + * @param id + * The ID of the add-on to install + * @param source + * The source nsIFile to install from + * @return an nsIFile indicating where the add-on was installed to + */ + installAddon({id, source}) { + let trashDir = this.getTrashDir(); + let transaction = new SafeInstallOperation(); + + // If any of these operations fails the finally block will clean up the + // temporary directory + try { + if (source.isFile()) { + flushJarCache(source); + } + + transaction.moveUnder(source, this._directory); + } finally { + // It isn't ideal if this cleanup fails but it isn't worth rolling back + // the install because of it. + try { + recursiveRemove(trashDir); + } catch (e) { + logger.warn("Failed to remove trash directory when installing " + id, e); + } + } + + let newFile = getFile(source.leafName, this._directory); + + try { + newFile.lastModifiedTime = Date.now(); + } catch (e) { + logger.warn("failed to set lastModifiedTime on " + newFile.path, e); + } + this._IDToFileMap[id] = newFile; + + return newFile; + } + + // old system add-on upgrade dirs get automatically removed + uninstallAddon(aAddon) {} +} + var XPIInstall = { + createLocalInstall, flushChromeCaches, flushJarCache, recursiveRemove, + syncLoadManifestFromFile, + + /** + * @param {string} id + * The expected ID of the add-on. + * @param {nsIFile} file + * The XPI file to install the add-on from. + * @param {InstallLocation} location + * The install location to install the add-on to. + * @returns {AddonInternal} + * The installed Addon object, upon success. + */ + async installDistributionAddon(id, file, location) { + let addon = await loadManifestFromFile(file, location); + + if (addon.id != id) { + throw new Error(`File file ${file.path} contains an add-on with an incorrect ID`); + } + + let existingEntry = null; + try { + existingEntry = location.getLocationForID(id); + } catch (e) { + } + + if (existingEntry) { + try { + let existingAddon = await loadManifestFromFile(existingEntry, location); + + if (Services.vc.compare(addon.version, existingAddon.version) <= 0) + return null; + } catch (e) { + // Bad add-on in the profile so just proceed and install over the top + logger.warn("Profile contains an add-on with a bad or missing install " + + `manifest at ${existingEntry.path}, overwriting`, e); + } + } else if (Services.prefs.getBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, false)) { + return null; + } + + // Install the add-on + addon._sourceBundle = location.installAddon({ id, source: file, action: "copy" }); + if (Services.prefs.getBoolPref(PREF_DISTRO_ADDONS_PERMS, false)) { + addon.userDisabled = true; + if (!XPIProvider.newDistroAddons) { + XPIProvider.newDistroAddons = new Set(); + } + XPIProvider.newDistroAddons.add(id); + } + + XPIStates.addAddon(addon); + logger.debug("Installed distribution add-on " + id); + + Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true); + + return addon; + }, + + MutableDirectoryInstallLocation, + SystemAddonInstallLocation, }; diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index f30229d3795d..691832318527 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -20,8 +20,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { Langpack: "resource://gre/modules/Extension.jsm", LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm", FileUtils: "resource://gre/modules/FileUtils.jsm", - ZipUtils: "resource://gre/modules/ZipUtils.jsm", - NetUtil: "resource://gre/modules/NetUtil.jsm", PermissionsUtils: "resource://gre/modules/PermissionsUtils.jsm", OS: "resource://gre/modules/osfile.jsm", ConsoleAPI: "resource://gre/modules/Console.jsm", @@ -77,7 +75,6 @@ const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall."; const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin"; const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons"; const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon."; -const PREF_DISTRO_ADDONS_PERMS = "extensions.distroAddons.promptForPermissions"; const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet"; const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url"; const PREF_ALLOW_LEGACY = "extensions.legacy.enabled"; @@ -289,10 +286,8 @@ function loadLazyObjects() { AddonInternal, XPIProvider, XPIStates, - syncLoadManifestFromFile, isUsableAddon, recordAddonTelemetry, - flushChromeCaches: XPIInstall.flushChromeCaches, descriptorToPath, }); @@ -316,6 +311,35 @@ LAZY_OBJECTS.forEach(name => { }); }); +/** + * Spins the event loop until the given promise resolves, and then eiter returns + * its success value or throws its rejection value. + * + * @param {Promise} promise + * The promise to await. + * @returns {any} + * The promise's resolution value, if any. + */ +function awaitPromise(promise) { + let success = undefined; + let result = null; + + promise.then(val => { + success = true; + result = val; + }, val => { + success = false; + result = val; + }); + + Services.tm.spinEventLoopUntil(() => success !== undefined); + + if (!success) + throw result; + return result; + +} + /** * Returns a nsIFile instance for the given path, relative to the given * base file, if provided. @@ -406,25 +430,6 @@ function descriptorToPath(descriptor, dir) { } } - -// Behaves like Promise.all except waits for all promises to resolve/reject -// before resolving/rejecting itself -function waitForAllPromises(promises) { - return new Promise((resolve, reject) => { - let shouldReject = false; - let rejectValue = null; - - let newPromises = promises.map( - p => p.catch(value => { - shouldReject = true; - rejectValue = value; - }) - ); - Promise.all(newPromises) - .then((results) => shouldReject ? reject(rejectValue) : resolve(results)); - }); -} - function findMatchingStaticBlocklistItem(aAddon) { for (let item of STATIC_BLOCKLIST_PATTERNS) { if ("creator" in item && typeof item.creator == "string") { @@ -484,253 +489,6 @@ function isTheme(type) { return gThemeAliases.includes(type); } -/** - * Sets permissions on a file - * - * @param aFile - * The file or directory to operate on. - * @param aPermissions - * The permissions to set - */ -function setFilePermissions(aFile, aPermissions) { - try { - aFile.permissions = aPermissions; - } catch (e) { - logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " + - aFile.path, e); - } -} - -/** - * Write a given string to a file - * - * @param file - * The nsIFile instance to write into - * @param string - * The string to write - */ -function writeStringToFile(file, string) { - let stream = Cc["@mozilla.org/network/file-output-stream;1"]. - createInstance(Ci.nsIFileOutputStream); - let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. - createInstance(Ci.nsIConverterOutputStream); - - try { - stream.init(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | - FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE, - 0); - converter.init(stream, "UTF-8"); - converter.writeString(string); - } finally { - converter.close(); - stream.close(); - } -} - -/** - * A safe way to install a file or the contents of a directory to a new - * directory. The file or directory is moved or copied recursively and if - * anything fails an attempt is made to rollback the entire operation. The - * operation may also be rolled back to its original state after it has - * completed by calling the rollback method. - * - * Operations can be chained. Calling move or copy multiple times will remember - * the whole set and if one fails all of the operations will be rolled back. - */ -function SafeInstallOperation() { - this._installedFiles = []; - this._createdDirs = []; -} - -SafeInstallOperation.prototype = { - _installedFiles: null, - _createdDirs: null, - - _installFile(aFile, aTargetDirectory, aCopy) { - let oldFile = aCopy ? null : aFile.clone(); - let newFile = aFile.clone(); - try { - if (aCopy) { - newFile.copyTo(aTargetDirectory, null); - // copyTo does not update the nsIFile with the new. - newFile = getFile(aFile.leafName, aTargetDirectory); - // Windows roaming profiles won't properly sync directories if a new file - // has an older lastModifiedTime than a previous file, so update. - newFile.lastModifiedTime = Date.now(); - } else { - newFile.moveTo(aTargetDirectory, null); - } - } catch (e) { - logger.error("Failed to " + (aCopy ? "copy" : "move") + " file " + aFile.path + - " to " + aTargetDirectory.path, e); - throw e; - } - this._installedFiles.push({ oldFile, newFile }); - }, - - _installDirectory(aDirectory, aTargetDirectory, aCopy) { - if (aDirectory.contains(aTargetDirectory)) { - let err = new Error(`Not installing ${aDirectory} into its own descendent ${aTargetDirectory}`); - logger.error(err); - throw err; - } - - let newDir = getFile(aDirectory.leafName, aTargetDirectory); - try { - newDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - } catch (e) { - logger.error("Failed to create directory " + newDir.path, e); - throw e; - } - this._createdDirs.push(newDir); - - // Use a snapshot of the directory contents to avoid possible issues with - // iterating over a directory while removing files from it (the YAFFS2 - // embedded filesystem has this issue, see bug 772238), and to remove - // normal files before their resource forks on OSX (see bug 733436). - let entries = getDirectoryEntries(aDirectory, true); - for (let entry of entries) { - try { - this._installDirEntry(entry, newDir, aCopy); - } catch (e) { - logger.error("Failed to " + (aCopy ? "copy" : "move") + " entry " + - entry.path, e); - throw e; - } - } - - // If this is only a copy operation then there is nothing else to do - if (aCopy) - return; - - // The directory should be empty by this point. If it isn't this will throw - // and all of the operations will be rolled back - try { - setFilePermissions(aDirectory, FileUtils.PERMS_DIRECTORY); - aDirectory.remove(false); - } catch (e) { - logger.error("Failed to remove directory " + aDirectory.path, e); - throw e; - } - - // Note we put the directory move in after all the file moves so the - // directory is recreated before all the files are moved back - this._installedFiles.push({ oldFile: aDirectory, newFile: newDir }); - }, - - _installDirEntry(aDirEntry, aTargetDirectory, aCopy) { - let isDir = null; - - try { - isDir = aDirEntry.isDirectory() && !aDirEntry.isSymlink(); - } catch (e) { - // If the file has already gone away then don't worry about it, this can - // happen on OSX where the resource fork is automatically moved with the - // data fork for the file. See bug 733436. - if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) - return; - - logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path + - " to " + aTargetDirectory.path); - throw e; - } - - try { - if (isDir) - this._installDirectory(aDirEntry, aTargetDirectory, aCopy); - else - this._installFile(aDirEntry, aTargetDirectory, aCopy); - } catch (e) { - logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path + - " to " + aTargetDirectory.path); - throw e; - } - }, - - /** - * Moves a file or directory into a new directory. If an error occurs then all - * files that have been moved will be moved back to their original location. - * - * @param aFile - * The file or directory to be moved. - * @param aTargetDirectory - * The directory to move into, this is expected to be an empty - * directory. - */ - moveUnder(aFile, aTargetDirectory) { - try { - this._installDirEntry(aFile, aTargetDirectory, false); - } catch (e) { - this.rollback(); - throw e; - } - }, - - /** - * Renames a file to a new location. If an error occurs then all - * files that have been moved will be moved back to their original location. - * - * @param aOldLocation - * The old location of the file. - * @param aNewLocation - * The new location of the file. - */ - moveTo(aOldLocation, aNewLocation) { - try { - let oldFile = aOldLocation.clone(), newFile = aNewLocation.clone(); - oldFile.moveTo(newFile.parent, newFile.leafName); - this._installedFiles.push({ oldFile, newFile, isMoveTo: true}); - } catch (e) { - this.rollback(); - throw e; - } - }, - - /** - * Copies a file or directory into a new directory. If an error occurs then - * all new files that have been created will be removed. - * - * @param aFile - * The file or directory to be copied. - * @param aTargetDirectory - * The directory to copy into, this is expected to be an empty - * directory. - */ - copy(aFile, aTargetDirectory) { - try { - this._installDirEntry(aFile, aTargetDirectory, true); - } catch (e) { - this.rollback(); - throw e; - } - }, - - /** - * Rolls back all the moves that this operation performed. If an exception - * occurs here then both old and new directories are left in an indeterminate - * state - */ - rollback() { - while (this._installedFiles.length > 0) { - let move = this._installedFiles.pop(); - if (move.isMoveTo) { - move.newFile.moveTo(move.oldDir.parent, move.oldDir.leafName); - } else if (move.newFile.isDirectory() && !move.newFile.isSymlink()) { - let oldDir = getFile(move.oldFile.leafName, move.oldFile.parent); - oldDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - } else if (!move.oldFile) { - // No old file means this was a copied file - move.newFile.remove(true); - } else { - move.newFile.moveTo(move.oldFile.parent, null); - } - } - - while (this._createdDirs.length > 0) - XPIInstall.recursiveRemove(this._createdDirs.pop()); - } -}; - /** * Evaluates whether an add-on is allowed to run in safe mode. * @@ -897,29 +655,6 @@ function getAllAliasesForTypes(aTypes) { return [...typeset]; } -/** - * A synchronous method for loading an add-on's manifest. This should only ever - * be used during startup or a sync load of the add-ons DB - */ -function syncLoadManifestFromFile(aFile, aInstallLocation, aOldAddon) { - let success = undefined; - let result = null; - - loadManifestFromFile(aFile, aInstallLocation, aOldAddon).then(val => { - success = true; - result = val; - }, val => { - success = false; - result = val; - }); - - Services.tm.spinEventLoopUntil(() => success !== undefined); - - if (!success) - throw result; - return result; -} - /** * Gets an nsIURI for a file within another file, either a directory or an XPI * file. If aFile is a directory then this will return a file: URI, if it is an @@ -2641,7 +2376,7 @@ var XPIProvider = { let addon; try { - addon = syncLoadManifestFromFile(source, location); + addon = XPIInstall.syncLoadManifestFromFile(source, location); } catch (e) { logger.error(`Unable to read add-on manifest from ${source.path}`, e); cleanNames.push(source.leafName); @@ -2753,8 +2488,8 @@ var XPIProvider = { let id = entry.leafName; if (entry.isFile()) { - if (id.substring(id.length - 4).toLowerCase() == ".xpi") { - id = id.substring(0, id.length - 4); + if (id.endsWith(".xpi")) { + id = id.slice(0, -4); } else { logger.debug("Ignoring distribution add-on that isn't an XPI: " + entry.path); continue; @@ -2777,65 +2512,18 @@ var XPIProvider = { continue; } - let addon; try { - addon = syncLoadManifestFromFile(entry, profileLocation); - } catch (e) { - logger.warn("File entry " + entry.path + " contains an invalid add-on", e); - continue; - } + let addon = awaitPromise(XPIInstall.installDistributionAddon(id, entry, profileLocation)); - if (addon.id != id) { - logger.warn("File entry " + entry.path + " contains an add-on with an " + - "incorrect ID"); - continue; - } - - let existingEntry = null; - try { - existingEntry = profileLocation.getLocationForID(id); - } catch (e) { - } - - if (existingEntry) { - let existingAddon; - try { - existingAddon = syncLoadManifestFromFile(existingEntry, profileLocation); - - if (Services.vc.compare(addon.version, existingAddon.version) <= 0) - continue; - } catch (e) { - // Bad add-on in the profile so just proceed and install over the top - logger.warn("Profile contains an add-on with a bad or missing install " + - "manifest at " + existingEntry.path + ", overwriting", e); + if (addon) { + // aManifests may contain a copy of a newly installed add-on's manifest + // and we'll have overwritten that so instead cache our install manifest + // which will later be put into the database in processFileChanges + if (!(KEY_APP_PROFILE in aManifests)) + aManifests[KEY_APP_PROFILE] = {}; + aManifests[KEY_APP_PROFILE][id] = addon; + changed = true; } - } else if (Services.prefs.getBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, false)) { - continue; - } - - // Install the add-on - try { - addon._sourceBundle = profileLocation.installAddon({ id, source: entry, action: "copy" }); - if (Services.prefs.getBoolPref(PREF_DISTRO_ADDONS_PERMS, false)) { - addon.userDisabled = true; - if (!this.newDistroAddons) { - this.newDistroAddons = new Set(); - } - this.newDistroAddons.add(id); - } - - XPIStates.addAddon(addon); - logger.debug("Installed distribution add-on " + id); - - Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true); - - // aManifests may contain a copy of a newly installed add-on's manifest - // and we'll have overwritten that so instead cache our install manifest - // which will later be put into the database in processFileChanges - if (!(KEY_APP_PROFILE in aManifests)) - aManifests[KEY_APP_PROFILE] = {}; - aManifests[KEY_APP_PROFILE][id] = addon; - changed = true; } catch (e) { logger.error("Failed to install distribution add-on " + entry.path, e); } @@ -3143,7 +2831,7 @@ var XPIProvider = { * The file to be installed */ async getInstallForFile(aFile) { - let install = await createLocalInstall(aFile); + let install = await XPIInstall.createLocalInstall(aFile); return install ? install.wrapper : null; }, @@ -4168,32 +3856,6 @@ var XPIProvider = { } }; -/** - * Creates a new AddonInstall to install an add-on from a local file. - * - * @param file - * The file to install - * @param location - * The location to install to - * @returns Promise - * A Promise that resolves with the new install object. - */ -function createLocalInstall(file, location) { - if (!location) { - location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; - } - let url = Services.io.newFileURI(file); - - try { - let install = new LocalAddonInstall(location, url); - return install.init().then(() => install); - } catch (e) { - logger.error("Error creating install", e); - XPIProvider.removeActiveInstall(this); - return Promise.resolve(null); - } -} - // Maps instances of AddonInternal to AddonWrapper const wrapperMap = new WeakMap(); let addonFor = wrapper => wrapperMap.get(wrapper); @@ -5202,6 +4864,14 @@ PROP_LOCALE_MULTI.forEach(function(aProp) { }); }); +function forwardInstallMethods(cls, methods) { + for (let meth of methods) { + cls.prototype[meth] = function() { + return XPIInstall[cls.name].prototype[meth].apply(this, arguments); + }; + } +} + /** * An object which identifies a directory install location for add-ons. The * location consists of a directory which contains the add-ons installed in the @@ -5431,271 +5101,11 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { this.locked = false; this._stagingDirLock = 0; } - - /** - * Gets the staging directory to put add-ons that are pending install and - * uninstall into. - * - * @return an nsIFile - */ - getStagingDir() { - return getFile(DIR_STAGE, this._directory); - } - - requestStagingDir() { - this._stagingDirLock++; - - if (this._stagingDirPromise) - return this._stagingDirPromise; - - OS.File.makeDir(this._directory.path); - let stagepath = OS.Path.join(this._directory.path, DIR_STAGE); - return this._stagingDirPromise = OS.File.makeDir(stagepath).catch((e) => { - if (e instanceof OS.File.Error && e.becauseExists) - return; - logger.error("Failed to create staging directory", e); - throw e; - }); - } - - releaseStagingDir() { - this._stagingDirLock--; - - if (this._stagingDirLock == 0) { - this._stagingDirPromise = null; - this.cleanStagingDir(); - } - - return Promise.resolve(); - } - - /** - * Removes the specified files or directories in the staging directory and - * then if the staging directory is empty attempts to remove it. - * - * @param aLeafNames - * An array of file or directory to remove from the directory, the - * array may be empty - */ - cleanStagingDir(aLeafNames = []) { - let dir = this.getStagingDir(); - - for (let name of aLeafNames) { - let file = getFile(name, dir); - XPIInstall.recursiveRemove(file); - } - - if (this._stagingDirLock > 0) - return; - - let dirEntries = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - try { - if (dirEntries.nextFile) - return; - } finally { - dirEntries.close(); - } - - try { - setFilePermissions(dir, FileUtils.PERMS_DIRECTORY); - dir.remove(false); - } catch (e) { - logger.warn("Failed to remove staging dir", e); - // Failing to remove the staging directory is ignorable - } - } - - /** - * Returns a directory that is normally on the same filesystem as the rest of - * the install location and can be used for temporarily storing files during - * safe move operations. Calling this method will delete the existing trash - * directory and its contents. - * - * @return an nsIFile - */ - getTrashDir() { - let trashDir = getFile(DIR_TRASH, this._directory); - let trashDirExists = trashDir.exists(); - try { - if (trashDirExists) - XPIInstall.recursiveRemove(trashDir); - trashDirExists = false; - } catch (e) { - logger.warn("Failed to remove trash directory", e); - } - if (!trashDirExists) - trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - - return trashDir; - } - - /** - * Installs an add-on into the install location. - * - * @param id - * The ID of the add-on to install - * @param source - * The source nsIFile to install from - * @param existingAddonID - * The ID of an existing add-on to uninstall at the same time - * @param action - * What to we do with the given source file: - * "move" - * Default action, the source files will be moved to the new - * location, - * "copy" - * The source files will be copied, - * "proxy" - * A "proxy file" is going to refer to the source file path - * @return an nsIFile indicating where the add-on was installed to - */ - installAddon({ id, source, existingAddonID, action = "move" }) { - let trashDir = this.getTrashDir(); - - let transaction = new SafeInstallOperation(); - - let moveOldAddon = aId => { - let file = getFile(aId, this._directory); - if (file.exists()) - transaction.moveUnder(file, trashDir); - - file = getFile(`${aId}.xpi`, this._directory); - if (file.exists()) { - XPIInstall.flushJarCache(file); - transaction.moveUnder(file, trashDir); - } - }; - - // If any of these operations fails the finally block will clean up the - // temporary directory - try { - moveOldAddon(id); - if (existingAddonID && existingAddonID != id) { - moveOldAddon(existingAddonID); - - { - // Move the data directories. - /* XXX ajvincent We can't use OS.File: installAddon isn't compatible - * with Promises, nor is SafeInstallOperation. Bug 945540 has been filed - * for porting to OS.File. - */ - let oldDataDir = FileUtils.getDir( - KEY_PROFILEDIR, ["extension-data", existingAddonID], false, true - ); - - if (oldDataDir.exists()) { - let newDataDir = FileUtils.getDir( - KEY_PROFILEDIR, ["extension-data", id], false, true - ); - if (newDataDir.exists()) { - let trashData = getFile("data-directory", trashDir); - transaction.moveUnder(newDataDir, trashData); - } - - transaction.moveTo(oldDataDir, newDataDir); - } - } - } - - if (action == "copy") { - transaction.copy(source, this._directory); - } else if (action == "move") { - if (source.isFile()) - XPIInstall.flushJarCache(source); - - transaction.moveUnder(source, this._directory); - } - // Do nothing for the proxy file as we sideload an addon permanently - } finally { - // It isn't ideal if this cleanup fails but it isn't worth rolling back - // the install because of it. - try { - XPIInstall.recursiveRemove(trashDir); - } catch (e) { - logger.warn("Failed to remove trash directory when installing " + id, e); - } - } - - let newFile = this._directory.clone(); - - if (action == "proxy") { - // When permanently installing sideloaded addon, we just put a proxy file - // referring to the addon sources - newFile.append(id); - - writeStringToFile(newFile, source.path); - } else { - newFile.append(source.leafName); - } - - try { - newFile.lastModifiedTime = Date.now(); - } catch (e) { - logger.warn("failed to set lastModifiedTime on " + newFile.path, e); - } - this._IDToFileMap[id] = newFile; - - if (existingAddonID && existingAddonID != id && - existingAddonID in this._IDToFileMap) { - delete this._IDToFileMap[existingAddonID]; - } - - return newFile; - } - - /** - * Uninstalls an add-on from this location. - * - * @param aId - * The ID of the add-on to uninstall - * @throws if the ID does not match any of the add-ons installed - */ - uninstallAddon(aId) { - let file = this._IDToFileMap[aId]; - if (!file) { - logger.warn("Attempted to remove " + aId + " from " + - this._name + " but it was already gone"); - return; - } - - file = getFile(aId, this._directory); - if (!file.exists()) - file.leafName += ".xpi"; - - if (!file.exists()) { - logger.warn("Attempted to remove " + aId + " from " + - this._name + " but it was already gone"); - - delete this._IDToFileMap[aId]; - return; - } - - let trashDir = this.getTrashDir(); - - if (file.leafName != aId) { - logger.debug("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId); - XPIInstall.flushJarCache(file); - } - - let transaction = new SafeInstallOperation(); - - try { - transaction.moveUnder(file, trashDir); - } finally { - // It isn't ideal if this cleanup fails, but it is probably better than - // rolling back the uninstall at this point - try { - XPIInstall.recursiveRemove(trashDir); - } catch (e) { - logger.warn("Failed to remove trash directory when uninstalling " + aId, e); - } - } - - XPIStates.removeAddon(this.name, aId); - - delete this._IDToFileMap[aId]; - } } +forwardInstallMethods(MutableDirectoryInstallLocation, + ["cleanStagingDir", "getStagingDir", "getTrashDir", + "installAddon", "releaseStagingDir", "requestStagingDir", + "uninstallAddon"]); /** * An object which identifies a built-in install location for add-ons, such @@ -5786,33 +5196,6 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { this.locked = false; } - /** - * Gets the staging directory to put add-ons that are pending install and - * uninstall into. - * - * @return {nsIFile} - staging directory for system add-on upgrades. - */ - getStagingDir() { - this._addonSet = SystemAddonInstallLocation._loadAddonSet(); - let dir = null; - if (this._addonSet.directory) { - this._directory = getFile(this._addonSet.directory, this._baseDir); - dir = getFile(DIR_STAGE, this._directory); - } else { - logger.info("SystemAddonInstallLocation directory is missing"); - } - - return dir; - } - - requestStagingDir() { - this._addonSet = SystemAddonInstallLocation._loadAddonSet(); - if (this._addonSet.directory) { - this._directory = getFile(this._addonSet.directory, this._baseDir); - } - return super.requestStagingDir(); - } - /** * Reads the current set of system add-ons */ @@ -5832,16 +5215,6 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { return { schema: 1, addons: {} }; } - /** - * Saves the current set of system add-ons - * - * @param {Object} aAddonSet - object containing schema, directory and set - * of system add-on IDs and versions. - */ - static _saveAddonSet(aAddonSet) { - Services.prefs.setStringPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet)); - } - getAddonLocations() { // Updated system add-ons are ignored in safe mode if (Services.appinfo.inSafeMode) { @@ -5866,335 +5239,22 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { isActive() { return this._directory != null; } - - isValidAddon(aAddon) { - if (aAddon.appDisabled) { - logger.warn(`System add-on ${aAddon.id} isn't compatible with the application.`); - return false; - } - - return true; - } - - /** - * Tests whether the loaded add-on information matches what is expected. - */ - isValid(aAddons) { - for (let id of Object.keys(this._addonSet.addons)) { - if (!aAddons.has(id)) { - logger.warn(`Expected add-on ${id} is missing from the system add-on location.`); - return false; - } - - let addon = aAddons.get(id); - if (addon.version != this._addonSet.addons[id].version) { - logger.warn(`Expected system add-on ${id} to be version ${this._addonSet.addons[id].version} but was ${addon.version}.`); - return false; - } - - if (!this.isValidAddon(addon)) - return false; - } - - return true; - } - - /** - * Resets the add-on set so on the next startup the default set will be used. - */ - async resetAddonSet() { - logger.info("Removing all system add-on upgrades."); - - // remove everything from the pref first, if uninstall - // fails then at least they will not be re-activated on - // next restart. - this._addonSet = { schema: 1, addons: {} }; - SystemAddonInstallLocation._saveAddonSet(this._addonSet); - - // If this is running at app startup, the pref being cleared - // will cause later stages of startup to notice that the - // old updates are now gone. - // - // Updates will only be explicitly uninstalled if they are - // removed restartlessly, for instance if they are no longer - // part of the latest update set. - if (this._addonSet) { - let ids = Object.keys(this._addonSet.addons); - for (let addon of await AddonManager.getAddonsByIDs(ids)) { - if (addon) { - addon.uninstall(); - } - } - } - } - - /** - * Removes any directories not currently in use or pending use after a - * restart. Any errors that happen here don't really matter as we'll attempt - * to cleanup again next time. - */ - async cleanDirectories() { - // System add-ons directory does not exist - if (!(await OS.File.exists(this._baseDir.path))) { - return; - } - - let iterator; - try { - iterator = new OS.File.DirectoryIterator(this._baseDir.path); - } catch (e) { - logger.error("Failed to clean updated system add-ons directories.", e); - return; - } - - try { - for (;;) { - let {value: entry, done} = await iterator.next(); - if (done) { - break; - } - - // Skip the directory currently in use - if (this._directory && this._directory.path == entry.path) { - continue; - } - - // Skip the next directory - if (this._nextDir && this._nextDir.path == entry.path) { - continue; - } - - if (entry.isDir) { - await OS.File.removeDir(entry.path, { - ignoreAbsent: true, - ignorePermissions: true, - }); - } else { - await OS.File.remove(entry.path, { - ignoreAbsent: true, - }); - } - } - - } catch (e) { - logger.error("Failed to clean updated system add-ons directories.", e); - } finally { - iterator.close(); - } - } - - /** - * Installs a new set of system add-ons into the location and updates the - * add-on set in prefs. - * - * @param {Array} aAddons - An array of addons to install. - */ - async installAddonSet(aAddons) { - // Make sure the base dir exists - await OS.File.makeDir(this._baseDir.path, { ignoreExisting: true }); - - let addonSet = SystemAddonInstallLocation._loadAddonSet(); - - // Remove any add-ons that are no longer part of the set. - for (let addonID of Object.keys(addonSet.addons)) { - if (!aAddons.includes(addonID)) { - AddonManager.getAddonByID(addonID).then(a => a.uninstall()); - } - } - - let newDir = this._baseDir.clone(); - - let uuidGen = Cc["@mozilla.org/uuid-generator;1"]. - getService(Ci.nsIUUIDGenerator); - newDir.append("blank"); - - while (true) { - newDir.leafName = uuidGen.generateUUID().toString(); - - try { - await OS.File.makeDir(newDir.path, { ignoreExisting: false }); - break; - } catch (e) { - logger.debug("Could not create new system add-on updates dir, retrying", e); - } - } - - // Record the new upgrade directory. - let state = { schema: 1, directory: newDir.leafName, addons: {} }; - SystemAddonInstallLocation._saveAddonSet(state); - - this._nextDir = newDir; - let location = this; - - let installs = []; - for (let addon of aAddons) { - let install = await createLocalInstall(addon._sourceBundle, location); - installs.push(install); - } - - async function installAddon(install) { - // Make the new install own its temporary file. - install.ownsTempFile = true; - install.install(); - } - - async function postponeAddon(install) { - let resumeFn; - if (AddonManagerPrivate.hasUpgradeListener(install.addon.id)) { - logger.info(`system add-on ${install.addon.id} has an upgrade listener, postponing upgrade set until restart`); - resumeFn = () => { - logger.info(`${install.addon.id} has resumed a previously postponed addon set`); - install.installLocation.resumeAddonSet(installs); - }; - } - await install.postpone(resumeFn); - } - - let previousState; - - try { - // All add-ons in position, create the new state and store it in prefs - state = { schema: 1, directory: newDir.leafName, addons: {} }; - for (let addon of aAddons) { - state.addons[addon.id] = { - version: addon.version - }; - } - - previousState = SystemAddonInstallLocation._loadAddonSet(); - SystemAddonInstallLocation._saveAddonSet(state); - - let blockers = aAddons.filter( - addon => AddonManagerPrivate.hasUpgradeListener(addon.id) - ); - - if (blockers.length > 0) { - await waitForAllPromises(installs.map(postponeAddon)); - } else { - await waitForAllPromises(installs.map(installAddon)); - } - } catch (e) { - // Roll back to previous upgrade set (if present) on restart. - if (previousState) { - SystemAddonInstallLocation._saveAddonSet(previousState); - } - // Otherwise, roll back to built-in set on restart. - // TODO try to do these restartlessly - this.resetAddonSet(); - - try { - await OS.File.removeDir(newDir.path, { ignorePermissions: true }); - } catch (e) { - logger.warn(`Failed to remove failed system add-on directory ${newDir.path}.`, e); - } - throw e; - } - } - - /** - * Resumes upgrade of a previously-delayed add-on set. - */ - async resumeAddonSet(installs) { - async function resumeAddon(install) { - install.state = AddonManager.STATE_DOWNLOADED; - install.installLocation.releaseStagingDir(); - install.install(); - } - - let blockers = installs.filter( - install => AddonManagerPrivate.hasUpgradeListener(install.addon.id) - ); - - if (blockers.length > 1) { - logger.warn("Attempted to resume system add-on install but upgrade blockers are still present"); - } else { - await waitForAllPromises(installs.map(resumeAddon)); - } - } - - /** - * Returns a directory that is normally on the same filesystem as the rest of - * the install location and can be used for temporarily storing files during - * safe move operations. Calling this method will delete the existing trash - * directory and its contents. - * - * @return an nsIFile - */ - getTrashDir() { - let trashDir = getFile(DIR_TRASH, this._directory); - let trashDirExists = trashDir.exists(); - try { - if (trashDirExists) - XPIInstall.recursiveRemove(trashDir); - trashDirExists = false; - } catch (e) { - logger.warn("Failed to remove trash directory", e); - } - if (!trashDirExists) - trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - - return trashDir; - } - - /** - * Installs an add-on into the install location. - * - * @param id - * The ID of the add-on to install - * @param source - * The source nsIFile to install from - * @return an nsIFile indicating where the add-on was installed to - */ - installAddon({id, source}) { - let trashDir = this.getTrashDir(); - let transaction = new SafeInstallOperation(); - - // If any of these operations fails the finally block will clean up the - // temporary directory - try { - if (source.isFile()) { - XPIInstall.flushJarCache(source); - } - - transaction.moveUnder(source, this._directory); - } finally { - // It isn't ideal if this cleanup fails but it isn't worth rolling back - // the install because of it. - try { - XPIInstall.recursiveRemove(trashDir); - } catch (e) { - logger.warn("Failed to remove trash directory when installing " + id, e); - } - } - - let newFile = getFile(source.leafName, this._directory); - - try { - newFile.lastModifiedTime = Date.now(); - } catch (e) { - logger.warn("failed to set lastModifiedTime on " + newFile.path, e); - } - this._IDToFileMap[id] = newFile; - - return newFile; - } - - // old system add-on upgrade dirs get automatically removed - uninstallAddon(aAddon) {} } -/** - * An object which identifies an install location for temporary add-ons. +forwardInstallMethods(SystemAddonInstallLocation, + ["cleanDirectories", "cleanStagingDir", "getStagingDir", + "getTrashDir", "installAddon", "installAddon", + "installAddonSet", "isValid", "isValidAddon", + "releaseStagingDir", "requestStagingDir", + "resetAddonSet", "resumeAddonSet", "uninstallAddon", + "uninstallAddon"]); + +/** An object which identifies an install location for temporary add-ons. */ -const TemporaryInstallLocation = { - locked: false, - name: KEY_APP_TEMPORARY, +const TemporaryInstallLocation = { locked: false, name: KEY_APP_TEMPORARY, scope: AddonManager.SCOPE_TEMPORARY, - getAddonLocations: () => [], - isLinkedAddon: () => false, - installAddon: () => {}, - uninstallAddon: (aAddon) => {}, - getStagingDir: () => {}, + getAddonLocations: () => [], isLinkedAddon: () => false, installAddon: + () => {}, uninstallAddon: (aAddon) => {}, getStagingDir: () => {}, }; /** @@ -6299,10 +5359,14 @@ var XPIInternal = { KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, + PREF_BRANCH_INSTALLED_ADDON, + PREF_SYSTEM_ADDON_SET, SIGNED_TYPES, + SystemAddonInstallLocation, TEMPORARY_ADDON_SUFFIX, TOOLKIT_ID, XPIStates, + awaitPromise, getExternalType, isTheme, isUsableAddon, diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 177c8ad8d82f..c2b578803fb9 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -6,9 +6,9 @@ // These are injected from XPIProvider.jsm /* globals ADDON_SIGNING, SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA, - AddonInternal, XPIProvider, XPIStates, syncLoadManifestFromFile, + AddonInternal, XPIProvider, XPIStates, isUsableAddon, recordAddonTelemetry, - flushChromeCaches, descriptorToPath */ + descriptorToPath */ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -20,6 +20,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { FileUtils: "resource://gre/modules/FileUtils.jsm", OS: "resource://gre/modules/osfile.jsm", Services: "resource://gre/modules/Services.jsm", + XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm", }); ChromeUtils.import("resource://gre/modules/Log.jsm"); @@ -1039,7 +1040,7 @@ this.XPIDatabaseReconcile = { if (!aNewAddon) { // Load the manifest from the add-on. let file = new nsIFile(aAddonState.path); - aNewAddon = syncLoadManifestFromFile(file, aInstallLocation); + aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aInstallLocation); } // The add-on in the manifest should match the add-on ID. if (aNewAddon.id != aId) { @@ -1126,7 +1127,7 @@ this.XPIDatabaseReconcile = { // If there isn't an updated install manifest for this add-on then load it. if (!aNewAddon) { let file = new nsIFile(aAddonState.path); - aNewAddon = syncLoadManifestFromFile(file, aInstallLocation, aOldAddon); + aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aInstallLocation, aOldAddon); } // The ID in the manifest that was loaded must match the ID of the old @@ -1200,7 +1201,7 @@ this.XPIDatabaseReconcile = { if (checkSigning || aReloadMetadata) { try { let file = new nsIFile(aAddonState.path); - manifest = syncLoadManifestFromFile(file, aInstallLocation); + manifest = XPIInstall.syncLoadManifestFromFile(file, aInstallLocation); } catch (err) { // If we can no longer read the manifest, it is no longer compatible. aOldAddon.brokenManifest = true; @@ -1485,7 +1486,7 @@ this.XPIDatabaseReconcile = { } // Make sure to flush the cache when an old add-on has gone away - flushChromeCaches(); + XPIInstall.flushChromeCaches(); if (currentAddon.bootstrap) { // Visible bootstrapped add-ons need to have their install method called @@ -1528,7 +1529,7 @@ this.XPIDatabaseReconcile = { XPIStates.removeAddon(previousAddon.location, id); // Make sure to flush the cache when an old add-on has gone away - flushChromeCaches(); + XPIInstall.flushChromeCaches(); } // Make sure add-ons from hidden locations are marked invisible and inactive diff --git a/xpcom/io/nsIBinaryOutputStream.idl b/xpcom/io/nsIBinaryOutputStream.idl index 4d426d580e9e..c36d0d9df84c 100644 --- a/xpcom/io/nsIBinaryOutputStream.idl +++ b/xpcom/io/nsIBinaryOutputStream.idl @@ -55,13 +55,14 @@ interface nsIBinaryOutputStream : nsIOutputStream { /** * Write an opaque byte array to the stream. */ - void writeBytes([size_is(aLength)] in string aString, in uint32_t aLength); + void writeBytes([size_is(aLength)] in string aString, + [optional] in uint32_t aLength); /** * Write an opaque byte array to the stream. */ void writeByteArray([array, size_is(aLength)] in uint8_t aBytes, - in uint32_t aLength); + [optional] in uint32_t aLength); }; From 58a8659f7778467a320eda4ca83fa0ad0f12eb71 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 21 Apr 2018 19:06:44 -0700 Subject: [PATCH 24/44] Bug 1363925: Part 4 - Move XPIProvider install methods to XPIInstall. r=aswan MozReview-Commit-ID: DiPA01emGA9 --HG-- extra : rebase_source : e7e755c7ced75b2d884e85349989326d57cdd533 extra : histedit_source : e1227a92de4edbb1994c4f8981d4fc7ec46a637e --- .../extensions/internal/XPIInstall.jsm | 621 ++++++++++++++++- .../extensions/internal/XPIProvider.jsm | 651 +----------------- 2 files changed, 637 insertions(+), 635 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index 056ca2f77e17..c4fb42f256d2 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -5,11 +5,8 @@ "use strict"; var EXPORTED_SYMBOLS = [ - "DownloadAddonInstall", - "LocalAddonInstall", "UpdateChecker", "XPIInstall", - "loadManifestFromFile", "verifyBundleSignedState", ]; @@ -36,12 +33,14 @@ ChromeUtils.defineModuleGetter(this, "FileUtils", XPCOMUtils.defineLazyGetter(this, "IconDetails", () => { return ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm", {}).ExtensionParent.IconDetails; }); -ChromeUtils.defineModuleGetter(this, "LightweightThemeManager", - "resource://gre/modules/LightweightThemeManager.jsm"); ChromeUtils.defineModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); +ChromeUtils.defineModuleGetter(this, "ProductAddonChecker", + "resource://gre/modules/addons/ProductAddonChecker.jsm"); +ChromeUtils.defineModuleGetter(this, "UpdateUtils", + "resource://gre/modules/UpdateUtils.jsm"); ChromeUtils.defineModuleGetter(this, "ZipUtils", "resource://gre/modules/ZipUtils.jsm"); @@ -76,8 +75,15 @@ ChromeUtils.defineModuleGetter(this, "XPIProvider", const PREF_ALLOW_NON_RESTARTLESS = "extensions.legacy.non-restartless.enabled"; const PREF_DISTRO_ADDONS_PERMS = "extensions.distroAddons.promptForPermissions"; +const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin"; +const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; +const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url"; +const PREF_XPI_ENABLED = "xpinstall.enabled"; +const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; +const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; +const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; -/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPIDatabase, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */ +/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPIDatabase, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */ const XPI_INTERNAL_SYMBOLS = [ "AddonInternal", "BOOTSTRAP_REASONS", @@ -89,6 +95,7 @@ const XPI_INTERNAL_SYMBOLS = [ "SIGNED_TYPES", "TEMPORARY_ADDON_SUFFIX", "TOOLKIT_ID", + "XPI_PERMISSION", "XPIDatabase", "XPIStates", "getExternalType", @@ -405,6 +412,21 @@ XPIPackage = class XPIPackage extends Package { } }; +/** + * Determine the reason to pass to an extension's bootstrap methods when + * switch between versions. + * + * @param {string} oldVersion The version of the existing extension instance. + * @param {string} newVersion The version of the extension being installed. + * + * @return {BOOSTRAP_REASONS.ADDON_UPGRADE|BOOSTRAP_REASONS.ADDON_DOWNGRADE} + */ +function newVersionReason(oldVersion, newVersion) { + return Services.vc.compare(oldVersion, newVersion) <= 0 ? + BOOTSTRAP_REASONS.ADDON_UPGRADE : + BOOTSTRAP_REASONS.ADDON_DOWNGRADE; +} + // Behaves like Promise.all except waits for all promises to resolve/reject // before resolving/rejecting itself function waitForAllPromises(promises) { @@ -1084,6 +1106,13 @@ function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion) { return uri; } +/** + * Converts an iterable of addon objects into a map with the add-on's ID as key. + */ +function addonMap(addons) { + return new Map(addons.map(a => [a.id, a])); +} + async function removeAsync(aFile) { let info = null; try { @@ -3563,6 +3592,7 @@ var XPIInstall = { createLocalInstall, flushChromeCaches, flushJarCache, + newVersionReason, recursiveRemove, syncLoadManifestFromFile, @@ -3622,6 +3652,585 @@ var XPIInstall = { return addon; }, + async updateSystemAddons() { + let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS]; + if (!systemAddonLocation) + return; + + // Don't do anything in safe mode + if (Services.appinfo.inSafeMode) + return; + + // Download the list of system add-ons + let url = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_UPDATE_URL, null); + if (!url) { + await systemAddonLocation.cleanDirectories(); + return; + } + + url = await UpdateUtils.formatUpdateURL(url); + + logger.info(`Starting system add-on update check from ${url}.`); + let res = await ProductAddonChecker.getProductAddonList(url); + + // If there was no list then do nothing. + if (!res || !res.gmpAddons) { + logger.info("No system add-ons list was returned."); + await systemAddonLocation.cleanDirectories(); + return; + } + + let addonList = new Map( + res.gmpAddons.map(spec => [spec.id, { spec, path: null, addon: null }])); + + let setMatches = (wanted, existing) => { + if (wanted.size != existing.size) + return false; + + for (let [id, addon] of existing) { + let wantedInfo = wanted.get(id); + + if (!wantedInfo) + return false; + if (wantedInfo.spec.version != addon.version) + return false; + } + + return true; + }; + + // If this matches the current set in the profile location then do nothing. + let updatedAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_ADDONS)); + if (setMatches(addonList, updatedAddons)) { + logger.info("Retaining existing updated system add-ons."); + await systemAddonLocation.cleanDirectories(); + return; + } + + // If this matches the current set in the default location then reset the + // updated set. + let defaultAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS)); + if (setMatches(addonList, defaultAddons)) { + logger.info("Resetting system add-ons."); + systemAddonLocation.resetAddonSet(); + await systemAddonLocation.cleanDirectories(); + return; + } + + // Download all the add-ons + async function downloadAddon(item) { + try { + let sourceAddon = updatedAddons.get(item.spec.id); + if (sourceAddon && sourceAddon.version == item.spec.version) { + // Copying the file to a temporary location has some benefits. If the + // file is locked and cannot be read then we'll fall back to + // downloading a fresh copy. It also means we don't have to remember + // whether to delete the temporary copy later. + try { + let path = OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon"); + let unique = await OS.File.openUnique(path); + unique.file.close(); + await OS.File.copy(sourceAddon._sourceBundle.path, unique.path); + // Make sure to update file modification times so this is detected + // as a new add-on. + await OS.File.setDates(unique.path); + item.path = unique.path; + } catch (e) { + logger.warn(`Failed make temporary copy of ${sourceAddon._sourceBundle.path}.`, e); + } + } + if (!item.path) { + item.path = await ProductAddonChecker.downloadAddon(item.spec); + } + item.addon = await loadManifestFromFile(nsIFile(item.path), systemAddonLocation); + } catch (e) { + logger.error(`Failed to download system add-on ${item.spec.id}`, e); + } + } + await Promise.all(Array.from(addonList.values()).map(downloadAddon)); + + // The download promises all resolve regardless, now check if they all + // succeeded + let validateAddon = (item) => { + if (item.spec.id != item.addon.id) { + logger.warn(`Downloaded system add-on expected to be ${item.spec.id} but was ${item.addon.id}.`); + return false; + } + + if (item.spec.version != item.addon.version) { + logger.warn(`Expected system add-on ${item.spec.id} to be version ${item.spec.version} but was ${item.addon.version}.`); + return false; + } + + if (!systemAddonLocation.isValidAddon(item.addon)) + return false; + + return true; + }; + + if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) { + throw new Error("Rejecting updated system add-on set that either could not " + + "be downloaded or contained unusable add-ons."); + } + + // Install into the install location + logger.info("Installing new system add-on set"); + await systemAddonLocation.installAddonSet(Array.from(addonList.values()) + .map(a => a.addon)); + }, + + /** + * Called to test whether installing XPI add-ons is enabled. + * + * @return true if installing is enabled + */ + isInstallEnabled() { + // Default to enabled if the preference does not exist + return Services.prefs.getBoolPref(PREF_XPI_ENABLED, true); + }, + + /** + * Called to test whether installing XPI add-ons by direct URL requests is + * whitelisted. + * + * @return true if installing by direct requests is whitelisted + */ + isDirectRequestWhitelisted() { + // Default to whitelisted if the preference does not exist. + return Services.prefs.getBoolPref(PREF_XPI_DIRECT_WHITELISTED, true); + }, + + /** + * Called to test whether installing XPI add-ons from file referrers is + * whitelisted. + * + * @return true if installing from file referrers is whitelisted + */ + isFileRequestWhitelisted() { + // Default to whitelisted if the preference does not exist. + return Services.prefs.getBoolPref(PREF_XPI_FILE_WHITELISTED, true); + }, + + /** + * Called to test whether installing XPI add-ons from a URI is allowed. + * + * @param aInstallingPrincipal + * The nsIPrincipal that initiated the install + * @return true if installing is allowed + */ + isInstallAllowed(aInstallingPrincipal) { + if (!this.isInstallEnabled()) + return false; + + let uri = aInstallingPrincipal.URI; + + // Direct requests without a referrer are either whitelisted or blocked. + if (!uri) + return this.isDirectRequestWhitelisted(); + + // Local referrers can be whitelisted. + if (this.isFileRequestWhitelisted() && + (uri.schemeIs("chrome") || uri.schemeIs("file"))) + return true; + + XPIProvider.importPermissions(); + + let permission = Services.perms.testPermissionFromPrincipal(aInstallingPrincipal, XPI_PERMISSION); + if (permission == Ci.nsIPermissionManager.DENY_ACTION) + return false; + + let requireWhitelist = Services.prefs.getBoolPref(PREF_XPI_WHITELIST_REQUIRED, true); + if (requireWhitelist && (permission != Ci.nsIPermissionManager.ALLOW_ACTION)) + return false; + + let requireSecureOrigin = Services.prefs.getBoolPref(PREF_INSTALL_REQUIRESECUREORIGIN, true); + let safeSchemes = ["https", "chrome", "file"]; + if (requireSecureOrigin && !safeSchemes.includes(uri.scheme)) + return false; + + return true; + }, + + /** + * Called to get an AddonInstall to download and install an add-on from a URL. + * + * @param aUrl + * The URL to be installed + * @param aHash + * A hash for the install + * @param aName + * A name for the install + * @param aIcons + * Icon URLs for the install + * @param aVersion + * A version for the install + * @param aBrowser + * The browser performing the install + */ + async getInstallForURL(aUrl, aHash, aName, aIcons, aVersion, aBrowser) { + let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; + let url = Services.io.newURI(aUrl); + + let options = { + hash: aHash, + browser: aBrowser, + name: aName, + icons: aIcons, + version: aVersion, + }; + + if (url instanceof Ci.nsIFileURL) { + let install = new LocalAddonInstall(location, url, options); + await install.init(); + return install.wrapper; + } + + let install = new DownloadAddonInstall(location, url, options); + return install.wrapper; + }, + + /** + * Called to get an AddonInstall to install an add-on from a local file. + * + * @param aFile + * The file to be installed + */ + async getInstallForFile(aFile) { + let install = await createLocalInstall(aFile); + return install ? install.wrapper : null; + }, + + /** + * Temporarily installs add-on from a local XPI file or directory. + * As this is intended for development, the signature is not checked and + * the add-on does not persist on application restart. + * + * @param aFile + * An nsIFile for the unpacked add-on directory or XPI file. + * + * @return See installAddonFromLocation return value. + */ + installTemporaryAddon(aFile) { + return this.installAddonFromLocation(aFile, XPIInternal.TemporaryInstallLocation); + }, + + /** + * Permanently installs add-on from a local XPI file or directory. + * The signature is checked but the add-on persist on application restart. + * + * @param aFile + * An nsIFile for the unpacked add-on directory or XPI file. + * + * @return See installAddonFromLocation return value. + */ + async installAddonFromSources(aFile) { + let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; + return this.installAddonFromLocation(aFile, location, "proxy"); + }, + + /** + * Installs add-on from a local XPI file or directory. + * + * @param aFile + * An nsIFile for the unpacked add-on directory or XPI file. + * @param aInstallLocation + * Define a custom install location object to use for the install. + * @param aInstallAction + * Optional action mode to use when installing the addon + * (see MutableDirectoryInstallLocation.installAddon) + * + * @return a Promise that resolves to an Addon object on success, or rejects + * if the add-on is not a valid restartless add-on or if the + * same ID is already installed. + */ + async installAddonFromLocation(aFile, aInstallLocation, aInstallAction) { + if (aFile.exists() && aFile.isFile()) { + flushJarCache(aFile); + } + let addon = await loadManifestFromFile(aFile, aInstallLocation); + + aInstallLocation.installAddon({ id: addon.id, source: aFile, action: aInstallAction }); + + if (addon.appDisabled) { + let message = `Add-on ${addon.id} is not compatible with application version.`; + + let app = addon.matchingTargetApplication; + if (app) { + if (app.minVersion) { + message += ` add-on minVersion: ${app.minVersion}.`; + } + if (app.maxVersion) { + message += ` add-on maxVersion: ${app.maxVersion}.`; + } + } + throw new Error(message); + } + + if (!addon.bootstrap) { + throw new Error(`Only restartless (bootstrap) add-ons can be installed from sources: ${addon.id}`); + } + let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL; + let oldAddon = await XPIDatabase.getVisibleAddonForID(addon.id); + let callUpdate = false; + + let extraParams = {}; + extraParams.temporarilyInstalled = aInstallLocation === XPIInternal.TemporaryInstallLocation; + if (oldAddon) { + if (!oldAddon.bootstrap) { + logger.warn("Non-restartless Add-on is already installed", addon.id); + throw new Error("Non-restartless add-on with ID " + + oldAddon.id + " is already installed"); + } else { + logger.warn("Addon with ID " + oldAddon.id + " already installed," + + " older version will be disabled"); + + addon.installDate = oldAddon.installDate; + + let existingAddonID = oldAddon.id; + let existingAddon = oldAddon._sourceBundle; + + // We'll be replacing a currently active bootstrapped add-on so + // call its uninstall method + let newVersion = addon.version; + let oldVersion = oldAddon.version; + + installReason = newVersionReason(oldVersion, newVersion); + let uninstallReason = installReason; + + extraParams.newVersion = newVersion; + extraParams.oldVersion = oldVersion; + + callUpdate = isWebExtension(oldAddon.type) && isWebExtension(addon.type); + + if (oldAddon.active) { + XPIProvider.callBootstrapMethod(oldAddon, existingAddon, + "shutdown", uninstallReason, + extraParams); + } + + if (!callUpdate) { + XPIProvider.callBootstrapMethod(oldAddon, existingAddon, + "uninstall", uninstallReason, extraParams); + } + XPIProvider.unloadBootstrapScope(existingAddonID); + flushChromeCaches(); + } + } else { + addon.installDate = Date.now(); + } + + let file = addon._sourceBundle; + + let method = callUpdate ? "update" : "install"; + XPIProvider.callBootstrapMethod(addon, file, method, installReason, extraParams); + addon.state = AddonManager.STATE_INSTALLED; + logger.debug("Install of temporary addon in " + aFile.path + " completed."); + addon.visible = true; + addon.enabled = true; + addon.active = true; + // WebExtension themes are installed as disabled, fix that here. + addon.userDisabled = false; + + addon = XPIDatabase.addAddonMetadata(addon, file.path); + + XPIStates.addAddon(addon); + XPIDatabase.saveChanges(); + XPIStates.save(); + + AddonManagerPrivate.callAddonListeners("onInstalling", addon.wrapper, + false); + XPIProvider.callBootstrapMethod(addon, file, "startup", installReason, extraParams); + AddonManagerPrivate.callInstallListeners("onExternalInstall", + null, addon.wrapper, + oldAddon ? oldAddon.wrapper : null, + false); + AddonManagerPrivate.callAddonListeners("onInstalled", addon.wrapper); + + // Notify providers that a new theme has been enabled. + if (isTheme(addon.type)) + AddonManagerPrivate.notifyAddonChanged(addon.id, addon.type, false); + + return addon.wrapper; + }, + + /** + * Uninstalls an add-on, immediately if possible or marks it as pending + * uninstall if not. + * + * @param aAddon + * The DBAddonInternal to uninstall + * @param aForcePending + * Force this addon into the pending uninstall state (used + * e.g. while the add-on manager is open and offering an + * "undo" button) + * @throws if the addon cannot be uninstalled because it is in an install + * location that does not allow it + */ + async uninstallAddon(aAddon, aForcePending) { + if (!(aAddon.inDatabase)) + throw new Error("Cannot uninstall addon " + aAddon.id + " because it is not installed"); + + if (aAddon._installLocation.locked) + throw new Error("Cannot uninstall addon " + aAddon.id + + " from locked install location " + aAddon._installLocation.name); + + if (aForcePending && aAddon.pendingUninstall) + throw new Error("Add-on is already marked to be uninstalled"); + + aAddon._hasResourceCache.clear(); + + if (aAddon._updateCheck) { + logger.debug("Cancel in-progress update check for " + aAddon.id); + aAddon._updateCheck.cancel(); + } + + let wasPending = aAddon.pendingUninstall; + + if (aForcePending) { + // We create an empty directory in the staging directory to indicate + // that an uninstall is necessary on next startup. Temporary add-ons are + // automatically uninstalled on shutdown anyway so there is no need to + // do this for them. + if (aAddon._installLocation.name != KEY_APP_TEMPORARY) { + let stage = getFile(aAddon.id, aAddon._installLocation.getStagingDir()); + if (!stage.exists()) + stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + } + + XPIDatabase.setAddonProperties(aAddon, { + pendingUninstall: true + }); + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id); + if (xpiState) { + xpiState.enabled = false; + XPIStates.save(); + } else { + logger.warn("Can't find XPI state while uninstalling ${id} from ${location}", aAddon); + } + } + + // If the add-on is not visible then there is no need to notify listeners. + if (!aAddon.visible) + return; + + let wrapper = aAddon.wrapper; + + // If the add-on wasn't already pending uninstall then notify listeners. + if (!wasPending) { + AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, + !!aForcePending); + } + + let reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL; + let callUpdate = false; + let existingAddon = XPIStates.findAddon(aAddon.id, loc => + loc.name != aAddon._installLocation.name); + if (existingAddon) { + reason = newVersionReason(aAddon.version, existingAddon.version); + callUpdate = isWebExtension(aAddon.type) && isWebExtension(existingAddon.type); + } + + if (!aForcePending) { + if (aAddon.bootstrap) { + if (aAddon.active) { + XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", + reason); + } + + if (!callUpdate) { + XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall", + reason); + } + XPIStates.disableAddon(aAddon.id); + XPIProvider.unloadBootstrapScope(aAddon.id); + flushChromeCaches(); + } + aAddon._installLocation.uninstallAddon(aAddon.id); + XPIDatabase.removeAddonMetadata(aAddon); + XPIStates.removeAddon(aAddon.location, aAddon.id); + AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); + + if (existingAddon) { + let existing = await XPIDatabase.getAddonInLocation(aAddon.id, existingAddon.location.name); + XPIDatabase.makeAddonVisible(existing); + + let wrappedAddon = existing.wrapper; + AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false); + + if (!existing.disabled) { + XPIDatabase.updateAddonActive(existing, true); + } + + if (aAddon.bootstrap) { + let method = callUpdate ? "update" : "install"; + XPIProvider.callBootstrapMethod(existing, existing._sourceBundle, + method, reason); + + if (existing.active) { + XPIProvider.callBootstrapMethod(existing, existing._sourceBundle, + "startup", reason); + } else { + XPIProvider.unloadBootstrapScope(existing.id); + } + } + + AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon); + } + } else if (aAddon.bootstrap && aAddon.active) { + XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", reason); + XPIStates.disableAddon(aAddon.id); + XPIProvider.unloadBootstrapScope(aAddon.id); + XPIDatabase.updateAddonActive(aAddon, false); + } + + // Notify any other providers that a new theme has been enabled + if (isTheme(aAddon.type) && aAddon.active) + AddonManagerPrivate.notifyAddonChanged(null, aAddon.type); + }, + + /** + * Cancels the pending uninstall of an add-on. + * + * @param aAddon + * The DBAddonInternal to cancel uninstall for + */ + cancelUninstallAddon(aAddon) { + if (!(aAddon.inDatabase)) + throw new Error("Can only cancel uninstall for installed addons."); + if (!aAddon.pendingUninstall) + throw new Error("Add-on is not marked to be uninstalled"); + + if (aAddon._installLocation.name != KEY_APP_TEMPORARY) + aAddon._installLocation.cleanStagingDir([aAddon.id]); + + XPIDatabase.setAddonProperties(aAddon, { + pendingUninstall: false + }); + + if (!aAddon.visible) + return; + + XPIStates.getAddon(aAddon.location, aAddon.id).syncWithDB(aAddon); + XPIStates.save(); + + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + + // TODO hide hidden add-ons (bug 557710) + let wrapper = aAddon.wrapper; + AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper); + + if (aAddon.bootstrap && !aAddon.disabled) { + XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup", + BOOTSTRAP_REASONS.ADDON_INSTALL); + XPIDatabase.updateAddonActive(aAddon, true); + } + + // Notify any other providers that this theme is now enabled again. + if (isTheme(aAddon.type) && aAddon.active) + AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false); + }, + MutableDirectoryInstallLocation, SystemAddonInstallLocation, }; diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 691832318527..599097c0930e 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -23,31 +23,21 @@ XPCOMUtils.defineLazyModuleGetters(this, { PermissionsUtils: "resource://gre/modules/PermissionsUtils.jsm", OS: "resource://gre/modules/osfile.jsm", ConsoleAPI: "resource://gre/modules/Console.jsm", - ProductAddonChecker: "resource://gre/modules/addons/ProductAddonChecker.jsm", - UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", JSONFile: "resource://gre/modules/JSONFile.jsm", LegacyExtensionsUtils: "resource://gre/modules/LegacyExtensionsUtils.jsm", setTimeout: "resource://gre/modules/Timer.jsm", clearTimeout: "resource://gre/modules/Timer.jsm", - DownloadAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm", - LocalAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm", UpdateChecker: "resource://gre/modules/addons/XPIInstall.jsm", XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm", - loadManifestFromFile: "resource://gre/modules/addons/XPIInstall.jsm", verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.jsm", }); const {nsIBlocklistService} = Ci; -XPCOMUtils.defineLazyServiceGetters(this, { - AddonPolicyService: ["@mozilla.org/addons/policy-service;1", "nsIAddonPolicyService"], - aomStartup: ["@mozilla.org/addons/addon-manager-startup;1", "amIAddonManagerStartup"], -}); - -XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => { - return new TextDecoder(); -}); +XPCOMUtils.defineLazyServiceGetter(this, "aomStartup", + "@mozilla.org/addons/addon-manager-startup;1", + "amIAddonManagerStartup"); Cu.importGlobalProperties(["URL"]); @@ -63,20 +53,14 @@ const PREF_EM_EXTENSION_FORMAT = "extensions."; const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes"; const PREF_EM_STARTUP_SCAN_SCOPES = "extensions.startupScanScopes"; const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI"; -const PREF_XPI_ENABLED = "xpinstall.enabled"; -const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; -const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; -const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; // xpinstall.signatures.required only supported in dev builds const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required"; const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root"; const PREF_LANGPACK_SIGNATURES = "extensions.langpacks.signatures.required"; const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall."; -const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin"; const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons"; const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon."; const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet"; -const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url"; const PREF_ALLOW_LEGACY = "extensions.legacy.enabled"; const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; @@ -442,28 +426,6 @@ function findMatchingStaticBlocklistItem(aAddon) { return null; } -/** - * Determine the reason to pass to an extension's bootstrap methods when - * switch between versions. - * - * @param {string} oldVersion The version of the existing extension instance. - * @param {string} newVersion The version of the extension being installed. - * - * @return {BOOSTRAP_REASONS.ADDON_UPGRADE|BOOSTRAP_REASONS.ADDON_DOWNGRADE} - */ -function newVersionReason(oldVersion, newVersion) { - return Services.vc.compare(oldVersion, newVersion) <= 0 ? - BOOTSTRAP_REASONS.ADDON_UPGRADE : - BOOTSTRAP_REASONS.ADDON_DOWNGRADE; -} - -/** - * Converts an iterable of addon objects into a map with the add-on's ID as key. - */ -function addonMap(addons) { - return new Map(addons.map(a => [a.id, a])); -} - /** * Helper function that determines whether an addon of a certain type is a * WebExtension. @@ -1842,7 +1804,7 @@ var XPIProvider = { let existing = XPIStates.findAddon(addon.id, loc => loc.name != TemporaryInstallLocation.name); if (existing) { - reason = newVersionReason(addon.version, existing.version); + reason = XPIInstall.newVersionReason(addon.version, existing.version); } } XPIProvider.callBootstrapMethod(addon, addon.file, @@ -1971,7 +1933,7 @@ var XPIProvider = { let existing = XPIStates.findAddon(id, loc => loc != tempLocation); let callUpdate = false; if (existing) { - reason = newVersionReason(addon.version, existing.version); + reason = XPIInstall.newVersionReason(addon.version, existing.version); callUpdate = (isWebExtension(addon.type) && isWebExtension(existing.type)); } @@ -2145,133 +2107,6 @@ var XPIProvider = { return started; }, - async updateSystemAddons() { - let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS]; - if (!systemAddonLocation) - return; - - // Don't do anything in safe mode - if (Services.appinfo.inSafeMode) - return; - - // Download the list of system add-ons - let url = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_UPDATE_URL, null); - if (!url) { - await systemAddonLocation.cleanDirectories(); - return; - } - - url = await UpdateUtils.formatUpdateURL(url); - - logger.info(`Starting system add-on update check from ${url}.`); - let res = await ProductAddonChecker.getProductAddonList(url); - - // If there was no list then do nothing. - if (!res || !res.gmpAddons) { - logger.info("No system add-ons list was returned."); - await systemAddonLocation.cleanDirectories(); - return; - } - - let addonList = new Map( - res.gmpAddons.map(spec => [spec.id, { spec, path: null, addon: null }])); - - let setMatches = (wanted, existing) => { - if (wanted.size != existing.size) - return false; - - for (let [id, addon] of existing) { - let wantedInfo = wanted.get(id); - - if (!wantedInfo) - return false; - if (wantedInfo.spec.version != addon.version) - return false; - } - - return true; - }; - - // If this matches the current set in the profile location then do nothing. - let updatedAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_ADDONS)); - if (setMatches(addonList, updatedAddons)) { - logger.info("Retaining existing updated system add-ons."); - await systemAddonLocation.cleanDirectories(); - return; - } - - // If this matches the current set in the default location then reset the - // updated set. - let defaultAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS)); - if (setMatches(addonList, defaultAddons)) { - logger.info("Resetting system add-ons."); - systemAddonLocation.resetAddonSet(); - await systemAddonLocation.cleanDirectories(); - return; - } - - // Download all the add-ons - async function downloadAddon(item) { - try { - let sourceAddon = updatedAddons.get(item.spec.id); - if (sourceAddon && sourceAddon.version == item.spec.version) { - // Copying the file to a temporary location has some benefits. If the - // file is locked and cannot be read then we'll fall back to - // downloading a fresh copy. It also means we don't have to remember - // whether to delete the temporary copy later. - try { - let path = OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon"); - let unique = await OS.File.openUnique(path); - unique.file.close(); - await OS.File.copy(sourceAddon._sourceBundle.path, unique.path); - // Make sure to update file modification times so this is detected - // as a new add-on. - await OS.File.setDates(unique.path); - item.path = unique.path; - } catch (e) { - logger.warn(`Failed make temporary copy of ${sourceAddon._sourceBundle.path}.`, e); - } - } - if (!item.path) { - item.path = await ProductAddonChecker.downloadAddon(item.spec); - } - item.addon = await loadManifestFromFile(nsIFile(item.path), systemAddonLocation); - } catch (e) { - logger.error(`Failed to download system add-on ${item.spec.id}`, e); - } - } - await Promise.all(Array.from(addonList.values()).map(downloadAddon)); - - // The download promises all resolve regardless, now check if they all - // succeeded - let validateAddon = (item) => { - if (item.spec.id != item.addon.id) { - logger.warn(`Downloaded system add-on expected to be ${item.spec.id} but was ${item.addon.id}.`); - return false; - } - - if (item.spec.version != item.addon.version) { - logger.warn(`Expected system add-on ${item.spec.id} to be version ${item.spec.version} but was ${item.addon.version}.`); - return false; - } - - if (!systemAddonLocation.isValidAddon(item.addon)) - return false; - - return true; - }; - - if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) { - throw new Error("Rejecting updated system add-on set that either could not " + - "be downloaded or contained unusable add-ons."); - } - - // Install into the install location - logger.info("Installing new system add-on set"); - await systemAddonLocation.installAddonSet(Array.from(addonList.values()) - .map(a => a.addon)); - }, - /** * Verifies that all installed add-ons are still correctly signed. */ @@ -2406,7 +2241,7 @@ var XPIProvider = { // call its uninstall method let newVersion = addon.version; let oldVersion = existingAddon; - let uninstallReason = newVersionReason(oldVersion, newVersion); + let uninstallReason = XPIInstall.newVersionReason(oldVersion, newVersion); this.callBootstrapMethod(existingAddon, file, "uninstall", uninstallReason, @@ -2709,286 +2544,11 @@ var XPIProvider = { return aMimetype == "application/x-xpinstall"; }, - /** - * Called to test whether installing XPI add-ons is enabled. - * - * @return true if installing is enabled - */ - isInstallEnabled() { - // Default to enabled if the preference does not exist - return Services.prefs.getBoolPref(PREF_XPI_ENABLED, true); - }, - - /** - * Called to test whether installing XPI add-ons by direct URL requests is - * whitelisted. - * - * @return true if installing by direct requests is whitelisted - */ - isDirectRequestWhitelisted() { - // Default to whitelisted if the preference does not exist. - return Services.prefs.getBoolPref(PREF_XPI_DIRECT_WHITELISTED, true); - }, - - /** - * Called to test whether installing XPI add-ons from file referrers is - * whitelisted. - * - * @return true if installing from file referrers is whitelisted - */ - isFileRequestWhitelisted() { - // Default to whitelisted if the preference does not exist. - return Services.prefs.getBoolPref(PREF_XPI_FILE_WHITELISTED, true); - }, - - /** - * Called to test whether installing XPI add-ons from a URI is allowed. - * - * @param aInstallingPrincipal - * The nsIPrincipal that initiated the install - * @return true if installing is allowed - */ - isInstallAllowed(aInstallingPrincipal) { - if (!this.isInstallEnabled()) - return false; - - let uri = aInstallingPrincipal.URI; - - // Direct requests without a referrer are either whitelisted or blocked. - if (!uri) - return this.isDirectRequestWhitelisted(); - - // Local referrers can be whitelisted. - if (this.isFileRequestWhitelisted() && - (uri.schemeIs("chrome") || uri.schemeIs("file"))) - return true; - - this.importPermissions(); - - let permission = Services.perms.testPermissionFromPrincipal(aInstallingPrincipal, XPI_PERMISSION); - if (permission == Ci.nsIPermissionManager.DENY_ACTION) - return false; - - let requireWhitelist = Services.prefs.getBoolPref(PREF_XPI_WHITELIST_REQUIRED, true); - if (requireWhitelist && (permission != Ci.nsIPermissionManager.ALLOW_ACTION)) - return false; - - let requireSecureOrigin = Services.prefs.getBoolPref(PREF_INSTALL_REQUIRESECUREORIGIN, true); - let safeSchemes = ["https", "chrome", "file"]; - if (requireSecureOrigin && !safeSchemes.includes(uri.scheme)) - return false; - - return true; - }, - // Identify temporary install IDs. isTemporaryInstallID(id) { return id.endsWith(TEMPORARY_ADDON_SUFFIX); }, - /** - * Called to get an AddonInstall to download and install an add-on from a URL. - * - * @param aUrl - * The URL to be installed - * @param aHash - * A hash for the install - * @param aName - * A name for the install - * @param aIcons - * Icon URLs for the install - * @param aVersion - * A version for the install - * @param aBrowser - * The browser performing the install - */ - async getInstallForURL(aUrl, aHash, aName, aIcons, aVersion, aBrowser) { - let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; - let url = Services.io.newURI(aUrl); - - let options = { - hash: aHash, - browser: aBrowser, - name: aName, - icons: aIcons, - version: aVersion, - }; - - if (url instanceof Ci.nsIFileURL) { - let install = new LocalAddonInstall(location, url, options); - await install.init(); - return install.wrapper; - } - - let install = new DownloadAddonInstall(location, url, options); - return install.wrapper; - }, - - /** - * Called to get an AddonInstall to install an add-on from a local file. - * - * @param aFile - * The file to be installed - */ - async getInstallForFile(aFile) { - let install = await XPIInstall.createLocalInstall(aFile); - return install ? install.wrapper : null; - }, - - /** - * Temporarily installs add-on from a local XPI file or directory. - * As this is intended for development, the signature is not checked and - * the add-on does not persist on application restart. - * - * @param aFile - * An nsIFile for the unpacked add-on directory or XPI file. - * - * @return See installAddonFromLocation return value. - */ - installTemporaryAddon(aFile) { - return this.installAddonFromLocation(aFile, TemporaryInstallLocation); - }, - - /** - * Permanently installs add-on from a local XPI file or directory. - * The signature is checked but the add-on persist on application restart. - * - * @param aFile - * An nsIFile for the unpacked add-on directory or XPI file. - * - * @return See installAddonFromLocation return value. - */ - async installAddonFromSources(aFile) { - let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; - return this.installAddonFromLocation(aFile, location, "proxy"); - }, - - /** - * Installs add-on from a local XPI file or directory. - * - * @param aFile - * An nsIFile for the unpacked add-on directory or XPI file. - * @param aInstallLocation - * Define a custom install location object to use for the install. - * @param aInstallAction - * Optional action mode to use when installing the addon - * (see MutableDirectoryInstallLocation.installAddon) - * - * @return a Promise that resolves to an Addon object on success, or rejects - * if the add-on is not a valid restartless add-on or if the - * same ID is already installed. - */ - async installAddonFromLocation(aFile, aInstallLocation, aInstallAction) { - if (aFile.exists() && aFile.isFile()) { - XPIInstall.flushJarCache(aFile); - } - let addon = await loadManifestFromFile(aFile, aInstallLocation); - - aInstallLocation.installAddon({ id: addon.id, source: aFile, action: aInstallAction }); - - if (addon.appDisabled) { - let message = `Add-on ${addon.id} is not compatible with application version.`; - - let app = addon.matchingTargetApplication; - if (app) { - if (app.minVersion) { - message += ` add-on minVersion: ${app.minVersion}.`; - } - if (app.maxVersion) { - message += ` add-on maxVersion: ${app.maxVersion}.`; - } - } - throw new Error(message); - } - - if (!addon.bootstrap) { - throw new Error("Only restartless (bootstrap) add-ons" - + " can be installed from sources:", addon.id); - } - let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL; - let oldAddon = await XPIDatabase.getVisibleAddonForID(addon.id); - let callUpdate = false; - - let extraParams = {}; - extraParams.temporarilyInstalled = aInstallLocation === TemporaryInstallLocation; - if (oldAddon) { - if (!oldAddon.bootstrap) { - logger.warn("Non-restartless Add-on is already installed", addon.id); - throw new Error("Non-restartless add-on with ID " - + oldAddon.id + " is already installed"); - } else { - logger.warn("Addon with ID " + oldAddon.id + " already installed," - + " older version will be disabled"); - - addon.installDate = oldAddon.installDate; - - let existingAddonID = oldAddon.id; - let existingAddon = oldAddon._sourceBundle; - - // We'll be replacing a currently active bootstrapped add-on so - // call its uninstall method - let newVersion = addon.version; - let oldVersion = oldAddon.version; - - installReason = newVersionReason(oldVersion, newVersion); - let uninstallReason = installReason; - - extraParams.newVersion = newVersion; - extraParams.oldVersion = oldVersion; - - callUpdate = isWebExtension(oldAddon.type) && isWebExtension(addon.type); - - if (oldAddon.active) { - XPIProvider.callBootstrapMethod(oldAddon, existingAddon, - "shutdown", uninstallReason, - extraParams); - } - - if (!callUpdate) { - this.callBootstrapMethod(oldAddon, existingAddon, - "uninstall", uninstallReason, extraParams); - } - this.unloadBootstrapScope(existingAddonID); - XPIInstall.flushChromeCaches(); - } - } else { - addon.installDate = Date.now(); - } - - let file = addon._sourceBundle; - - let method = callUpdate ? "update" : "install"; - XPIProvider.callBootstrapMethod(addon, file, method, installReason, extraParams); - addon.state = AddonManager.STATE_INSTALLED; - logger.debug("Install of temporary addon in " + aFile.path + " completed."); - addon.visible = true; - addon.enabled = true; - addon.active = true; - // WebExtension themes are installed as disabled, fix that here. - addon.userDisabled = false; - - addon = XPIDatabase.addAddonMetadata(addon, file.path); - - XPIStates.addAddon(addon); - XPIDatabase.saveChanges(); - XPIStates.save(); - - AddonManagerPrivate.callAddonListeners("onInstalling", addon.wrapper, - false); - XPIProvider.callBootstrapMethod(addon, file, "startup", installReason, extraParams); - AddonManagerPrivate.callInstallListeners("onExternalInstall", - null, addon.wrapper, - oldAddon ? oldAddon.wrapper : null, - false); - AddonManagerPrivate.callAddonListeners("onInstalled", addon.wrapper); - - // Notify providers that a new theme has been enabled. - if (isTheme(addon.type)) - AddonManagerPrivate.notifyAddonChanged(addon.id, addon.type, false); - - return addon.wrapper; - }, - /** * Sets startupData for the given addon. The provided data will be stored * in addonsStartup.json so it is available early during browser startup. @@ -3676,186 +3236,17 @@ var XPIProvider = { return isDisabled; }, - - /** - * Uninstalls an add-on, immediately if possible or marks it as pending - * uninstall if not. - * - * @param aAddon - * The DBAddonInternal to uninstall - * @param aForcePending - * Force this addon into the pending uninstall state (used - * e.g. while the add-on manager is open and offering an - * "undo" button) - * @throws if the addon cannot be uninstalled because it is in an install - * location that does not allow it - */ - async uninstallAddon(aAddon, aForcePending) { - if (!(aAddon.inDatabase)) - throw new Error("Cannot uninstall addon " + aAddon.id + " because it is not installed"); - - if (aAddon._installLocation.locked) - throw new Error("Cannot uninstall addon " + aAddon.id - + " from locked install location " + aAddon._installLocation.name); - - if (aForcePending && aAddon.pendingUninstall) - throw new Error("Add-on is already marked to be uninstalled"); - - aAddon._hasResourceCache.clear(); - - if (aAddon._updateCheck) { - logger.debug("Cancel in-progress update check for " + aAddon.id); - aAddon._updateCheck.cancel(); - } - - let wasPending = aAddon.pendingUninstall; - - if (aForcePending) { - // We create an empty directory in the staging directory to indicate - // that an uninstall is necessary on next startup. Temporary add-ons are - // automatically uninstalled on shutdown anyway so there is no need to - // do this for them. - if (aAddon._installLocation.name != KEY_APP_TEMPORARY) { - let stage = getFile(aAddon.id, aAddon._installLocation.getStagingDir()); - if (!stage.exists()) - stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - } - - XPIDatabase.setAddonProperties(aAddon, { - pendingUninstall: true - }); - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id); - if (xpiState) { - xpiState.enabled = false; - XPIStates.save(); - } else { - logger.warn("Can't find XPI state while uninstalling ${id} from ${location}", aAddon); - } - } - - // If the add-on is not visible then there is no need to notify listeners. - if (!aAddon.visible) - return; - - let wrapper = aAddon.wrapper; - - // If the add-on wasn't already pending uninstall then notify listeners. - if (!wasPending) { - AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, - !!aForcePending); - } - - let reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL; - let callUpdate = false; - let existingAddon = XPIStates.findAddon(aAddon.id, loc => - loc.name != aAddon._installLocation.name); - if (existingAddon) { - reason = newVersionReason(aAddon.version, existingAddon.version); - callUpdate = isWebExtension(aAddon.type) && isWebExtension(existingAddon.type); - } - - if (!aForcePending) { - if (aAddon.bootstrap) { - if (aAddon.active) { - this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", - reason); - } - - if (!callUpdate) { - this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall", - reason); - } - XPIStates.disableAddon(aAddon.id); - this.unloadBootstrapScope(aAddon.id); - XPIInstall.flushChromeCaches(); - } - aAddon._installLocation.uninstallAddon(aAddon.id); - XPIDatabase.removeAddonMetadata(aAddon); - XPIStates.removeAddon(aAddon.location, aAddon.id); - AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); - - if (existingAddon) { - let existing = await XPIDatabase.getAddonInLocation(aAddon.id, existingAddon.location.name); - XPIDatabase.makeAddonVisible(existing); - - let wrappedAddon = existing.wrapper; - AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false); - - if (!existing.disabled) { - XPIDatabase.updateAddonActive(existing, true); - } - - if (aAddon.bootstrap) { - let method = callUpdate ? "update" : "install"; - XPIProvider.callBootstrapMethod(existing, existing._sourceBundle, - method, reason); - - if (existing.active) { - XPIProvider.callBootstrapMethod(existing, existing._sourceBundle, - "startup", reason); - } else { - XPIProvider.unloadBootstrapScope(existing.id); - } - } - - AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon); - } - } else if (aAddon.bootstrap && aAddon.active) { - this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", reason); - XPIStates.disableAddon(aAddon.id); - this.unloadBootstrapScope(aAddon.id); - XPIDatabase.updateAddonActive(aAddon, false); - } - - // Notify any other providers that a new theme has been enabled - if (isTheme(aAddon.type) && aAddon.active) - AddonManagerPrivate.notifyAddonChanged(null, aAddon.type); - }, - - /** - * Cancels the pending uninstall of an add-on. - * - * @param aAddon - * The DBAddonInternal to cancel uninstall for - */ - cancelUninstallAddon(aAddon) { - if (!(aAddon.inDatabase)) - throw new Error("Can only cancel uninstall for installed addons."); - if (!aAddon.pendingUninstall) - throw new Error("Add-on is not marked to be uninstalled"); - - if (aAddon._installLocation.name != KEY_APP_TEMPORARY) - aAddon._installLocation.cleanStagingDir([aAddon.id]); - - XPIDatabase.setAddonProperties(aAddon, { - pendingUninstall: false - }); - - if (!aAddon.visible) - return; - - XPIStates.getAddon(aAddon.location, aAddon.id).syncWithDB(aAddon); - XPIStates.save(); - - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - - // TODO hide hidden add-ons (bug 557710) - let wrapper = aAddon.wrapper; - AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper); - - if (aAddon.bootstrap && !aAddon.disabled) { - this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup", - BOOTSTRAP_REASONS.ADDON_INSTALL); - XPIDatabase.updateAddonActive(aAddon, true); - } - - // Notify any other providers that this theme is now enabled again. - if (isTheme(aAddon.type) && aAddon.active) - AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false); - } }; +for (let meth of ["cancelUninstallAddon", "getInstallForFile", + "getInstallForURL", "installAddonFromLocation", + "installAddonFromSources", "installTemporaryAddon", + "isInstallAllowed", "uninstallAddon", "updateSystemAddons"]) { + XPIProvider[meth] = function() { + return XPIInstall[meth](...arguments); + }; +} + // Maps instances of AddonInternal to AddonWrapper const wrapperMap = new WeakMap(); let addonFor = wrapper => wrapperMap.get(wrapper); @@ -5243,11 +4634,11 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { forwardInstallMethods(SystemAddonInstallLocation, ["cleanDirectories", "cleanStagingDir", "getStagingDir", - "getTrashDir", "installAddon", "installAddon", - "installAddonSet", "isValid", "isValidAddon", - "releaseStagingDir", "requestStagingDir", - "resetAddonSet", "resumeAddonSet", "uninstallAddon", - "uninstallAddon"]); + "getTrashDir", "installAddon", "installAddon", + "installAddonSet", "isValid", "isValidAddon", + "releaseStagingDir", "requestStagingDir", + "resetAddonSet", "resumeAddonSet", "uninstallAddon", + "uninstallAddon"]); /** An object which identifies an install location for temporary add-ons. */ @@ -5363,8 +4754,10 @@ var XPIInternal = { PREF_SYSTEM_ADDON_SET, SIGNED_TYPES, SystemAddonInstallLocation, + TemporaryInstallLocation, TEMPORARY_ADDON_SUFFIX, TOOLKIT_ID, + XPI_PERMISSION, XPIStates, awaitPromise, getExternalType, From d099436c306f38d64851b30b359e50fa64fd4f34 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 21 Apr 2018 19:47:16 -0700 Subject: [PATCH 25/44] Bug 1363925: Part 6 - Move staged add-on install logic to XPIInstall. r=aswan MozReview-Commit-ID: IDXsbKvl5U3 --HG-- extra : rebase_source : a17fb46c989f05c4519b9bce380d89eaca118edd extra : histedit_source : e3065c8f52311f67eefdb51b13abe26bbba9adaf --- .../extensions/internal/XPIInstall.jsm | 73 ++++++++++++++++ .../extensions/internal/XPIProvider.jsm | 87 ++++--------------- 2 files changed, 89 insertions(+), 71 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index c4fb42f256d2..ca624d5d98ed 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -3652,6 +3652,79 @@ var XPIInstall = { return addon; }, + /** + * Completes the install of an add-on which was staged during the last + * session. + * + * @param {string} id + * The expected ID of the add-on. + * @param {object} metadata + * The parsed metadata for the staged install. + * @param {InstallLocation} location + * The install location to install the add-on to. + * @returns {AddonInternal} + * The installed Addon object, upon success. + */ + async installStagedAddon(id, metadata, location) { + let source = getFile(`${id}.xpi`, location.getStagingDir()); + + // Check that the directory's name is a valid ID. + if (!gIDTest.test(id) || !source.exists() || !source.isFile()) { + throw new Error(`Ignoring invalid staging directory entry: ${id}`); + } + + let addon = await loadManifestFromFile(source, location); + + if (mustSign(addon.type) && + addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) { + throw new Error(`Refusing to install staged add-on ${id} with signed state ${addon.signedState}`); + } + + addon.importMetadata(metadata); + + var oldBootstrap = null; + logger.debug(`Processing install of ${id} in ${location.name}`); + let existingAddon = XPIStates.findAddon(id); + if (existingAddon && existingAddon.bootstrapped) { + try { + var file = existingAddon.file; + if (file.exists()) { + oldBootstrap = existingAddon; + + // We'll be replacing a currently active bootstrapped add-on so + // call its uninstall method + let newVersion = addon.version; + let oldVersion = existingAddon; + let uninstallReason = newVersionReason(oldVersion, newVersion); + + XPIProvider.callBootstrapMethod(existingAddon, + file, "uninstall", uninstallReason, + { newVersion }); + XPIProvider.unloadBootstrapScope(id); + flushChromeCaches(); + } + } catch (e) { + Cu.reportError(e); + } + } + + try { + addon._sourceBundle = location.installAddon({ + id, source, existingAddonID: id, + }); + XPIStates.addAddon(addon); + } catch (e) { + if (oldBootstrap) { + // Re-install the old add-on + XPIProvider.callBootstrapMethod(oldBootstrap, existingAddon, "install", + BOOTSTRAP_REASONS.ADDON_INSTALL); + } + throw e; + } + + return addon; + }, + async updateSystemAddons() { let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS]; if (!systemAddonLocation) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 599097c0930e..11bb5dfe8855 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -2194,83 +2194,28 @@ var XPIProvider = { let state = XPIStates.getLocation(location.name); let cleanNames = []; + let promises = []; for (let [id, metadata] of state.getStagedAddons()) { state.unstageAddon(id); - let source = getFile(`${id}.xpi`, location.getStagingDir()); - - // Check that the directory's name is a valid ID. - if (!gIDTest.test(id) || !source.exists() || !source.isFile()) { - logger.warn("Ignoring invalid staging directory entry: ${id}", {id}); - cleanNames.push(source.leafName); - continue; - } - - changed = true; aManifests[location.name][id] = null; + promises.push( + XPIInstall.installStagedAddon(id, metadata, location).then( + addon => { + aManifests[location.name][id] = addon; + }, + error => { + delete aManifests[location.name][id]; + cleanNames.push(`${id}.xpi`); - let addon; - try { - addon = XPIInstall.syncLoadManifestFromFile(source, location); - } catch (e) { - logger.error(`Unable to read add-on manifest from ${source.path}`, e); - cleanNames.push(source.leafName); - continue; - } + logger.error(`Failed to install staged add-on ${id} in ${location.name}`, + error); + })); + } - if (mustSign(addon.type) && - addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) { - logger.warn(`Refusing to install staged add-on ${id} with signed state ${addon.signedState}`); - cleanNames.push(source.leafName); - continue; - } - - addon.importMetadata(metadata); - aManifests[location.name][id] = addon; - - var oldBootstrap = null; - logger.debug(`Processing install of ${id} in ${location.name}`); - let existingAddon = XPIStates.findAddon(id); - if (existingAddon && existingAddon.bootstrapped) { - try { - var file = existingAddon.file; - if (file.exists()) { - oldBootstrap = existingAddon; - - // We'll be replacing a currently active bootstrapped add-on so - // call its uninstall method - let newVersion = addon.version; - let oldVersion = existingAddon; - let uninstallReason = XPIInstall.newVersionReason(oldVersion, newVersion); - - this.callBootstrapMethod(existingAddon, - file, "uninstall", uninstallReason, - { newVersion }); - this.unloadBootstrapScope(id); - XPIInstall.flushChromeCaches(); - } - } catch (e) { - Cu.reportError(e); - } - } - - try { - addon._sourceBundle = location.installAddon({ - id, source, existingAddonID: id, - }); - XPIStates.addAddon(addon); - } catch (e) { - logger.error("Failed to install staged add-on " + id + " in " + location.name, - e); - - delete aManifests[location.name][id]; - - if (oldBootstrap) { - // Re-install the old add-on - this.callBootstrapMethod(oldBootstrap, existingAddon, "install", - BOOTSTRAP_REASONS.ADDON_INSTALL); - } - } + if (promises.length) { + changed = true; + awaitPromise(Promise.all(promises)); } try { From 65b6ddb7e21d20d0dda6678d9512e13b25121267 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 21 Apr 2018 20:28:19 -0700 Subject: [PATCH 26/44] Bug 1363925: Part 7a - Turn on valid-jsdoc rule for XPIInstall.jsm. r=me,npotb MozReview-Commit-ID: Ch1NaeLAxtJ --HG-- extra : rebase_source : 26ebeec5db42c7ed27bb536cbfb81c41983cb1cc extra : histedit_source : 4764ba7f0c7239fdca1fb2540e1e92ed07a4569f --- .../extensions/internal/XPIInstall.jsm | 570 ++++++++++-------- 1 file changed, 323 insertions(+), 247 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index ca624d5d98ed..ac1e3a93b177 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -4,6 +4,8 @@ "use strict"; +/* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */ + var EXPORTED_SYMBOLS = [ "UpdateChecker", "XPIInstall", @@ -142,7 +144,7 @@ function getFile(path, base = null) { /** * Sends local and remote notifications to flush a JAR file cache entry * - * @param aJarFile + * @param {nsIFile} aJarFile * The ZIP/XPI/JAR file as a nsIFile */ function flushJarCache(aJarFile) { @@ -419,7 +421,8 @@ XPIPackage = class XPIPackage extends Package { * @param {string} oldVersion The version of the existing extension instance. * @param {string} newVersion The version of the extension being installed. * - * @return {BOOSTRAP_REASONS.ADDON_UPGRADE|BOOSTRAP_REASONS.ADDON_DOWNGRADE} + * @returns {integer} + * BOOSTRAP_REASONS.ADDON_UPGRADE or BOOSTRAP_REASONS.ADDON_DOWNGRADE */ function newVersionReason(oldVersion, newVersion) { return Services.vc.compare(oldVersion, newVersion) <= 0 ? @@ -452,9 +455,10 @@ function EM_R(aProperty) { /** * Converts an RDF literal, resource or integer into a string. * - * @param aLiteral - * The RDF object to convert - * @return a string if the object could be converted or null + * @param {nsISupports} aLiteral + * The RDF object to convert + * @returns {string?} + * A string if the object could be converted or null */ function getRDFValue(aLiteral) { if (aLiteral instanceof Ci.nsIRDFLiteral) @@ -469,13 +473,14 @@ function getRDFValue(aLiteral) { /** * Gets an RDF property as a string * - * @param aDs - * The RDF datasource to read the property from - * @param aResource - * The RDF resource to read the property from - * @param aProperty - * The property to read - * @return a string if the property existed or null + * @param {nsIRDFDataSource} aDs + * The RDF datasource to read the property from + * @param {nsIRDFResource} aResource + * The RDF resource to read the property from + * @param {string} aProperty + * The property to read + * @returns {string?} + * A string if the property existed or null */ function getRDFProperty(aDs, aResource, aProperty) { return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true)); @@ -484,9 +489,9 @@ function getRDFProperty(aDs, aResource, aProperty) { /** * Reads an AddonInternal object from a manifest stream. * - * @param aUri - * A |file:| or |jar:| URL for the manifest - * @return an AddonInternal object + * @param {nsIURI} aUri + * A |file:| or |jar:| URL for the manifest + * @returns {AddonInternal} * @throws if the install manifest in the stream is corrupt or could not * be read */ @@ -614,11 +619,11 @@ async function loadManifestFromWebManifest(aUri) { /** * Reads an AddonInternal object from an RDF stream. * - * @param aUri - * The URI that the manifest is being read from - * @param aData - * The manifest text - * @return an AddonInternal object + * @param {nsIURI} aUri + * The URI that the manifest is being read from + * @param {string} aData + * The manifest text + * @returns {AddonInternal} * @throws if the install manifest in the RDF stream is corrupt or could not * be read */ @@ -636,18 +641,19 @@ async function loadManifestFromRDF(aUri, aData) { * Reads locale properties from either the main install manifest root or * an em:localized section in the install manifest. * - * @param aDs - * The nsIRDFDatasource to read from - * @param aSource - * The nsIRDFResource to read the properties from - * @param isDefault - * True if the locale is to be read from the main install manifest - * root - * @param aSeenLocales - * An array of locale names already seen for this install manifest. - * Any locale names seen as a part of this function will be added to - * this array - * @return an object containing the locale properties + * @param {nsIRDFDataSource} aDs + * The datasource to read from. + * @param {nsIRDFResource} aSource + * The resource to read the properties from. + * @param {boolean} isDefault + * True if the locale is to be read from the main install manifest + * root + * @param {string[]} aSeenLocales + * An array of locale names already seen for this install manifest. + * Any locale names seen as a part of this function will be added to + * this array + * @returns {Object} + * an object containing the locale properties */ function readLocale(aDs, aSource, isDefault, aSeenLocales) { let locale = { }; @@ -942,6 +948,21 @@ var loadManifest = async function(aPackage, aInstallLocation, aOldAddon) { return addon; }; +/** + * Loads an add-on's manifest from the given file or directory. + * + * @param {nsIFile} aFile + * The file to load the manifest from. + * @param {InstallLocation} aInstallLocation + * The install location the add-on is installed in, or will be + * installed to. + * @param {AddonInternal?} aOldAddon + * The currently-installed add-on with the same ID, if one exist. + * This is used to migrate user settings like the add-on's + * disabled state. + * @returns {AddonInternal} + * The parsed Addon object for the file's manifest. + */ var loadManifestFromFile = async function(aFile, aInstallLocation, aOldAddon) { let pkg = Package.get(aFile); try { @@ -952,9 +973,9 @@ var loadManifestFromFile = async function(aFile, aInstallLocation, aOldAddon) { } }; -/** - * A synchronous method for loading an add-on's manifest. This should only ever - * be used during startup or a sync load of the add-ons DB +/* + * A synchronous method for loading an add-on's manifest. Do not use + * this. */ function syncLoadManifestFromFile(aFile, aInstallLocation, aOldAddon) { return XPIInternal.awaitPromise(loadManifestFromFile(aFile, aInstallLocation, aOldAddon)); @@ -973,8 +994,9 @@ function flushChromeCaches() { * Creates and returns a new unique temporary file. The caller should delete * the file when it is no longer needed. * - * @return an nsIFile that points to a randomly named, initially empty file in - * the OS temporary files directory + * @returns {nsIFile} + * An nsIFile that points to a randomly named, initially empty file in + * the OS temporary files directory */ function getTemporaryFile() { let file = FileUtils.getDir(KEY_TEMPDIR, []); @@ -987,6 +1009,19 @@ function getTemporaryFile() { /** * Returns the signedState for a given return code and certificate by verifying * it against the expected ID. + * + * @param {nsresult} aRv + * The result code returned by the signature checker for the + * signature check operation. + * @param {nsIX509Cert?} aCert + * The certificate the add-on was signed with, if a valid + * certificate exists. + * @param {string?} aAddonID + * The expected ID of the add-on. If passed, this must match the + * ID in the certificate's CN field. + * @returns {number} + * A SIGNEDSTATE result code constant, as defined on the + * AddonManager class. */ function getSignedStatus(aRv, aCert, aAddonID) { let expectedCommonName = aAddonID; @@ -1048,11 +1083,12 @@ function shouldVerifySignedState(aAddon) { * Verifies that a bundle's contents are all correctly signed by an * AMO-issued certificate * - * @param aBundle - * the nsIFile for the bundle to check, either a directory or zip file - * @param aAddon - * the add-on object to verify - * @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant. + * @param {nsIFile}aBundle + * The nsIFile for the bundle to check, either a directory or zip file. + * @param {AddonInternal} aAddon + * The add-on object to verify. + * @returns {Prommise} + * A Promise that resolves to an AddonManager.SIGNEDSTATE_* constant. */ var verifyBundleSignedState = async function(aBundle, aAddon) { let pkg = Package.get(aBundle); @@ -1068,16 +1104,17 @@ var verifyBundleSignedState = async function(aBundle, aAddon) { * Replaces %...% strings in an addon url (update and updateInfo) with * appropriate values. * - * @param aAddon - * The AddonInternal representing the add-on - * @param aUri - * The uri to escape - * @param aUpdateType - * An optional number representing the type of update, only applicable - * when creating a url for retrieving an update manifest - * @param aAppVersion - * The optional application version to use for %APP_VERSION% - * @return the appropriately escaped uri. + * @param {AddonInternal} aAddon + * The AddonInternal representing the add-on + * @param {string} aUri + * The URI to escape + * @param {integer?} aUpdateType + * An optional number representing the type of update, only applicable + * when creating a url for retrieving an update manifest + * @param {string?} aAppVersion + * The optional application version to use for %APP_VERSION% + * @returns {string} + * The appropriately escaped URI. */ function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion) { let uri = AddonManager.escapeAddonURI(aAddon, aUri, aAppVersion); @@ -1108,6 +1145,11 @@ function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion) { /** * Converts an iterable of addon objects into a map with the add-on's ID as key. + * + * @param {sequence} addons + * A sequence of AddonInternal objects. + * + * @returns {Map} */ function addonMap(addons) { return new Map(addons.map(a => [a.id, a])); @@ -1131,8 +1173,8 @@ async function removeAsync(aFile) { /** * Recursively removes a directory or file fixing permissions when necessary. * - * @param aFile - * The nsIFile to remove + * @param {nsIFile} aFile + * The nsIFile to remove */ function recursiveRemove(aFile) { let isDir = null; @@ -1182,10 +1224,10 @@ function recursiveRemove(aFile) { /** * Sets permissions on a file * - * @param aFile - * The file or directory to operate on. - * @param aPermissions - * The permissions to set + * @param {nsIFile} aFile + * The file or directory to operate on. + * @param {integer} aPermissions + * The permissions to set */ function setFilePermissions(aFile, aPermissions) { try { @@ -1199,10 +1241,10 @@ function setFilePermissions(aFile, aPermissions) { /** * Write a given string to a file * - * @param file - * The nsIFile instance to write into - * @param string - * The string to write + * @param {nsIFile} file + * The nsIFile instance to write into + * @param {string} string + * The string to write */ function writeStringToFile(file, string) { let fileStream = new FileOutputStream( @@ -1343,11 +1385,11 @@ SafeInstallOperation.prototype = { * Moves a file or directory into a new directory. If an error occurs then all * files that have been moved will be moved back to their original location. * - * @param aFile - * The file or directory to be moved. - * @param aTargetDirectory - * The directory to move into, this is expected to be an empty - * directory. + * @param {nsIFile} aFile + * The file or directory to be moved. + * @param {nsIFile} aTargetDirectory + * The directory to move into, this is expected to be an empty + * directory. */ moveUnder(aFile, aTargetDirectory) { try { @@ -1362,10 +1404,10 @@ SafeInstallOperation.prototype = { * Renames a file to a new location. If an error occurs then all * files that have been moved will be moved back to their original location. * - * @param aOldLocation - * The old location of the file. - * @param aNewLocation - * The new location of the file. + * @param {nsIFile} aOldLocation + * The old location of the file. + * @param {nsIFile} aNewLocation + * The new location of the file. */ moveTo(aOldLocation, aNewLocation) { try { @@ -1382,11 +1424,11 @@ SafeInstallOperation.prototype = { * Copies a file or directory into a new directory. If an error occurs then * all new files that have been created will be removed. * - * @param aFile - * The file or directory to be copied. - * @param aTargetDirectory - * The directory to copy into, this is expected to be an empty - * directory. + * @param {nsIFile} aFile + * The file or directory to be copied. + * @param {nsIFile} aTargetDirectory + * The directory to copy into, this is expected to be an empty + * directory. */ copy(aFile, aTargetDirectory) { try { @@ -1426,11 +1468,12 @@ SafeInstallOperation.prototype = { /** * Gets a snapshot of directory entries. * - * @param aDir - * Directory to look at - * @param aSortEntries - * True to sort entries by filename - * @return An array of nsIFile, or an empty array if aDir is not a readable directory + * @param {nsIFile} aDir + * Directory to look at + * @param {boolean} aSortEntries + * True to sort entries by filename + * @returns {nsIFile[]} + * An array of nsIFile, or an empty array if aDir is not a readable directory */ function getDirectoryEntries(aDir, aSortEntries) { let dirEnum; @@ -1477,27 +1520,27 @@ class AddonInstall { /** * Instantiates an AddonInstall. * - * @param installLocation - * The install location the add-on will be installed into - * @param url - * The nsIURL to get the add-on from. If this is an nsIFileURL then - * the add-on will not need to be downloaded - * @param options - * Additional options for the install - * @param options.hash - * An optional hash for the add-on - * @param options.existingAddon - * The add-on this install will update if known - * @param options.name - * An optional name for the add-on - * @param options.type - * An optional type for the add-on - * @param options.icons - * Optional icons for the add-on - * @param options.version - * An optional version for the add-on - * @param options.promptHandler - * A callback to prompt the user before installing. + * @param {InstallLocation} installLocation + * The install location the add-on will be installed into + * @param {nsIURL} url + * The nsIURL to get the add-on from. If this is an nsIFileURL then + * the add-on will not need to be downloaded + * @param {Object} [options = {}] + * Additional options for the install + * @param {string} [options.hash] + * An optional hash for the add-on + * @param {AddonInternal} [options.existingAddon] + * The add-on this install will update if known + * @param {string} [options.name] + * An optional name for the add-on + * @param {string} [options.type] + * An optional type for the add-on + * @param {object} [options.icons] + * Optional icons for the add-on + * @param {string} [options.version] + * An optional version for the add-on + * @param {function(string) : Promise} [options.promptHandler] + * A callback to prompt the user before installing. */ constructor(installLocation, url, options = {}) { this.wrapper = new AddonInstallWrapper(this); @@ -1553,6 +1596,7 @@ class AddonInstall { * Note this method is overridden to handle additional state in * the subclassses below. * + * @returns {Promise} * @throws if installation cannot proceed from the current state */ install() { @@ -1648,8 +1692,8 @@ class AddonInstall { * Adds an InstallListener for this instance if the listener is not already * registered. * - * @param aListener - * The InstallListener to add + * @param {InstallListener} aListener + * The InstallListener to add */ addListener(aListener) { if (!this.listeners.some(function(i) { return i == aListener; })) @@ -1659,8 +1703,8 @@ class AddonInstall { /** * Removes an InstallListener for this instance if it is registered. * - * @param aListener - * The InstallListener to remove + * @param {InstallListener} aListener + * The InstallListener to remove */ removeListener(aListener) { this.listeners = this.listeners.filter(function(i) { @@ -1704,10 +1748,9 @@ class AddonInstall { * Called after the add-on is a local file and the signature and install * manifest can be read. * - * @param aCallback - * A function to call when the manifest has been loaded - * @throws if the add-on does not contain a valid install manifest or the - * XPI is incorrectly signed + * @param {nsIFile} file + * The file from which to load the manifest. + * @returns {Promise} */ async loadManifest(file) { let pkg; @@ -2020,7 +2063,16 @@ class AddonInstall { } /** - * Stages an upgrade for next application restart. + * Stages an add-on for install. + * + * @param {boolean} restartRequired + * If true, the final installation will be deferred until the + * next app startup. + * @param {AddonInternal} stagedAddon + * The AddonInternal object for the staged install. + * @param {boolean} isUpgrade + * True if this installation is an upgrade for an existing + * add-on. */ async stageInstall(restartRequired, stagedAddon, isUpgrade) { // First stage the file regardless of whether restarting is necessary @@ -2055,20 +2107,23 @@ class AddonInstall { /** * Removes any previously staged upgrade. + * + * @param {nsIFile} stagingDir + * The staging directory from which to unstage the install. */ - async unstageInstall(stagedAddon) { + async unstageInstall(stagingDir) { XPIStates.getLocation(this.installLocation.name).unstageAddon(this.addon.id); - await removeAsync(getFile(this.addon.id, stagedAddon)); + await removeAsync(getFile(this.addon.id, stagingDir)); - await removeAsync(getFile(`${this.addon.id}.xpi`, stagedAddon)); + await removeAsync(getFile(`${this.addon.id}.xpi`, stagingDir)); } /** * Postone a pending update, until restart or until the add-on resumes. * - * @param {Function} resumeFn - a function for the add-on to run - * when resuming. + * @param {function} resumeFn + * A function for the add-on to run when resuming. */ async postpone(resumeFn) { this.state = AddonManager.STATE_POSTPONED; @@ -2132,9 +2187,6 @@ class AddonInstall { var LocalAddonInstall = class extends AddonInstall { /** * Initialises this install to be an install from a local file. - * - * @returns Promise - * A Promise that resolves when the object is ready to use. */ async init() { this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file; @@ -2233,29 +2285,29 @@ var DownloadAddonInstall = class extends AddonInstall { /** * Instantiates a DownloadAddonInstall * - * @param installLocation - * The InstallLocation the add-on will be installed into - * @param url - * The nsIURL to get the add-on from - * @param options - * Additional options for the install - * @param options.hash - * An optional hash for the add-on - * @param options.existingAddon - * The add-on this install will update if known - * @param options.browser - * The browser performing the install, used to display - * authentication prompts. - * @param options.name - * An optional name for the add-on - * @param options.type - * An optional type for the add-on - * @param options.icons - * Optional icons for the add-on - * @param options.version - * An optional version for the add-on - * @param options.promptHandler - * A callback to prompt the user before installing. + * @param {InstallLocation} installLocation + * The InstallLocation the add-on will be installed into + * @param {nsIURL} url + * The nsIURL to get the add-on from + * @param {Object} [options = {}] + * Additional options for the install + * @param {string} [options.hash] + * An optional hash for the add-on + * @param {AddonInternal} [options.existingAddon] + * The add-on this install will update if known + * @param {XULElement} [options.browser] + * The browser performing the install, used to display + * authentication prompts. + * @param {string} [options.name] + * An optional name for the add-on + * @param {string} [options.type] + * An optional type for the add-on + * @param {Object} [options.icons] + * Optional icons for the add-on + * @param {string} [options.version] + * An optional version for the add-on + * @param {function(string) : Promise} [options.promptHandler] + * A callback to prompt the user before installing. */ constructor(installLocation, url, options = {}) { super(installLocation, url, options); @@ -2386,7 +2438,7 @@ var DownloadAddonInstall = class extends AddonInstall { } } - /** + /* * Update the crypto hasher with the new data and call the progress listeners. * * @see nsIStreamListener @@ -2399,7 +2451,7 @@ var DownloadAddonInstall = class extends AddonInstall { } } - /** + /* * Check the redirect response for a hash of the target XPI and verify that * we don't end up on an insecure channel. * @@ -2429,7 +2481,7 @@ var DownloadAddonInstall = class extends AddonInstall { this.channel = aNewChannel; } - /** + /* * This is the first chance to get at real headers on the channel. * * @see nsIStreamListener @@ -2464,7 +2516,7 @@ var DownloadAddonInstall = class extends AddonInstall { } } - /** + /* * The download is complete. * * @see nsIStreamListener @@ -2547,10 +2599,10 @@ var DownloadAddonInstall = class extends AddonInstall { /** * Notify listeners that the download failed. * - * @param aReason - * Something to log about the failure - * @param error - * The error code to pass to the listeners + * @param {string} aReason + * Something to log about the failure + * @param {integer} aError + * The error code to pass to the listeners */ downloadFailed(aReason, aError) { logger.warn("Download of " + this.sourceURI.spec + " failed", aError); @@ -2624,12 +2676,12 @@ var DownloadAddonInstall = class extends AddonInstall { /** * Creates a new AddonInstall for an update. * - * @param aCallback - * The callback to pass the new AddonInstall to - * @param aAddon - * The add-on being updated - * @param aUpdate - * The metadata about the new version from the update manifest + * @param {function} aCallback + * The callback to pass the new AddonInstall to + * @param {AddonInternal} aAddon + * The add-on being updated + * @param {Object} aUpdate + * The metadata about the new version from the update manifest */ function createUpdate(aCallback, aAddon, aUpdate) { let url = Services.io.newURI(aUpdate.updateURL); @@ -2668,8 +2720,8 @@ let installFor = wrapper => wrapperMap.get(wrapper); /** * Creates a wrapper for an AddonInstall that only exposes the public API * - * @param install - * The AddonInstall to create a wrapper for + * @param {AddonInstall} aInstall + * The AddonInstall to create a wrapper for */ function AddonInstallWrapper(aInstall) { wrapperMap.set(this, aInstall); @@ -2736,16 +2788,16 @@ AddonInstallWrapper.prototype = { /** * Creates a new update checker. * - * @param aAddon - * The add-on to check for updates - * @param aListener - * An UpdateListener to notify of updates - * @param aReason - * The reason for the update check - * @param aAppVersion - * An optional application version to check for updates for - * @param aPlatformVersion - * An optional platform version to check for updates for + * @param {AddonInternal} aAddon + * The add-on to check for updates + * @param {UpdateListener} aListener + * An UpdateListener to notify of updates + * @param {integer} aReason + * The reason for the update check + * @param {string} [aAppVersion] + * An optional application version to check for updates for + * @param {string} [aPlatformVersion] + * An optional platform version to check for updates for * @throws if the aListener or aReason arguments are not valid */ var UpdateChecker = function(aAddon, aListener, aReason, aAppVersion, aPlatformVersion) { @@ -2794,8 +2846,10 @@ UpdateChecker.prototype = { * Calls a method on the listener passing any number of arguments and * consuming any exceptions. * - * @param aMethod - * The method to call on the listener + * @param {string} aMethod + * The method to call on the listener + * @param {any[]} aArgs + * Additional arguments to pass to the listener. */ callListener(aMethod, ...aArgs) { if (!(aMethod in this.listener)) @@ -2811,8 +2865,8 @@ UpdateChecker.prototype = { /** * Called when AddonUpdateChecker completes the update check * - * @param updates - * The list of update details for the add-on + * @param {object[]} aUpdates + * The list of update details for the add-on */ async onUpdateCheckComplete(aUpdates) { XPIProvider.done(this.addon._updateCheck); @@ -2908,8 +2962,8 @@ UpdateChecker.prototype = { /** * Called when AddonUpdateChecker fails the update check * - * @param aError - * An error status + * @param {any} aError + * An error status */ onUpdateCheckError(aError) { XPIProvider.done(this.addon._updateCheck); @@ -2935,12 +2989,12 @@ UpdateChecker.prototype = { /** * Creates a new AddonInstall to install an add-on from a local file. * - * @param file - * The file to install - * @param location - * The location to install to - * @returns Promise - * A Promise that resolves with the new install object. + * @param {nsIFile} file + * The file to install + * @param {InstallLocation} location + * The location to install to + * @returns {Promise} + * A Promise that resolves with the new install object. */ function createLocalInstall(file, location) { if (!location) { @@ -2969,7 +3023,7 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { * Gets the staging directory to put add-ons that are pending install and * uninstall into. * - * @return an nsIFile + * @returns {nsIFile} */ getStagingDir() { return getFile(DIR_STAGE, this._directory); @@ -3006,9 +3060,9 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { * Removes the specified files or directories in the staging directory and * then if the staging directory is empty attempts to remove it. * - * @param aLeafNames - * An array of file or directory to remove from the directory, the - * array may be empty + * @param {string[]} [aLeafNames = []] + * An array of file or directory to remove from the directory, the + * array may be empty */ cleanStagingDir(aLeafNames = []) { let dir = this.getStagingDir(); @@ -3044,7 +3098,7 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { * safe move operations. Calling this method will delete the existing trash * directory and its contents. * - * @return an nsIFile + * @returns {nsIFile} */ getTrashDir() { let trashDir = getFile(DIR_TRASH, this._directory); @@ -3065,22 +3119,25 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { /** * Installs an add-on into the install location. * - * @param id - * The ID of the add-on to install - * @param source - * The source nsIFile to install from - * @param existingAddonID - * The ID of an existing add-on to uninstall at the same time - * @param action - * What to we do with the given source file: - * "move" - * Default action, the source files will be moved to the new - * location, - * "copy" - * The source files will be copied, - * "proxy" - * A "proxy file" is going to refer to the source file path - * @return an nsIFile indicating where the add-on was installed to + * @param {Object} options + * Installation options. + * @param {string} options.id + * The ID of the add-on to install + * @param {nsIFile} options.source + * The source nsIFile to install from + * @param {string?} [options.existingAddonID] + * The ID of an existing add-on to uninstall at the same time + * @param {string} options.action + * What to we do with the given source file: + * "move" + * Default action, the source files will be moved to the new + * location, + * "copy" + * The source files will be copied, + * "proxy" + * A "proxy file" is going to refer to the source file path + * @returns {nsIFile} + * An nsIFile indicating where the add-on was installed to */ installAddon({ id, source, existingAddonID, action = "move" }) { let trashDir = this.getTrashDir(); @@ -3179,8 +3236,8 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { /** * Uninstalls an add-on from this location. * - * @param aId - * The ID of the add-on to uninstall + * @param {string} aId + * The ID of the add-on to uninstall * @throws if the ID does not match any of the add-ons installed */ uninstallAddon(aId) { @@ -3249,7 +3306,8 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { * Gets the staging directory to put add-ons that are pending install and * uninstall into. * - * @return {nsIFile} - staging directory for system add-on upgrades. + * @returns {nsIFile} + * Staging directory for system add-on upgrades. */ getStagingDir() { this._addonSet = SystemAddonInstallLocation._loadAddonSet(); @@ -3283,6 +3341,11 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { /** * Tests whether the loaded add-on information matches what is expected. + * + * @param {Map} aAddons + * The set of add-ons to check. + * @returns {boolean} + * True if all of the given add-ons are valid. */ isValid(aAddons) { for (let id of Object.keys(this._addonSet.addons)) { @@ -3498,6 +3561,9 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { /** * Resumes upgrade of a previously-delayed add-on set. + * + * @param {AddonInstall[]} installs + * The set of installs to resume. */ async resumeAddonSet(installs) { async function resumeAddon(install) { @@ -3523,7 +3589,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { * safe move operations. Calling this method will delete the existing trash * directory and its contents. * - * @return an nsIFile + * @returns {nsIFile} */ getTrashDir() { let trashDir = getFile(DIR_TRASH, this._directory); @@ -3544,11 +3610,12 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { /** * Installs an add-on into the install location. * - * @param id - * The ID of the add-on to install - * @param source - * The source nsIFile to install from - * @return an nsIFile indicating where the add-on was installed to + * @param {string} id + * The ID of the add-on to install + * @param {nsIFile} source + * The source nsIFile to install from + * @returns {nsIFile} + * An nsIFile indicating where the add-on was installed to */ installAddon({id, source}) { let trashDir = this.getTrashDir(); @@ -3855,7 +3922,8 @@ var XPIInstall = { /** * Called to test whether installing XPI add-ons is enabled. * - * @return true if installing is enabled + * @returns {boolean} + * True if installing is enabled. */ isInstallEnabled() { // Default to enabled if the preference does not exist @@ -3866,7 +3934,8 @@ var XPIInstall = { * Called to test whether installing XPI add-ons by direct URL requests is * whitelisted. * - * @return true if installing by direct requests is whitelisted + * @returns {boolean} + * True if installing by direct requests is whitelisted */ isDirectRequestWhitelisted() { // Default to whitelisted if the preference does not exist. @@ -3877,7 +3946,8 @@ var XPIInstall = { * Called to test whether installing XPI add-ons from file referrers is * whitelisted. * - * @return true if installing from file referrers is whitelisted + * @returns {boolean} + * True if installing from file referrers is whitelisted */ isFileRequestWhitelisted() { // Default to whitelisted if the preference does not exist. @@ -3887,9 +3957,10 @@ var XPIInstall = { /** * Called to test whether installing XPI add-ons from a URI is allowed. * - * @param aInstallingPrincipal - * The nsIPrincipal that initiated the install - * @return true if installing is allowed + * @param {nsIPrincipal} aInstallingPrincipal + * The nsIPrincipal that initiated the install + * @returns {boolean} + * True if installing is allowed */ isInstallAllowed(aInstallingPrincipal) { if (!this.isInstallEnabled()) @@ -3927,18 +3998,19 @@ var XPIInstall = { /** * Called to get an AddonInstall to download and install an add-on from a URL. * - * @param aUrl + * @param {nsIURI} aUrl * The URL to be installed - * @param aHash - * A hash for the install - * @param aName - * A name for the install - * @param aIcons - * Icon URLs for the install - * @param aVersion - * A version for the install - * @param aBrowser - * The browser performing the install + * @param {string?} [aHash] + * A hash for the install + * @param {string} [aName] + * A name for the install + * @param {Object} [aIcons] + * Icon URLs for the install + * @param {string} [aVersion] + * A version for the install + * @param {XULElement?} [aBrowser] + * The browser performing the install + * @returns {AddonInstall} */ async getInstallForURL(aUrl, aHash, aName, aIcons, aVersion, aBrowser) { let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; @@ -3965,8 +4037,9 @@ var XPIInstall = { /** * Called to get an AddonInstall to install an add-on from a local file. * - * @param aFile - * The file to be installed + * @param {nsIFile} aFile + * The file to be installed + * @returns {AddonInstall?} */ async getInstallForFile(aFile) { let install = await createLocalInstall(aFile); @@ -3978,10 +4051,11 @@ var XPIInstall = { * As this is intended for development, the signature is not checked and * the add-on does not persist on application restart. * - * @param aFile + * @param {nsIFile} aFile * An nsIFile for the unpacked add-on directory or XPI file. * - * @return See installAddonFromLocation return value. + * @returns {Addon} + * See installAddonFromLocation return value. */ installTemporaryAddon(aFile) { return this.installAddonFromLocation(aFile, XPIInternal.TemporaryInstallLocation); @@ -3991,10 +4065,11 @@ var XPIInstall = { * Permanently installs add-on from a local XPI file or directory. * The signature is checked but the add-on persist on application restart. * - * @param aFile + * @param {nsIFile} aFile * An nsIFile for the unpacked add-on directory or XPI file. * - * @return See installAddonFromLocation return value. + * @returns {Addon} + * See installAddonFromLocation return value. */ async installAddonFromSources(aFile) { let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; @@ -4004,17 +4079,18 @@ var XPIInstall = { /** * Installs add-on from a local XPI file or directory. * - * @param aFile + * @param {nsIFile} aFile * An nsIFile for the unpacked add-on directory or XPI file. - * @param aInstallLocation + * @param {InstallLocation} aInstallLocation * Define a custom install location object to use for the install. - * @param aInstallAction + * @param {string?} [aInstallAction] * Optional action mode to use when installing the addon * (see MutableDirectoryInstallLocation.installAddon) * - * @return a Promise that resolves to an Addon object on success, or rejects - * if the add-on is not a valid restartless add-on or if the - * same ID is already installed. + * @returns {Promise} + * A Promise that resolves to an Addon object on success, or rejects + * if the add-on is not a valid restartless add-on or if the + * same ID is already installed. */ async installAddonFromLocation(aFile, aInstallLocation, aInstallAction) { if (aFile.exists() && aFile.isFile()) { @@ -4130,12 +4206,12 @@ var XPIInstall = { * Uninstalls an add-on, immediately if possible or marks it as pending * uninstall if not. * - * @param aAddon - * The DBAddonInternal to uninstall - * @param aForcePending - * Force this addon into the pending uninstall state (used - * e.g. while the add-on manager is open and offering an - * "undo" button) + * @param {DBAddonInternal} aAddon + * The DBAddonInternal to uninstall + * @param {boolean} aForcePending + * Force this addon into the pending uninstall state (used + * e.g. while the add-on manager is open and offering an + * "undo" button) * @throws if the addon cannot be uninstalled because it is in an install * location that does not allow it */ @@ -4265,8 +4341,8 @@ var XPIInstall = { /** * Cancels the pending uninstall of an add-on. * - * @param aAddon - * The DBAddonInternal to cancel uninstall for + * @param {DBAddonInternal} aAddon + * The DBAddonInternal to cancel uninstall for */ cancelUninstallAddon(aAddon) { if (!(aAddon.inDatabase)) From fdcc021e51917ef8eb65aa41029bb6da80bfc01e Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 21 Apr 2018 20:51:52 -0700 Subject: [PATCH 27/44] Bug 1363925: Part 7b - Turn on valid-jsdoc rule for XPIProvider.jsm. r=me,npotb MozReview-Commit-ID: BQ6N84B2pC3 --HG-- extra : rebase_source : 04b9d4f3331d0de1ef8225b56e0f069265d7d4f4 extra : histedit_source : 287ad8e3c17f19cc7d140fed4f4ef5f4a36a0dc9 --- .../extensions/internal/XPIProvider.jsm | 481 ++++++++++-------- 1 file changed, 281 insertions(+), 200 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 11bb5dfe8855..c94a5804972a 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -4,6 +4,8 @@ "use strict"; +/* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */ + var EXPORTED_SYMBOLS = ["XPIProvider", "XPIInternal"]; /* globals WebExtensionPolicy */ @@ -430,8 +432,9 @@ function findMatchingStaticBlocklistItem(aAddon) { * Helper function that determines whether an addon of a certain type is a * WebExtension. * - * @param {String} type - * @return {Boolean} + * @param {string} type + * The add-on type to check. + * @returns {boolean} */ function isWebExtension(type) { return type == "webextension" || type == "webextension-theme"; @@ -442,8 +445,9 @@ var gThemeAliases = null; * Helper function that determines whether an addon of a certain type is a * theme. * - * @param {String} type - * @return {Boolean} + * @param {string} type + * The add-on type to check. + * @returns {boolean} */ function isTheme(type) { if (!gThemeAliases) @@ -454,9 +458,10 @@ function isTheme(type) { /** * Evaluates whether an add-on is allowed to run in safe mode. * - * @param aAddon - * The add-on to check - * @return true if the add-on should run in safe mode + * @param {AddonInternal} aAddon + * The add-on to check + * @returns {boolean} + * True if the add-on should run in safe mode */ function canRunInSafeMode(aAddon) { // Even though the updated system add-ons aren't generally run in safe mode we @@ -502,9 +507,10 @@ function isDisabledLegacy(addon) { /** * Calculates whether an add-on should be appDisabled or not. * - * @param aAddon - * The add-on to check - * @return true if the add-on should not be appDisabled + * @param {AddonInternal} aAddon + * The add-on to check + * @returns {boolean} + * True if the add-on should not be appDisabled */ function isUsableAddon(aAddon) { if (mustSign(aAddon.type) && !aAddon.isCorrectlySigned) { @@ -570,9 +576,10 @@ function isUsableAddon(aAddon) { /** * Converts an internal add-on type to the type presented through the API. * - * @param aType - * The internal add-on type - * @return an external add-on type + * @param {string} aType + * The internal add-on type + * @returns {string} + * An external add-on type */ function getExternalType(aType) { if (aType in TYPE_ALIASES) @@ -594,9 +601,10 @@ function getManifestFileForDir(aDir) { * Converts a list of API types to a list of API types and any aliases for those * types. * - * @param aTypes - * An array of types or null for all types - * @return an array of types or null for all types + * @param {Array?} aTypes + * An array of types or null for all types + * @returns {Array?} + * An array of types or null for all types */ function getAllAliasesForTypes(aTypes) { if (!aTypes) @@ -622,13 +630,14 @@ function getAllAliasesForTypes(aTypes) { * file. If aFile is a directory then this will return a file: URI, if it is an * XPI file then it will return a jar: URI. * - * @param aFile - * The file containing the resources, must be either a directory or an - * XPI file - * @param aPath - * The path to find the resource at, "/" separated. If aPath is empty - * then the uri to the root of the contained files will be returned - * @return an nsIURI pointing at the resource + * @param {nsIFile} aFile + * The file containing the resources, must be either a directory or an + * XPI file + * @param {string} aPath + * The path to find the resource at, "/" separated. If aPath is empty + * then the uri to the root of the contained files will be returned + * @returns {nsIURI} + * An nsIURI pointing at the resource */ function getURIForResourceInFile(aFile, aPath) { if (aFile.exists() && aFile.isDirectory()) { @@ -645,11 +654,12 @@ function getURIForResourceInFile(aFile, aPath) { /** * Creates a jar: URI for a file inside a ZIP file. * - * @param aJarfile - * The ZIP file as an nsIFile - * @param aPath - * The path inside the ZIP file - * @return an nsIURI for the file + * @param {nsIFile} aJarfile + * The ZIP file as an nsIFile + * @param {string} aPath + * The path inside the ZIP file + * @returns {nsIURI} + * An nsIURI for the file */ function buildJarURI(aJarfile, aPath) { let uri = Services.io.newFileURI(aJarfile); @@ -660,11 +670,12 @@ function buildJarURI(aJarfile, aPath) { /** * Gets a snapshot of directory entries. * - * @param aDir - * Directory to look at - * @param aSortEntries - * True to sort entries by filename - * @return An array of nsIFile, or an empty array if aDir is not a readable directory + * @param {nsIURI} aDir + * Directory to look at + * @param {boolean} aSortEntries + * True to sort entries by filename + * @returns {Array} + * An array of nsIFile, or an empty array if aDir is not a readable directory */ function getDirectoryEntries(aDir, aSortEntries) { let dirEnum; @@ -694,8 +705,13 @@ function getDirectoryEntries(aDir, aSortEntries) { } /** - * Record a bit of per-addon telemetry - * @param aAddon the addon to record + * Record a bit of per-addon telemetry. + * + * Yes, this description is extremely helpful. How dare you question its + * utility? + * + * @param {AddonInternal} aAddon + * The addon to record */ function recordAddonTelemetry(aAddon) { let locale = aAddon.defaultLocale; @@ -763,11 +779,12 @@ class XPIState { * The location of the add-on. * @param {string} id * The ID of the add-on to migrate. - * @param {object} state + * @param {object} saved * The add-on's data from the xpiState preference. * @param {object} [bootstrapped] * The add-on's data from the bootstrappedAddons preference, if * applicable. + * @returns {XPIState} */ static migrate(location, id, saved, bootstrapped) { let data = { @@ -831,6 +848,8 @@ class XPIState { /** * Returns a JSON-compatible representation of this add-on's state * data, to be saved to addonStartup.json. + * + * @returns {Object} */ toJSON() { let json = { @@ -856,9 +875,13 @@ class XPIState { /** * Update the last modified time for an add-on on disk. - * @param aFile: nsIFile path of the add-on. - * @param aId: The add-on ID. - * @return True if the time stamp has changed. + * + * @param {nsIFile} aFile + * The location of the add-on. + * @param {string} aId + * The add-on ID. + * @returns {boolean} + * True if the time stamp has changed. */ getModTime(aFile, aId) { // Modified time is the install manifest time, if any. If no manifest @@ -879,9 +902,13 @@ class XPIState { * Update the XPIState to match an XPIDatabase entry; if 'enabled' is changed to true, * update the last-modified time. This should probably be made async, but for now we * don't want to maintain parallel sync and async versions of the scan. + * * Caller is responsible for doing XPIStates.save() if necessary. - * @param aDBAddon The DBAddonInternal for this add-on. - * @param aUpdated The add-on was updated, so we must record new modified time. + * + * @param {DBAddonInternal} aDBAddon + * The DBAddonInternal for this add-on. + * @param {boolean} [aUpdated = false] + * The add-on was updated, so we must record new modified time. */ syncWithDB(aDBAddon, aUpdated = false) { logger.debug("Updating XPIState for " + JSON.stringify(aDBAddon)); @@ -949,6 +976,8 @@ class XPIStateLocation extends Map { /** * Returns a JSON-compatible representation of this location's state * data, to be saved to addonStartup.json. + * + * @returns {Object} */ toJSON() { let json = { @@ -1144,6 +1173,8 @@ var XPIStates = { /** * Load extension state data from addonStartup.json, or migrates it * from legacy state preferences, if they exist. + * + * @returns {Object} */ loadExtensionState() { let state; @@ -1172,11 +1203,12 @@ var XPIStates = { * Walk through all install locations, highest priority first, * comparing the on-disk state of extensions to what is stored in prefs. * - * @param {bool} [ignoreSideloads = true] + * @param {boolean} [ignoreSideloads = true] * If true, ignore changes in scopes where we don't accept * side-loads. * - * @return true if anything has changed. + * @returns {boolean} + * True if anything has changed. */ getInstallState(ignoreSideloads = true) { if (!this.db) { @@ -1252,8 +1284,17 @@ var XPIStates = { /** * Get the Map of XPI states for a particular location. - * @param name The name of the install location. - * @return XPIStateLocation (id -> XPIState) or null if there are no add-ons in the location. + * + * @param {string} name + * The name of the install location. + * @param {string?} [path] + * The expected path of the location, if known. + * @param {Object?} [saved] + * The saved data for the location, as read from the + * addonStartup.json file. + * + * @returns {XPIStateLocation?} + * (id -> XPIState) or null if there are no add-ons in the location. */ getLocation(name, path, saved) { let location = this.db.get(name); @@ -1276,9 +1317,14 @@ var XPIStates = { /** * Get the XPI state for a specific add-on in a location. * If the state is not in our cache, return null. - * @param aLocation The name of the location where the add-on is installed. - * @param aId The add-on ID - * @return The XPIState entry for the add-on, or null. + * + * @param {string} aLocation + * The name of the location where the add-on is installed. + * @param {string} aId + * The add-on ID + * + * @returns {XPIState?} + * The XPIState entry for the add-on, or null. */ getAddon(aLocation, aId) { let location = this.db.get(aLocation); @@ -1294,7 +1340,7 @@ var XPIStates = { * An optional filter to apply to install locations. If provided, * addons in locations that do not match the filter are not considered. * - * @return {XPIState?} + * @returns {XPIState?} */ findAddon(aId, aFilter = location => true) { // Fortunately the Map iterator returns in order of insertion, which is @@ -1348,7 +1394,9 @@ var XPIStates = { /** * Add a new XPIState for an add-on and synchronize it with the DBAddonInternal. - * @param aAddon DBAddonInternal for the new add-on. + * + * @param {DBAddonInternal} aAddon + * The add-on to add. */ addAddon(aAddon) { let location = this.getLocation(aAddon._installLocation.name); @@ -1383,8 +1431,12 @@ var XPIStates = { /** * Remove the XPIState for an add-on and save the new state. - * @param aLocation The name of the add-on location. - * @param aId The ID of the add-on. + * + * @param {string} aLocation + * The name of the add-on location. + * @param {string} aId + * The ID of the add-on. + * */ removeAddon(aLocation, aId) { logger.debug("Removing XPIState for " + aLocation + ":" + aId); @@ -1400,6 +1452,9 @@ var XPIStates = { /** * Disable the XPIState for an add-on. + * + * @param {string} aId + * The ID of the add-on. */ disableAddon(aId) { logger.debug(`Disabling XPIState for ${aId}`); @@ -1550,18 +1605,18 @@ var XPIProvider = { /** * Starts the XPI provider initializes the install locations and prefs. * - * @param aAppChanged - * A tri-state value. Undefined means the current profile was created - * for this session, true means the profile already existed but was - * last used with an application with a different version number, - * false means that the profile was last used by this version of the - * application. - * @param aOldAppVersion - * The version of the application last run with this profile or null - * if it is a new profile or the version is unknown - * @param aOldPlatformVersion - * The version of the platform last run with this profile or null - * if it is a new profile or the version is unknown + * @param {boolean?} aAppChanged + * A tri-state value. Undefined means the current profile was created + * for this session, true means the profile already existed but was + * last used with an application with a different version number, + * false means that the profile was last used by this version of the + * application. + * @param {string?} [aOldAppVersion] + * The version of the application last run with this profile or null + * if it is a new profile or the version is unknown + * @param {string?} [aOldPlatformVersion] + * The version of the platform last run with this profile or null + * if it is a new profile or the version is unknown */ startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) { function addDirectoryInstallLocation(aName, aKey, aPaths, aScope, aLocked) { @@ -2177,10 +2232,11 @@ var XPIProvider = { * Check the staging directories of install locations for any add-ons to be * installed or add-ons to be uninstalled. * - * @param aManifests + * @param {Object} aManifests * A dictionary to add detected install manifests to for the purpose * of passing through updated compatibility information - * @return true if an add-on was installed or uninstalled + * @returns {boolean} + * True if an add-on was installed or uninstalled */ processPendingFileChanges(aManifests) { let changed = false; @@ -2236,12 +2292,13 @@ var XPIProvider = { * newer version already exists or the user has previously uninstalled the * distributed add-on. * - * @param aManifests - * A dictionary to add new install manifests to to save having to - * reload them later - * @param aAppChanged - * See checkForChanges - * @return true if any new add-ons were installed + * @param {Object} aManifests + * A dictionary to add new install manifests to to save having to + * reload them later + * @param {string} [aAppChanged] + * See checkForChanges + * @returns {boolean} + * True if any new add-ons were installed */ installDistributionAddons(aManifests, aAppChanged) { let distroDir; @@ -2338,19 +2395,20 @@ var XPIProvider = { * Checks for any changes that have occurred since the last time the * application was launched. * - * @param aAppChanged - * A tri-state value. Undefined means the current profile was created - * for this session, true means the profile already existed but was - * last used with an application with a different version number, - * false means that the profile was last used by this version of the - * application. - * @param aOldAppVersion - * The version of the application last run with this profile or null - * if it is a new profile or the version is unknown - * @param aOldPlatformVersion - * The version of the platform last run with this profile or null - * if it is a new profile or the version is unknown - * @return true if a change requiring a restart was detected + * @param {boolean?} [aAppChanged] + * A tri-state value. Undefined means the current profile was created + * for this session, true means the profile already existed but was + * last used with an application with a different version number, + * false means that the profile was last used by this version of the + * application. + * @param {string?} [aOldAppVersion] + * The version of the application last run with this profile or null + * if it is a new profile or the version is unknown + * @param {string?} [aOldPlatformVersion] + * The version of the platform last run with this profile or null + * if it is a new profile or the version is unknown + * @returns {boolean} + * True if a change requiring a restart was detected */ checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion) { logger.debug("checkForChanges"); @@ -2481,9 +2539,10 @@ var XPIProvider = { * Called to test whether this provider supports installing a particular * mimetype. * - * @param aMimetype - * The mimetype to check for - * @return true if the mimetype is application/x-xpinstall + * @param {string} aMimetype + * The mimetype to check for + * @returns {boolean} + * True if the mimetype is application/x-xpinstall */ supportsMimetype(aMimetype) { return aMimetype == "application/x-xpinstall"; @@ -2513,12 +2572,13 @@ var XPIProvider = { /** * Returns an Addon corresponding to an instance ID. - * @param aInstanceID + * + * @param {Symbol} aInstanceID * An Addon Instance ID - * @return {Promise} - * @resolves The found Addon or null if no such add-on exists. - * @rejects Never - * @throws if the aInstanceID argument is not specified + * + * @returns {AddonInternal?} + * + * @throws if the aInstanceID argument is not valid. */ getAddonByInstanceID(aInstanceID) { let id = this.getAddonIDByInstanceID(aInstanceID); @@ -2546,8 +2606,8 @@ var XPIProvider = { /** * Removes an AddonInstall from the list of active installs. * - * @param install - * The AddonInstall to remove + * @param {AddonInstall} aInstall + * The AddonInstall to remove */ removeActiveInstall(aInstall) { this.installs.delete(aInstall); @@ -2556,8 +2616,9 @@ var XPIProvider = { /** * Called to get an Addon with a particular ID. * - * @param aId - * The ID of the add-on to retrieve + * @param {string} aId + * The ID of the add-on to retrieve + * @returns {Addon?} */ async getAddonByID(aId) { let aAddon = await XPIDatabase.getVisibleAddonForID(aId); @@ -2587,8 +2648,9 @@ var XPIProvider = { /** * Called to get Addons of a particular type. * - * @param aTypes - * An array of types to fetch. Can be null to get all types. + * @param {Array?} aTypes + * An array of types to fetch. Can be null to get all types. + * @returns {Addon[]} */ async getAddonsByTypes(aTypes) { let typesToGet = getAllAliasesForTypes(aTypes); @@ -2603,8 +2665,8 @@ var XPIProvider = { /** * Called to get active Addons of a particular type * - * @param aTypes - * An array of types to fetch. Can be null to get all types. + * @param {Array?} aTypes + * An array of types to fetch. Can be null to get all types. * @returns {Promise>} */ async getActiveAddons(aTypes) { @@ -2651,8 +2713,9 @@ var XPIProvider = { /** * Obtain an Addon having the specified Sync GUID. * - * @param aGUID - * String GUID of add-on to retrieve + * @param {string} aGUID + * String GUID of add-on to retrieve + * @returns {Addon?} */ async getAddonBySyncGUID(aGUID) { let addon = await XPIDatabase.getAddonBySyncGUID(aGUID); @@ -2662,8 +2725,9 @@ var XPIProvider = { /** * Called to get Addons that have pending operations. * - * @param aTypes - * An array of types to fetch. Can be null to get all types + * @param {Array?} aTypes + * An array of types to fetch. Can be null to get all types + * @returns {Addon[]} */ async getAddonsWithOperationsByTypes(aTypes) { let typesToGet = getAllAliasesForTypes(aTypes); @@ -2682,8 +2746,9 @@ var XPIProvider = { * Called to get the current AddonInstalls, optionally limiting to a list of * types. * - * @param aTypes - * An array of types or null to get all types + * @param {Array?} aTypes + * An array of types or null to get all types + * @returns {AddonInstall[]} */ getInstallsByTypes(aTypes) { let results = [...this.installs]; @@ -2700,10 +2765,10 @@ var XPIProvider = { * Called when a new add-on has been enabled when only one add-on of that type * can be enabled. * - * @param aId - * The ID of the newly enabled add-on - * @param aType - * The type of the newly enabled add-on + * @param {string} aId + * The ID of the newly enabled add-on + * @param {string} aType + * The type of the newly enabled add-on */ addonChanged(aId, aType) { // We only care about themes in this provider @@ -2764,7 +2829,7 @@ var XPIProvider = { } }, - /** + /* * Notified when a preference we're interested in has changed. * * @see nsIObserver @@ -2807,21 +2872,20 @@ var XPIProvider = { * add-on to the bootstrappedAddons dictionary and notify the crash reporter * that new add-ons have been loaded. * - * @param aId - * The add-on's ID - * @param aFile - * The nsIFile for the add-on - * @param aVersion - * The add-on's version - * @param aType - * The type for the add-on - * @param aRunInSafeMode - * Boolean indicating whether the add-on can run in safe mode. - * @param aDependencies - * An array of add-on IDs on which this add-on depends. - * @param hasEmbeddedWebExtension - * Boolean indicating whether the add-on has an embedded webextension. - * @return a JavaScript scope + * @param {string} aId + * The add-on's ID + * @param {nsIFile} aFile + * The nsIFile for the add-on + * @param {string} aVersion + * The add-on's version + * @param {string} aType + * The type for the add-on + * @param {boolean} aRunInSafeMode + * Boolean indicating whether the add-on can run in safe mode. + * @param {string[]} aDependencies + * An array of add-on IDs on which this add-on depends. + * @param {boolean} hasEmbeddedWebExtension + * Boolean indicating whether the add-on has an embedded webextension. */ loadBootstrapScope(aId, aFile, aVersion, aType, aRunInSafeMode, aDependencies, hasEmbeddedWebExtension) { @@ -2896,8 +2960,8 @@ var XPIProvider = { * Unloads a bootstrap scope by dropping all references to it and then * updating the list of active add-ons with the crash reporter. * - * @param aId - * The add-on's ID + * @param {string} aId + * The add-on's ID */ unloadBootstrapScope(aId) { this.activeAddons.delete(aId); @@ -2911,17 +2975,17 @@ var XPIProvider = { /** * Calls a bootstrap method for an add-on. * - * @param aAddon - * An object representing the add-on, with `id`, `type` and `version` - * @param aFile - * The nsIFile for the add-on - * @param aMethod - * The name of the bootstrap method to call - * @param aReason - * The reason flag to pass to the bootstrap's startup method - * @param aExtraParams - * An object of additional key/value pairs to pass to the method in - * the params argument + * @param {Object} aAddon + * An object representing the add-on, with `id`, `type` and `version` + * @param {nsIFile} aFile + * The nsIFile for the add-on + * @param {string} aMethod + * The name of the bootstrap method to call + * @param {integer} aReason + * The reason flag to pass to the bootstrap's startup method + * @param {Object?} [aExtraParams] + * An object of additional key/value pairs to pass to the method in + * the params argument */ callBootstrapMethod(aAddon, aFile, aMethod, aReason, aExtraParams) { if (!aAddon.id || !aAddon.version || !aAddon.type) { @@ -3053,18 +3117,19 @@ var XPIProvider = { * calculated and if the add-on is changed the database will be saved and * appropriate notifications will be sent out to the registered AddonListeners. * - * @param aAddon - * The DBAddonInternal to update - * @param aUserDisabled - * Value for the userDisabled property. If undefined the value will - * not change - * @param aSoftDisabled - * Value for the softDisabled property. If undefined the value will - * not change. If true this will force userDisabled to be true - * @param {boolean} aBecauseSelecting + * @param {DBAddonInternal} aAddon + * The DBAddonInternal to update + * @param {boolean?} [aUserDisabled] + * Value for the userDisabled property. If undefined the value will + * not change + * @param {boolean?} [aSoftDisabled] + * Value for the softDisabled property. If undefined the value will + * not change. If true this will force userDisabled to be true + * @param {boolean?} [aBecauseSelecting] * True if we're disabling this add-on because we're selecting * another. - * @return a tri-state indicating the action taken for the add-on: + * @returns {boolean?} + * A tri-state indicating the action taken for the add-on: * - undefined: The add-on did not change state * - true: The add-on because disabled * - false: The add-on became enabled @@ -3510,14 +3575,11 @@ AddonInternal.prototype = { * with copies of all non-private properties. Functions, getters and setters * are not copied. * - * @param aKey - * The key that this object is being serialized as in the JSON. - * Unused here since this is always the main object serialized - * - * @return an object containing copies of the properties of this object - * ignoring private properties, functions, getters and setters + * @returns {Object} + * An object containing copies of the properties of this object + * ignoring private properties, functions, getters and setters. */ - toJSON(aKey) { + toJSON() { let obj = {}; for (let prop in this) { // Ignore the wrapper property @@ -3551,8 +3613,8 @@ AddonInternal.prototype = { * This method reads particular properties of that metadata that may be newer * than that in the install manifest, like compatibility information. * - * @param aObj - * A JS object containing the cached metadata + * @param {Object} aObj + * A JS object containing the cached metadata */ importMetadata(aObj) { for (let prop of PENDING_INSTALL_METADATA) { @@ -3607,6 +3669,9 @@ AddonInternal.prototype = { /** * The AddonWrapper wraps an Addon to provide the data visible to consumers of * the public API. + * + * @param {AddonInternal} aAddon + * The add-on object to wrap. */ function AddonWrapper(aAddon) { wrapperMap.set(this, aAddon); @@ -4024,7 +4089,7 @@ AddonWrapper.prototype = { * add-on. Otherwise, the addon is disabled and then re-enabled, and the cache * is flushed. * - * @return Promise + * @returns {Promise} */ reload() { return new Promise((resolve) => { @@ -4051,10 +4116,10 @@ AddonWrapper.prototype = { * will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is * still an XPI file. * - * @param aPath - * The path in the add-on to get the URI for or null to get a URI to - * the file or directory the add-on is installed as. - * @return an nsIURI + * @param {string?} aPath + * The path in the add-on to get the URI for or null to get a URI to + * the file or directory the add-on is installed as. + * @returns {nsIURI} */ getResourceURI(aPath) { let addon = addonFor(this); @@ -4221,12 +4286,12 @@ class DirectoryInstallLocation { * containing the add-ons files. The directory or text file must have the same * name as the add-on's ID. * - * @param aName - * The string identifier for the install location - * @param aDirectory - * The nsIFile directory for the install location - * @param aScope - * The scope of add-ons installed in this location + * @param {string} aName + * The string identifier for the install location + * @param {nsIFile} aDirectory + * The nsIFile directory for the install location + * @param {integer} aScope + * The scope of add-ons installed in this location */ constructor(aName, aDirectory, aScope) { this._name = aName; @@ -4254,9 +4319,10 @@ class DirectoryInstallLocation { /** * Reads a directory linked to in a file. * - * @param file - * The file containing the directory path - * @return An nsIFile object representing the linked directory. + * @param {nsIFile} aFile + * The file containing the directory path + * @returns {nsIFile} + * An nsIFile object representing the linked directory. */ _readDirectoryFromFile(aFile) { let linkedDirectory; @@ -4311,6 +4377,10 @@ class DirectoryInstallLocation { /** * Finds all the add-ons installed in this location. + * + * @param {boolean} [rescan = false] + * True if the directory should be re-scanned, even if it has + * already been initialized. */ _readAddons(rescan = false) { if ((this.initialized && !rescan) || !this._directory) { @@ -4378,6 +4448,12 @@ class DirectoryInstallLocation { /** * Gets an array of nsIFiles for add-ons installed in this location. + * + * @param {boolean} [rescan = false] + * True if the directory should be re-scanned, even if it has + * already been initialized. + * + * @returns {nsIFile[]} */ getAddonLocations(rescan = false) { this._readAddons(rescan); @@ -4392,9 +4468,9 @@ class DirectoryInstallLocation { /** * Gets the directory that the add-on with the given ID is installed in. * - * @param aId - * The ID of the add-on - * @return The nsIFile + * @param {string} aId + * The ID of the add-on + * @returns {nsIFile} * @throws if the ID does not match any of the add-ons installed */ getLocationForID(aId) { @@ -4410,8 +4486,9 @@ class DirectoryInstallLocation { * Returns true if the given addon was installed in this location by a text * file pointing to its real path. * - * @param aId + * @param {string} aId * The ID of the addon + * @returns {boolean} */ isLinkedAddon(aId) { return this._linkedAddons.includes(aId); @@ -4424,12 +4501,12 @@ class DirectoryInstallLocation { */ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { /** - * @param aName - * The string identifier for the install location - * @param aDirectory - * The nsIFile directory for the install location - * @param aScope - * The scope of add-ons installed in this location + * @param {string} aName + * The string identifier for the install location + * @param {nsIFile} aDirectory + * The nsIFile directory for the install location + * @param {integer} aScope + * The scope of add-ons installed in this location */ constructor(aName, aDirectory, aScope) { super(aName, aDirectory, aScope); @@ -4493,14 +4570,14 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { /** * The location consists of a directory which contains the add-ons installed. * - * @param aName - * The string identifier for the install location - * @param aDirectory - * The nsIFile directory for the install location - * @param aScope - * The scope of add-ons installed in this location - * @param aResetSet - * True to throw away the current add-on set + * @param {string} aName + * The string identifier for the install location + * @param {nsIFile} aDirectory + * The nsIFile directory for the install location + * @param {integer} aScope + * The scope of add-ons installed in this location + * @param {boolean} aResetSet + * True to throw away the current add-on set */ constructor(aName, aDirectory, aScope, aResetSet) { let addonSet = SystemAddonInstallLocation._loadAddonSet(); @@ -4534,6 +4611,8 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { /** * Reads the current set of system add-ons + * + * @returns {Object} */ static _loadAddonSet() { try { @@ -4571,6 +4650,8 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { /** * Tests whether updated system add-ons are expected. + * + * @returns {boolean} */ isActive() { return this._directory != null; @@ -4601,12 +4682,12 @@ const TemporaryInstallLocation = { locked: false, name: KEY_APP_TEMPORARY, */ class WinRegInstallLocation extends DirectoryInstallLocation { /** - * @param aName - * The string identifier of this Install Location. - * @param aRootKey - * The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey). - * @param scope - * The scope of add-ons installed in this location + * @param {string} aName + * The string identifier of this Install Location. + * @param {integer} aRootKey + * The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey). + * @param {integer} aScope + * The scope of add-ons installed in this location */ constructor(aName, aRootKey, aScope) { super(aName, undefined, aScope); @@ -4655,8 +4736,8 @@ class WinRegInstallLocation extends DirectoryInstallLocation { * Read the registry and build a mapping between ID and path for each * installed add-on. * - * @param key - * The key that contains the ID to path mapping + * @param {nsIWindowsRegKey} aKey + * The key that contains the ID to path mapping */ _readAddons(aKey) { let count = aKey.valueCount; @@ -4681,7 +4762,7 @@ class WinRegInstallLocation extends DirectoryInstallLocation { return this._name; } - /** + /* * @see DirectoryInstallLocation */ isLinkedAddon(aId) { From 17554348ca03720897425d95f581c037137f4b8c Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 22 Apr 2018 14:05:17 -0700 Subject: [PATCH 28/44] Bug 1363925: Part 7c - Turn on valid-jsdoc rule for XPIProviderUtils.js. r=me,npotb MozReview-Commit-ID: ENdbZ8dicVX --HG-- extra : rebase_source : 5b87da8abd55ed49d2b03c686545ad4cbacb09c6 extra : histedit_source : 098718bfd845fcc1b3bfff4aecafcdf1d84a82c3 --- .../extensions/internal/XPIProviderUtils.js | 361 +++++++++++------- 1 file changed, 217 insertions(+), 144 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index c2b578803fb9..afb4cdc742b9 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -4,6 +4,8 @@ "use strict"; +/* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */ + // These are injected from XPIProvider.jsm /* globals ADDON_SIGNING, SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA, AddonInternal, XPIProvider, XPIStates, @@ -67,6 +69,11 @@ const ASYNC_SAVE_DELAY_MS = 20; /** * Asynchronously fill in the _repositoryAddon field for one addon + * + * @param {AddonInternal} aAddon + * The add-on to annotate. + * @returns {AddonInternal} + * The annotated add-on. */ async function getRepositoryAddon(aAddon) { if (aAddon) { @@ -79,13 +86,14 @@ async function getRepositoryAddon(aAddon) { * Copies properties from one object to another. If no target object is passed * a new object will be created and returned. * - * @param aObject - * An object to copy from - * @param aProperties - * An array of properties to be copied - * @param aTarget - * An optional target object to copy the properties to - * @return the object that the properties were copied onto + * @param {object} aObject + * An object to copy from + * @param {string[]} aProperties + * An array of properties to be copied + * @param {object?} [aTarget] + * An optional target object to copy the properties to + * @returns {Object} + * The object that the properties were copied onto */ function copyProperties(aObject, aProperties, aTarget) { if (!aTarget) @@ -103,7 +111,7 @@ function copyProperties(aObject, aProperties, aTarget) { * of fields, which could come from either the JSON store or as an * XPIProvider.AddonInternal created from an addon's manifest * @constructor - * @param aLoaded + * @param {Object} aLoaded * Addon data fields loaded from JSON or the addon manifest. */ function DBAddonInternal(aLoaded) { @@ -168,7 +176,19 @@ Object.assign(DBAddonInternal.prototype, { }); /** - * Internal interface: find an addon from an already loaded addonDB + * @typedef {Map} AddonDB + */ + +/** + * Internal interface: find an addon from an already loaded addonDB. + * + * @param {AddonDB} addonDB + * The add-on database. + * @param {function(DBAddonInternal) : boolean} aFilter + * The filter predecate. The first add-on for which it returns + * true will be returned. + * @returns {DBAddonInternal?} + * The first matching add-on, if one is found. */ function _findAddon(addonDB, aFilter) { for (let addon of addonDB.values()) { @@ -181,6 +201,14 @@ function _findAddon(addonDB, aFilter) { /** * Internal interface to get a filtered list of addons from a loaded addonDB + * + * @param {AddonDB} addonDB + * The add-on database. + * @param {function(DBAddonInternal) : boolean} aFilter + * The filter predecate. Add-ons which match this predicate will + * be returned. + * @returns {Array} + * The list of matching add-ons. */ function _filterDB(addonDB, aFilter) { return Array.from(addonDB.values()).filter(aFilter); @@ -271,6 +299,8 @@ this.XPIDatabase = { /** * Converts the current internal state of the XPI addon database to * a JSON.stringify()-ready structure + * + * @returns {Object} */ toJSON() { if (!this.addonDB) { @@ -299,10 +329,11 @@ this.XPIDatabase = { * 6) usable RDF DB => upgrade * 7) useless RDF DB => build new JSON * 8) Nothing at all => build new JSON - * @param aRebuildOnError - * A boolean indicating whether add-on information should be loaded - * from the install locations if the database needs to be rebuilt. - * (if false, caller is XPIProvider.checkForChanges() which will rebuild) + * + * @param {boolean} aRebuildOnError + * A boolean indicating whether add-on information should be loaded + * from the install locations if the database needs to be rebuilt. + * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ syncLoadDB(aRebuildOnError) { this.migrateData = null; @@ -358,13 +389,15 @@ this.XPIDatabase = { /** * Parse loaded data, reconstructing the database if the loaded data is not valid - * @param aRebuildOnError + * + * @param {string} aData + * The stringified add-on JSON to parse. + * @param {boolean} aRebuildOnError * If true, synchronously reconstruct the database from installed add-ons */ parseDB(aData, aRebuildOnError) { let parseTimer = AddonManagerPrivate.simpleTimer("XPIDB_parseDB_MS"); try { - // dump("Loaded JSON:\n" + aData + "\n"); let inputAddons = JSON.parse(aData); // Now do some sanity checks on our JSON db if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { @@ -430,6 +463,9 @@ this.XPIDatabase = { /** * Upgrade database from earlier (sqlite or RDF) version if available + * + * @param {boolean} aRebuildOnError + * If true, synchronously reconstruct the database from installed add-ons */ upgradeDB(aRebuildOnError) { let upgradeTimer = AddonManagerPrivate.simpleTimer("XPIDB_upgradeDB_MS"); @@ -448,6 +484,11 @@ this.XPIDatabase = { /** * Reconstruct when the DB file exists but is unreadable * (for example because read permission is denied) + * + * @param {Error} aError + * The error that triggered the rebuild. + * @param {boolean} aRebuildOnError + * If true, synchronously reconstruct the database from installed add-ons */ rebuildUnreadableDB(aError, aRebuildOnError) { let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildUnreadableDB_MS"); @@ -466,8 +507,9 @@ this.XPIDatabase = { * necessary. If any DB load operation fails, we need to * synchronously rebuild the DB from the installed extensions. * - * @return Promise resolves to the Map of loaded JSON data stored - * in this.addonDB; never rejects. + * @returns {Promise} + * Resolves to the Map of loaded JSON data stored in + * this.addonDB; never rejects. */ asyncLoadDB() { // Already started (and possibly finished) loading @@ -524,10 +566,11 @@ this.XPIDatabase = { * Rebuild the database from addon install directories. If this.migrateData * is available, uses migrated information for settings on the addons found * during rebuild - * @param aRebuildOnError - * A boolean indicating whether add-on information should be loaded - * from the install locations if the database needs to be rebuilt. - * (if false, caller is XPIProvider.checkForChanges() which will rebuild) + * + * @param {boolean} aRebuildOnError + * A boolean indicating whether add-on information should be loaded + * from the install locations if the database needs to be rebuilt. + * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ rebuildDatabase(aRebuildOnError) { this.addonDB = new Map(); @@ -603,11 +646,14 @@ this.XPIDatabase = { /** * Asynchronously list all addons that match the filter function - * @param aFilter - * Function that takes an addon instance and returns - * true if that addon should be included in the selected array - * @return a Promise that resolves to the list of add-ons matching aFilter or - * an empty array if none match + * + * @param {function(DBAddonInternal) : boolean} aFilter + * Function that takes an addon instance and returns + * true if that addon should be included in the selected array + * + * @returns {Array} + * A Promise that resolves to the list of add-ons matching + * aFilter or an empty array if none match */ async getAddonList(aFilter) { try { @@ -622,10 +668,12 @@ this.XPIDatabase = { }, /** - * (Possibly asynchronously) get the first addon that matches the filter function - * @param aFilter - * Function that takes an addon instance and returns - * true if that addon should be selected + * Get the first addon that matches the filter function + * + * @param {function(DBAddonInternal) : boolean} aFilter + * Function that takes an addon instance and returns + * true if that addon should be selected + * @returns {Promise} */ getAddon(aFilter) { return this.asyncLoadDB() @@ -644,10 +692,11 @@ this.XPIDatabase = { * Asynchronously gets an add-on with a particular ID in a particular * install location. * - * @param aId - * The ID of the add-on to retrieve - * @param aLocation - * The name of the install location + * @param {string} aId + * The ID of the add-on to retrieve + * @param {string} aLocation + * The name of the install location + * @returns {Promise} */ getAddonInLocation(aId, aLocation) { return this.asyncLoadDB().then( @@ -657,8 +706,9 @@ this.XPIDatabase = { /** * Asynchronously get all the add-ons in a particular install location. * - * @param aLocation - * The name of the install location + * @param {string} aLocation + * The name of the install location + * @returns {Promise>} */ getAddonsInLocation(aLocation) { return this.getAddonList(aAddon => aAddon._installLocation.name == aLocation); @@ -667,8 +717,9 @@ this.XPIDatabase = { /** * Asynchronously gets the add-on with the specified ID that is visible. * - * @param aId - * The ID of the add-on to retrieve + * @param {string} aId + * The ID of the add-on to retrieve + * @returns {Promise} */ getVisibleAddonForID(aId) { return this.getAddon(aAddon => ((aAddon.id == aId) && aAddon.visible)); @@ -681,8 +732,9 @@ this.XPIDatabase = { /** * Asynchronously gets the visible add-ons, optionally restricting by type. * - * @param aTypes - * An array of types to include or null to include all types + * @param {Array?} aTypes + * An array of types to include or null to include all types + * @returns {Promise>} */ getVisibleAddons(aTypes) { return this.getAddonList(aAddon => (aAddon.visible && @@ -693,9 +745,9 @@ this.XPIDatabase = { /** * Synchronously gets all add-ons of a particular type(s). * - * @param aType, aType2, ... - * The type(s) of add-on to retrieve - * @return an array of DBAddonInternals + * @param {Array} aTypes + * The type(s) of add-on to retrieve + * @returns {Array} */ getAddonsByType(...aTypes) { if (!this.addonDB) { @@ -715,8 +767,9 @@ this.XPIDatabase = { /** * Asynchronously gets all add-ons with pending operations. * - * @param aTypes - * The types of add-ons to retrieve or null to get all types + * @param {Array?} aTypes + * The types of add-ons to retrieve or null to get all types + * @returns {Promise>} */ getVisibleAddonsWithPendingOperations(aTypes) { return this.getAddonList( @@ -728,8 +781,9 @@ this.XPIDatabase = { /** * Asynchronously get an add-on by its Sync GUID. * - * @param aGUID - * Sync GUID of add-on to fetch + * @param {string} aGUID + * Sync GUID of add-on to fetch + * @returns {Promise} */ getAddonBySyncGUID(aGUID) { return this.getAddon(aAddon => aAddon.syncGUID == aGUID); @@ -741,7 +795,7 @@ this.XPIDatabase = { * compatibility version preference, so we can return an empty list if * we haven't loaded the database yet. * - * @return an array of DBAddonInternals + * @returns {Array} */ getAddons() { if (!this.addonDB) { @@ -753,11 +807,12 @@ this.XPIDatabase = { /** * Synchronously adds an AddonInternal's metadata to the database. * - * @param aAddon - * AddonInternal to add - * @param aPath - * The file path of the add-on - * @return The DBAddonInternal that was added to the database + * @param {AddonInternal} aAddon + * AddonInternal to add + * @param {string} aPath + * The file path of the add-on + * @returns {DBAddonInternal} + * the DBAddonInternal that was added to the database */ addAddonMetadata(aAddon, aPath) { if (!this.addonDB) { @@ -781,13 +836,14 @@ this.XPIDatabase = { * Synchronously updates an add-on's metadata in the database. Currently just * removes and recreates. * - * @param aOldAddon - * The DBAddonInternal to be replaced - * @param aNewAddon - * The new AddonInternal to add - * @param aPath - * The file path of the add-on - * @return The DBAddonInternal that was added to the database + * @param {DBAddonInternal} aOldAddon + * The DBAddonInternal to be replaced + * @param {AddonInternal} aNewAddon + * The new AddonInternal to add + * @param {string} aPath + * The file path of the add-on + * @returns {DBAddonInternal} + * The DBAddonInternal that was added to the database */ updateAddonMetadata(aOldAddon, aNewAddon, aPath) { this.removeAddonMetadata(aOldAddon); @@ -805,8 +861,8 @@ this.XPIDatabase = { /** * Synchronously removes an add-on from the database. * - * @param aAddon - * The DBAddonInternal being removed + * @param {DBAddonInternal} aAddon + * The DBAddonInternal being removed */ removeAddonMetadata(aAddon) { this.addonDB.delete(aAddon._key); @@ -825,8 +881,8 @@ this.XPIDatabase = { * Synchronously marks a DBAddonInternal as visible marking all other * instances with the same ID as not visible. * - * @param aAddon - * The DBAddonInternal to make visible + * @param {DBAddonInternal} aAddon + * The DBAddonInternal to make visible */ makeAddonVisible(aAddon) { logger.debug("Make addon " + aAddon._key + " visible"); @@ -848,8 +904,12 @@ this.XPIDatabase = { * Synchronously marks a given add-on ID visible in a given location, * instances with the same ID as not visible. * - * @param aAddon - * The DBAddonInternal to make visible + * @param {string} aId + * The ID of the add-on to make visible + * @param {InstallLocation} aLocation + * The location in which to make the add-on visible. + * @returns {DBAddonInternal?} + * The add-on instance which was marked visible, if any. */ makeAddonLocationVisible(aId, aLocation) { logger.debug(`Make addon ${aId} visible in location ${aLocation}`); @@ -878,10 +938,10 @@ this.XPIDatabase = { /** * Synchronously sets properties for an add-on. * - * @param aAddon - * The DBAddonInternal being updated - * @param aProperties - * A dictionary of properties to set + * @param {DBAddonInternal} aAddon + * The DBAddonInternal being updated + * @param {Object} aProperties + * A dictionary of properties to set */ setAddonProperties(aAddon, aProperties) { for (let key in aProperties) { @@ -894,10 +954,10 @@ this.XPIDatabase = { * Synchronously sets the Sync GUID for an add-on. * Only called when the database is already loaded. * - * @param aAddon - * The DBAddonInternal being updated - * @param aGUID - * GUID string to set the value to + * @param {DBAddonInternal} aAddon + * The DBAddonInternal being updated + * @param {string} aGUID + * GUID string to set the value to * @throws if another addon already has the specified GUID */ setAddonSyncGUID(aAddon, aGUID) { @@ -917,8 +977,10 @@ this.XPIDatabase = { /** * Synchronously updates an add-on's active flag in the database. * - * @param aAddon - * The DBAddonInternal to update + * @param {DBAddonInternal} aAddon + * The DBAddonInternal to update + * @param {boolean} aActive + * The new active state for the add-on. */ updateAddonActive(aAddon, aActive) { logger.debug("Updating active state for add-on " + aAddon.id + " to " + aActive); @@ -953,6 +1015,12 @@ this.XPIDatabaseReconcile = { /** * Returns a map of ID -> add-on. When the same add-on ID exists in multiple * install locations the highest priority location is chosen. + * + * @param {Map} addonMap + * The add-on map to flatten. + * @param {string?} [hideLocation] + * An optional location from which to hide any add-ons. + * @returns {Map} */ flattenByID(addonMap, hideLocation) { let map = new Map(); @@ -976,6 +1044,10 @@ this.XPIDatabaseReconcile = { /** * Finds the visible add-ons from the map. + * + * @param {Map} addonMap + * The add-on map to filter. + * @returns {Map} */ getVisibleAddons(addonMap) { let map = new Map(); @@ -1005,22 +1077,23 @@ this.XPIDatabaseReconcile = { * has been upgraded or become corrupt and add-on data has to be reloaded * into it. * - * @param aInstallLocation - * The install location containing the add-on - * @param aId - * The ID of the add-on - * @param aAddonState - * The new state of the add-on - * @param aNewAddon - * The manifest for the new add-on if it has already been loaded - * @param aOldAppVersion - * The version of the application last run with this profile or null - * if it is a new profile or the version is unknown - * @param aOldPlatformVersion - * The version of the platform last run with this profile or null - * if it is a new profile or the version is unknown - * @return a boolean indicating if flushing caches is required to complete - * changing this add-on + * @param {InstallLocation} aInstallLocation + * The install location containing the add-on + * @param {string} aId + * The ID of the add-on + * @param {XPIState} aAddonState + * The new state of the add-on + * @param {AddonInternal?} [aNewAddon] + * The manifest for the new add-on if it has already been loaded + * @param {string?} [aOldAppVersion] + * The version of the application last run with this profile or null + * if it is a new profile or the version is unknown + * @param {string?} [aOldPlatformVersion] + * The version of the platform last run with this profile or null + * if it is a new profile or the version is unknown + * @returns {boolean} + * A boolean indicating if flushing caches is required to complete + * changing this add-on */ addMetadata(aInstallLocation, aId, aAddonState, aNewAddon, aOldAppVersion, aOldPlatformVersion) { @@ -1092,11 +1165,9 @@ this.XPIDatabaseReconcile = { /** * Called when an add-on has been removed. * - * @param aOldAddon - * The AddonInternal as it appeared the last time the application - * ran - * @return a boolean indicating if flushing caches is required to complete - * changing this add-on + * @param {AddonInternal} aOldAddon + * The AddonInternal as it appeared the last time the application + * ran */ removeMetadata(aOldAddon) { // This add-on has disappeared @@ -1108,17 +1179,18 @@ this.XPIDatabaseReconcile = { * Updates an add-on's metadata and determines. This is called when either the * add-on's install directory path or last modified time has changed. * - * @param aInstallLocation - * The install location containing the add-on - * @param aOldAddon - * The AddonInternal as it appeared the last time the application - * ran - * @param aAddonState - * The new state of the add-on - * @param aNewAddon - * The manifest for the new add-on if it has already been loaded - * @return a boolean indicating if flushing caches is required to complete - * changing this add-on + * @param {InstallLocation} aInstallLocation + * The install location containing the add-on + * @param {AddonInternal} aOldAddon + * The AddonInternal as it appeared the last time the application + * ran + * @param {XPIState} aAddonState + * The new state of the add-on + * @param {AddonInternal?} [aNewAddon] + * The manifest for the new add-on if it has already been loaded + * @returns {boolean?} + * A boolean indicating if flushing caches is required to complete + * changing this add-on */ updateMetadata(aInstallLocation, aOldAddon, aAddonState, aNewAddon) { logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name); @@ -1157,15 +1229,14 @@ this.XPIDatabaseReconcile = { * Updates an add-on's path for when the add-on has moved in the * filesystem but hasn't changed in any other way. * - * @param aInstallLocation - * The install location containing the add-on - * @param aOldAddon - * The AddonInternal as it appeared the last time the application - * ran - * @param aAddonState - * The new state of the add-on - * @return a boolean indicating if flushing caches is required to complete - * changing this add-on + * @param {InstallLocation} aInstallLocation + * The install location containing the add-on + * @param {AddonInternal} aOldAddon + * The AddonInternal as it appeared the last time the application + * ran + * @param {XPIState} aAddonState + * The new state of the add-on + * @returns {AddonInternal} */ updatePath(aInstallLocation, aOldAddon, aAddonState) { logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.path); @@ -1179,17 +1250,18 @@ this.XPIDatabaseReconcile = { * Called when no change has been detected for an add-on's metadata but the * application has changed so compatibility may have changed. * - * @param aInstallLocation - * The install location containing the add-on - * @param aOldAddon - * The AddonInternal as it appeared the last time the application - * ran - * @param aAddonState - * The new state of the add-on - * @param aReloadMetadata - * A boolean which indicates whether metadata should be reloaded from - * the addon manifests. Default to false. - * @return the new addon. + * @param {InstallLocation} aInstallLocation + * The install location containing the add-on + * @param {AddonInternal} aOldAddon + * The AddonInternal as it appeared the last time the application + * ran + * @param {XPIState} aAddonState + * The new state of the add-on + * @param {boolean} [aReloadMetadata = false] + * A boolean which indicates whether metadata should be reloaded from + * the addon manifests. Default to false. + * @returns {DBAddonInternal} + * The new addon. */ updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aReloadMetadata) { logger.debug("Updating compatibility for add-on " + aOldAddon.id + " in " + aInstallLocation.name); @@ -1242,22 +1314,23 @@ this.XPIDatabaseReconcile = { * observerservice if it detects that data may have changed. * Always called after XPIProviderUtils.js and extensions.json have been loaded. * - * @param aManifests - * A dictionary of cached AddonInstalls for add-ons that have been - * installed - * @param aUpdateCompatibility - * true to update add-ons appDisabled property when the application - * version has changed - * @param aOldAppVersion - * The version of the application last run with this profile or null - * if it is a new profile or the version is unknown - * @param aOldPlatformVersion - * The version of the platform last run with this profile or null - * if it is a new profile or the version is unknown - * @param aSchemaChange - * The schema has changed and all add-on manifests should be re-read. - * @return a boolean indicating if a change requiring flushing the caches was - * detected + * @param {Object} aManifests + * A dictionary of cached AddonInstalls for add-ons that have been + * installed + * @param {boolean} aUpdateCompatibility + * true to update add-ons appDisabled property when the application + * version has changed + * @param {string?} [aOldAppVersion] + * The version of the application last run with this profile or null + * if it is a new profile or the version is unknown + * @param {string?} [aOldPlatformVersion] + * The version of the platform last run with this profile or null + * if it is a new profile or the version is unknown + * @param {boolean} aSchemaChange + * The schema has changed and all add-on manifests should be re-read. + * @returns {boolean} + * A boolean indicating if a change requiring flushing the caches was + * detected */ processFileChanges(aManifests, aUpdateCompatibility, aOldAppVersion, aOldPlatformVersion, aSchemaChange) { From a6683dff36d40647e7d3c1bd87075ac295db11bc Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 22 Apr 2018 14:22:59 -0700 Subject: [PATCH 29/44] Bug 1363925: Part 8a - Migrate XPIProviderUtils.js to XPIDatabase.jsm. r=aswan MozReview-Commit-ID: CNfHgCYCkNd --HG-- rename : toolkit/mozapps/extensions/internal/XPIProviderUtils.js => toolkit/mozapps/extensions/internal/XPIDatabase.jsm extra : rebase_source : 968e0072b9dfaf7d9f3f9b2c551ae34582ff1934 extra : histedit_source : a5ad9c4e61f8a92f3830953d3db88213f12a5c8b --- .../extensions/internal/AddonRepository.jsm | 2 +- .../extensions/internal/AddonTestUtils.jsm | 1 + .../{XPIProviderUtils.js => XPIDatabase.jsm} | 35 +++++++--- .../extensions/internal/XPIInstall.jsm | 5 +- .../extensions/internal/XPIProvider.jsm | 65 ++++--------------- toolkit/mozapps/extensions/internal/moz.build | 2 +- 6 files changed, 46 insertions(+), 64 deletions(-) rename toolkit/mozapps/extensions/internal/{XPIProviderUtils.js => XPIDatabase.jsm} (98%) diff --git a/toolkit/mozapps/extensions/internal/AddonRepository.jsm b/toolkit/mozapps/extensions/internal/AddonRepository.jsm index 26a35840334d..5aa9f584711d 100644 --- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm +++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm @@ -371,7 +371,7 @@ var AddonRepository = { * disabled, null is passed to the specified callback. * * The callback variant exists only for existing code in XPIProvider.jsm - * and XPIProviderUtils.jsm that requires a synchronous callback, yuck. + * and XPIDatabase.jsm that requires a synchronous callback, yuck. * * @param aId * The id of the add-on to get diff --git a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm index 50162510a792..e3bd76661d36 100644 --- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm +++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm @@ -787,6 +787,7 @@ var AddonTestUtils = { AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider); Cu.unload("resource://gre/modules/addons/XPIProvider.jsm"); + Cu.unload("resource://gre/modules/addons/XPIDatabase.jsm"); Cu.unload("resource://gre/modules/addons/XPIInstall.jsm"); if (shutdownError) diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm similarity index 98% rename from toolkit/mozapps/extensions/internal/XPIProviderUtils.js rename to toolkit/mozapps/extensions/internal/XPIDatabase.jsm index afb4cdc742b9..fcabc9287186 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm @@ -6,11 +6,7 @@ /* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */ -// These are injected from XPIProvider.jsm -/* globals ADDON_SIGNING, SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA, - AddonInternal, XPIProvider, XPIStates, - isUsableAddon, recordAddonTelemetry, - descriptorToPath */ +var EXPORTED_SYMBOLS = ["XPIDatabase", "XPIDatabaseReconcile"]; ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -18,13 +14,35 @@ XPCOMUtils.defineLazyModuleGetters(this, { AddonManager: "resource://gre/modules/AddonManager.jsm", AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm", AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm", + AddonSettings: "resource://gre/modules/addons/AddonSettings.jsm", DeferredTask: "resource://gre/modules/DeferredTask.jsm", FileUtils: "resource://gre/modules/FileUtils.jsm", OS: "resource://gre/modules/osfile.jsm", Services: "resource://gre/modules/Services.jsm", XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm", + XPIInternal: "resource://gre/modules/addons/XPIProvider.jsm", }); +// These are injected from XPIProvider.jsm +/* globals SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA, + AddonInternal, XPIProvider, XPIStates, + isUsableAddon, recordAddonTelemetry, + descriptorToPath */ + +for (let sym of [ + "AddonInternal", + "BOOTSTRAP_REASONS", + "DB_SCHEMA", + "SIGNED_TYPES", + "XPIProvider", + "XPIStates", + "descriptorToPath", + "isUsableAddon", + "recordAddonTelemetry", +]) { + XPCOMUtils.defineLazyGetter(this, sym, () => XPIInternal[sym]); +} + ChromeUtils.import("resource://gre/modules/Log.jsm"); const LOGGER_ID = "addons.xpi-utils"; @@ -1266,8 +1284,9 @@ this.XPIDatabaseReconcile = { updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aReloadMetadata) { logger.debug("Updating compatibility for add-on " + aOldAddon.id + " in " + aInstallLocation.name); - let checkSigning = aOldAddon.signedState === undefined && ADDON_SIGNING && - SIGNED_TYPES.has(aOldAddon.type); + let checkSigning = (aOldAddon.signedState === undefined && + AddonSettings.ADDON_SIGNING && + SIGNED_TYPES.has(aOldAddon.type)); let manifest = null; if (checkSigning || aReloadMetadata) { @@ -1312,7 +1331,7 @@ this.XPIDatabaseReconcile = { * known to be installed when the application last ran and applies any * changes found to the database. Also sends "startupcache-invalidate" signal to * observerservice if it detects that data may have changed. - * Always called after XPIProviderUtils.js and extensions.json have been loaded. + * Always called after XPIDatabase.jsm and extensions.json have been loaded. * * @param {Object} aManifests * A dictionary of cached AddonInstalls for add-ons that have been diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index ac1e3a93b177..b5f13ddf013d 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -43,6 +43,8 @@ ChromeUtils.defineModuleGetter(this, "ProductAddonChecker", "resource://gre/modules/addons/ProductAddonChecker.jsm"); ChromeUtils.defineModuleGetter(this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "XPIDatabase", + "resource://gre/modules/addons/XPIDatabase.jsm"); ChromeUtils.defineModuleGetter(this, "ZipUtils", "resource://gre/modules/ZipUtils.jsm"); @@ -85,7 +87,7 @@ const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; -/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPIDatabase, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */ +/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */ const XPI_INTERNAL_SYMBOLS = [ "AddonInternal", "BOOTSTRAP_REASONS", @@ -98,7 +100,6 @@ const XPI_INTERNAL_SYMBOLS = [ "TEMPORARY_ADDON_SUFFIX", "TOOLKIT_ID", "XPI_PERMISSION", - "XPIDatabase", "XPIStates", "getExternalType", "isTheme", diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index c94a5804972a..142f494588f2 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -31,6 +31,8 @@ XPCOMUtils.defineLazyModuleGetters(this, { clearTimeout: "resource://gre/modules/Timer.jsm", UpdateChecker: "resource://gre/modules/addons/XPIInstall.jsm", + XPIDatabase: "resource://gre/modules/addons/XPIDatabase.jsm", + XPIDatabaseReconcile: "resource://gre/modules/addons/XPIDatabase.jsm", XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm", verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.jsm", }); @@ -240,11 +242,6 @@ const LOGGER_ID = "addons.xpi"; // (Requires AddonManager.jsm) var logger = Log.repository.getLogger(LOGGER_ID); -const LAZY_OBJECTS = ["XPIDatabase", "XPIDatabaseReconcile"]; -/* globals XPIDatabase, XPIDatabaseReconcile*/ - -var gLazyObjectsLoaded = false; - XPCOMUtils.defineLazyGetter(this, "gStartupScanScopes", () => { let appBuildID = Services.appinfo.appBuildID; let oldAppBuildID = Services.prefs.getCharPref(PREF_EM_LAST_APP_BUILD_ID, ""); @@ -257,46 +254,6 @@ XPCOMUtils.defineLazyGetter(this, "gStartupScanScopes", () => { return Services.prefs.getIntPref(PREF_EM_STARTUP_SCAN_SCOPES, 0); }); -function loadLazyObjects() { - let uri = "resource://gre/modules/addons/XPIProviderUtils.js"; - let scope = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { - sandboxName: uri, - wantGlobalProperties: ["ChromeUtils", "TextDecoder"], - }); - - Object.assign(scope, { - ADDON_SIGNING: AddonSettings.ADDON_SIGNING, - SIGNED_TYPES, - BOOTSTRAP_REASONS, - DB_SCHEMA, - AddonInternal, - XPIProvider, - XPIStates, - isUsableAddon, - recordAddonTelemetry, - descriptorToPath, - }); - - Services.scriptloader.loadSubScript(uri, scope); - - for (let name of LAZY_OBJECTS) { - delete gGlobalScope[name]; - gGlobalScope[name] = scope[name]; - } - gLazyObjectsLoaded = true; - return scope; -} - -LAZY_OBJECTS.forEach(name => { - Object.defineProperty(gGlobalScope, name, { - get() { - let objs = loadLazyObjects(); - return objs[name]; - }, - configurable: true - }); -}); - /** * Spins the event loop until the given promise resolves, and then eiter returns * its success value or throws its rejection value. @@ -809,7 +766,7 @@ class XPIState { return new XPIState(location, id, data); } - // Compatibility shim getters for legacy callers in XPIProviderUtils: + // Compatibility shim getters for legacy callers in XPIDatabase.jsm. get mtime() { return this.lastModifiedTime; } @@ -1501,7 +1458,10 @@ var XPIProvider = { // Check if the XPIDatabase has been loaded (without actually // triggering unwanted imports or I/O) get isDBLoaded() { - return gLazyObjectsLoaded && XPIDatabase.initialized; + // Make sure we don't touch the XPIDatabase getter before it's + // actually loaded, and force an early load. + return (Object.getOwnPropertyDescriptor(gGlobalScope, "XPIDatabase").value && + XPIDatabase.initialized); }, /** @@ -3263,7 +3223,7 @@ let addonFor = wrapper => wrapperMap.get(wrapper); /** * The AddonInternal is an internal only representation of add-ons. It may - * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm) + * have come from the database (see DBAddonInternal in XPIDatabase.jsm) * or an install manifest. */ function AddonInternal() { @@ -4773,6 +4733,7 @@ class WinRegInstallLocation extends DirectoryInstallLocation { var XPIInternal = { AddonInternal, BOOTSTRAP_REASONS, + DB_SCHEMA, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, @@ -4780,20 +4741,20 @@ var XPIInternal = { PREF_SYSTEM_ADDON_SET, SIGNED_TYPES, SystemAddonInstallLocation, - TemporaryInstallLocation, TEMPORARY_ADDON_SUFFIX, TOOLKIT_ID, - XPI_PERMISSION, + TemporaryInstallLocation, + XPIProvider, XPIStates, + XPI_PERMISSION, awaitPromise, + descriptorToPath, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry, - - get XPIDatabase() { return gGlobalScope.XPIDatabase; }, }; var addonTypes = [ diff --git a/toolkit/mozapps/extensions/internal/moz.build b/toolkit/mozapps/extensions/internal/moz.build index b03842471882..18c728197137 100644 --- a/toolkit/mozapps/extensions/internal/moz.build +++ b/toolkit/mozapps/extensions/internal/moz.build @@ -14,9 +14,9 @@ EXTRA_JS_MODULES.addons += [ 'ProductAddonChecker.jsm', 'SpellCheckDictionaryBootstrap.js', 'UpdateRDFConverter.jsm', + 'XPIDatabase.jsm', 'XPIInstall.jsm', 'XPIProvider.jsm', - 'XPIProviderUtils.js', ] TESTING_JS_MODULES += [ From 980c561d2e70d2876231aa39d8bdeb8afbed2b5c Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 22 Apr 2018 14:45:02 -0700 Subject: [PATCH 30/44] Bug 1363925: Part 8b - Move AddonInternal to XPIDatabase.jsm. r=aswan AddonInternal objects are only ever created after the database is loaded, so there's no reason to load that code beforehand. More importantly, creating the AddonWrapper class is expecially expensive, since most of their properties are created dynamically. We should avoid doing that at startup when at all possible. MozReview-Commit-ID: AaRVN12e1qM --HG-- extra : rebase_source : d5f9da1f8a54cdeecc45aa9857c6a6329821bb34 extra : histedit_source : 73c67373ad04a397bf9b2c2174c637f8ba645a95 --- .../extensions/internal/XPIDatabase.jsm | 1104 ++++++++++++++++- .../extensions/internal/XPIInstall.jsm | 53 +- .../extensions/internal/XPIProvider.jsm | 1072 +--------------- 3 files changed, 1119 insertions(+), 1110 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm index fcabc9287186..ccb048fddeec 100644 --- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm +++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm @@ -6,7 +6,7 @@ /* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */ -var EXPORTED_SYMBOLS = ["XPIDatabase", "XPIDatabaseReconcile"]; +var EXPORTED_SYMBOLS = ["AddonInternal", "XPIDatabase", "XPIDatabaseReconcile"]; ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -15,29 +15,43 @@ XPCOMUtils.defineLazyModuleGetters(this, { AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm", AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm", AddonSettings: "resource://gre/modules/addons/AddonSettings.jsm", + AppConstants: "resource://gre/modules/AppConstants.jsm", DeferredTask: "resource://gre/modules/DeferredTask.jsm", FileUtils: "resource://gre/modules/FileUtils.jsm", OS: "resource://gre/modules/osfile.jsm", Services: "resource://gre/modules/Services.jsm", + + UpdateChecker: "resource://gre/modules/addons/XPIInstall.jsm", XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm", XPIInternal: "resource://gre/modules/addons/XPIProvider.jsm", }); +const {nsIBlocklistService} = Ci; + // These are injected from XPIProvider.jsm -/* globals SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA, - AddonInternal, XPIProvider, XPIStates, - isUsableAddon, recordAddonTelemetry, - descriptorToPath */ +/* globals + * BOOTSTRAP_REASONS, + * DB_SCHEMA, + * SIGNED_TYPES, + * XPIProvider, + * XPIStates, + * descriptorToPath, + * isTheme, + * isUsableAddon, + * isWebExtension, + * recordAddonTelemetry, + */ for (let sym of [ - "AddonInternal", "BOOTSTRAP_REASONS", "DB_SCHEMA", "SIGNED_TYPES", "XPIProvider", "XPIStates", "descriptorToPath", + "isTheme", "isUsableAddon", + "isWebExtension", "recordAddonTelemetry", ]) { XPCOMUtils.defineLazyGetter(this, sym, () => XPIInternal[sym]); @@ -58,15 +72,38 @@ const FILE_JSON_DB = "extensions.json"; // The last version of DB_SCHEMA implemented in SQLITE const LAST_SQLITE_DB_SCHEMA = 14; + +const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; const PREF_DB_SCHEMA = "extensions.databaseSchema"; -const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes"; +const PREF_EM_EXTENSION_FORMAT = "extensions."; +const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; + +const TOOLKIT_ID = "toolkit@mozilla.org"; const KEY_APP_SYSTEM_ADDONS = "app-system-addons"; const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults"; +const KEY_APP_SYSTEM_LOCAL = "app-system-local"; +const KEY_APP_SYSTEM_SHARE = "app-system-share"; const KEY_APP_GLOBAL = "app-global"; +const KEY_APP_PROFILE = "app-profile"; const KEY_APP_TEMPORARY = "app-temporary"; +// Properties to cache and reload when an addon installation is pending +const PENDING_INSTALL_METADATA = + ["syncGUID", "targetApplications", "userDisabled", "softDisabled", + "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", + "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]; + +const COMPATIBLE_BY_DEFAULT_TYPES = { + extension: true, + dictionary: true +}; + +// Properties that exist in the install manifest +const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"]; +const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"]; + // Properties to save in JSON file const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", "updateURL", "optionsURL", @@ -85,6 +122,29 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", // Time to wait before async save of XPI JSON database, in milliseconds const ASYNC_SAVE_DELAY_MS = 20; +// Note: When adding/changing/removing items here, remember to change the +// DB schema version to ensure changes are picked up ASAP. +const STATIC_BLOCKLIST_PATTERNS = [ + { creator: "Mozilla Corp.", + level: nsIBlocklistService.STATE_BLOCKED, + blockID: "i162" }, + { creator: "Mozilla.org", + level: nsIBlocklistService.STATE_BLOCKED, + blockID: "i162" } +]; + +function findMatchingStaticBlocklistItem(aAddon) { + for (let item of STATIC_BLOCKLIST_PATTERNS) { + if ("creator" in item && typeof item.creator == "string") { + if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) || + (aAddon.selectedLocale && aAddon.selectedLocale.creator == item.creator)) { + return item; + } + } + } + return null; +} + /** * Asynchronously fill in the _repositoryAddon field for one addon * @@ -123,6 +183,1015 @@ function copyProperties(aObject, aProperties, aTarget) { return aTarget; } +// Maps instances of AddonInternal to AddonWrapper +const wrapperMap = new WeakMap(); +let addonFor = wrapper => wrapperMap.get(wrapper); + +/** + * The AddonInternal is an internal only representation of add-ons. It may + * have come from the database (see DBAddonInternal in XPIDatabase.jsm) + * or an install manifest. + */ +function AddonInternal() { + this._hasResourceCache = new Map(); + + XPCOMUtils.defineLazyGetter(this, "wrapper", () => { + return new AddonWrapper(this); + }); +} + +AddonInternal.prototype = { + _selectedLocale: null, + _hasResourceCache: null, + active: false, + visible: false, + userDisabled: false, + appDisabled: false, + softDisabled: false, + blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + blocklistURL: null, + sourceURI: null, + releaseNotesURI: null, + foreignInstall: false, + seen: true, + skinnable: false, + startupData: null, + + /** + * @property {Array} dependencies + * An array of bootstrapped add-on IDs on which this add-on depends. + * The add-on will remain appDisabled if any of the dependent + * add-ons is not installed and enabled. + */ + dependencies: Object.freeze([]), + hasEmbeddedWebExtension: false, + + get selectedLocale() { + if (this._selectedLocale) + return this._selectedLocale; + + /** + * this.locales is a list of objects that have property `locales`. + * It's value is an array of locale codes. + * + * First, we reduce this nested structure to a flat list of locale codes. + */ + const locales = [].concat(...this.locales.map(loc => loc.locales)); + + let requestedLocales = Services.locale.getRequestedLocales(); + + /** + * If en-US is not in the list, add it as the last fallback. + */ + if (!requestedLocales.includes("en-US")) { + requestedLocales.push("en-US"); + } + + /** + * Then we negotiate best locale code matching the app locales. + */ + let bestLocale = Services.locale.negotiateLanguages( + requestedLocales, + locales, + "und", + Services.locale.langNegStrategyLookup + )[0]; + + /** + * If no match has been found, we'll assign the default locale as + * the selected one. + */ + if (bestLocale === "und") { + this._selectedLocale = this.defaultLocale; + } else { + /** + * Otherwise, we'll go through all locale entries looking for the one + * that has the best match in it's locales list. + */ + this._selectedLocale = this.locales.find(loc => + loc.locales.includes(bestLocale)); + } + + return this._selectedLocale; + }, + + get providesUpdatesSecurely() { + return !this.updateURL || this.updateURL.startsWith("https:"); + }, + + get isCorrectlySigned() { + switch (this._installLocation.name) { + case KEY_APP_SYSTEM_ADDONS: + // System add-ons must be signed by the system key. + return this.signedState == AddonManager.SIGNEDSTATE_SYSTEM; + + case KEY_APP_SYSTEM_DEFAULTS: + case KEY_APP_TEMPORARY: + // Temporary and built-in system add-ons do not require signing. + return true; + + case KEY_APP_SYSTEM_SHARE: + case KEY_APP_SYSTEM_LOCAL: + // On UNIX platforms except OSX, an additional location for system + // add-ons exists in /usr/{lib,share}/mozilla/extensions. Add-ons + // installed there do not require signing. + if (Services.appinfo.OS != "Darwin") + return true; + break; + } + + if (this.signedState === AddonManager.SIGNEDSTATE_NOT_REQUIRED) + return true; + return this.signedState > AddonManager.SIGNEDSTATE_MISSING; + }, + + get unpack() { + return this.type === "dictionary"; + }, + + get isCompatible() { + return this.isCompatibleWith(); + }, + + get disabled() { + return (this.userDisabled || this.appDisabled || this.softDisabled); + }, + + get isPlatformCompatible() { + if (this.targetPlatforms.length == 0) + return true; + + let matchedOS = false; + + // If any targetPlatform matches the OS and contains an ABI then we will + // only match a targetPlatform that contains both the current OS and ABI + let needsABI = false; + + // Some platforms do not specify an ABI, test against null in that case. + let abi = null; + try { + abi = Services.appinfo.XPCOMABI; + } catch (e) { } + + // Something is causing errors in here + try { + for (let platform of this.targetPlatforms) { + if (platform.os == Services.appinfo.OS) { + if (platform.abi) { + needsABI = true; + if (platform.abi === abi) + return true; + } else { + matchedOS = true; + } + } + } + } catch (e) { + let message = "Problem with addon " + this.id + " targetPlatforms " + + JSON.stringify(this.targetPlatforms); + logger.error(message, e); + AddonManagerPrivate.recordException("XPI", message, e); + // don't trust this add-on + return false; + } + + return matchedOS && !needsABI; + }, + + isCompatibleWith(aAppVersion, aPlatformVersion) { + let app = this.matchingTargetApplication; + if (!app) + return false; + + // set reasonable defaults for minVersion and maxVersion + let minVersion = app.minVersion || "0"; + let maxVersion = app.maxVersion || "*"; + + if (!aAppVersion) + aAppVersion = Services.appinfo.version; + if (!aPlatformVersion) + aPlatformVersion = Services.appinfo.platformVersion; + + let version; + if (app.id == Services.appinfo.ID) + version = aAppVersion; + else if (app.id == TOOLKIT_ID) + version = aPlatformVersion; + + // Only extensions and dictionaries can be compatible by default; themes + // and language packs always use strict compatibility checking. + if (this.type in COMPATIBLE_BY_DEFAULT_TYPES && + !AddonManager.strictCompatibility && !this.strictCompatibility) { + + // The repository can specify compatibility overrides. + // Note: For now, only blacklisting is supported by overrides. + let overrides = AddonRepository.getCompatibilityOverridesSync(this.id); + if (overrides) { + let override = AddonRepository.findMatchingCompatOverride(this.version, + overrides); + if (override) { + return false; + } + } + + // Extremely old extensions should not be compatible by default. + let minCompatVersion; + if (app.id == Services.appinfo.ID) + minCompatVersion = XPIProvider.minCompatibleAppVersion; + else if (app.id == TOOLKIT_ID) + minCompatVersion = XPIProvider.minCompatiblePlatformVersion; + + if (minCompatVersion && + Services.vc.compare(minCompatVersion, maxVersion) > 0) + return false; + + return Services.vc.compare(version, minVersion) >= 0; + } + + return (Services.vc.compare(version, minVersion) >= 0) && + (Services.vc.compare(version, maxVersion) <= 0); + }, + + get matchingTargetApplication() { + let app = null; + for (let targetApp of this.targetApplications) { + if (targetApp.id == Services.appinfo.ID) + return targetApp; + if (targetApp.id == TOOLKIT_ID) + app = targetApp; + } + return app; + }, + + async findBlocklistEntry() { + let staticItem = findMatchingStaticBlocklistItem(this); + if (staticItem) { + let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); + return { + state: staticItem.level, + url: url.replace(/%blockID%/g, staticItem.blockID) + }; + } + + return Services.blocklist.getAddonBlocklistEntry(this.wrapper); + }, + + async updateBlocklistState(options = {}) { + let {applySoftBlock = true, oldAddon = null, updateDatabase = true} = options; + + if (oldAddon) { + this.userDisabled = oldAddon.userDisabled; + this.softDisabled = oldAddon.softDisabled; + this.blocklistState = oldAddon.blocklistState; + } + let oldState = this.blocklistState; + + let entry = await this.findBlocklistEntry(); + let newState = entry ? entry.state : Services.blocklist.STATE_NOT_BLOCKED; + + this.blocklistState = newState; + this.blocklistURL = entry && entry.url; + + let userDisabled, softDisabled; + // After a blocklist update, the blocklist service manually applies + // new soft blocks after displaying a UI, in which cases we need to + // skip updating it here. + if (applySoftBlock && oldState != newState) { + if (newState == Services.blocklist.STATE_SOFTBLOCKED) { + if (this.type == "theme") { + userDisabled = true; + } else { + softDisabled = !this.userDisabled; + } + } else { + softDisabled = false; + } + } + + if (this.inDatabase && updateDatabase) { + XPIProvider.updateAddonDisabledState(this, userDisabled, softDisabled); + XPIDatabase.saveChanges(); + } else { + this.appDisabled = !isUsableAddon(this); + if (userDisabled !== undefined) { + this.userDisabled = userDisabled; + } + if (softDisabled !== undefined) { + this.softDisabled = softDisabled; + } + } + }, + + applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { + for (let targetApp of this.targetApplications) { + for (let updateTarget of aUpdate.targetApplications) { + if (targetApp.id == updateTarget.id && (aSyncCompatibility || + Services.vc.compare(targetApp.maxVersion, updateTarget.maxVersion) < 0)) { + targetApp.minVersion = updateTarget.minVersion; + targetApp.maxVersion = updateTarget.maxVersion; + } + } + } + this.appDisabled = !isUsableAddon(this); + }, + + /** + * toJSON is called by JSON.stringify in order to create a filtered version + * of this object to be serialized to a JSON file. A new object is returned + * with copies of all non-private properties. Functions, getters and setters + * are not copied. + * + * @returns {Object} + * An object containing copies of the properties of this object + * ignoring private properties, functions, getters and setters. + */ + toJSON() { + let obj = {}; + for (let prop in this) { + // Ignore the wrapper property + if (prop == "wrapper") + continue; + + // Ignore private properties + if (prop.substring(0, 1) == "_") + continue; + + // Ignore getters + if (this.__lookupGetter__(prop)) + continue; + + // Ignore setters + if (this.__lookupSetter__(prop)) + continue; + + // Ignore functions + if (typeof this[prop] == "function") + continue; + + obj[prop] = this[prop]; + } + + return obj; + }, + + /** + * When an add-on install is pending its metadata will be cached in a file. + * This method reads particular properties of that metadata that may be newer + * than that in the install manifest, like compatibility information. + * + * @param {Object} aObj + * A JS object containing the cached metadata + */ + importMetadata(aObj) { + for (let prop of PENDING_INSTALL_METADATA) { + if (!(prop in aObj)) + continue; + + this[prop] = aObj[prop]; + } + + // Compatibility info may have changed so update appDisabled + this.appDisabled = !isUsableAddon(this); + }, + + permissions() { + let permissions = 0; + + // Add-ons that aren't installed cannot be modified in any way + if (!(this.inDatabase)) + return permissions; + + if (!this.appDisabled) { + if (this.userDisabled || this.softDisabled) { + permissions |= AddonManager.PERM_CAN_ENABLE; + } else if (this.type != "theme") { + permissions |= AddonManager.PERM_CAN_DISABLE; + } + } + + // Add-ons that are in locked install locations, or are pending uninstall + // cannot be upgraded or uninstalled + if (!this._installLocation.locked && !this.pendingUninstall) { + // System add-on upgrades are triggered through a different mechanism (see updateSystemAddons()) + let isSystem = this._installLocation.isSystem; + // Add-ons that are installed by a file link cannot be upgraded. + if (!this._installLocation.isLinkedAddon(this.id) && !isSystem) { + permissions |= AddonManager.PERM_CAN_UPGRADE; + } + + permissions |= AddonManager.PERM_CAN_UNINSTALL; + } + + if (Services.policies && + !Services.policies.isAllowed(`modify-extension:${this.id}`)) { + permissions &= ~AddonManager.PERM_CAN_UNINSTALL; + permissions &= ~AddonManager.PERM_CAN_DISABLE; + } + + return permissions; + }, +}; + +/** + * The AddonWrapper wraps an Addon to provide the data visible to consumers of + * the public API. + * + * @param {AddonInternal} aAddon + * The add-on object to wrap. + */ +function AddonWrapper(aAddon) { + wrapperMap.set(this, aAddon); +} + +AddonWrapper.prototype = { + get __AddonInternal__() { + return AppConstants.DEBUG ? addonFor(this) : undefined; + }, + + get seen() { + return addonFor(this).seen; + }, + + get hasEmbeddedWebExtension() { + return addonFor(this).hasEmbeddedWebExtension; + }, + + markAsSeen() { + addonFor(this).seen = true; + XPIDatabase.saveChanges(); + }, + + get type() { + return XPIInternal.getExternalType(addonFor(this).type); + }, + + get isWebExtension() { + return isWebExtension(addonFor(this).type); + }, + + get temporarilyInstalled() { + return addonFor(this)._installLocation == XPIInternal.TemporaryInstallLocation; + }, + + get aboutURL() { + return this.isActive ? addonFor(this).aboutURL : null; + }, + + get optionsURL() { + if (!this.isActive) { + return null; + } + + let addon = addonFor(this); + if (addon.optionsURL) { + if (this.isWebExtension || this.hasEmbeddedWebExtension) { + // The internal object's optionsURL property comes from the addons + // DB and should be a relative URL. However, extensions with + // options pages installed before bug 1293721 was fixed got absolute + // URLs in the addons db. This code handles both cases. + let policy = WebExtensionPolicy.getByID(addon.id); + if (!policy) { + return null; + } + let base = policy.getURL(); + return new URL(addon.optionsURL, base).href; + } + return addon.optionsURL; + } + + return null; + }, + + get optionsType() { + if (!this.isActive) + return null; + + let addon = addonFor(this); + let hasOptionsURL = !!this.optionsURL; + + if (addon.optionsType) { + switch (parseInt(addon.optionsType, 10)) { + case AddonManager.OPTIONS_TYPE_TAB: + case AddonManager.OPTIONS_TYPE_INLINE_BROWSER: + return hasOptionsURL ? addon.optionsType : null; + } + return null; + } + + return null; + }, + + get optionsBrowserStyle() { + let addon = addonFor(this); + return addon.optionsBrowserStyle; + }, + + get iconURL() { + return AddonManager.getPreferredIconURL(this, 48); + }, + + get icon64URL() { + return AddonManager.getPreferredIconURL(this, 64); + }, + + get icons() { + let addon = addonFor(this); + let icons = {}; + + if (addon._repositoryAddon) { + for (let size in addon._repositoryAddon.icons) { + icons[size] = addon._repositoryAddon.icons[size]; + } + } + + if (addon.icons) { + for (let size in addon.icons) { + icons[size] = this.getResourceURI(addon.icons[size]).spec; + } + } else { + // legacy add-on that did not update its icon data yet + if (this.hasResource("icon.png")) { + icons[32] = icons[48] = this.getResourceURI("icon.png").spec; + } + if (this.hasResource("icon64.png")) { + icons[64] = this.getResourceURI("icon64.png").spec; + } + } + + let canUseIconURLs = this.isActive; + if (canUseIconURLs && addon.iconURL) { + icons[32] = addon.iconURL; + icons[48] = addon.iconURL; + } + + if (canUseIconURLs && addon.icon64URL) { + icons[64] = addon.icon64URL; + } + + Object.freeze(icons); + return icons; + }, + + get screenshots() { + let addon = addonFor(this); + let repositoryAddon = addon._repositoryAddon; + if (repositoryAddon && ("screenshots" in repositoryAddon)) { + let repositoryScreenshots = repositoryAddon.screenshots; + if (repositoryScreenshots && repositoryScreenshots.length > 0) + return repositoryScreenshots; + } + + if (isTheme(addon.type) && this.hasResource("preview.png")) { + let url = this.getResourceURI("preview.png").spec; + return [new AddonManagerPrivate.AddonScreenshot(url)]; + } + + return null; + }, + + get applyBackgroundUpdates() { + return addonFor(this).applyBackgroundUpdates; + }, + set applyBackgroundUpdates(val) { + let addon = addonFor(this); + if (val != AddonManager.AUTOUPDATE_DEFAULT && + val != AddonManager.AUTOUPDATE_DISABLE && + val != AddonManager.AUTOUPDATE_ENABLE) { + val = val ? AddonManager.AUTOUPDATE_DEFAULT : + AddonManager.AUTOUPDATE_DISABLE; + } + + if (val == addon.applyBackgroundUpdates) + return val; + + XPIDatabase.setAddonProperties(addon, { + applyBackgroundUpdates: val + }); + AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]); + + return val; + }, + + set syncGUID(val) { + let addon = addonFor(this); + if (addon.syncGUID == val) + return val; + + if (addon.inDatabase) + XPIDatabase.setAddonSyncGUID(addon, val); + + addon.syncGUID = val; + + return val; + }, + + get install() { + let addon = addonFor(this); + if (!("_install" in addon) || !addon._install) + return null; + return addon._install.wrapper; + }, + + get pendingUpgrade() { + let addon = addonFor(this); + return addon.pendingUpgrade ? addon.pendingUpgrade.wrapper : null; + }, + + get scope() { + let addon = addonFor(this); + if (addon._installLocation) + return addon._installLocation.scope; + + return AddonManager.SCOPE_PROFILE; + }, + + get pendingOperations() { + let addon = addonFor(this); + let pending = 0; + if (!(addon.inDatabase)) { + // Add-on is pending install if there is no associated install (shouldn't + // happen here) or if the install is in the process of or has successfully + // completed the install. If an add-on is pending install then we ignore + // any other pending operations. + if (!addon._install || addon._install.state == AddonManager.STATE_INSTALLING || + addon._install.state == AddonManager.STATE_INSTALLED) + return AddonManager.PENDING_INSTALL; + } else if (addon.pendingUninstall) { + // If an add-on is pending uninstall then we ignore any other pending + // operations + return AddonManager.PENDING_UNINSTALL; + } + + if (addon.active && addon.disabled) + pending |= AddonManager.PENDING_DISABLE; + else if (!addon.active && !addon.disabled) + pending |= AddonManager.PENDING_ENABLE; + + if (addon.pendingUpgrade) + pending |= AddonManager.PENDING_UPGRADE; + + return pending; + }, + + get operationsRequiringRestart() { + return 0; + }, + + get isDebuggable() { + return this.isActive && addonFor(this).bootstrap; + }, + + get permissions() { + return addonFor(this).permissions(); + }, + + get isActive() { + let addon = addonFor(this); + if (!addon.active) + return false; + if (!Services.appinfo.inSafeMode) + return true; + return addon.bootstrap && XPIInternal.canRunInSafeMode(addon); + }, + + get startupPromise() { + let addon = addonFor(this); + if (!addon.bootstrap || !this.isActive) + return null; + + let activeAddon = XPIProvider.activeAddons.get(addon.id); + if (activeAddon) + return activeAddon.startupPromise || null; + return null; + }, + + updateBlocklistState(applySoftBlock = true) { + return addonFor(this).updateBlocklistState({applySoftBlock}); + }, + + get userDisabled() { + let addon = addonFor(this); + return addon.softDisabled || addon.userDisabled; + }, + set userDisabled(val) { + let addon = addonFor(this); + if (val == this.userDisabled) { + return val; + } + + if (addon.inDatabase) { + // hidden and system add-ons should not be user disabled, + // as there is no UI to re-enable them. + if (this.hidden) { + throw new Error(`Cannot disable hidden add-on ${addon.id}`); + } + XPIProvider.updateAddonDisabledState(addon, val); + } else { + addon.userDisabled = val; + // When enabling remove the softDisabled flag + if (!val) + addon.softDisabled = false; + } + + return val; + }, + + set softDisabled(val) { + let addon = addonFor(this); + if (val == addon.softDisabled) + return val; + + if (addon.inDatabase) { + // When softDisabling a theme just enable the active theme + if (isTheme(addon.type) && val && !addon.userDisabled) { + if (isWebExtension(addon.type)) + XPIProvider.updateAddonDisabledState(addon, undefined, val); + } else { + XPIProvider.updateAddonDisabledState(addon, undefined, val); + } + } else if (!addon.userDisabled) { + // Only set softDisabled if not already disabled + addon.softDisabled = val; + } + + return val; + }, + + get hidden() { + let addon = addonFor(this); + if (addon._installLocation.name == KEY_APP_TEMPORARY) + return false; + + return addon._installLocation.isSystem; + }, + + get isSystem() { + let addon = addonFor(this); + return addon._installLocation.isSystem; + }, + + // Returns true if Firefox Sync should sync this addon. Only addons + // in the profile install location are considered syncable. + get isSyncable() { + let addon = addonFor(this); + return (addon._installLocation.name == KEY_APP_PROFILE); + }, + + get userPermissions() { + return addonFor(this).userPermissions; + }, + + isCompatibleWith(aAppVersion, aPlatformVersion) { + return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion); + }, + + uninstall(alwaysAllowUndo) { + let addon = addonFor(this); + XPIProvider.uninstallAddon(addon, alwaysAllowUndo); + }, + + cancelUninstall() { + let addon = addonFor(this); + XPIProvider.cancelUninstallAddon(addon); + }, + + findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) { + new UpdateChecker(addonFor(this), aListener, aReason, aAppVersion, aPlatformVersion); + }, + + // Returns true if there was an update in progress, false if there was no update to cancel + cancelUpdate() { + let addon = addonFor(this); + if (addon._updateCheck) { + addon._updateCheck.cancel(); + return true; + } + return false; + }, + + hasResource(aPath) { + let addon = addonFor(this); + if (addon._hasResourceCache.has(aPath)) + return addon._hasResourceCache.get(aPath); + + let bundle = addon._sourceBundle.clone(); + + // Bundle may not exist any more if the addon has just been uninstalled, + // but explicitly first checking .exists() results in unneeded file I/O. + try { + var isDir = bundle.isDirectory(); + } catch (e) { + addon._hasResourceCache.set(aPath, false); + return false; + } + + if (isDir) { + if (aPath) + aPath.split("/").forEach(part => bundle.append(part)); + let result = bundle.exists(); + addon._hasResourceCache.set(aPath, result); + return result; + } + + let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. + createInstance(Ci.nsIZipReader); + try { + zipReader.open(bundle); + let result = zipReader.hasEntry(aPath); + addon._hasResourceCache.set(aPath, result); + return result; + } catch (e) { + addon._hasResourceCache.set(aPath, false); + return false; + } finally { + zipReader.close(); + } + }, + + /** + * Reloads the add-on. + * + * For temporarily installed add-ons, this uninstalls and re-installs the + * add-on. Otherwise, the addon is disabled and then re-enabled, and the cache + * is flushed. + * + * @returns {Promise} + */ + reload() { + return new Promise((resolve) => { + const addon = addonFor(this); + + logger.debug(`reloading add-on ${addon.id}`); + + if (!this.temporarilyInstalled) { + let addonFile = addon.getResourceURI; + XPIProvider.updateAddonDisabledState(addon, true); + Services.obs.notifyObservers(addonFile, "flush-cache-entry"); + XPIProvider.updateAddonDisabledState(addon, false); + resolve(); + } else { + // This function supports re-installing an existing add-on. + resolve(AddonManager.installTemporaryAddon(addon._sourceBundle)); + } + }); + }, + + /** + * Returns a URI to the selected resource or to the add-on bundle if aPath + * is null. URIs to the bundle will always be file: URIs. URIs to resources + * will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is + * still an XPI file. + * + * @param {string?} aPath + * The path in the add-on to get the URI for or null to get a URI to + * the file or directory the add-on is installed as. + * @returns {nsIURI} + */ + getResourceURI(aPath) { + let addon = addonFor(this); + if (!aPath) + return Services.io.newFileURI(addon._sourceBundle); + + return XPIInternal.getURIForResourceInFile(addon._sourceBundle, aPath); + } +}; + +function chooseValue(aAddon, aObj, aProp) { + let repositoryAddon = aAddon._repositoryAddon; + let objValue = aObj[aProp]; + + if (repositoryAddon && (aProp in repositoryAddon) && + (objValue === undefined || objValue === null)) { + return [repositoryAddon[aProp], true]; + } + + return [objValue, false]; +} + +function defineAddonWrapperProperty(name, getter) { + Object.defineProperty(AddonWrapper.prototype, name, { + get: getter, + enumerable: true, + }); +} + +["id", "syncGUID", "version", "isCompatible", "isPlatformCompatible", + "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled", + "softDisabled", "skinnable", "size", "foreignInstall", + "strictCompatibility", "updateURL", "dependencies", + "signedState", "isCorrectlySigned"].forEach(function(aProp) { + defineAddonWrapperProperty(aProp, function() { + let addon = addonFor(this); + return (aProp in addon) ? addon[aProp] : undefined; + }); +}); + +["fullDescription", "developerComments", "supportURL", + "contributionURL", "averageRating", "reviewCount", + "reviewURL", "weeklyDownloads"].forEach(function(aProp) { + defineAddonWrapperProperty(aProp, function() { + let addon = addonFor(this); + if (addon._repositoryAddon) + return addon._repositoryAddon[aProp]; + + return null; + }); +}); + +["installDate", "updateDate"].forEach(function(aProp) { + defineAddonWrapperProperty(aProp, function() { + return new Date(addonFor(this)[aProp]); + }); +}); + +["sourceURI", "releaseNotesURI"].forEach(function(aProp) { + defineAddonWrapperProperty(aProp, function() { + let addon = addonFor(this); + + // Temporary Installed Addons do not have a "sourceURI", + // But we can use the "_sourceBundle" as an alternative, + // which points to the path of the addon xpi installed + // or its source dir (if it has been installed from a + // directory). + if (aProp == "sourceURI" && this.temporarilyInstalled) { + return Services.io.newFileURI(addon._sourceBundle); + } + + let [target, fromRepo] = chooseValue(addon, addon, aProp); + if (!target) + return null; + if (fromRepo) + return target; + return Services.io.newURI(target); + }); +}); + +PROP_LOCALE_SINGLE.forEach(function(aProp) { + defineAddonWrapperProperty(aProp, function() { + let addon = addonFor(this); + // Override XPI creator if repository creator is defined + if (aProp == "creator" && + addon._repositoryAddon && addon._repositoryAddon.creator) { + return addon._repositoryAddon.creator; + } + + let result = null; + + if (addon.active) { + try { + let pref = PREF_EM_EXTENSION_FORMAT + addon.id + "." + aProp; + let value = Services.prefs.getPrefType(pref) != Ci.nsIPrefBranch.PREF_INVALID ? Services.prefs.getComplexValue(pref, Ci.nsIPrefLocalizedString).data : null; + if (value) + result = value; + } catch (e) { + } + } + + if (result == null) + [result] = chooseValue(addon, addon.selectedLocale, aProp); + + if (aProp == "creator") + return result ? new AddonManagerPrivate.AddonAuthor(result) : null; + + return result; + }); +}); + +PROP_LOCALE_MULTI.forEach(function(aProp) { + defineAddonWrapperProperty(aProp, function() { + let addon = addonFor(this); + let results = null; + let usedRepository = false; + + if (addon.active) { + let pref = PREF_EM_EXTENSION_FORMAT + addon.id + "." + + aProp.substring(0, aProp.length - 1); + let list = Services.prefs.getChildList(pref, {}); + if (list.length > 0) { + list.sort(); + results = []; + for (let childPref of list) { + let value = Services.prefs.getPrefType(childPref) != Ci.nsIPrefBranch.PREF_INVALID ? Services.prefs.getComplexValue(childPref, Ci.nsIPrefLocalizedString).data : null; + if (value) + results.push(value); + } + } + } + + if (results == null) + [results, usedRepository] = chooseValue(addon, addon.selectedLocale, aProp); + + if (results && !usedRepository) { + results = results.map(function(aResult) { + return new AddonManagerPrivate.AddonAuthor(aResult); + }); + } + + return results; + }); +}); + + /** * The DBAddonInternal is a special AddonInternal that has been retrieved from * the database. The constructor will initialize the DBAddonInternal with a set @@ -1027,6 +2096,25 @@ this.XPIDatabase = { } } }, + + /** + * Record a bit of per-addon telemetry. + * + * Yes, this description is extremely helpful. How dare you question its + * utility? + * + * @param {AddonInternal} aAddon + * The addon to record + */ + recordAddonTelemetry(aAddon) { + let locale = aAddon.defaultLocale; + if (locale) { + if (locale.name) + XPIProvider.setTelemetry(aAddon.id, "name", locale.name); + if (locale.creator) + XPIProvider.setTelemetry(aAddon.id, "creator", locale.creator); + } + }, }; this.XPIDatabaseReconcile = { @@ -1409,7 +2497,7 @@ this.XPIDatabaseReconcile = { let xpiState = states && states.get(id); if (xpiState) { // Here the add-on was present in the database and on disk - recordAddonTelemetry(oldAddon); + XPIDatabase.recordAddonTelemetry(oldAddon); // Check if the add-on has been changed outside the XPI provider if (oldAddon.updateDate != xpiState.mtime) { diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index b5f13ddf013d..39e735691f9f 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -20,33 +20,28 @@ ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); -ChromeUtils.defineModuleGetter(this, "AddonRepository", - "resource://gre/modules/addons/AddonRepository.jsm"); -ChromeUtils.defineModuleGetter(this, "AddonSettings", - "resource://gre/modules/addons/AddonSettings.jsm"); -ChromeUtils.defineModuleGetter(this, "AppConstants", - "resource://gre/modules/AppConstants.jsm"); -ChromeUtils.defineModuleGetter(this, "CertUtils", - "resource://gre/modules/CertUtils.jsm"); -ChromeUtils.defineModuleGetter(this, "ExtensionData", - "resource://gre/modules/Extension.jsm"); -ChromeUtils.defineModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetters(this, { + AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm", + AddonSettings: "resource://gre/modules/addons/AddonSettings.jsm", + AppConstants: "resource://gre/modules/AppConstants.jsm", + CertUtils: "resource://gre/modules/CertUtils.jsm", + ExtensionData: "resource://gre/modules/Extension.jsm", + FileUtils: "resource://gre/modules/FileUtils.jsm", + NetUtil: "resource://gre/modules/NetUtil.jsm", + OS: "resource://gre/modules/osfile.jsm", + ProductAddonChecker: "resource://gre/modules/addons/ProductAddonChecker.jsm", + UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", + ZipUtils: "resource://gre/modules/ZipUtils.jsm", + + AddonInternal: "resource://gre/modules/addons/XPIDatabase.jsm", + XPIDatabase: "resource://gre/modules/addons/XPIDatabase.jsm", + XPIInternal: "resource://gre/modules/addons/XPIProvider.jsm", + XPIProvider: "resource://gre/modules/addons/XPIProvider.jsm", +}); + XPCOMUtils.defineLazyGetter(this, "IconDetails", () => { return ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm", {}).ExtensionParent.IconDetails; }); -ChromeUtils.defineModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -ChromeUtils.defineModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -ChromeUtils.defineModuleGetter(this, "ProductAddonChecker", - "resource://gre/modules/addons/ProductAddonChecker.jsm"); -ChromeUtils.defineModuleGetter(this, "UpdateUtils", - "resource://gre/modules/UpdateUtils.jsm"); -ChromeUtils.defineModuleGetter(this, "XPIDatabase", - "resource://gre/modules/addons/XPIDatabase.jsm"); -ChromeUtils.defineModuleGetter(this, "ZipUtils", - "resource://gre/modules/ZipUtils.jsm"); const {nsIBlocklistService} = Ci; @@ -72,10 +67,6 @@ XPCOMUtils.defineLazyServiceGetters(this, { gRDF: ["@mozilla.org/rdf/rdf-service;1", "nsIRDFService"], }); -ChromeUtils.defineModuleGetter(this, "XPIInternal", - "resource://gre/modules/addons/XPIProvider.jsm"); -ChromeUtils.defineModuleGetter(this, "XPIProvider", - "resource://gre/modules/addons/XPIProvider.jsm"); const PREF_ALLOW_NON_RESTARTLESS = "extensions.legacy.non-restartless.enabled"; const PREF_DISTRO_ADDONS_PERMS = "extensions.distroAddons.promptForPermissions"; @@ -87,9 +78,8 @@ const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; -/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign, recordAddonTelemetry */ +/* globals BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign */ const XPI_INTERNAL_SYMBOLS = [ - "AddonInternal", "BOOTSTRAP_REASONS", "KEY_APP_SYSTEM_ADDONS", "KEY_APP_SYSTEM_DEFAULTS", @@ -106,7 +96,6 @@ const XPI_INTERNAL_SYMBOLS = [ "isUsableAddon", "isWebExtension", "mustSign", - "recordAddonTelemetry", ]; for (let name of XPI_INTERNAL_SYMBOLS) { @@ -2041,7 +2030,7 @@ class AddonInstall { XPIProvider.unloadBootstrapScope(this.addon.id); } } - recordAddonTelemetry(this.addon); + XPIDatabase.recordAddonTelemetry(this.addon); // Notify providers that a new theme has been enabled. if (isTheme(this.addon.type) && this.addon.active) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 142f494588f2..61bd1a1cd7e5 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -30,7 +30,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { setTimeout: "resource://gre/modules/Timer.jsm", clearTimeout: "resource://gre/modules/Timer.jsm", - UpdateChecker: "resource://gre/modules/addons/XPIInstall.jsm", XPIDatabase: "resource://gre/modules/addons/XPIDatabase.jsm", XPIDatabaseReconcile: "resource://gre/modules/addons/XPIDatabase.jsm", XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm", @@ -50,10 +49,8 @@ const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile", const PREF_DB_SCHEMA = "extensions.databaseSchema"; const PREF_XPI_STATE = "extensions.xpiState"; -const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; const PREF_BOOTSTRAP_ADDONS = "extensions.bootstrappedAddons"; const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; -const PREF_EM_EXTENSION_FORMAT = "extensions."; const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes"; const PREF_EM_STARTUP_SCAN_SCOPES = "extensions.startupScanScopes"; const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI"; @@ -129,27 +126,6 @@ XPCOMUtils.defineConstant(this, "DB_SCHEMA", 25); const NOTIFICATION_TOOLBOX_CONNECTION_CHANGE = "toolbox-connection-change"; -// Properties that exist in the install manifest -const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"]; -const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"]; - -// Properties to cache and reload when an addon installation is pending -const PENDING_INSTALL_METADATA = - ["syncGUID", "targetApplications", "userDisabled", "softDisabled", - "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", - "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]; - -// Note: When adding/changing/removing items here, remember to change the -// DB schema version to ensure changes are picked up ASAP. -const STATIC_BLOCKLIST_PATTERNS = [ - { creator: "Mozilla Corp.", - level: nsIBlocklistService.STATE_BLOCKED, - blockID: "i162" }, - { creator: "Mozilla.org", - level: nsIBlocklistService.STATE_BLOCKED, - blockID: "i162" } -]; - function encoded(strings, ...values) { let result = []; @@ -223,11 +199,6 @@ const XPI_BEFORE_UI_STARTUP = "BeforeFinalUIStartup"; // event happened after final-ui-startup const XPI_AFTER_UI_STARTUP = "AfterFinalUIStartup"; -const COMPATIBLE_BY_DEFAULT_TYPES = { - extension: true, - dictionary: true -}; - var gGlobalScope = this; /** @@ -373,18 +344,6 @@ function descriptorToPath(descriptor, dir) { } } -function findMatchingStaticBlocklistItem(aAddon) { - for (let item of STATIC_BLOCKLIST_PATTERNS) { - if ("creator" in item && typeof item.creator == "string") { - if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) || - (aAddon.selectedLocale && aAddon.selectedLocale.creator == item.creator)) { - return item; - } - } - } - return null; -} - /** * Helper function that determines whether an addon of a certain type is a * WebExtension. @@ -661,25 +620,6 @@ function getDirectoryEntries(aDir, aSortEntries) { } } -/** - * Record a bit of per-addon telemetry. - * - * Yes, this description is extremely helpful. How dare you question its - * utility? - * - * @param {AddonInternal} aAddon - * The addon to record - */ -function recordAddonTelemetry(aAddon) { - let locale = aAddon.defaultLocale; - if (locale) { - if (locale.name) - XPIProvider.setTelemetry(aAddon.id, "name", locale.name); - if (locale.creator) - XPIProvider.setTelemetry(aAddon.id, "creator", locale.creator); - } -} - /** * The on-disk state of an individual XPI, created from an Object * as stored in the addonStartup.json file. @@ -3217,1014 +3157,6 @@ for (let meth of ["cancelUninstallAddon", "getInstallForFile", }; } -// Maps instances of AddonInternal to AddonWrapper -const wrapperMap = new WeakMap(); -let addonFor = wrapper => wrapperMap.get(wrapper); - -/** - * The AddonInternal is an internal only representation of add-ons. It may - * have come from the database (see DBAddonInternal in XPIDatabase.jsm) - * or an install manifest. - */ -function AddonInternal() { - this._hasResourceCache = new Map(); - - XPCOMUtils.defineLazyGetter(this, "wrapper", () => { - return new AddonWrapper(this); - }); -} - -AddonInternal.prototype = { - _selectedLocale: null, - _hasResourceCache: null, - active: false, - visible: false, - userDisabled: false, - appDisabled: false, - softDisabled: false, - blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED, - blocklistURL: null, - sourceURI: null, - releaseNotesURI: null, - foreignInstall: false, - seen: true, - skinnable: false, - startupData: null, - - /** - * @property {Array} dependencies - * An array of bootstrapped add-on IDs on which this add-on depends. - * The add-on will remain appDisabled if any of the dependent - * add-ons is not installed and enabled. - */ - dependencies: Object.freeze([]), - hasEmbeddedWebExtension: false, - - get selectedLocale() { - if (this._selectedLocale) - return this._selectedLocale; - - /** - * this.locales is a list of objects that have property `locales`. - * It's value is an array of locale codes. - * - * First, we reduce this nested structure to a flat list of locale codes. - */ - const locales = [].concat(...this.locales.map(loc => loc.locales)); - - let requestedLocales = Services.locale.getRequestedLocales(); - - /** - * If en-US is not in the list, add it as the last fallback. - */ - if (!requestedLocales.includes("en-US")) { - requestedLocales.push("en-US"); - } - - /** - * Then we negotiate best locale code matching the app locales. - */ - let bestLocale = Services.locale.negotiateLanguages( - requestedLocales, - locales, - "und", - Services.locale.langNegStrategyLookup - )[0]; - - /** - * If no match has been found, we'll assign the default locale as - * the selected one. - */ - if (bestLocale === "und") { - this._selectedLocale = this.defaultLocale; - } else { - /** - * Otherwise, we'll go through all locale entries looking for the one - * that has the best match in it's locales list. - */ - this._selectedLocale = this.locales.find(loc => - loc.locales.includes(bestLocale)); - } - - return this._selectedLocale; - }, - - get providesUpdatesSecurely() { - return !this.updateURL || this.updateURL.startsWith("https:"); - }, - - get isCorrectlySigned() { - switch (this._installLocation.name) { - case KEY_APP_SYSTEM_ADDONS: - // System add-ons must be signed by the system key. - return this.signedState == AddonManager.SIGNEDSTATE_SYSTEM; - - case KEY_APP_SYSTEM_DEFAULTS: - case KEY_APP_TEMPORARY: - // Temporary and built-in system add-ons do not require signing. - return true; - - case KEY_APP_SYSTEM_SHARE: - case KEY_APP_SYSTEM_LOCAL: - // On UNIX platforms except OSX, an additional location for system - // add-ons exists in /usr/{lib,share}/mozilla/extensions. Add-ons - // installed there do not require signing. - if (Services.appinfo.OS != "Darwin") - return true; - break; - } - - if (this.signedState === AddonManager.SIGNEDSTATE_NOT_REQUIRED) - return true; - return this.signedState > AddonManager.SIGNEDSTATE_MISSING; - }, - - get unpack() { - return this.type === "dictionary"; - }, - - get isCompatible() { - return this.isCompatibleWith(); - }, - - get disabled() { - return (this.userDisabled || this.appDisabled || this.softDisabled); - }, - - get isPlatformCompatible() { - if (this.targetPlatforms.length == 0) - return true; - - let matchedOS = false; - - // If any targetPlatform matches the OS and contains an ABI then we will - // only match a targetPlatform that contains both the current OS and ABI - let needsABI = false; - - // Some platforms do not specify an ABI, test against null in that case. - let abi = null; - try { - abi = Services.appinfo.XPCOMABI; - } catch (e) { } - - // Something is causing errors in here - try { - for (let platform of this.targetPlatforms) { - if (platform.os == Services.appinfo.OS) { - if (platform.abi) { - needsABI = true; - if (platform.abi === abi) - return true; - } else { - matchedOS = true; - } - } - } - } catch (e) { - let message = "Problem with addon " + this.id + " targetPlatforms " - + JSON.stringify(this.targetPlatforms); - logger.error(message, e); - AddonManagerPrivate.recordException("XPI", message, e); - // don't trust this add-on - return false; - } - - return matchedOS && !needsABI; - }, - - isCompatibleWith(aAppVersion, aPlatformVersion) { - let app = this.matchingTargetApplication; - if (!app) - return false; - - // set reasonable defaults for minVersion and maxVersion - let minVersion = app.minVersion || "0"; - let maxVersion = app.maxVersion || "*"; - - if (!aAppVersion) - aAppVersion = Services.appinfo.version; - if (!aPlatformVersion) - aPlatformVersion = Services.appinfo.platformVersion; - - let version; - if (app.id == Services.appinfo.ID) - version = aAppVersion; - else if (app.id == TOOLKIT_ID) - version = aPlatformVersion; - - // Only extensions and dictionaries can be compatible by default; themes - // and language packs always use strict compatibility checking. - if (this.type in COMPATIBLE_BY_DEFAULT_TYPES && - !AddonManager.strictCompatibility && !this.strictCompatibility) { - - // The repository can specify compatibility overrides. - // Note: For now, only blacklisting is supported by overrides. - let overrides = AddonRepository.getCompatibilityOverridesSync(this.id); - if (overrides) { - let override = AddonRepository.findMatchingCompatOverride(this.version, - overrides); - if (override) { - return false; - } - } - - // Extremely old extensions should not be compatible by default. - let minCompatVersion; - if (app.id == Services.appinfo.ID) - minCompatVersion = XPIProvider.minCompatibleAppVersion; - else if (app.id == TOOLKIT_ID) - minCompatVersion = XPIProvider.minCompatiblePlatformVersion; - - if (minCompatVersion && - Services.vc.compare(minCompatVersion, maxVersion) > 0) - return false; - - return Services.vc.compare(version, minVersion) >= 0; - } - - return (Services.vc.compare(version, minVersion) >= 0) && - (Services.vc.compare(version, maxVersion) <= 0); - }, - - get matchingTargetApplication() { - let app = null; - for (let targetApp of this.targetApplications) { - if (targetApp.id == Services.appinfo.ID) - return targetApp; - if (targetApp.id == TOOLKIT_ID) - app = targetApp; - } - return app; - }, - - async findBlocklistEntry() { - let staticItem = findMatchingStaticBlocklistItem(this); - if (staticItem) { - let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); - return { - state: staticItem.level, - url: url.replace(/%blockID%/g, staticItem.blockID) - }; - } - - return Services.blocklist.getAddonBlocklistEntry(this.wrapper); - }, - - async updateBlocklistState(options = {}) { - let {applySoftBlock = true, oldAddon = null, updateDatabase = true} = options; - - if (oldAddon) { - this.userDisabled = oldAddon.userDisabled; - this.softDisabled = oldAddon.softDisabled; - this.blocklistState = oldAddon.blocklistState; - } - let oldState = this.blocklistState; - - let entry = await this.findBlocklistEntry(); - let newState = entry ? entry.state : Services.blocklist.STATE_NOT_BLOCKED; - - this.blocklistState = newState; - this.blocklistURL = entry && entry.url; - - let userDisabled, softDisabled; - // After a blocklist update, the blocklist service manually applies - // new soft blocks after displaying a UI, in which cases we need to - // skip updating it here. - if (applySoftBlock && oldState != newState) { - if (newState == Services.blocklist.STATE_SOFTBLOCKED) { - if (this.type == "theme") { - userDisabled = true; - } else { - softDisabled = !this.userDisabled; - } - } else { - softDisabled = false; - } - } - - if (this.inDatabase && updateDatabase) { - XPIProvider.updateAddonDisabledState(this, userDisabled, softDisabled); - XPIDatabase.saveChanges(); - } else { - this.appDisabled = !isUsableAddon(this); - if (userDisabled !== undefined) { - this.userDisabled = userDisabled; - } - if (softDisabled !== undefined) { - this.softDisabled = softDisabled; - } - } - }, - - applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { - for (let targetApp of this.targetApplications) { - for (let updateTarget of aUpdate.targetApplications) { - if (targetApp.id == updateTarget.id && (aSyncCompatibility || - Services.vc.compare(targetApp.maxVersion, updateTarget.maxVersion) < 0)) { - targetApp.minVersion = updateTarget.minVersion; - targetApp.maxVersion = updateTarget.maxVersion; - } - } - } - this.appDisabled = !isUsableAddon(this); - }, - - /** - * toJSON is called by JSON.stringify in order to create a filtered version - * of this object to be serialized to a JSON file. A new object is returned - * with copies of all non-private properties. Functions, getters and setters - * are not copied. - * - * @returns {Object} - * An object containing copies of the properties of this object - * ignoring private properties, functions, getters and setters. - */ - toJSON() { - let obj = {}; - for (let prop in this) { - // Ignore the wrapper property - if (prop == "wrapper") - continue; - - // Ignore private properties - if (prop.substring(0, 1) == "_") - continue; - - // Ignore getters - if (this.__lookupGetter__(prop)) - continue; - - // Ignore setters - if (this.__lookupSetter__(prop)) - continue; - - // Ignore functions - if (typeof this[prop] == "function") - continue; - - obj[prop] = this[prop]; - } - - return obj; - }, - - /** - * When an add-on install is pending its metadata will be cached in a file. - * This method reads particular properties of that metadata that may be newer - * than that in the install manifest, like compatibility information. - * - * @param {Object} aObj - * A JS object containing the cached metadata - */ - importMetadata(aObj) { - for (let prop of PENDING_INSTALL_METADATA) { - if (!(prop in aObj)) - continue; - - this[prop] = aObj[prop]; - } - - // Compatibility info may have changed so update appDisabled - this.appDisabled = !isUsableAddon(this); - }, - - permissions() { - let permissions = 0; - - // Add-ons that aren't installed cannot be modified in any way - if (!(this.inDatabase)) - return permissions; - - if (!this.appDisabled) { - if (this.userDisabled || this.softDisabled) { - permissions |= AddonManager.PERM_CAN_ENABLE; - } else if (this.type != "theme") { - permissions |= AddonManager.PERM_CAN_DISABLE; - } - } - - // Add-ons that are in locked install locations, or are pending uninstall - // cannot be upgraded or uninstalled - if (!this._installLocation.locked && !this.pendingUninstall) { - // System add-on upgrades are triggered through a different mechanism (see updateSystemAddons()) - let isSystem = this._installLocation.isSystem; - // Add-ons that are installed by a file link cannot be upgraded. - if (!this._installLocation.isLinkedAddon(this.id) && !isSystem) { - permissions |= AddonManager.PERM_CAN_UPGRADE; - } - - permissions |= AddonManager.PERM_CAN_UNINSTALL; - } - - if (Services.policies && - !Services.policies.isAllowed(`modify-extension:${this.id}`)) { - permissions &= ~AddonManager.PERM_CAN_UNINSTALL; - permissions &= ~AddonManager.PERM_CAN_DISABLE; - } - - return permissions; - }, -}; - -/** - * The AddonWrapper wraps an Addon to provide the data visible to consumers of - * the public API. - * - * @param {AddonInternal} aAddon - * The add-on object to wrap. - */ -function AddonWrapper(aAddon) { - wrapperMap.set(this, aAddon); -} - -AddonWrapper.prototype = { - get __AddonInternal__() { - return AppConstants.DEBUG ? addonFor(this) : undefined; - }, - - get seen() { - return addonFor(this).seen; - }, - - get hasEmbeddedWebExtension() { - return addonFor(this).hasEmbeddedWebExtension; - }, - - markAsSeen() { - addonFor(this).seen = true; - XPIDatabase.saveChanges(); - }, - - get type() { - return getExternalType(addonFor(this).type); - }, - - get isWebExtension() { - return isWebExtension(addonFor(this).type); - }, - - get temporarilyInstalled() { - return addonFor(this)._installLocation == TemporaryInstallLocation; - }, - - get aboutURL() { - return this.isActive ? addonFor(this).aboutURL : null; - }, - - get optionsURL() { - if (!this.isActive) { - return null; - } - - let addon = addonFor(this); - if (addon.optionsURL) { - if (this.isWebExtension || this.hasEmbeddedWebExtension) { - // The internal object's optionsURL property comes from the addons - // DB and should be a relative URL. However, extensions with - // options pages installed before bug 1293721 was fixed got absolute - // URLs in the addons db. This code handles both cases. - let policy = WebExtensionPolicy.getByID(addon.id); - if (!policy) { - return null; - } - let base = policy.getURL(); - return new URL(addon.optionsURL, base).href; - } - return addon.optionsURL; - } - - return null; - }, - - get optionsType() { - if (!this.isActive) - return null; - - let addon = addonFor(this); - let hasOptionsURL = !!this.optionsURL; - - if (addon.optionsType) { - switch (parseInt(addon.optionsType, 10)) { - case AddonManager.OPTIONS_TYPE_TAB: - case AddonManager.OPTIONS_TYPE_INLINE_BROWSER: - return hasOptionsURL ? addon.optionsType : null; - } - return null; - } - - return null; - }, - - get optionsBrowserStyle() { - let addon = addonFor(this); - return addon.optionsBrowserStyle; - }, - - get iconURL() { - return AddonManager.getPreferredIconURL(this, 48); - }, - - get icon64URL() { - return AddonManager.getPreferredIconURL(this, 64); - }, - - get icons() { - let addon = addonFor(this); - let icons = {}; - - if (addon._repositoryAddon) { - for (let size in addon._repositoryAddon.icons) { - icons[size] = addon._repositoryAddon.icons[size]; - } - } - - if (addon.icons) { - for (let size in addon.icons) { - icons[size] = this.getResourceURI(addon.icons[size]).spec; - } - } else { - // legacy add-on that did not update its icon data yet - if (this.hasResource("icon.png")) { - icons[32] = icons[48] = this.getResourceURI("icon.png").spec; - } - if (this.hasResource("icon64.png")) { - icons[64] = this.getResourceURI("icon64.png").spec; - } - } - - let canUseIconURLs = this.isActive; - if (canUseIconURLs && addon.iconURL) { - icons[32] = addon.iconURL; - icons[48] = addon.iconURL; - } - - if (canUseIconURLs && addon.icon64URL) { - icons[64] = addon.icon64URL; - } - - Object.freeze(icons); - return icons; - }, - - get screenshots() { - let addon = addonFor(this); - let repositoryAddon = addon._repositoryAddon; - if (repositoryAddon && ("screenshots" in repositoryAddon)) { - let repositoryScreenshots = repositoryAddon.screenshots; - if (repositoryScreenshots && repositoryScreenshots.length > 0) - return repositoryScreenshots; - } - - if (isTheme(addon.type) && this.hasResource("preview.png")) { - let url = this.getResourceURI("preview.png").spec; - return [new AddonManagerPrivate.AddonScreenshot(url)]; - } - - return null; - }, - - get applyBackgroundUpdates() { - return addonFor(this).applyBackgroundUpdates; - }, - set applyBackgroundUpdates(val) { - let addon = addonFor(this); - if (val != AddonManager.AUTOUPDATE_DEFAULT && - val != AddonManager.AUTOUPDATE_DISABLE && - val != AddonManager.AUTOUPDATE_ENABLE) { - val = val ? AddonManager.AUTOUPDATE_DEFAULT : - AddonManager.AUTOUPDATE_DISABLE; - } - - if (val == addon.applyBackgroundUpdates) - return val; - - XPIDatabase.setAddonProperties(addon, { - applyBackgroundUpdates: val - }); - AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]); - - return val; - }, - - set syncGUID(val) { - let addon = addonFor(this); - if (addon.syncGUID == val) - return val; - - if (addon.inDatabase) - XPIDatabase.setAddonSyncGUID(addon, val); - - addon.syncGUID = val; - - return val; - }, - - get install() { - let addon = addonFor(this); - if (!("_install" in addon) || !addon._install) - return null; - return addon._install.wrapper; - }, - - get pendingUpgrade() { - let addon = addonFor(this); - return addon.pendingUpgrade ? addon.pendingUpgrade.wrapper : null; - }, - - get scope() { - let addon = addonFor(this); - if (addon._installLocation) - return addon._installLocation.scope; - - return AddonManager.SCOPE_PROFILE; - }, - - get pendingOperations() { - let addon = addonFor(this); - let pending = 0; - if (!(addon.inDatabase)) { - // Add-on is pending install if there is no associated install (shouldn't - // happen here) or if the install is in the process of or has successfully - // completed the install. If an add-on is pending install then we ignore - // any other pending operations. - if (!addon._install || addon._install.state == AddonManager.STATE_INSTALLING || - addon._install.state == AddonManager.STATE_INSTALLED) - return AddonManager.PENDING_INSTALL; - } else if (addon.pendingUninstall) { - // If an add-on is pending uninstall then we ignore any other pending - // operations - return AddonManager.PENDING_UNINSTALL; - } - - if (addon.active && addon.disabled) - pending |= AddonManager.PENDING_DISABLE; - else if (!addon.active && !addon.disabled) - pending |= AddonManager.PENDING_ENABLE; - - if (addon.pendingUpgrade) - pending |= AddonManager.PENDING_UPGRADE; - - return pending; - }, - - get operationsRequiringRestart() { - return 0; - }, - - get isDebuggable() { - return this.isActive && addonFor(this).bootstrap; - }, - - get permissions() { - return addonFor(this).permissions(); - }, - - get isActive() { - let addon = addonFor(this); - if (!addon.active) - return false; - if (!Services.appinfo.inSafeMode) - return true; - return addon.bootstrap && canRunInSafeMode(addon); - }, - - get startupPromise() { - let addon = addonFor(this); - if (!addon.bootstrap || !this.isActive) - return null; - - let activeAddon = XPIProvider.activeAddons.get(addon.id); - if (activeAddon) - return activeAddon.startupPromise || null; - return null; - }, - - updateBlocklistState(applySoftBlock = true) { - return addonFor(this).updateBlocklistState({applySoftBlock}); - }, - - get userDisabled() { - let addon = addonFor(this); - return addon.softDisabled || addon.userDisabled; - }, - set userDisabled(val) { - let addon = addonFor(this); - if (val == this.userDisabled) { - return val; - } - - if (addon.inDatabase) { - // hidden and system add-ons should not be user disabled, - // as there is no UI to re-enable them. - if (this.hidden) { - throw new Error(`Cannot disable hidden add-on ${addon.id}`); - } - XPIProvider.updateAddonDisabledState(addon, val); - } else { - addon.userDisabled = val; - // When enabling remove the softDisabled flag - if (!val) - addon.softDisabled = false; - } - - return val; - }, - - set softDisabled(val) { - let addon = addonFor(this); - if (val == addon.softDisabled) - return val; - - if (addon.inDatabase) { - // When softDisabling a theme just enable the active theme - if (isTheme(addon.type) && val && !addon.userDisabled) { - if (isWebExtension(addon.type)) - XPIProvider.updateAddonDisabledState(addon, undefined, val); - } else { - XPIProvider.updateAddonDisabledState(addon, undefined, val); - } - } else if (!addon.userDisabled) { - // Only set softDisabled if not already disabled - addon.softDisabled = val; - } - - return val; - }, - - get hidden() { - let addon = addonFor(this); - if (addon._installLocation.name == KEY_APP_TEMPORARY) - return false; - - return addon._installLocation.isSystem; - }, - - get isSystem() { - let addon = addonFor(this); - return addon._installLocation.isSystem; - }, - - // Returns true if Firefox Sync should sync this addon. Only addons - // in the profile install location are considered syncable. - get isSyncable() { - let addon = addonFor(this); - return (addon._installLocation.name == KEY_APP_PROFILE); - }, - - get userPermissions() { - return addonFor(this).userPermissions; - }, - - isCompatibleWith(aAppVersion, aPlatformVersion) { - return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion); - }, - - uninstall(alwaysAllowUndo) { - let addon = addonFor(this); - XPIProvider.uninstallAddon(addon, alwaysAllowUndo); - }, - - cancelUninstall() { - let addon = addonFor(this); - XPIProvider.cancelUninstallAddon(addon); - }, - - findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) { - new UpdateChecker(addonFor(this), aListener, aReason, aAppVersion, aPlatformVersion); - }, - - // Returns true if there was an update in progress, false if there was no update to cancel - cancelUpdate() { - let addon = addonFor(this); - if (addon._updateCheck) { - addon._updateCheck.cancel(); - return true; - } - return false; - }, - - hasResource(aPath) { - let addon = addonFor(this); - if (addon._hasResourceCache.has(aPath)) - return addon._hasResourceCache.get(aPath); - - let bundle = addon._sourceBundle.clone(); - - // Bundle may not exist any more if the addon has just been uninstalled, - // but explicitly first checking .exists() results in unneeded file I/O. - try { - var isDir = bundle.isDirectory(); - } catch (e) { - addon._hasResourceCache.set(aPath, false); - return false; - } - - if (isDir) { - if (aPath) - aPath.split("/").forEach(part => bundle.append(part)); - let result = bundle.exists(); - addon._hasResourceCache.set(aPath, result); - return result; - } - - let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(Ci.nsIZipReader); - try { - zipReader.open(bundle); - let result = zipReader.hasEntry(aPath); - addon._hasResourceCache.set(aPath, result); - return result; - } catch (e) { - addon._hasResourceCache.set(aPath, false); - return false; - } finally { - zipReader.close(); - } - }, - - /** - * Reloads the add-on. - * - * For temporarily installed add-ons, this uninstalls and re-installs the - * add-on. Otherwise, the addon is disabled and then re-enabled, and the cache - * is flushed. - * - * @returns {Promise} - */ - reload() { - return new Promise((resolve) => { - const addon = addonFor(this); - - logger.debug(`reloading add-on ${addon.id}`); - - if (!this.temporarilyInstalled) { - let addonFile = addon.getResourceURI; - XPIProvider.updateAddonDisabledState(addon, true); - Services.obs.notifyObservers(addonFile, "flush-cache-entry"); - XPIProvider.updateAddonDisabledState(addon, false); - resolve(); - } else { - // This function supports re-installing an existing add-on. - resolve(AddonManager.installTemporaryAddon(addon._sourceBundle)); - } - }); - }, - - /** - * Returns a URI to the selected resource or to the add-on bundle if aPath - * is null. URIs to the bundle will always be file: URIs. URIs to resources - * will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is - * still an XPI file. - * - * @param {string?} aPath - * The path in the add-on to get the URI for or null to get a URI to - * the file or directory the add-on is installed as. - * @returns {nsIURI} - */ - getResourceURI(aPath) { - let addon = addonFor(this); - if (!aPath) - return Services.io.newFileURI(addon._sourceBundle); - - return getURIForResourceInFile(addon._sourceBundle, aPath); - } -}; - -function chooseValue(aAddon, aObj, aProp) { - let repositoryAddon = aAddon._repositoryAddon; - let objValue = aObj[aProp]; - - if (repositoryAddon && (aProp in repositoryAddon) && - (objValue === undefined || objValue === null)) { - return [repositoryAddon[aProp], true]; - } - - return [objValue, false]; -} - -function defineAddonWrapperProperty(name, getter) { - Object.defineProperty(AddonWrapper.prototype, name, { - get: getter, - enumerable: true, - }); -} - -["id", "syncGUID", "version", "isCompatible", "isPlatformCompatible", - "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled", - "softDisabled", "skinnable", "size", "foreignInstall", - "strictCompatibility", "updateURL", "dependencies", - "signedState", "isCorrectlySigned"].forEach(function(aProp) { - defineAddonWrapperProperty(aProp, function() { - let addon = addonFor(this); - return (aProp in addon) ? addon[aProp] : undefined; - }); -}); - -["fullDescription", "developerComments", "supportURL", - "contributionURL", "averageRating", "reviewCount", - "reviewURL", "weeklyDownloads"].forEach(function(aProp) { - defineAddonWrapperProperty(aProp, function() { - let addon = addonFor(this); - if (addon._repositoryAddon) - return addon._repositoryAddon[aProp]; - - return null; - }); -}); - -["installDate", "updateDate"].forEach(function(aProp) { - defineAddonWrapperProperty(aProp, function() { - return new Date(addonFor(this)[aProp]); - }); -}); - -["sourceURI", "releaseNotesURI"].forEach(function(aProp) { - defineAddonWrapperProperty(aProp, function() { - let addon = addonFor(this); - - // Temporary Installed Addons do not have a "sourceURI", - // But we can use the "_sourceBundle" as an alternative, - // which points to the path of the addon xpi installed - // or its source dir (if it has been installed from a - // directory). - if (aProp == "sourceURI" && this.temporarilyInstalled) { - return Services.io.newFileURI(addon._sourceBundle); - } - - let [target, fromRepo] = chooseValue(addon, addon, aProp); - if (!target) - return null; - if (fromRepo) - return target; - return Services.io.newURI(target); - }); -}); - -PROP_LOCALE_SINGLE.forEach(function(aProp) { - defineAddonWrapperProperty(aProp, function() { - let addon = addonFor(this); - // Override XPI creator if repository creator is defined - if (aProp == "creator" && - addon._repositoryAddon && addon._repositoryAddon.creator) { - return addon._repositoryAddon.creator; - } - - let result = null; - - if (addon.active) { - try { - let pref = PREF_EM_EXTENSION_FORMAT + addon.id + "." + aProp; - let value = Services.prefs.getPrefType(pref) != Ci.nsIPrefBranch.PREF_INVALID ? Services.prefs.getComplexValue(pref, Ci.nsIPrefLocalizedString).data : null; - if (value) - result = value; - } catch (e) { - } - } - - if (result == null) - [result] = chooseValue(addon, addon.selectedLocale, aProp); - - if (aProp == "creator") - return result ? new AddonManagerPrivate.AddonAuthor(result) : null; - - return result; - }); -}); - -PROP_LOCALE_MULTI.forEach(function(aProp) { - defineAddonWrapperProperty(aProp, function() { - let addon = addonFor(this); - let results = null; - let usedRepository = false; - - if (addon.active) { - let pref = PREF_EM_EXTENSION_FORMAT + addon.id + "." + - aProp.substring(0, aProp.length - 1); - let list = Services.prefs.getChildList(pref, {}); - if (list.length > 0) { - list.sort(); - results = []; - for (let childPref of list) { - let value = Services.prefs.getPrefType(childPref) != Ci.nsIPrefBranch.PREF_INVALID ? Services.prefs.getComplexValue(childPref, Ci.nsIPrefLocalizedString).data : null; - if (value) - results.push(value); - } - } - } - - if (results == null) - [results, usedRepository] = chooseValue(addon, addon.selectedLocale, aProp); - - if (results && !usedRepository) { - results = results.map(function(aResult) { - return new AddonManagerPrivate.AddonAuthor(aResult); - }); - } - - return results; - }); -}); - function forwardInstallMethods(cls, methods) { for (let meth of methods) { cls.prototype[meth] = function() { @@ -4731,7 +3663,6 @@ class WinRegInstallLocation extends DirectoryInstallLocation { } var XPIInternal = { - AddonInternal, BOOTSTRAP_REASONS, DB_SCHEMA, KEY_APP_SYSTEM_ADDONS, @@ -4748,13 +3679,14 @@ var XPIInternal = { XPIStates, XPI_PERMISSION, awaitPromise, + canRunInSafeMode, descriptorToPath, getExternalType, + getURIForResourceInFile, isTheme, isUsableAddon, isWebExtension, mustSign, - recordAddonTelemetry, }; var addonTypes = [ From 4f17dc56da2484fcb309f03f0e6de23d0005ebed Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 22 Apr 2018 14:52:27 -0700 Subject: [PATCH 31/44] Bug 1363925: Part 8c - Move isUsableAddon to XPIDatabase.jsm. r=aswan MozReview-Commit-ID: 1aIA9Lu5sS2 --HG-- extra : rebase_source : ed39e7050d21d116338000234ab0cc4c6d34cf51 extra : histedit_source : f23e525032390d32e203d0b505cbf19d228d5ff6 --- .../extensions/internal/XPIDatabase.jsm | 128 +++++++++++++++++- .../extensions/internal/XPIInstall.jsm | 12 +- .../extensions/internal/XPIProvider.jsm | 115 +--------------- 3 files changed, 127 insertions(+), 128 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm index ccb048fddeec..9e51d29f80b1 100644 --- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm +++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm @@ -37,7 +37,6 @@ const {nsIBlocklistService} = Ci; * XPIStates, * descriptorToPath, * isTheme, - * isUsableAddon, * isWebExtension, * recordAddonTelemetry, */ @@ -50,7 +49,6 @@ for (let sym of [ "XPIStates", "descriptorToPath", "isTheme", - "isUsableAddon", "isWebExtension", "recordAddonTelemetry", ]) { @@ -78,6 +76,7 @@ const PREF_DB_SCHEMA = "extensions.databaseSchema"; const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes"; const PREF_EM_EXTENSION_FORMAT = "extensions."; const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; +const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root"; const TOOLKIT_ID = "toolkit@mozilla.org"; @@ -119,6 +118,10 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", "userPermissions", "icons", "iconURL", "icon64URL", "blocklistState", "blocklistURL", "startupData"]; +const LEGACY_TYPES = new Set([ + "extension", +]); + // Time to wait before async save of XPI JSON database, in milliseconds const ASYNC_SAVE_DELAY_MS = 20; @@ -472,7 +475,7 @@ AddonInternal.prototype = { XPIProvider.updateAddonDisabledState(this, userDisabled, softDisabled); XPIDatabase.saveChanges(); } else { - this.appDisabled = !isUsableAddon(this); + this.appDisabled = !XPIDatabase.isUsableAddon(this); if (userDisabled !== undefined) { this.userDisabled = userDisabled; } @@ -492,7 +495,7 @@ AddonInternal.prototype = { } } } - this.appDisabled = !isUsableAddon(this); + this.appDisabled = !XPIDatabase.isUsableAddon(this); }, /** @@ -551,7 +554,7 @@ AddonInternal.prototype = { } // Compatibility info may have changed so update appDisabled - this.appDisabled = !isUsableAddon(this); + this.appDisabled = !XPIDatabase.isUsableAddon(this); }, permissions() { @@ -1891,6 +1894,117 @@ this.XPIDatabase = { return _filterDB(this.addonDB, aAddon => true); }, + + /** + * Returns true if signing is required for the given add-on type. + * + * @param {string} aType + * The add-on type to check. + * @returns {boolean} + */ + mustSign(aType) { + if (!SIGNED_TYPES.has(aType)) + return false; + + if (aType == "webextension-langpack") { + return AddonSettings.LANGPACKS_REQUIRE_SIGNING; + } + + return AddonSettings.REQUIRE_SIGNING; + }, + + /** + * Determine if this addon should be disabled due to being legacy + * + * @param {Addon} addon The addon to check + * + * @returns {boolean} Whether the addon should be disabled for being legacy + */ + isDisabledLegacy(addon) { + return (!AddonSettings.ALLOW_LEGACY_EXTENSIONS && + LEGACY_TYPES.has(addon.type) && + + // Legacy add-ons are allowed in the system location. + !addon._installLocation.isSystem && + + // Legacy extensions may be installed temporarily in + // non-release builds. + !(AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS && + addon._installLocation.name == KEY_APP_TEMPORARY) && + + // Properly signed legacy extensions are allowed. + addon.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED); + }, + + /** + * Calculates whether an add-on should be appDisabled or not. + * + * @param {AddonInternal} aAddon + * The add-on to check + * @returns {boolean} + * True if the add-on should not be appDisabled + */ + isUsableAddon(aAddon) { + if (this.mustSign(aAddon.type) && !aAddon.isCorrectlySigned) { + logger.warn(`Add-on ${aAddon.id} is not correctly signed.`); + if (Services.prefs.getBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, false)) { + logger.warn(`Preference ${PREF_XPI_SIGNATURES_DEV_ROOT} is set.`); + } + return false; + } + + if (aAddon.blocklistState == nsIBlocklistService.STATE_BLOCKED) { + logger.warn(`Add-on ${aAddon.id} is blocklisted.`); + return false; + } + + // If we can't read it, it's not usable: + if (aAddon.brokenManifest) { + return false; + } + + if (AddonManager.checkUpdateSecurity && !aAddon.providesUpdatesSecurely) { + logger.warn(`Updates for add-on ${aAddon.id} must be provided over HTTPS.`); + return false; + } + + + if (!aAddon.isPlatformCompatible) { + logger.warn(`Add-on ${aAddon.id} is not compatible with platform.`); + return false; + } + + if (aAddon.dependencies.length) { + let isActive = id => { + let active = XPIProvider.activeAddons.get(id); + return active && !active.disable; + }; + + if (aAddon.dependencies.some(id => !isActive(id))) + return false; + } + + if (this.isDisabledLegacy(aAddon)) { + logger.warn(`disabling legacy extension ${aAddon.id}`); + return false; + } + + if (AddonManager.checkCompatibility) { + if (!aAddon.isCompatible) { + logger.warn(`Add-on ${aAddon.id} is not compatible with application version.`); + return false; + } + } else { + let app = aAddon.matchingTargetApplication; + if (!app) { + logger.warn(`Add-on ${aAddon.id} is not compatible with target application.`); + return false; + } + } + + return true; + }, + /** * Synchronously adds an AddonInternal's metadata to the database. * @@ -2251,7 +2365,7 @@ this.XPIDatabaseReconcile = { aInstallLocation.name != KEY_APP_SYSTEM_DEFAULTS; // appDisabled depends on whether the add-on is a foreignInstall so update - aNewAddon.appDisabled = !isUsableAddon(aNewAddon); + aNewAddon.appDisabled = !XPIDatabase.isUsableAddon(aNewAddon); if (isDetectedInstall && aNewAddon.foreignInstall) { // If the add-on is a foreign install and is in a scope where add-ons @@ -2409,7 +2523,7 @@ this.XPIDatabaseReconcile = { copyProperties(manifest, props, aOldAddon); } - aOldAddon.appDisabled = !isUsableAddon(aOldAddon); + aOldAddon.appDisabled = !XPIDatabase.isUsableAddon(aOldAddon); return aOldAddon; }, diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index 39e735691f9f..661206ffa8f2 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -78,7 +78,7 @@ const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; -/* globals BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, mustSign */ +/* globals BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isWebExtension */ const XPI_INTERNAL_SYMBOLS = [ "BOOTSTRAP_REASONS", "KEY_APP_SYSTEM_ADDONS", @@ -93,9 +93,7 @@ const XPI_INTERNAL_SYMBOLS = [ "XPIStates", "getExternalType", "isTheme", - "isUsableAddon", "isWebExtension", - "mustSign", ]; for (let name of XPI_INTERNAL_SYMBOLS) { @@ -931,7 +929,7 @@ var loadManifest = async function(aPackage, aInstallLocation, aOldAddon) { } await addon.updateBlocklistState({oldAddon: aOldAddon}); - addon.appDisabled = !isUsableAddon(addon); + addon.appDisabled = !XPIDatabase.isUsableAddon(addon); defineSyncGUID(addon); @@ -1775,7 +1773,7 @@ class AddonInstall { } } - if (mustSign(this.addon.type)) { + if (XPIDatabase.mustSign(this.addon.type)) { if (this.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) { // This add-on isn't properly signed by a signature that chains to the // trusted root. @@ -1818,7 +1816,7 @@ class AddonInstall { this.addon._repositoryAddon = repoAddon; this.name = this.name || this.addon._repositoryAddon.name; - this.addon.appDisabled = !isUsableAddon(this.addon); + this.addon.appDisabled = !XPIDatabase.isUsableAddon(this.addon); return undefined; } @@ -3732,7 +3730,7 @@ var XPIInstall = { let addon = await loadManifestFromFile(source, location); - if (mustSign(addon.type) && + if (XPIDatabase.mustSign(addon.type) && addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) { throw new Error(`Refusing to install staged add-on ${id} with signed state ${addon.signedState}`); } diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 61bd1a1cd7e5..9a1495b4abba 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -36,8 +36,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.jsm", }); -const {nsIBlocklistService} = Ci; - XPCOMUtils.defineLazyServiceGetter(this, "aomStartup", "@mozilla.org/addons/addon-manager-startup;1", "amIAddonManagerStartup"); @@ -56,7 +54,6 @@ const PREF_EM_STARTUP_SCAN_SCOPES = "extensions.startupScanScopes"; const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI"; // xpinstall.signatures.required only supported in dev builds const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required"; -const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root"; const PREF_LANGPACK_SIGNATURES = "extensions.langpacks.signatures.required"; const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall."; const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons"; @@ -168,10 +165,6 @@ const SIGNED_TYPES = new Set([ "webextension-theme", ]); -const LEGACY_TYPES = new Set([ - "extension", -]); - const ALL_EXTERNAL_TYPES = new Set([ "dictionary", "extension", @@ -179,18 +172,6 @@ const ALL_EXTERNAL_TYPES = new Set([ "theme", ]); -// Whether add-on signing is required. -function mustSign(aType) { - if (!SIGNED_TYPES.has(aType)) - return false; - - if (aType == "webextension-langpack") { - return AddonSettings.LANGPACKS_REQUIRE_SIGNING; - } - - return AddonSettings.REQUIRE_SIGNING; -} - // Keep track of where we are in startup for telemetry // event happened during XPIDatabase.startup() const XPI_STARTING = "XPIStarting"; @@ -397,98 +378,6 @@ function canRunInSafeMode(aAddon) { return location.isSystem; } -/** - * Determine if this addon should be disabled due to being legacy - * - * @param {Addon} addon The addon to check - * - * @returns {boolean} Whether the addon should be disabled for being legacy - */ -function isDisabledLegacy(addon) { - return (!AddonSettings.ALLOW_LEGACY_EXTENSIONS && - LEGACY_TYPES.has(addon.type) && - - // Legacy add-ons are allowed in the system location. - !addon._installLocation.isSystem && - - // Legacy extensions may be installed temporarily in - // non-release builds. - !(AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS && - addon._installLocation.name == KEY_APP_TEMPORARY) && - - // Properly signed legacy extensions are allowed. - addon.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED); -} - -/** - * Calculates whether an add-on should be appDisabled or not. - * - * @param {AddonInternal} aAddon - * The add-on to check - * @returns {boolean} - * True if the add-on should not be appDisabled - */ -function isUsableAddon(aAddon) { - if (mustSign(aAddon.type) && !aAddon.isCorrectlySigned) { - logger.warn(`Add-on ${aAddon.id} is not correctly signed.`); - if (Services.prefs.getBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, false)) { - logger.warn(`Preference ${PREF_XPI_SIGNATURES_DEV_ROOT} is set.`); - } - return false; - } - - if (aAddon.blocklistState == nsIBlocklistService.STATE_BLOCKED) { - logger.warn(`Add-on ${aAddon.id} is blocklisted.`); - return false; - } - - // If we can't read it, it's not usable: - if (aAddon.brokenManifest) { - return false; - } - - if (AddonManager.checkUpdateSecurity && !aAddon.providesUpdatesSecurely) { - logger.warn(`Updates for add-on ${aAddon.id} must be provided over HTTPS.`); - return false; - } - - - if (!aAddon.isPlatformCompatible) { - logger.warn(`Add-on ${aAddon.id} is not compatible with platform.`); - return false; - } - - if (aAddon.dependencies.length) { - let isActive = id => { - let active = XPIProvider.activeAddons.get(id); - return active && !active.disable; - }; - - if (aAddon.dependencies.some(id => !isActive(id))) - return false; - } - - if (isDisabledLegacy(aAddon)) { - logger.warn(`disabling legacy extension ${aAddon.id}`); - return false; - } - - if (AddonManager.checkCompatibility) { - if (!aAddon.isCompatible) { - logger.warn(`Add-on ${aAddon.id} is not compatible with application version.`); - return false; - } - } else { - let app = aAddon.matchingTargetApplication; - if (!app) { - logger.warn(`Add-on ${aAddon.id} is not compatible with target application.`); - return false; - } - } - - return true; -} - /** * Converts an internal add-on type to the type presented through the API. * @@ -3055,7 +2944,7 @@ var XPIProvider = { if (aSoftDisabled === undefined || aUserDisabled) aSoftDisabled = aAddon.softDisabled; - let appDisabled = !isUsableAddon(aAddon); + let appDisabled = !XPIDatabase.isUsableAddon(aAddon); // No change means nothing to do here if (aAddon.userDisabled == aUserDisabled && aAddon.appDisabled == appDisabled && @@ -3684,9 +3573,7 @@ var XPIInternal = { getExternalType, getURIForResourceInFile, isTheme, - isUsableAddon, isWebExtension, - mustSign, }; var addonTypes = [ From 294399b3fbbeb72ec048b478ec5142ce60859dbd Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 22 Apr 2018 15:00:08 -0700 Subject: [PATCH 32/44] Bug 1363925: Part 8d - Move updateAddonDisabledState to XPIDatabase. r=aswan This code is large and complex, and can only be called when we have an AddonInternal object from XPIDatabase.jsm. It should live with that code. MozReview-Commit-ID: 3ssV5aH9NUJ --HG-- extra : rebase_source : d54474f67213420678706f04291b246ceee154de extra : histedit_source : d1a3ca6fd05856a6675380717351e2e1d1568cf1 --- .../extensions/internal/XPIDatabase.jsm | 157 +++++++++++++++++- .../extensions/internal/XPIInstall.jsm | 7 + .../extensions/internal/XPIProvider.jsm | 154 ++--------------- 3 files changed, 170 insertions(+), 148 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm index 9e51d29f80b1..3b7c262b340a 100644 --- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm +++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm @@ -4,6 +4,14 @@ "use strict"; +/** + * This file contains most of the logic required to maintain the + * extensions database, including querying and modifying extension + * metadata. In general, we try to avoid loading it during startup when + * at all possible. Please keep that in mind when deciding whether to + * add code here or elsewhere. + */ + /* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */ var EXPORTED_SYMBOLS = ["AddonInternal", "XPIDatabase", "XPIDatabaseReconcile"]; @@ -472,7 +480,7 @@ AddonInternal.prototype = { } if (this.inDatabase && updateDatabase) { - XPIProvider.updateAddonDisabledState(this, userDisabled, softDisabled); + XPIDatabase.updateAddonDisabledState(this, userDisabled, softDisabled); XPIDatabase.saveChanges(); } else { this.appDisabled = !XPIDatabase.isUsableAddon(this); @@ -888,7 +896,7 @@ AddonWrapper.prototype = { if (this.hidden) { throw new Error(`Cannot disable hidden add-on ${addon.id}`); } - XPIProvider.updateAddonDisabledState(addon, val); + XPIDatabase.updateAddonDisabledState(addon, val); } else { addon.userDisabled = val; // When enabling remove the softDisabled flag @@ -908,9 +916,9 @@ AddonWrapper.prototype = { // When softDisabling a theme just enable the active theme if (isTheme(addon.type) && val && !addon.userDisabled) { if (isWebExtension(addon.type)) - XPIProvider.updateAddonDisabledState(addon, undefined, val); + XPIDatabase.updateAddonDisabledState(addon, undefined, val); } else { - XPIProvider.updateAddonDisabledState(addon, undefined, val); + XPIDatabase.updateAddonDisabledState(addon, undefined, val); } } else if (!addon.userDisabled) { // Only set softDisabled if not already disabled @@ -1028,9 +1036,9 @@ AddonWrapper.prototype = { if (!this.temporarilyInstalled) { let addonFile = addon.getResourceURI; - XPIProvider.updateAddonDisabledState(addon, true); + XPIDatabase.updateAddonDisabledState(addon, true); Services.obs.notifyObservers(addonFile, "flush-cache-entry"); - XPIProvider.updateAddonDisabledState(addon, false); + XPIDatabase.updateAddonDisabledState(addon, false); resolve(); } else { // This function supports re-installing an existing add-on. @@ -1253,7 +1261,7 @@ Object.assign(DBAddonInternal.prototype, { }); if (wasCompatible != this.isCompatible) - XPIProvider.updateAddonDisabledState(this); + XPIDatabase.updateAddonDisabledState(this); }, toJSON() { @@ -2211,6 +2219,141 @@ this.XPIDatabase = { } }, + /** + * Updates the disabled state for an add-on. Its appDisabled property will be + * calculated and if the add-on is changed the database will be saved and + * appropriate notifications will be sent out to the registered AddonListeners. + * + * @param {DBAddonInternal} aAddon + * The DBAddonInternal to update + * @param {boolean?} [aUserDisabled] + * Value for the userDisabled property. If undefined the value will + * not change + * @param {boolean?} [aSoftDisabled] + * Value for the softDisabled property. If undefined the value will + * not change. If true this will force userDisabled to be true + * @param {boolean?} [aBecauseSelecting] + * True if we're disabling this add-on because we're selecting + * another. + * @returns {boolean?} + * A tri-state indicating the action taken for the add-on: + * - undefined: The add-on did not change state + * - true: The add-on because disabled + * - false: The add-on became enabled + * @throws if addon is not a DBAddonInternal + */ + updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled, aBecauseSelecting) { + if (!(aAddon.inDatabase)) + throw new Error("Can only update addon states for installed addons."); + if (aUserDisabled !== undefined && aSoftDisabled !== undefined) { + throw new Error("Cannot change userDisabled and softDisabled at the " + + "same time"); + } + + if (aUserDisabled === undefined) { + aUserDisabled = aAddon.userDisabled; + } else if (!aUserDisabled) { + // If enabling the add-on then remove softDisabled + aSoftDisabled = false; + } + + // If not changing softDisabled or the add-on is already userDisabled then + // use the existing value for softDisabled + if (aSoftDisabled === undefined || aUserDisabled) + aSoftDisabled = aAddon.softDisabled; + + let appDisabled = !this.isUsableAddon(aAddon); + // No change means nothing to do here + if (aAddon.userDisabled == aUserDisabled && + aAddon.appDisabled == appDisabled && + aAddon.softDisabled == aSoftDisabled) + return undefined; + + let wasDisabled = aAddon.disabled; + let isDisabled = aUserDisabled || aSoftDisabled || appDisabled; + + // If appDisabled changes but addon.disabled doesn't, + // no onDisabling/onEnabling is sent - so send a onPropertyChanged. + let appDisabledChanged = aAddon.appDisabled != appDisabled; + + // Update the properties in the database. + this.setAddonProperties(aAddon, { + userDisabled: aUserDisabled, + appDisabled, + softDisabled: aSoftDisabled + }); + + let wrapper = aAddon.wrapper; + + if (appDisabledChanged) { + AddonManagerPrivate.callAddonListeners("onPropertyChanged", + wrapper, + ["appDisabled"]); + } + + // If the add-on is not visible or the add-on is not changing state then + // there is no need to do anything else + if (!aAddon.visible || (wasDisabled == isDisabled)) + return undefined; + + // Flag that active states in the database need to be updated on shutdown + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + + // Sync with XPIStates. + let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id); + if (xpiState) { + xpiState.syncWithDB(aAddon); + XPIStates.save(); + } else { + // There should always be an xpiState + logger.warn("No XPIState for ${id} in ${location}", aAddon); + } + + // Have we just gone back to the current state? + if (isDisabled != aAddon.active) { + AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper); + } else { + if (isDisabled) { + AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false); + } else { + AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false); + } + + this.updateAddonActive(aAddon, !isDisabled); + + if (isDisabled) { + if (aAddon.bootstrap && XPIProvider.activeAddons.has(aAddon.id)) { + XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", + BOOTSTRAP_REASONS.ADDON_DISABLE); + XPIProvider.unloadBootstrapScope(aAddon.id); + } + AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); + } else { + if (aAddon.bootstrap) { + XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup", + BOOTSTRAP_REASONS.ADDON_ENABLE); + } + AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); + } + } + + // Notify any other providers that a new theme has been enabled + if (isTheme(aAddon.type)) { + if (!isDisabled) { + AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type); + + if (xpiState) { + xpiState.syncWithDB(aAddon); + XPIStates.save(); + } + } else if (isDisabled && !aBecauseSelecting) { + AddonManagerPrivate.notifyAddonChanged(null, "theme"); + } + } + + return isDisabled; + }, + /** * Record a bit of per-addon telemetry. * diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index 661206ffa8f2..944bafd82dfe 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -4,6 +4,13 @@ "use strict"; +/** + * This file contains most of the logic required to install extensions. + * In general, we try to avoid loading it until extension installation + * or update is required. Please keep that in mind when deciding whether + * to add code here or elsewhere. + */ + /* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */ var EXPORTED_SYMBOLS = [ diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 9a1495b4abba..db365a6cbf24 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -4,6 +4,13 @@ "use strict"; +/** + * This file contains most of the logic required to load and run + * extensions at startup. Anything which is not required immediately at + * startup should go in XPIInstall.jsm or XPIDatabase.jsm if at all + * possible, in order to minimize the impact on startup performance. + */ + /* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */ var EXPORTED_SYMBOLS = ["XPIProvider", "XPIInternal"]; @@ -1977,7 +1984,7 @@ var XPIProvider = { ["signedState"]); } - let disabled = XPIProvider.updateAddonDisabledState(addon); + let disabled = XPIDatabase.updateAddonDisabledState(addon); if (disabled !== undefined) changes[disabled ? "disabled" : "enabled"].push(addon.id); } @@ -2567,7 +2574,7 @@ var XPIProvider = { let addons = XPIDatabase.getAddonsByType("webextension-theme"); for (let theme of addons) { if (theme.visible && theme.id != aId) - this.updateAddonDisabledState(theme, true, undefined, true); + XPIDatabase.updateAddonDisabledState(theme, true, undefined, true); } if (!aId && (!LightweightThemeManager.currentTheme || @@ -2587,7 +2594,7 @@ var XPIProvider = { updateAddonAppDisabledStates() { let addons = XPIDatabase.getAddons(); for (let addon of addons) { - this.updateAddonDisabledState(addon); + XPIDatabase.updateAddonDisabledState(addon); } }, @@ -2603,7 +2610,7 @@ var XPIProvider = { if (aRepoAddon || AddonRepository.getCompatibilityOverridesSync(addon.id)) { logger.debug("updateAddonRepositoryData got info for " + addon.id); addon._repositoryAddon = aRepoAddon; - this.updateAddonDisabledState(addon); + XPIDatabase.updateAddonDisabledState(addon); } }))); }, @@ -2828,7 +2835,7 @@ var XPIProvider = { activeAddon.disable = true; for (let addon of this.getDependentAddons(aAddon)) { if (addon.active) - this.updateAddonDisabledState(addon); + XPIDatabase.updateAddonDisabledState(addon); } } } @@ -2890,7 +2897,7 @@ var XPIProvider = { // Extensions are automatically initialized in the correct order at startup. if (aMethod == "startup" && aReason != BOOTSTRAP_REASONS.APP_STARTUP) { for (let addon of this.getDependentAddons(aAddon)) - this.updateAddonDisabledState(addon); + XPIDatabase.updateAddonDisabledState(addon); } if (CHROME_TYPES.has(aAddon.type) && aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) { @@ -2900,141 +2907,6 @@ var XPIProvider = { this.setTelemetry(aAddon.id, aMethod + "_MS", new Date() - timeStart); } }, - - /** - * Updates the disabled state for an add-on. Its appDisabled property will be - * calculated and if the add-on is changed the database will be saved and - * appropriate notifications will be sent out to the registered AddonListeners. - * - * @param {DBAddonInternal} aAddon - * The DBAddonInternal to update - * @param {boolean?} [aUserDisabled] - * Value for the userDisabled property. If undefined the value will - * not change - * @param {boolean?} [aSoftDisabled] - * Value for the softDisabled property. If undefined the value will - * not change. If true this will force userDisabled to be true - * @param {boolean?} [aBecauseSelecting] - * True if we're disabling this add-on because we're selecting - * another. - * @returns {boolean?} - * A tri-state indicating the action taken for the add-on: - * - undefined: The add-on did not change state - * - true: The add-on because disabled - * - false: The add-on became enabled - * @throws if addon is not a DBAddonInternal - */ - updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled, aBecauseSelecting) { - if (!(aAddon.inDatabase)) - throw new Error("Can only update addon states for installed addons."); - if (aUserDisabled !== undefined && aSoftDisabled !== undefined) { - throw new Error("Cannot change userDisabled and softDisabled at the " + - "same time"); - } - - if (aUserDisabled === undefined) { - aUserDisabled = aAddon.userDisabled; - } else if (!aUserDisabled) { - // If enabling the add-on then remove softDisabled - aSoftDisabled = false; - } - - // If not changing softDisabled or the add-on is already userDisabled then - // use the existing value for softDisabled - if (aSoftDisabled === undefined || aUserDisabled) - aSoftDisabled = aAddon.softDisabled; - - let appDisabled = !XPIDatabase.isUsableAddon(aAddon); - // No change means nothing to do here - if (aAddon.userDisabled == aUserDisabled && - aAddon.appDisabled == appDisabled && - aAddon.softDisabled == aSoftDisabled) - return undefined; - - let wasDisabled = aAddon.disabled; - let isDisabled = aUserDisabled || aSoftDisabled || appDisabled; - - // If appDisabled changes but addon.disabled doesn't, - // no onDisabling/onEnabling is sent - so send a onPropertyChanged. - let appDisabledChanged = aAddon.appDisabled != appDisabled; - - // Update the properties in the database. - XPIDatabase.setAddonProperties(aAddon, { - userDisabled: aUserDisabled, - appDisabled, - softDisabled: aSoftDisabled - }); - - let wrapper = aAddon.wrapper; - - if (appDisabledChanged) { - AddonManagerPrivate.callAddonListeners("onPropertyChanged", - wrapper, - ["appDisabled"]); - } - - // If the add-on is not visible or the add-on is not changing state then - // there is no need to do anything else - if (!aAddon.visible || (wasDisabled == isDisabled)) - return undefined; - - // Flag that active states in the database need to be updated on shutdown - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - - // Sync with XPIStates. - let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id); - if (xpiState) { - xpiState.syncWithDB(aAddon); - XPIStates.save(); - } else { - // There should always be an xpiState - logger.warn("No XPIState for ${id} in ${location}", aAddon); - } - - // Have we just gone back to the current state? - if (isDisabled != aAddon.active) { - AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper); - } else { - if (isDisabled) { - AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false); - } else { - AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false); - } - - XPIDatabase.updateAddonActive(aAddon, !isDisabled); - - if (isDisabled) { - if (aAddon.bootstrap && this.activeAddons.has(aAddon.id)) { - this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", - BOOTSTRAP_REASONS.ADDON_DISABLE); - this.unloadBootstrapScope(aAddon.id); - } - AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); - } else { - if (aAddon.bootstrap) { - this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup", - BOOTSTRAP_REASONS.ADDON_ENABLE); - } - AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); - } - } - - // Notify any other providers that a new theme has been enabled - if (isTheme(aAddon.type)) { - if (!isDisabled) { - AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type); - - if (xpiState) { - xpiState.syncWithDB(aAddon); - XPIStates.save(); - } - } else if (isDisabled && !aBecauseSelecting) { - AddonManagerPrivate.notifyAddonChanged(null, "theme"); - } - } - - return isDisabled; - }, }; for (let meth of ["cancelUninstallAddon", "getInstallForFile", From e109e359c0159b98e9c60dfaea11913474d24784 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 22 Apr 2018 15:21:30 -0700 Subject: [PATCH 33/44] Bug 1363925: Part 8e - Convert AddonInternal classes to ES6 classes. r=aswan MozReview-Commit-ID: LUwU0JbRn2H --HG-- extra : rebase_source : f4bbed728a20c799c239640f6676e35ebf0324ef extra : histedit_source : 2f6dce596319efb28181bafcb7a0eefd3c15b0a9 --- .../extensions/internal/XPIDatabase.jsm | 256 +++++++++--------- 1 file changed, 131 insertions(+), 125 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm index 3b7c262b340a..61b6180b9b43 100644 --- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm +++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm @@ -198,44 +198,51 @@ function copyProperties(aObject, aProperties, aTarget) { const wrapperMap = new WeakMap(); let addonFor = wrapper => wrapperMap.get(wrapper); +const EMPTY_ARRAY = Object.freeze([]); + +let AddonWrapper; + /** * The AddonInternal is an internal only representation of add-ons. It may * have come from the database (see DBAddonInternal in XPIDatabase.jsm) * or an install manifest. */ -function AddonInternal() { - this._hasResourceCache = new Map(); +class AddonInternal { + constructor() { + this._hasResourceCache = new Map(); - XPCOMUtils.defineLazyGetter(this, "wrapper", () => { - return new AddonWrapper(this); - }); -} + this._wrapper = null; + this._selectedLocale = null; + this.active = false; + this.visible = false; + this.userDisabled = false; + this.appDisabled = false; + this.softDisabled = false; + this.blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + this.blocklistURL = null; + this.sourceURI = null; + this.releaseNotesURI = null; + this.foreignInstall = false; + this.seen = true; + this.skinnable = false; + this.startupData = null; -AddonInternal.prototype = { - _selectedLocale: null, - _hasResourceCache: null, - active: false, - visible: false, - userDisabled: false, - appDisabled: false, - softDisabled: false, - blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED, - blocklistURL: null, - sourceURI: null, - releaseNotesURI: null, - foreignInstall: false, - seen: true, - skinnable: false, - startupData: null, + /** + * @property {Array} dependencies + * An array of bootstrapped add-on IDs on which this add-on depends. + * The add-on will remain appDisabled if any of the dependent + * add-ons is not installed and enabled. + */ + this.dependencies = EMPTY_ARRAY; + this.hasEmbeddedWebExtension = false; + } - /** - * @property {Array} dependencies - * An array of bootstrapped add-on IDs on which this add-on depends. - * The add-on will remain appDisabled if any of the dependent - * add-ons is not installed and enabled. - */ - dependencies: Object.freeze([]), - hasEmbeddedWebExtension: false, + get wrapper() { + if (!this._wrapper) { + this._wrapper = new AddonWrapper(this); + } + return this._wrapper; + } get selectedLocale() { if (this._selectedLocale) @@ -284,11 +291,11 @@ AddonInternal.prototype = { } return this._selectedLocale; - }, + } get providesUpdatesSecurely() { return !this.updateURL || this.updateURL.startsWith("https:"); - }, + } get isCorrectlySigned() { switch (this._installLocation.name) { @@ -314,19 +321,19 @@ AddonInternal.prototype = { if (this.signedState === AddonManager.SIGNEDSTATE_NOT_REQUIRED) return true; return this.signedState > AddonManager.SIGNEDSTATE_MISSING; - }, + } get unpack() { return this.type === "dictionary"; - }, + } get isCompatible() { return this.isCompatibleWith(); - }, + } get disabled() { return (this.userDisabled || this.appDisabled || this.softDisabled); - }, + } get isPlatformCompatible() { if (this.targetPlatforms.length == 0) @@ -367,7 +374,7 @@ AddonInternal.prototype = { } return matchedOS && !needsABI; - }, + } isCompatibleWith(aAppVersion, aPlatformVersion) { let app = this.matchingTargetApplication; @@ -421,7 +428,7 @@ AddonInternal.prototype = { return (Services.vc.compare(version, minVersion) >= 0) && (Services.vc.compare(version, maxVersion) <= 0); - }, + } get matchingTargetApplication() { let app = null; @@ -432,7 +439,7 @@ AddonInternal.prototype = { app = targetApp; } return app; - }, + } async findBlocklistEntry() { let staticItem = findMatchingStaticBlocklistItem(this); @@ -445,7 +452,7 @@ AddonInternal.prototype = { } return Services.blocklist.getAddonBlocklistEntry(this.wrapper); - }, + } async updateBlocklistState(options = {}) { let {applySoftBlock = true, oldAddon = null, updateDatabase = true} = options; @@ -491,7 +498,7 @@ AddonInternal.prototype = { this.softDisabled = softDisabled; } } - }, + } applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { for (let targetApp of this.targetApplications) { @@ -504,7 +511,7 @@ AddonInternal.prototype = { } } this.appDisabled = !XPIDatabase.isUsableAddon(this); - }, + } /** * toJSON is called by JSON.stringify in order to create a filtered version @@ -543,7 +550,7 @@ AddonInternal.prototype = { } return obj; - }, + } /** * When an add-on install is pending its metadata will be cached in a file. @@ -563,7 +570,7 @@ AddonInternal.prototype = { // Compatibility info may have changed so update appDisabled this.appDisabled = !XPIDatabase.isUsableAddon(this); - }, + } permissions() { let permissions = 0; @@ -600,8 +607,8 @@ AddonInternal.prototype = { } return permissions; - }, -}; + } +} /** * The AddonWrapper wraps an Addon to provide the data visible to consumers of @@ -610,43 +617,43 @@ AddonInternal.prototype = { * @param {AddonInternal} aAddon * The add-on object to wrap. */ -function AddonWrapper(aAddon) { - wrapperMap.set(this, aAddon); -} +AddonWrapper = class { + constructor(aAddon) { + wrapperMap.set(this, aAddon); + } -AddonWrapper.prototype = { get __AddonInternal__() { return AppConstants.DEBUG ? addonFor(this) : undefined; - }, + } get seen() { return addonFor(this).seen; - }, + } get hasEmbeddedWebExtension() { return addonFor(this).hasEmbeddedWebExtension; - }, + } markAsSeen() { addonFor(this).seen = true; XPIDatabase.saveChanges(); - }, + } get type() { return XPIInternal.getExternalType(addonFor(this).type); - }, + } get isWebExtension() { return isWebExtension(addonFor(this).type); - }, + } get temporarilyInstalled() { return addonFor(this)._installLocation == XPIInternal.TemporaryInstallLocation; - }, + } get aboutURL() { return this.isActive ? addonFor(this).aboutURL : null; - }, + } get optionsURL() { if (!this.isActive) { @@ -671,7 +678,7 @@ AddonWrapper.prototype = { } return null; - }, + } get optionsType() { if (!this.isActive) @@ -690,20 +697,20 @@ AddonWrapper.prototype = { } return null; - }, + } get optionsBrowserStyle() { let addon = addonFor(this); return addon.optionsBrowserStyle; - }, + } get iconURL() { return AddonManager.getPreferredIconURL(this, 48); - }, + } get icon64URL() { return AddonManager.getPreferredIconURL(this, 64); - }, + } get icons() { let addon = addonFor(this); @@ -741,7 +748,7 @@ AddonWrapper.prototype = { Object.freeze(icons); return icons; - }, + } get screenshots() { let addon = addonFor(this); @@ -758,11 +765,11 @@ AddonWrapper.prototype = { } return null; - }, + } get applyBackgroundUpdates() { return addonFor(this).applyBackgroundUpdates; - }, + } set applyBackgroundUpdates(val) { let addon = addonFor(this); if (val != AddonManager.AUTOUPDATE_DEFAULT && @@ -781,7 +788,7 @@ AddonWrapper.prototype = { AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]); return val; - }, + } set syncGUID(val) { let addon = addonFor(this); @@ -794,19 +801,19 @@ AddonWrapper.prototype = { addon.syncGUID = val; return val; - }, + } get install() { let addon = addonFor(this); if (!("_install" in addon) || !addon._install) return null; return addon._install.wrapper; - }, + } get pendingUpgrade() { let addon = addonFor(this); return addon.pendingUpgrade ? addon.pendingUpgrade.wrapper : null; - }, + } get scope() { let addon = addonFor(this); @@ -814,7 +821,7 @@ AddonWrapper.prototype = { return addon._installLocation.scope; return AddonManager.SCOPE_PROFILE; - }, + } get pendingOperations() { let addon = addonFor(this); @@ -842,19 +849,19 @@ AddonWrapper.prototype = { pending |= AddonManager.PENDING_UPGRADE; return pending; - }, + } get operationsRequiringRestart() { return 0; - }, + } get isDebuggable() { return this.isActive && addonFor(this).bootstrap; - }, + } get permissions() { return addonFor(this).permissions(); - }, + } get isActive() { let addon = addonFor(this); @@ -863,7 +870,7 @@ AddonWrapper.prototype = { if (!Services.appinfo.inSafeMode) return true; return addon.bootstrap && XPIInternal.canRunInSafeMode(addon); - }, + } get startupPromise() { let addon = addonFor(this); @@ -874,16 +881,16 @@ AddonWrapper.prototype = { if (activeAddon) return activeAddon.startupPromise || null; return null; - }, + } updateBlocklistState(applySoftBlock = true) { return addonFor(this).updateBlocklistState({applySoftBlock}); - }, + } get userDisabled() { let addon = addonFor(this); return addon.softDisabled || addon.userDisabled; - }, + } set userDisabled(val) { let addon = addonFor(this); if (val == this.userDisabled) { @@ -905,7 +912,7 @@ AddonWrapper.prototype = { } return val; - }, + } set softDisabled(val) { let addon = addonFor(this); @@ -926,7 +933,7 @@ AddonWrapper.prototype = { } return val; - }, + } get hidden() { let addon = addonFor(this); @@ -934,41 +941,41 @@ AddonWrapper.prototype = { return false; return addon._installLocation.isSystem; - }, + } get isSystem() { let addon = addonFor(this); return addon._installLocation.isSystem; - }, + } // Returns true if Firefox Sync should sync this addon. Only addons // in the profile install location are considered syncable. get isSyncable() { let addon = addonFor(this); return (addon._installLocation.name == KEY_APP_PROFILE); - }, + } get userPermissions() { return addonFor(this).userPermissions; - }, + } isCompatibleWith(aAppVersion, aPlatformVersion) { return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion); - }, + } uninstall(alwaysAllowUndo) { let addon = addonFor(this); XPIProvider.uninstallAddon(addon, alwaysAllowUndo); - }, + } cancelUninstall() { let addon = addonFor(this); XPIProvider.cancelUninstallAddon(addon); - }, + } findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) { new UpdateChecker(addonFor(this), aListener, aReason, aAppVersion, aPlatformVersion); - }, + } // Returns true if there was an update in progress, false if there was no update to cancel cancelUpdate() { @@ -978,7 +985,7 @@ AddonWrapper.prototype = { return true; } return false; - }, + } hasResource(aPath) { let addon = addonFor(this); @@ -1017,7 +1024,7 @@ AddonWrapper.prototype = { } finally { zipReader.close(); } - }, + } /** * Reloads the add-on. @@ -1045,7 +1052,7 @@ AddonWrapper.prototype = { resolve(AddonManager.installTemporaryAddon(addon._sourceBundle)); } }); - }, + } /** * Returns a URI to the selected resource or to the add-on bundle if aPath @@ -1212,40 +1219,39 @@ PROP_LOCALE_MULTI.forEach(function(aProp) { * @param {Object} aLoaded * Addon data fields loaded from JSON or the addon manifest. */ -function DBAddonInternal(aLoaded) { - AddonInternal.call(this); +class DBAddonInternal extends AddonInternal { + constructor(aLoaded) { + super(); - if (aLoaded.descriptor) { - if (!aLoaded.path) { - aLoaded.path = descriptorToPath(aLoaded.descriptor); + if (aLoaded.descriptor) { + if (!aLoaded.path) { + aLoaded.path = descriptorToPath(aLoaded.descriptor); + } + delete aLoaded.descriptor; } - delete aLoaded.descriptor; + + copyProperties(aLoaded, PROP_JSON_FIELDS, this); + + if (!this.dependencies) + this.dependencies = []; + Object.freeze(this.dependencies); + + if (aLoaded._installLocation) { + this._installLocation = aLoaded._installLocation; + this.location = aLoaded._installLocation.name; + } else if (aLoaded.location) { + this._installLocation = XPIProvider.installLocationsByName[this.location]; + } + + this._key = this.location + ":" + this.id; + + if (!aLoaded._sourceBundle) { + throw new Error("Expected passed argument to contain a path"); + } + + this._sourceBundle = aLoaded._sourceBundle; } - copyProperties(aLoaded, PROP_JSON_FIELDS, this); - - if (!this.dependencies) - this.dependencies = []; - Object.freeze(this.dependencies); - - if (aLoaded._installLocation) { - this._installLocation = aLoaded._installLocation; - this.location = aLoaded._installLocation.name; - } else if (aLoaded.location) { - this._installLocation = XPIProvider.installLocationsByName[this.location]; - } - - this._key = this.location + ":" + this.id; - - if (!aLoaded._sourceBundle) { - throw new Error("Expected passed argument to contain a path"); - } - - this._sourceBundle = aLoaded._sourceBundle; -} - -DBAddonInternal.prototype = Object.create(AddonInternal.prototype); -Object.assign(DBAddonInternal.prototype, { applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { let wasCompatible = this.isCompatible; @@ -1262,16 +1268,16 @@ Object.assign(DBAddonInternal.prototype, { if (wasCompatible != this.isCompatible) XPIDatabase.updateAddonDisabledState(this); - }, + } toJSON() { return copyProperties(this, PROP_JSON_FIELDS); - }, + } get inDatabase() { return true; } -}); +} /** * @typedef {Map} AddonDB From c655361fd7c5a644e2a8cfaa8c000b7af529789a Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 23 Apr 2018 20:37:06 -0700 Subject: [PATCH 34/44] Bug 1363925: Follow-up: Fix eslint bustage after rebase. r=bustage DONTBUILD MozReview-Commit-ID: JOPfyCxb65B --- toolkit/mozapps/extensions/internal/XPIProvider.jsm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index db365a6cbf24..114211a41726 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1809,6 +1809,11 @@ var XPIProvider = { } }, + // Hello, whichever sheriff is doing this merge. You will likely see a + // merge conflict here. Fear not. Please just delete these + // eslint-disable and eslint-enable comments. And also this comment. + // Thanks. + /* eslint-disable valid-jsdoc, no-undef */ /** * If the application has been upgraded and there are add-ons outside the * application directory then we may need to synchronize compatibility @@ -1843,6 +1848,7 @@ var XPIProvider = { return null; }, + /* eslint-enable valid-jsdoc */ /** * Perform startup check for updates of legacy extensions. From 1d89ab6a3adaf824a4184f6fc88a7fc7f2806a45 Mon Sep 17 00:00:00 2001 From: Andreea Pavel Date: Tue, 24 Apr 2018 06:47:19 +0300 Subject: [PATCH 35/44] Backed out changeset 42781c597745 (bug 1448703) cpu process leakcchecks --- gfx/webrender_bindings/Moz2DImageRenderer.cpp | 2 +- gfx/webrender_bindings/WebRenderAPI.cpp | 17 +++++++++++++++++ gfx/webrender_bindings/src/bindings.rs | 2 +- gfx/webrender_bindings/src/moz2d_renderer.rs | 6 ++---- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/gfx/webrender_bindings/Moz2DImageRenderer.cpp b/gfx/webrender_bindings/Moz2DImageRenderer.cpp index 34019636ad60..0e2fb592b141 100644 --- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp +++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp @@ -94,7 +94,6 @@ static struct FontDeleteLog { } } sFontDeleteLog; -extern "C" { void ClearBlobImageResources(WrIdNamespace aNamespace) { StaticMutexAutoLock lock(sFontDataTableLock); @@ -111,6 +110,7 @@ ClearBlobImageResources(WrIdNamespace aNamespace) { } } +extern "C" { void AddFontData(WrFontKey aKey, const uint8_t *aData, size_t aSize, uint32_t aIndex, const ArcVecU8 *aVec) { StaticMutexAutoLock lock(sFontDataTableLock); diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp index 0ae9af83dd59..18905a1d6916 100644 --- a/gfx/webrender_bindings/WebRenderAPI.cpp +++ b/gfx/webrender_bindings/WebRenderAPI.cpp @@ -357,6 +357,8 @@ WebRenderAPI::GetNamespace() { return wr_api_get_namespace(mDocHandle); } +extern void ClearBlobImageResources(WrIdNamespace aNamespace); + WebRenderAPI::~WebRenderAPI() { if (!mRootDocumentApi) { @@ -374,6 +376,21 @@ WebRenderAPI::~WebRenderAPI() wr_api_shut_down(mDocHandle); } + // wr_api_get_namespace cannot be marked destructor-safe because it has a + // return value, and we can't call it if MOZ_BUILD_WEBRENDER is not defined + // because it's not destructor-safe. So let's just ifdef around it. This is + // basically a hack to get around compile-time warnings, this code never runs + // unless MOZ_BUILD_WEBRENDER is defined anyway. +#ifdef MOZ_BUILD_WEBRENDER + wr::WrIdNamespace ns = GetNamespace(); +#else + wr::WrIdNamespace ns{0}; +#endif + + // Clean up any resources the blob image renderer is holding onto that + // can no longer be used once this WR API instance goes away. + ClearBlobImageResources(ns); + wr_api_delete(mDocHandle); } diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs index 811f26c23788..2d9c0d16cc62 100644 --- a/gfx/webrender_bindings/src/bindings.rs +++ b/gfx/webrender_bindings/src/bindings.rs @@ -63,7 +63,7 @@ type WrEpoch = Epoch; /// cbindgen:derive-lt=true /// cbindgen:derive-lte=true /// cbindgen:derive-neq=true -pub type WrIdNamespace = IdNamespace; +type WrIdNamespace = IdNamespace; /// cbindgen:field-names=[mNamespace, mHandle] type WrPipelineId = PipelineId; diff --git a/gfx/webrender_bindings/src/moz2d_renderer.rs b/gfx/webrender_bindings/src/moz2d_renderer.rs index 468fa3337cd3..cce4f1f01dba 100644 --- a/gfx/webrender_bindings/src/moz2d_renderer.rs +++ b/gfx/webrender_bindings/src/moz2d_renderer.rs @@ -494,19 +494,17 @@ impl BlobImageRenderer for Moz2dImageRenderer { fn delete_font_instance(&mut self, _key: FontInstanceKey) { } - fn clear_namespace(&mut self, namespace: IdNamespace) { - unsafe { ClearBlobImageResources(namespace); } + fn clear_namespace(&mut self, _namespace: IdNamespace) { } } -use bindings::{WrFontKey, WrIdNamespace}; +use bindings::WrFontKey; #[allow(improper_ctypes)] // this is needed so that rustc doesn't complain about passing the &Arc to an extern function extern "C" { fn AddFontData(key: WrFontKey, data: *const u8, size: usize, index: u32, vec: &ArcVecU8); fn AddNativeFontHandle(key: WrFontKey, handle: *mut c_void, index: u32); fn DeleteFontData(key: WrFontKey); - fn ClearBlobImageResources(namespace: WrIdNamespace); } impl Moz2dImageRenderer { From b34897b3b6911de4f0d137120ce075fc363c16db Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 23 Apr 2018 20:50:54 -0700 Subject: [PATCH 36/44] Bug 1456291: Follow-up: Fix dummy GMP data. r=bustage MozReview-Commit-ID: 94xc6yDoHls --HG-- extra : rebase_source : 4fc3d8b7f8b058b568d1f3455a43aee9f9a870e8 --- toolkit/components/telemetry/TelemetryEnvironment.jsm | 2 +- toolkit/mozapps/extensions/internal/XPIProvider.jsm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/toolkit/components/telemetry/TelemetryEnvironment.jsm b/toolkit/components/telemetry/TelemetryEnvironment.jsm index cd2c8864149b..d57d51f61621 100644 --- a/toolkit/components/telemetry/TelemetryEnvironment.jsm +++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm @@ -832,7 +832,7 @@ EnvironmentAddonBuilder.prototype = { this._blocklistObserverAdded = true; } return { - "dummy-gmp": {version: "0.1", userDisabled: false, applyBackgroundUpdates: true} + "dummy-gmp": {version: "0.1", userDisabled: false, applyBackgroundUpdates: 1} }; } // Request plugins, asynchronously. diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 114211a41726..dcc3f651c92a 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1297,7 +1297,7 @@ var XPIProvider = { // Make sure we don't touch the XPIDatabase getter before it's // actually loaded, and force an early load. return (Object.getOwnPropertyDescriptor(gGlobalScope, "XPIDatabase").value && - XPIDatabase.initialized); + XPIDatabase.initialized) || false; }, /** From 7f57158c87ec32e2caad61e489148ccd6d1bbb15 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 23 Apr 2018 21:15:56 -0700 Subject: [PATCH 37/44] Bug 1363925: Follow-up: Fix more rebase bustage. r=bustage MozReview-Commit-ID: CpJkphCaNuE --- toolkit/mozapps/extensions/internal/XPIProvider.jsm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index dcc3f651c92a..29cad1e9d87e 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -2918,7 +2918,8 @@ var XPIProvider = { for (let meth of ["cancelUninstallAddon", "getInstallForFile", "getInstallForURL", "installAddonFromLocation", "installAddonFromSources", "installTemporaryAddon", - "isInstallAllowed", "uninstallAddon", "updateSystemAddons"]) { + "isInstallAllowed", "isInstallEnabled", "uninstallAddon", + "updateSystemAddons"]) { XPIProvider[meth] = function() { return XPIInstall[meth](...arguments); }; From 39792c8bdf3910b9c6fdd19eb4bbe5853e40461a Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 23 Apr 2018 21:30:39 -0700 Subject: [PATCH 38/44] Bug 1449071: Disable browser_file_xpi_no_process_switch for too frequent failures. r=me,test-only DONTBUILD MozReview-Commit-ID: IUOwdb4XHv3 --- toolkit/mozapps/extensions/test/browser/browser.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/mozapps/extensions/test/browser/browser.ini b/toolkit/mozapps/extensions/test/browser/browser.ini index 5307d304d7bc..813401edd663 100644 --- a/toolkit/mozapps/extensions/test/browser/browser.ini +++ b/toolkit/mozapps/extensions/test/browser/browser.ini @@ -60,7 +60,7 @@ skip-if = os == "linux" && !debug # Bug 1395539 - fails on multi-core skip-if = buildapp == 'mulet' [browser_dragdrop_incompat.js] [browser_file_xpi_no_process_switch.js] -skip-if = !debug && ((os == 'linux' && bits == '64') || (os == 'win' && os_version == '6.1')) # Bug 1449071 - disable on Linux64 and Windows 7 due to frequent failures +skip-if = true # Bug 1449071 - Frequent failures [browser_getmorethemes.js] [browser_globalwarnings.js] [browser_gmpProvider.js] From 92148e02edd57a61900459517ba722544f020f78 Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Tue, 24 Apr 2018 00:43:50 -0400 Subject: [PATCH 39/44] Bug 1448095 - 1. Add AIDL output to javadoc source; r=me Add AIDL output directory to javadoc source paths so AIDL classes are picked up. --- mobile/android/geckoview/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/android/geckoview/build.gradle b/mobile/android/geckoview/build.gradle index d352df9e124d..625a68078ec8 100644 --- a/mobile/android/geckoview/build.gradle +++ b/mobile/android/geckoview/build.gradle @@ -189,7 +189,8 @@ android.libraryVariants.all { variant -> include 'org/mozilla/geckoview/**' options.addPathOption('sourcepath', ':').setValue( variant.sourceSets.collect({ it.javaDirectories }).flatten() + - variant.generateBuildConfig.sourceOutputDir) + variant.generateBuildConfig.sourceOutputDir + + variant.aidlCompile.sourceOutputDir) // javadoc 8 has a bug that requires the rt.jar file from the JRE to be // in the bootclasspath (https://stackoverflow.com/a/30458820). From 1184465553424b0bf9782e07a2f8dbe54120d718 Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Tue, 24 Apr 2018 00:43:51 -0400 Subject: [PATCH 40/44] Bug 1448095 - 2. Fix javadoc warnings; r=me --- .../main/java/org/mozilla/geckoview/GeckoRuntime.java | 1 + .../java/org/mozilla/geckoview/GeckoRuntimeSettings.java | 9 +++++++++ .../java/org/mozilla/geckoview/SessionAccessibility.java | 8 +++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java index fd30deebacd4..a97f310a6b95 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java @@ -34,6 +34,7 @@ public final class GeckoRuntime implements Parcelable { * Note: Only use this for session-less apps. * For regular apps, use create() and createSession() instead. * + * @param context An application context for the default runtime. * @return The (static) default runtime for the context. */ public static synchronized @NonNull GeckoRuntime getDefault( diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java index 9433a8302c24..64975dc75bb1 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java @@ -40,6 +40,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set the content process hint flag. * * @param use If true, this will reload the content process for future use. + * @return This Builder instance. */ public @NonNull Builder useContentProcessHint(final boolean use) { mSettings.mUseContentProcess = use; @@ -50,6 +51,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set the custom Gecko process arguments. * * @param args The Gecko process arguments. + * @return This Builder instance. */ public @NonNull Builder arguments(final @NonNull String[] args) { if (args == null) { @@ -63,6 +65,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set the custom Gecko intent extras. * * @param extras The Gecko intent extras. + * @return This Builder instance. */ public @NonNull Builder extras(final @NonNull Bundle extras) { if (extras == null) { @@ -76,6 +79,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set whether JavaScript support should be enabled. * * @param flag A flag determining whether JavaScript should be enabled. + * @return This Builder instance. */ public @NonNull Builder javaScriptEnabled(final boolean flag) { mSettings.mJavaScript.set(flag); @@ -86,6 +90,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set whether remote debugging support should be enabled. * * @param enabled True if remote debugging should be enabled. + * @return This Builder instance. */ public @NonNull Builder remoteDebuggingEnabled(final boolean enabled) { mSettings.mRemoteDebugging.set(enabled); @@ -96,6 +101,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set whether support for web fonts should be enabled. * * @param flag A flag determining whether web fonts should be enabled. + * @return This Builder instance. */ public @NonNull Builder webFontsEnabled(final boolean flag) { mSettings.mWebFonts.set(flag); @@ -222,6 +228,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set whether JavaScript support should be enabled. * * @param flag A flag determining whether JavaScript should be enabled. + * @return This GeckoRuntimeSettings instance. */ public @NonNull GeckoRuntimeSettings setJavaScriptEnabled(final boolean flag) { mJavaScript.set(flag); @@ -241,6 +248,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set whether remote debugging support should be enabled. * * @param enabled True if remote debugging should be enabled. + * @return This GeckoRuntimeSettings instance. */ public @NonNull GeckoRuntimeSettings setRemoteDebuggingEnabled(final boolean enabled) { mRemoteDebugging.set(enabled); @@ -260,6 +268,7 @@ public final class GeckoRuntimeSettings implements Parcelable { * Set whether support for web fonts should be enabled. * * @param flag A flag determining whether web fonts should be enabled. + * @return This GeckoRuntimeSettings instance. */ public @NonNull GeckoRuntimeSettings setWebFontsEnabled(final boolean flag) { mWebFonts.set(flag); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java index c12db75df9ce..e297cbabdc2e 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java @@ -60,16 +60,18 @@ public class SessionAccessibility { } /** - * Get the GeckoView instance that delegates accessibility to this session. + * Get the View instance that delegates accessibility to this session. * - * @return GeckoView instance. + * @return View instance. */ public View getView() { return mView; } /** - * Set the GeckoView instance that should delegate accessibility to this session. + * Set the View instance that should delegate accessibility to this session. + * + * @param view View instance. */ public void setView(final View view) { if (mView != null) { From 25fe6ae3a06b169db6c5da8adc209de09818c3ad Mon Sep 17 00:00:00 2001 From: Tom Prince Date: Mon, 23 Apr 2018 22:59:59 -0600 Subject: [PATCH 41/44] Bug 1455448: [taskcluster] Use `taskgraph` indexed decision tasks for finding parameters; r=me Now that 28db2c96ac69 has been uplifted, get parameters from the new index paths. --HG-- extra : rebase_source : 17166c55ad85ee153196e1797fe027dc6895beb2 --- taskcluster/taskgraph/parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskcluster/taskgraph/parameters.py b/taskcluster/taskgraph/parameters.py index 3e70b967edda..f63b12f242d5 100644 --- a/taskcluster/taskgraph/parameters.py +++ b/taskcluster/taskgraph/parameters.py @@ -188,7 +188,7 @@ def load_parameters_file(filename, strict=True): if filename.startswith("task-id="): task_id = filename.split("=")[1] elif filename.startswith("project="): - index = "gecko.v2.{project}.latest.firefox.decision".format( + index = "gecko.v2.{project}.latest.taskgraph.decision".format( project=filename.split("=")[1], ) task_id = find_task_id(index) From ca622386bd05913052bac3875f732267f666f070 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 24 Apr 2018 02:17:17 -0400 Subject: [PATCH 42/44] Bug 1456169 part 1. Remove the now-unnecessary nsITextControlElement::GetRootEditorNode. r=emilio MozReview-Commit-ID: 6DYEtQMTCRF --- dom/html/HTMLInputElement.cpp | 10 ----- dom/html/HTMLInputElement.h | 1 - dom/html/HTMLTextAreaElement.cpp | 6 --- dom/html/HTMLTextAreaElement.h | 1 - dom/html/nsITextControlElement.h | 5 --- layout/forms/nsTextControlFrame.cpp | 57 ++++++++++------------------- layout/forms/nsTextControlFrame.h | 10 ----- 7 files changed, 20 insertions(+), 70 deletions(-) diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 072fe72bd4f7..89479eac74cb 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2460,16 +2460,6 @@ HTMLInputElement::CreateEditor() return NS_ERROR_FAILURE; } -NS_IMETHODIMP_(Element*) -HTMLInputElement::GetRootEditorNode() -{ - nsTextEditorState* state = GetEditorState(); - if (state) { - return state->GetRootNode(); - } - return nullptr; -} - NS_IMETHODIMP_(Element*) HTMLInputElement::GetPlaceholderNode() { diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index dda075a81763..e741cb967b36 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -246,7 +246,6 @@ public: NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD CreateEditor() override; - NS_IMETHOD_(Element*) GetRootEditorNode() override; NS_IMETHOD_(Element*) GetPlaceholderNode() override; NS_IMETHOD_(Element*) GetPreviewNode() override; NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override; diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index 710bbcdbdf81..246cff6c91c0 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -261,12 +261,6 @@ HTMLTextAreaElement::CreateEditor() return mState.PrepareEditor(); } -NS_IMETHODIMP_(Element*) -HTMLTextAreaElement::GetRootEditorNode() -{ - return mState.GetRootNode(); -} - NS_IMETHODIMP_(Element*) HTMLTextAreaElement::GetPlaceholderNode() { diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h index e956439a3f24..442827bb46fd 100644 --- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -99,7 +99,6 @@ public: NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD CreateEditor() override; - NS_IMETHOD_(Element*) GetRootEditorNode() override; NS_IMETHOD_(Element*) GetPlaceholderNode() override; NS_IMETHOD_(Element*) GetPreviewNode() override; NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override; diff --git a/dom/html/nsITextControlElement.h b/dom/html/nsITextControlElement.h index e58ca83b0bb8..31b3cfe5d195 100644 --- a/dom/html/nsITextControlElement.h +++ b/dom/html/nsITextControlElement.h @@ -133,11 +133,6 @@ public: */ NS_IMETHOD CreateEditor() = 0; - /** - * Get the anonymous root node for the text control. - */ - NS_IMETHOD_(mozilla::dom::Element*) GetRootEditorNode() = 0; - /** * Get the placeholder anonymous node for the text control. */ diff --git a/layout/forms/nsTextControlFrame.cpp b/layout/forms/nsTextControlFrame.cpp index f09d12b85630..d2b694a00c03 100644 --- a/layout/forms/nsTextControlFrame.cpp +++ b/layout/forms/nsTextControlFrame.cpp @@ -927,37 +927,25 @@ nsTextControlFrame::ScrollSelectionIntoView() return NS_ERROR_FAILURE; } -nsresult -nsTextControlFrame::GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement) -{ - NS_ENSURE_ARG_POINTER(aRootElement); - - RefPtr textEditor = GetTextEditor(); - if (!textEditor) { - return NS_OK; - } - return textEditor->GetRootElement(aRootElement); -} - nsresult nsTextControlFrame::SelectAllOrCollapseToEndOfText(bool aSelect) { - nsCOMPtr rootElement; - nsresult rv = GetRootNodeAndInitializeEditor(getter_AddRefs(rootElement)); - NS_ENSURE_SUCCESS(rv, rv); + nsresult rv = EnsureEditorInitialized(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } - nsCOMPtr rootContent = do_QueryInterface(rootElement); nsCOMPtr rootNode; - rootNode = rootContent; + rootNode= mRootNode; - NS_ENSURE_TRUE(rootNode && rootContent, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); - int32_t numChildren = rootContent->GetChildCount(); + int32_t numChildren = mRootNode->GetChildCount(); if (numChildren > 0) { // We never want to place the selection after the last // br under the root node! - nsIContent *child = rootContent->GetLastChild(); + nsIContent *child = mRootNode->GetLastChild(); if (child) { if (child->IsHTMLElement(nsGkAtoms::br)) { child = child->GetPreviousSibling(); @@ -1047,11 +1035,12 @@ nsTextControlFrame::OffsetToDOMPoint(uint32_t aOffset, *aResult = nullptr; *aPosition = 0; - nsCOMPtr rootElement; - nsresult rv = GetRootNodeAndInitializeEditor(getter_AddRefs(rootElement)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr rootNode(do_QueryInterface(rootElement)); + nsresult rv = EnsureEditorInitialized(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + RefPtr rootNode = mRootNode; NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); nsCOMPtr nodeList = rootNode->ChildNodes(); @@ -1063,7 +1052,7 @@ nsTextControlFrame::OffsetToDOMPoint(uint32_t aOffset, Text* textNode = firstNode ? firstNode->GetAsText() : nullptr; if (length == 0) { - NS_IF_ADDREF(*aResult = rootNode); + rootNode.forget(aResult); *aPosition = 0; } else if (textNode) { uint32_t textLength = textNode->Length(); @@ -1352,13 +1341,10 @@ nsTextControlFrame::GetOwnedFrameSelection() UniquePtr nsTextControlFrame::SaveState() { - nsCOMPtr txtCtrl = do_QueryInterface(GetContent()); - NS_ASSERTION(txtCtrl, "Content not a text control element"); - - nsIContent* rootNode = txtCtrl->GetRootEditorNode(); - if (rootNode) { + if (mRootNode) { // Query the nsIStatefulFrame from the HTMLScrollFrame - nsIStatefulFrame* scrollStateFrame = do_QueryFrame(rootNode->GetPrimaryFrame()); + nsIStatefulFrame* scrollStateFrame = + do_QueryFrame(mRootNode->GetPrimaryFrame()); if (scrollStateFrame) { return scrollStateFrame->SaveState(); } @@ -1372,13 +1358,10 @@ nsTextControlFrame::RestoreState(PresState* aState) { NS_ENSURE_ARG_POINTER(aState); - nsCOMPtr txtCtrl = do_QueryInterface(GetContent()); - NS_ASSERTION(txtCtrl, "Content not a text control element"); - - nsIContent* rootNode = txtCtrl->GetRootEditorNode(); - if (rootNode) { + if (mRootNode) { // Query the nsIStatefulFrame from the HTMLScrollFrame - nsIStatefulFrame* scrollStateFrame = do_QueryFrame(rootNode->GetPrimaryFrame()); + nsIStatefulFrame* scrollStateFrame = + do_QueryFrame(mRootNode->GetPrimaryFrame()); if (scrollStateFrame) { return scrollStateFrame->RestoreState(aState); } diff --git a/layout/forms/nsTextControlFrame.h b/layout/forms/nsTextControlFrame.h index 636f29537d5b..0930fc3db74b 100644 --- a/layout/forms/nsTextControlFrame.h +++ b/layout/forms/nsTextControlFrame.h @@ -327,16 +327,6 @@ private: nsresult SetSelectionEndPoints(uint32_t aSelStart, uint32_t aSelEnd, SelectionDirection aDirection = eNone); - /** - * Return the root DOM element, and implicitly initialize the editor if - * needed. - * - * XXXbz This function is slow. Very slow. Consider using - * EnsureEditorInitialized() if you need that, and - * nsITextControlElement::GetRootEditorNode on our content if you need that. - */ - nsresult GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement); - void FinishedInitializer() { DeleteProperty(TextControlInitializer()); } From f82660f8317a57d2ba89c1a3d846a99d37816cc5 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 24 Apr 2018 02:17:17 -0400 Subject: [PATCH 43/44] Bug 1456169 part 2. Remove the now-unnecessary nsITextControlElement::GetPlaceholderNode. r=emilio MozReview-Commit-ID: FcVbeHEwkMz --- dom/html/HTMLInputElement.cpp | 10 ---------- dom/html/HTMLInputElement.h | 1 - dom/html/HTMLTextAreaElement.cpp | 6 ------ dom/html/HTMLTextAreaElement.h | 1 - dom/html/nsITextControlElement.h | 5 ----- dom/html/nsTextEditorState.cpp | 6 ------ dom/html/nsTextEditorState.h | 1 - layout/forms/nsTextControlFrame.cpp | 5 ++--- layout/forms/nsTextControlFrame.h | 4 ---- 9 files changed, 2 insertions(+), 37 deletions(-) diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 89479eac74cb..a45fd6f71d0c 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2460,16 +2460,6 @@ HTMLInputElement::CreateEditor() return NS_ERROR_FAILURE; } -NS_IMETHODIMP_(Element*) -HTMLInputElement::GetPlaceholderNode() -{ - nsTextEditorState* state = GetEditorState(); - if (state) { - return state->GetPlaceholderNode(); - } - return nullptr; -} - NS_IMETHODIMP_(void) HTMLInputElement::UpdateOverlayTextVisibility(bool aNotify) { diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index e741cb967b36..93f8698bb49e 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -246,7 +246,6 @@ public: NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD CreateEditor() override; - NS_IMETHOD_(Element*) GetPlaceholderNode() override; NS_IMETHOD_(Element*) GetPreviewNode() override; NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override; NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) override; diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index 246cff6c91c0..d4f6173814e7 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -261,12 +261,6 @@ HTMLTextAreaElement::CreateEditor() return mState.PrepareEditor(); } -NS_IMETHODIMP_(Element*) -HTMLTextAreaElement::GetPlaceholderNode() -{ - return mState.GetPlaceholderNode(); -} - NS_IMETHODIMP_(void) HTMLTextAreaElement::UpdateOverlayTextVisibility(bool aNotify) { diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h index 442827bb46fd..0e797ad624fb 100644 --- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -99,7 +99,6 @@ public: NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD CreateEditor() override; - NS_IMETHOD_(Element*) GetPlaceholderNode() override; NS_IMETHOD_(Element*) GetPreviewNode() override; NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override; NS_IMETHOD_(bool) GetPlaceholderVisibility() override; diff --git a/dom/html/nsITextControlElement.h b/dom/html/nsITextControlElement.h index 31b3cfe5d195..90540a125ecd 100644 --- a/dom/html/nsITextControlElement.h +++ b/dom/html/nsITextControlElement.h @@ -133,11 +133,6 @@ public: */ NS_IMETHOD CreateEditor() = 0; - /** - * Get the placeholder anonymous node for the text control. - */ - NS_IMETHOD_(mozilla::dom::Element*) GetPlaceholderNode() = 0; - /** * Get the preview anonymous node for the text control. */ diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp index 3d433b3b7284..bec398ba0db3 100644 --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -1107,12 +1107,6 @@ nsTextEditorState::GetRootNode() return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr; } -Element* -nsTextEditorState::GetPlaceholderNode() -{ - return mBoundFrame ? mBoundFrame->GetPlaceholderNode() : nullptr; -} - Element* nsTextEditorState::GetPreviewNode() { diff --git a/dom/html/nsTextEditorState.h b/dom/html/nsTextEditorState.h index 12b37aa1aad2..9c4a713cd8df 100644 --- a/dom/html/nsTextEditorState.h +++ b/dom/html/nsTextEditorState.h @@ -199,7 +199,6 @@ public: bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; } mozilla::dom::Element* GetRootNode(); - mozilla::dom::Element* GetPlaceholderNode(); mozilla::dom::Element* GetPreviewNode(); bool IsSingleLineTextControl() const { diff --git a/layout/forms/nsTextControlFrame.cpp b/layout/forms/nsTextControlFrame.cpp index d2b694a00c03..37522de066a8 100644 --- a/layout/forms/nsTextControlFrame.cpp +++ b/layout/forms/nsTextControlFrame.cpp @@ -1407,7 +1407,7 @@ nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, while (kid) { // If the frame is the placeholder or preview frame, we should only show // it if it has to be visible. - if (!((kid->GetContent() == txtCtrl->GetPlaceholderNode() && + if (!((kid->GetContent() == mPlaceholderDiv && !txtCtrl->GetPlaceholderVisibility()) || (kid->GetContent() == txtCtrl->GetPreviewNode() && !txtCtrl->GetPreviewVisibility()))) { @@ -1421,8 +1421,7 @@ mozilla::dom::Element* nsTextControlFrame::GetPseudoElement(CSSPseudoElementType aType) { if (aType == CSSPseudoElementType::placeholder) { - nsCOMPtr txtCtrl = do_QueryInterface(GetContent()); - return txtCtrl->GetPlaceholderNode(); + return mPlaceholderDiv; } return nsContainerFrame::GetPseudoElement(aType); diff --git a/layout/forms/nsTextControlFrame.h b/layout/forms/nsTextControlFrame.h index 0930fc3db74b..478b126916f3 100644 --- a/layout/forms/nsTextControlFrame.h +++ b/layout/forms/nsTextControlFrame.h @@ -193,10 +193,6 @@ public: //for methods who access nsTextControlFrame directly return mRootNode; } - mozilla::dom::Element* GetPlaceholderNode() const { - return mPlaceholderDiv; - } - mozilla::dom::Element* GetPreviewNode() const { return mPreviewDiv; } From 36ff5f731bbf7c904f5d39437f8d42c75bdc0cd9 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 24 Apr 2018 02:17:18 -0400 Subject: [PATCH 44/44] Bug 1456169 part 3. Remove the now-unnecessary nsITextControlElement::GetPreviewNode. r=emilio MozReview-Commit-ID: 3goF5VXvQap --- dom/html/HTMLInputElement.cpp | 10 ---------- dom/html/HTMLInputElement.h | 1 - dom/html/HTMLTextAreaElement.cpp | 6 ------ dom/html/HTMLTextAreaElement.h | 1 - dom/html/nsITextControlElement.h | 5 ----- layout/forms/nsTextControlFrame.cpp | 2 +- 6 files changed, 1 insertion(+), 24 deletions(-) diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index a45fd6f71d0c..941dc76861eb 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2480,16 +2480,6 @@ HTMLInputElement::GetPlaceholderVisibility() return state->GetPlaceholderVisibility(); } -NS_IMETHODIMP_(Element*) -HTMLInputElement::GetPreviewNode() -{ - nsTextEditorState* state = GetEditorState(); - if (state) { - return state->GetPreviewNode(); - } - return nullptr; -} - NS_IMETHODIMP_(void) HTMLInputElement::SetPreviewValue(const nsAString& aValue) { diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 93f8698bb49e..5e2152423118 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -246,7 +246,6 @@ public: NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD CreateEditor() override; - NS_IMETHOD_(Element*) GetPreviewNode() override; NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override; NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) override; NS_IMETHOD_(void) GetPreviewValue(nsAString& aValue) override; diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index d4f6173814e7..3ae898e0d9ee 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -273,12 +273,6 @@ HTMLTextAreaElement::GetPlaceholderVisibility() return mState.GetPlaceholderVisibility(); } -NS_IMETHODIMP_(Element*) -HTMLTextAreaElement::GetPreviewNode() -{ - return mState.GetPreviewNode(); -} - NS_IMETHODIMP_(void) HTMLTextAreaElement::SetPreviewValue(const nsAString& aValue) { diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h index 0e797ad624fb..395caaccbe0f 100644 --- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -99,7 +99,6 @@ public: NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD CreateEditor() override; - NS_IMETHOD_(Element*) GetPreviewNode() override; NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override; NS_IMETHOD_(bool) GetPlaceholderVisibility() override; NS_IMETHOD_(bool) GetPreviewVisibility() override; diff --git a/dom/html/nsITextControlElement.h b/dom/html/nsITextControlElement.h index 90540a125ecd..baaffc199e84 100644 --- a/dom/html/nsITextControlElement.h +++ b/dom/html/nsITextControlElement.h @@ -133,11 +133,6 @@ public: */ NS_IMETHOD CreateEditor() = 0; - /** - * Get the preview anonymous node for the text control. - */ - NS_IMETHOD_(mozilla::dom::Element*) GetPreviewNode() = 0; - /** * Update preview value for the text control. */ diff --git a/layout/forms/nsTextControlFrame.cpp b/layout/forms/nsTextControlFrame.cpp index 37522de066a8..1027821b48ad 100644 --- a/layout/forms/nsTextControlFrame.cpp +++ b/layout/forms/nsTextControlFrame.cpp @@ -1409,7 +1409,7 @@ nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, // it if it has to be visible. if (!((kid->GetContent() == mPlaceholderDiv && !txtCtrl->GetPlaceholderVisibility()) || - (kid->GetContent() == txtCtrl->GetPreviewNode() && + (kid->GetContent() == mPreviewDiv && !txtCtrl->GetPreviewVisibility()))) { BuildDisplayListForChild(aBuilder, kid, set, 0); }