Bug 1978217 - Don't share sheets using relative URIs when it's not safe to do so. r=firefox-style-system-reviewers,layout-reviewers,dshin a=dsmith
Track whether an inline sheet might have uris that depend on the base, and avoid caching them if appropriate. Differential Revision: https://phabricator.services.mozilla.com/D258292
This commit is contained in:
committed by
dsmith@mozilla.com
parent
7a19b92511
commit
41c3fd6f74
@@ -1648,23 +1648,72 @@ void Loader::MarkLoadTreeFailed(SheetLoadData& aLoadData,
|
|||||||
} while (data);
|
} while (data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool URIsEqual(nsIURI* aA, nsIURI* aB) {
|
||||||
|
if (aA == aB) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!aA || !aB) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool equal = false;
|
||||||
|
return NS_SUCCEEDED(aA->Equals(aB, &equal)) && equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The intention is that this would return true for inputs like
|
||||||
|
// (https://example.com/a, https://example.com/b)
|
||||||
|
// but not for:
|
||||||
|
// (https://example.com/a, https://example.com/b/c)
|
||||||
|
// where "regular" relative URIs would resolve differently.
|
||||||
|
static bool BaseURIsArePathCompatible(nsIURI* aA, nsIURI* aB) {
|
||||||
|
if (!aA || !aB) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
constexpr auto kDummyPath = "foo.css"_ns;
|
||||||
|
nsAutoCString resultA;
|
||||||
|
nsAutoCString resultB;
|
||||||
|
aA->Resolve(kDummyPath, resultA);
|
||||||
|
aB->Resolve(kDummyPath, resultB);
|
||||||
|
return resultA == resultB;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool CanReuseInlineSheet(StyleSheet* aSheet, nsIURI* aNewBaseURI) {
|
||||||
|
nsIURI* oldBase = aSheet->GetBaseURI();
|
||||||
|
if (URIsEqual(oldBase, aNewBaseURI)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (aSheet->OriginalContentsBaseUriDependency()) {
|
||||||
|
case StyleLikelyBaseUriDependency::No:
|
||||||
|
break;
|
||||||
|
case StyleLikelyBaseUriDependency::Path:
|
||||||
|
if (BaseURIsArePathCompatible(oldBase, aNewBaseURI)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
case StyleLikelyBaseUriDependency::Full:
|
||||||
|
LOG((" Can't reuse due to base URI dependency"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<StyleSheet> Loader::LookupInlineSheetInCache(
|
RefPtr<StyleSheet> Loader::LookupInlineSheetInCache(
|
||||||
const nsAString& aBuffer, nsIPrincipal* aSheetPrincipal) {
|
const nsAString& aBuffer, nsIPrincipal* aSheetPrincipal, nsIURI* aBaseURI) {
|
||||||
MOZ_ASSERT(mSheets, "Document associated loader should have sheet cache");
|
MOZ_ASSERT(mSheets, "Document associated loader should have sheet cache");
|
||||||
auto result = mSheets->LookupInline(LoaderPrincipal(), aBuffer);
|
auto result = mSheets->LookupInline(LoaderPrincipal(), aBuffer);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
StyleSheet* sheet = result.Data();
|
StyleSheet* sheet = result.Data();
|
||||||
MOZ_ASSERT(!sheet->HasModifiedRules(),
|
MOZ_ASSERT(!sheet->HasModifiedRules(),
|
||||||
"How did we end up with a dirty sheet?");
|
"How did we end up with a dirty sheet?");
|
||||||
|
|
||||||
if (NS_WARN_IF(!sheet->Principal()->Equals(aSheetPrincipal))) {
|
if (NS_WARN_IF(!sheet->Principal()->Equals(aSheetPrincipal))) {
|
||||||
// If the sheet is going to have different access rights, don't return it
|
// If the sheet is going to have different access rights, don't return it
|
||||||
// from the cache. XXX can this happen now that we eagerly clone?
|
// from the cache. XXX can this happen now that we eagerly clone?
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
if (!CanReuseInlineSheet(sheet, aBaseURI)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
return sheet->Clone(nullptr, nullptr);
|
return sheet->Clone(nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1732,7 +1781,8 @@ Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
|
|||||||
return LoaderPrincipal();
|
return LoaderPrincipal();
|
||||||
}();
|
}();
|
||||||
|
|
||||||
RefPtr<StyleSheet> sheet = LookupInlineSheetInCache(aBuffer, sheetPrincipal);
|
RefPtr<StyleSheet> sheet =
|
||||||
|
LookupInlineSheetInCache(aBuffer, sheetPrincipal, baseURI);
|
||||||
const bool isSheetFromCache = !!sheet;
|
const bool isSheetFromCache = !!sheet;
|
||||||
if (!isSheetFromCache) {
|
if (!isSheetFromCache) {
|
||||||
sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
|
sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
|
||||||
|
|||||||
@@ -567,7 +567,8 @@ class Loader final {
|
|||||||
CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity,
|
CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity,
|
||||||
uint64_t aEarlyHintPreloaderId, dom::FetchPriority aFetchPriority);
|
uint64_t aEarlyHintPreloaderId, dom::FetchPriority aFetchPriority);
|
||||||
|
|
||||||
RefPtr<StyleSheet> LookupInlineSheetInCache(const nsAString&, nsIPrincipal*);
|
RefPtr<StyleSheet> LookupInlineSheetInCache(const nsAString&, nsIPrincipal*,
|
||||||
|
nsIURI* aBaseURI);
|
||||||
|
|
||||||
// Synchronously notify of a cached load data.
|
// Synchronously notify of a cached load data.
|
||||||
void NotifyOfCachedLoad(RefPtr<SheetLoadData>);
|
void NotifyOfCachedLoad(RefPtr<SheetLoadData>);
|
||||||
|
|||||||
@@ -1254,6 +1254,20 @@ void StyleSheet::FinishAsyncParse(
|
|||||||
UnblockParsePromise();
|
UnblockParsePromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyleLikelyBaseUriDependency StyleSheet::OriginalContentsBaseUriDependency()
|
||||||
|
const {
|
||||||
|
const auto* counters = UseCounters();
|
||||||
|
if (Servo_IsCustomUseCounterRecorded(
|
||||||
|
counters, StyleCustomUseCounter::MaybeHasFullBaseUriDependency)) {
|
||||||
|
return StyleLikelyBaseUriDependency::Full;
|
||||||
|
}
|
||||||
|
if (Servo_IsCustomUseCounterRecorded(
|
||||||
|
counters, StyleCustomUseCounter::MaybeHasPathBaseUriDependency)) {
|
||||||
|
return StyleLikelyBaseUriDependency::Path;
|
||||||
|
}
|
||||||
|
return StyleLikelyBaseUriDependency::No;
|
||||||
|
}
|
||||||
|
|
||||||
const StyleUseCounters* StyleSheet::UseCounters() const {
|
const StyleUseCounters* StyleSheet::UseCounters() const {
|
||||||
return Servo_StyleSheet_UseCounters(RawContents());
|
return Servo_StyleSheet_UseCounters(RawContents());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ using StyleSheetParsePromise = MozPromise</* Dummy */ bool,
|
|||||||
/* IsExclusive = */ true>;
|
/* IsExclusive = */ true>;
|
||||||
|
|
||||||
enum class StyleRuleChangeKind : uint32_t;
|
enum class StyleRuleChangeKind : uint32_t;
|
||||||
|
enum class StyleLikelyBaseUriDependency : uint8_t;
|
||||||
|
|
||||||
struct StyleRuleChange {
|
struct StyleRuleChange {
|
||||||
StyleRuleChange() = delete;
|
StyleRuleChange() = delete;
|
||||||
@@ -159,6 +160,9 @@ class StyleSheet final : public nsICSSLoaderObserver, public nsWrapperCache {
|
|||||||
const StyleUseCounters* UseCounters() const;
|
const StyleUseCounters* UseCounters() const;
|
||||||
void PropagateUseCountersTo(dom::Document*) const;
|
void PropagateUseCountersTo(dom::Document*) const;
|
||||||
|
|
||||||
|
// Whether our original contents may be using relative URIs.
|
||||||
|
StyleLikelyBaseUriDependency OriginalContentsBaseUriDependency() const;
|
||||||
|
|
||||||
URLExtraData* URLData() const { return Inner().mURLData; }
|
URLExtraData* URLData() const { return Inner().mURLData; }
|
||||||
|
|
||||||
// nsICSSLoaderObserver interface
|
// nsICSSLoaderObserver interface
|
||||||
|
|||||||
@@ -59,6 +59,52 @@ impl PartialEq for CssUrlData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How an URI might depend on our base URI.
|
||||||
|
///
|
||||||
|
/// TODO(emilio): See if necko can provide this, but for our case local refs or empty URIs are
|
||||||
|
/// totally fine even though they wouldn't be in general...
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum LikelyBaseUriDependency {
|
||||||
|
/// No dependency on the base URI (either absolute uri, or relative URI).
|
||||||
|
No,
|
||||||
|
/// We might depend on our path depth. E.g. `https://example.com/foo` and
|
||||||
|
/// `https://example.com/bar` both resolve a relative URI like `baz.css` as
|
||||||
|
/// `https://example.com/baz.css`.
|
||||||
|
Path,
|
||||||
|
/// We might depend on our full URI. This is the case for query strings (and, in the general
|
||||||
|
/// case, local refs and empty URIs, but that's not the case for CSS as described below.
|
||||||
|
Full,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn likely_base_uri_dependency(specified: &str) -> LikelyBaseUriDependency {
|
||||||
|
if specified.is_empty() {
|
||||||
|
// In CSS the empty URL is special / invalid.
|
||||||
|
// https://drafts.csswg.org/css-values-4/#url-empty
|
||||||
|
return LikelyBaseUriDependency::No;
|
||||||
|
}
|
||||||
|
if specified.starts_with('#') || specified.starts_with('/') {
|
||||||
|
// Local refs and absolute paths are fair game.
|
||||||
|
return LikelyBaseUriDependency::No;
|
||||||
|
}
|
||||||
|
const COMMON_PROTOCOLS: [&str; 3] = [
|
||||||
|
"http:",
|
||||||
|
"https:",
|
||||||
|
"data:",
|
||||||
|
];
|
||||||
|
for protocol in COMMON_PROTOCOLS {
|
||||||
|
if specified.starts_with(protocol) {
|
||||||
|
// Common absolute URIs.
|
||||||
|
return LikelyBaseUriDependency::No;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if specified.starts_with('?') {
|
||||||
|
// Query string resolves differently for any two different base URIs
|
||||||
|
return LikelyBaseUriDependency::Full;
|
||||||
|
}
|
||||||
|
// Might be a relative URI, play it safe.
|
||||||
|
LikelyBaseUriDependency::Path
|
||||||
|
}
|
||||||
|
|
||||||
impl CssUrl {
|
impl CssUrl {
|
||||||
/// Parse a URL with a particular CORS mode.
|
/// Parse a URL with a particular CORS mode.
|
||||||
pub fn parse_with_cors_mode<'i, 't>(
|
pub fn parse_with_cors_mode<'i, 't>(
|
||||||
@@ -76,6 +122,21 @@ impl CssUrl {
|
|||||||
|
|
||||||
/// Parse a URL from a string value that is a valid CSS token for a URL.
|
/// Parse a URL from a string value that is a valid CSS token for a URL.
|
||||||
pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self {
|
pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self {
|
||||||
|
use crate::use_counters::CustomUseCounter;
|
||||||
|
if let Some(counters) = context.use_counters {
|
||||||
|
if !counters.custom.recorded(CustomUseCounter::MaybeHasFullBaseUriDependency) {
|
||||||
|
match likely_base_uri_dependency(&url) {
|
||||||
|
LikelyBaseUriDependency::No => {},
|
||||||
|
LikelyBaseUriDependency::Path => {
|
||||||
|
counters.custom.record(CustomUseCounter::MaybeHasPathBaseUriDependency);
|
||||||
|
},
|
||||||
|
LikelyBaseUriDependency::Full => {
|
||||||
|
counters.custom.record(CustomUseCounter::MaybeHasPathBaseUriDependency);
|
||||||
|
counters.custom.record(CustomUseCounter::MaybeHasFullBaseUriDependency);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
CssUrl(Arc::new(CssUrlData {
|
CssUrl(Arc::new(CssUrlData {
|
||||||
serialization: url.into(),
|
serialization: url.into(),
|
||||||
extra_data: context.url_data.clone(),
|
extra_data: context.url_data.clone(),
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ pub struct NonCustomPropertyUseCounters {
|
|||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum CustomUseCounter {
|
pub enum CustomUseCounter {
|
||||||
|
/// Whether we are likely to be using relative URIs that depend on our path depth.
|
||||||
|
MaybeHasPathBaseUriDependency = 0,
|
||||||
|
/// Whether we are likely to be using relative URIs that depend on our full URI.
|
||||||
|
MaybeHasFullBaseUriDependency,
|
||||||
/// Dummy value, used for indexing purposes.
|
/// Dummy value, used for indexing purposes.
|
||||||
Last,
|
Last,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ include = [
|
|||||||
"Overflow",
|
"Overflow",
|
||||||
"LengthPercentage",
|
"LengthPercentage",
|
||||||
"LetterSpacing",
|
"LetterSpacing",
|
||||||
|
"LikelyBaseUriDependency",
|
||||||
"NonNegativeLengthPercentage",
|
"NonNegativeLengthPercentage",
|
||||||
"LengthPercentageOrAuto",
|
"LengthPercentageOrAuto",
|
||||||
"LineHeight",
|
"LineHeight",
|
||||||
|
|||||||
Reference in New Issue
Block a user