tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

LinkStyle.cpp (13355B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /*
      8 * A base class which implements nsIStyleSheetLinkingElement and can
      9 * be subclassed by various content nodes that want to load
     10 * stylesheets (<style>, <link>, processing instructions, etc).
     11 */
     12 
     13 #include "mozilla/dom/LinkStyle.h"
     14 
     15 #include "mozilla/Preferences.h"
     16 #include "mozilla/StaticPrefs_dom.h"
     17 #include "mozilla/StyleSheet.h"
     18 #include "mozilla/StyleSheetInlines.h"
     19 #include "mozilla/css/Loader.h"
     20 #include "mozilla/dom/Document.h"
     21 #include "mozilla/dom/Element.h"
     22 #include "mozilla/dom/FragmentOrElement.h"
     23 #include "mozilla/dom/HTMLLinkElement.h"
     24 #include "mozilla/dom/HTMLStyleElement.h"
     25 #include "mozilla/dom/SRILogHelper.h"
     26 #include "mozilla/dom/SVGStyleElement.h"
     27 #include "mozilla/dom/ShadowRoot.h"
     28 #include "nsCRT.h"
     29 #include "nsContentUtils.h"
     30 #include "nsIContent.h"
     31 #include "nsQueryObject.h"
     32 #include "nsStyleUtil.h"
     33 #include "nsUnicharInputStream.h"
     34 #include "nsUnicharUtils.h"
     35 #include "nsXPCOMCIDInternal.h"
     36 
     37 namespace mozilla::dom {
     38 
     39 LinkStyle::SheetInfo::SheetInfo(
     40    const Document& aDocument, nsIContent* aContent,
     41    already_AddRefed<nsIURI> aURI,
     42    already_AddRefed<nsIPrincipal> aTriggeringPrincipal,
     43    already_AddRefed<nsIReferrerInfo> aReferrerInfo,
     44    mozilla::CORSMode aCORSMode, const nsAString& aTitle,
     45    const nsAString& aMedia, const nsAString& aIntegrity,
     46    const nsAString& aNonce, HasAlternateRel aHasAlternateRel,
     47    IsInline aIsInline, IsExplicitlyEnabled aIsExplicitlyEnabled,
     48    FetchPriority aFetchPriority)
     49    : mContent(aContent),
     50      mURI(aURI),
     51      mTriggeringPrincipal(aTriggeringPrincipal),
     52      mReferrerInfo(aReferrerInfo),
     53      mCORSMode(aCORSMode),
     54      mTitle(aTitle),
     55      mMedia(aMedia),
     56      mIntegrity(aIntegrity),
     57      mNonce(aNonce),
     58      mFetchPriority(aFetchPriority),
     59      mHasAlternateRel(aHasAlternateRel == HasAlternateRel::Yes),
     60      mIsInline(aIsInline == IsInline::Yes),
     61      mIsExplicitlyEnabled(aIsExplicitlyEnabled) {
     62  MOZ_ASSERT(!mIsInline || aContent);
     63  MOZ_ASSERT_IF(aContent, aContent->OwnerDoc() == &aDocument);
     64  MOZ_ASSERT(mReferrerInfo);
     65  MOZ_ASSERT(mIntegrity.IsEmpty() || !mIsInline,
     66             "Integrity only applies to <link>");
     67 }
     68 
     69 LinkStyle::SheetInfo::~SheetInfo() = default;
     70 LinkStyle::LinkStyle() = default;
     71 
     72 LinkStyle::~LinkStyle() { LinkStyle::SetStyleSheet(nullptr); }
     73 
     74 StyleSheet* LinkStyle::GetSheetForBindings() const {
     75  if (mStyleSheet && mStyleSheet->IsComplete()) {
     76    return mStyleSheet;
     77  }
     78  return nullptr;
     79 }
     80 
     81 void LinkStyle::GetTitleAndMediaForElement(const Element& aSelf,
     82                                           nsString& aTitle, nsString& aMedia) {
     83  // Only honor title as stylesheet name for elements in the document (that is,
     84  // ignore for Shadow DOM), per [1] and [2]. See [3].
     85  //
     86  // [1]: https://html.spec.whatwg.org/#attr-link-title
     87  // [2]: https://html.spec.whatwg.org/#attr-style-title
     88  // [3]: https://github.com/w3c/webcomponents/issues/535
     89  if (aSelf.IsInUncomposedDoc()) {
     90    aSelf.GetAttr(nsGkAtoms::title, aTitle);
     91    aTitle.CompressWhitespace();
     92  }
     93 
     94  aSelf.GetAttr(nsGkAtoms::media, aMedia);
     95  // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies
     96  // that media queries should be ASCII lowercased during serialization.
     97  //
     98  // FIXME(emilio): How does it matter? This is going to be parsed anyway, CSS
     99  // should take care of serializing it properly.
    100  nsContentUtils::ASCIIToLower(aMedia);
    101 }
    102 
    103 bool LinkStyle::IsCSSMimeTypeAttributeForStyleElement(const Element& aSelf) {
    104  // Per
    105  // https://html.spec.whatwg.org/multipage/semantics.html#the-style-element:update-a-style-block
    106  // step 4, for style elements we should only accept empty and "text/css" type
    107  // attribute values.
    108  nsAutoString type;
    109  aSelf.GetAttr(nsGkAtoms::type, type);
    110  return type.IsEmpty() || type.LowerCaseEqualsLiteral("text/css");
    111 }
    112 
    113 void LinkStyle::Unlink() { LinkStyle::SetStyleSheet(nullptr); }
    114 
    115 void LinkStyle::Traverse(nsCycleCollectionTraversalCallback& cb) {
    116  LinkStyle* tmp = this;
    117  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet);
    118 }
    119 
    120 void LinkStyle::SetStyleSheet(StyleSheet* aStyleSheet) {
    121  if (mStyleSheet) {
    122    mStyleSheet->SetOwningNode(nullptr);
    123  }
    124 
    125  mStyleSheet = aStyleSheet;
    126  if (mStyleSheet) {
    127    mStyleSheet->SetOwningNode(&AsContent());
    128  }
    129 }
    130 
    131 void LinkStyle::GetCharset(nsAString& aCharset) { aCharset.Truncate(); }
    132 
    133 static uint32_t ToLinkMask(const nsAString& aLink) {
    134  // Keep this in sync with sSupportedRelValues in HTMLLinkElement.cpp
    135  uint32_t mask = 0;
    136  if (aLink.EqualsLiteral("prefetch")) {
    137    mask = LinkStyle::ePREFETCH;
    138  } else if (aLink.EqualsLiteral("dns-prefetch")) {
    139    mask = LinkStyle::eDNS_PREFETCH;
    140  } else if (aLink.EqualsLiteral("stylesheet")) {
    141    mask = LinkStyle::eSTYLESHEET;
    142  } else if (aLink.EqualsLiteral("next")) {
    143    mask = LinkStyle::eNEXT;
    144  } else if (aLink.EqualsLiteral("alternate")) {
    145    mask = LinkStyle::eALTERNATE;
    146  } else if (aLink.EqualsLiteral("preconnect")) {
    147    mask = LinkStyle::ePRECONNECT;
    148  } else if (aLink.EqualsLiteral("preload")) {
    149    mask = LinkStyle::ePRELOAD;
    150  } else if (aLink.EqualsLiteral("modulepreload")) {
    151    mask = LinkStyle::eMODULE_PRELOAD;
    152  } else if (aLink.EqualsLiteral("compression-dictionary")) {
    153    mask = LinkStyle::eCOMPRESSION_DICTIONARY;
    154  }
    155 
    156  return mask;
    157 }
    158 
    159 uint32_t LinkStyle::ParseLinkTypes(const nsAString& aTypes) {
    160  uint32_t linkMask = 0;
    161  nsAString::const_iterator start, done;
    162  aTypes.BeginReading(start);
    163  aTypes.EndReading(done);
    164  if (start == done) return linkMask;
    165 
    166  nsAString::const_iterator current(start);
    167  bool inString = !nsContentUtils::IsHTMLWhitespace(*current);
    168  nsAutoString subString;
    169 
    170  while (current != done) {
    171    if (nsContentUtils::IsHTMLWhitespace(*current)) {
    172      if (inString) {
    173        nsContentUtils::ASCIIToLower(Substring(start, current), subString);
    174        linkMask |= ToLinkMask(subString);
    175        inString = false;
    176      }
    177    } else {
    178      if (!inString) {
    179        start = current;
    180        inString = true;
    181      }
    182    }
    183    ++current;
    184  }
    185  if (inString) {
    186    nsContentUtils::ASCIIToLower(Substring(start, current), subString);
    187    linkMask |= ToLinkMask(subString);
    188  }
    189  return linkMask;
    190 }
    191 
    192 Result<LinkStyle::Update, nsresult> LinkStyle::UpdateStyleSheetInternal(
    193    Document* aOldDocument, ShadowRoot* aOldShadowRoot,
    194    ForceUpdate aForceUpdate) {
    195  return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr,
    196                            aForceUpdate);
    197 }
    198 
    199 LinkStyle* LinkStyle::FromNode(Element& aElement) {
    200  nsAtom* name = aElement.NodeInfo()->NameAtom();
    201  if (name == nsGkAtoms::link) {
    202    MOZ_ASSERT(aElement.IsHTMLElement() == !!aElement.AsLinkStyle());
    203    return aElement.IsHTMLElement() ? static_cast<HTMLLinkElement*>(&aElement)
    204                                    : nullptr;
    205  }
    206  if (name == nsGkAtoms::style) {
    207    if (aElement.IsHTMLElement()) {
    208      MOZ_ASSERT(aElement.AsLinkStyle());
    209      return static_cast<HTMLStyleElement*>(&aElement);
    210    }
    211    if (aElement.IsSVGElement()) {
    212      MOZ_ASSERT(aElement.AsLinkStyle());
    213      return static_cast<SVGStyleElement*>(&aElement);
    214    }
    215  }
    216  MOZ_ASSERT(!aElement.AsLinkStyle());
    217  return nullptr;
    218 }
    219 
    220 void LinkStyle::BindToTree() {
    221  if (mUpdatesEnabled) {
    222    nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
    223        "LinkStyle::BindToTree",
    224        [this, pin = RefPtr{&AsContent()}] { UpdateStyleSheetInternal(); }));
    225  }
    226 }
    227 
    228 Result<LinkStyle::Update, nsresult> LinkStyle::DoUpdateStyleSheet(
    229    Document* aOldDocument, ShadowRoot* aOldShadowRoot,
    230    nsICSSLoaderObserver* aObserver, ForceUpdate aForceUpdate) {
    231  nsIContent& thisContent = AsContent();
    232  if (thisContent.IsInSVGUseShadowTree()) {
    233    // Stylesheets in <use>-cloned subtrees are disabled until we figure out
    234    // how they should behave.
    235    return Update{};
    236  }
    237 
    238  if (mStyleSheet && (aOldDocument || aOldShadowRoot)) {
    239    MOZ_ASSERT(!(aOldDocument && aOldShadowRoot),
    240               "ShadowRoot content is never in document, thus "
    241               "there should not be a old document and old "
    242               "ShadowRoot simultaneously.");
    243 
    244    // We're removing the link element from the document or shadow tree, unload
    245    // the stylesheet.
    246    //
    247    // We want to do this even if updates are disabled, since otherwise a sheet
    248    // with a stale linking element pointer will be hanging around -- not good!
    249    if (mStyleSheet->IsComplete()) {
    250      if (aOldShadowRoot) {
    251        aOldShadowRoot->RemoveStyleSheet(*mStyleSheet);
    252      } else {
    253        aOldDocument->RemoveStyleSheet(*mStyleSheet);
    254      }
    255    }
    256 
    257    SetStyleSheet(nullptr);
    258  }
    259 
    260  Document* doc = thisContent.GetComposedDoc();
    261 
    262  // Loader could be null during unlink, see bug 1425866.
    263  // ... No need to update if updating is disabled, as well.
    264  if (!doc || !doc->EnsureCSSLoader().GetEnabled() || !mUpdatesEnabled) {
    265    return Update{};
    266  }
    267 
    268  // When static documents are created, we need to finish up cloning
    269  // the stylesheet (See documentation for MaybeFinishCopyStyleSheet).
    270  if (doc->IsStaticDocument()) {
    271    MaybeFinishCopyStyleSheet(doc);
    272    return Update{};
    273  }
    274 
    275  Maybe<SheetInfo> info = GetStyleSheetInfo();
    276  if (aForceUpdate == ForceUpdate::No && mStyleSheet && info &&
    277      !info->mIsInline && info->mURI) {
    278    if (nsIURI* oldURI = mStyleSheet->GetOriginalURI()) {
    279      bool equal;
    280      nsresult rv = oldURI->Equals(info->mURI, &equal);
    281      if (NS_SUCCEEDED(rv) && equal) {
    282        return Update{};
    283      }
    284    }
    285  }
    286 
    287  if (mStyleSheet) {
    288    if (mStyleSheet->IsComplete()) {
    289      if (thisContent.IsInShadowTree()) {
    290        ShadowRoot* containingShadow = thisContent.GetContainingShadow();
    291        // Could be null only during unlink.
    292        if (MOZ_LIKELY(containingShadow)) {
    293          containingShadow->RemoveStyleSheet(*mStyleSheet);
    294        }
    295      } else {
    296        doc->RemoveStyleSheet(*mStyleSheet);
    297      }
    298    }
    299 
    300    SetStyleSheet(nullptr);
    301  }
    302 
    303  if (!info) {
    304    return Update{};
    305  }
    306 
    307  if (!info->mURI && !info->mIsInline) {
    308    // If href is empty and this is not inline style then just bail
    309    return Update{};
    310  }
    311 
    312  if (info->mIsInline) {
    313    nsAutoString text;
    314    if (!nsContentUtils::GetNodeTextContent(&thisContent, false, text,
    315                                            fallible)) {
    316      return Err(NS_ERROR_OUT_OF_MEMORY);
    317    }
    318 
    319    MOZ_ASSERT(thisContent.NodeInfo()->NameAtom() != nsGkAtoms::link,
    320               "<link> is not 'inline', and needs different CSP checks");
    321    MOZ_ASSERT(thisContent.IsElement());
    322    nsresult rv = NS_OK;
    323    if (!nsStyleUtil::CSPAllowsInlineStyle(
    324            thisContent.AsElement(), doc, info->mTriggeringPrincipal,
    325            mLineNumber, mColumnNumber, text, &rv)) {
    326      if (NS_FAILED(rv)) {
    327        return Err(rv);
    328      }
    329      return Update{};
    330    }
    331 
    332    // Parse the style sheet.
    333    return doc->EnsureCSSLoader().LoadInlineStyle(*info, text, aObserver);
    334  }
    335  if (thisContent.IsElement()) {
    336    nsAutoString integrity;
    337    thisContent.AsElement()->GetAttr(nsGkAtoms::integrity, integrity);
    338    if (!integrity.IsEmpty()) {
    339      MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
    340              ("LinkStyle::DoUpdateStyleSheet, integrity=%s",
    341               NS_ConvertUTF16toUTF8(integrity).get()));
    342    }
    343  }
    344  auto resultOrError = doc->EnsureCSSLoader().LoadStyleLink(*info, aObserver);
    345  if (resultOrError.isErr()) {
    346    // Don't propagate LoadStyleLink() errors further than this, since some
    347    // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
    348    // things like a stylesheet load being blocked by the security system.
    349    return Update{};
    350  }
    351  return resultOrError;
    352 }
    353 
    354 void LinkStyle::MaybeStartCopyStyleSheetTo(LinkStyle* aDest,
    355                                           Document* aDoc) const {
    356  MOZ_ASSERT(aDoc, "Copying to null Document?");
    357  if (!aDoc->IsStaticDocument() || !mStyleSheet ||
    358      !mStyleSheet->IsApplicable()) {
    359    return;
    360  }
    361 
    362  // We don't yet if know we're in shadow root, so the only thing we can do is
    363  // to keep an incomplete clone. Namely, the sheet does not have knowledge of
    364  // its owning node and which document or shadow root it belongs to.
    365  aDest->mStyleSheet = mStyleSheet->Clone(nullptr, nullptr);
    366 }
    367 
    368 void LinkStyle::MaybeFinishCopyStyleSheet(Document* aDocument) {
    369  if (!mStyleSheet) {
    370    return;
    371  }
    372  auto& thisContent = AsContent();
    373  // Are we in the holdover copy state?
    374  if (mStyleSheet->GetOwnerNode() == &thisContent) {
    375    return;
    376  }
    377  MOZ_ASSERT(aDocument->IsStaticDocument(),
    378             "Copying stylesheet over into a non-static document?");
    379 
    380  DocumentOrShadowRoot* root = aDocument;
    381  auto* shadowRoot = thisContent.GetContainingShadow();
    382  if (shadowRoot) {
    383    root = shadowRoot;
    384    if (MOZ_UNLIKELY(!root)) {
    385      // This can happen during unlink - just drop the holdover stylesheet.
    386      mStyleSheet = nullptr;
    387      return;
    388    }
    389  }
    390  RefPtr<StyleSheet> sheet = mStyleSheet->Clone(nullptr, root);
    391  SetStyleSheet(sheet.get());
    392  aDocument->EnsureCSSLoader().InsertSheetInTree(*sheet);
    393 }
    394 
    395 }  // namespace mozilla::dom