blob: 4a13307206e336becc101da7ab33d89654a617b6 [file] [log] [blame]
/*
* Copyright (C) 2008, 2010 Apple Inc. All Rights Reserved.
* Copyright (C) 2009 Torch Mobile, Inc. http://www.torchmobile.com/
* Copyright (C) 2010 Google Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/html/parser/CSSPreloadScanner.h"
#include <memory>
#include "core/dom/Document.h"
#include "core/frame/Settings.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/html/parser/HTMLResourcePreloader.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/resource/CSSStyleSheetResource.h"
#include "platform/Histogram.h"
#include "platform/loader/fetch/fetch_initiator_type_names.h"
#include "platform/network/http_names.h"
#include "platform/text/SegmentedString.h"
#include "platform/weborigin/SecurityPolicy.h"
namespace blink {
CSSPreloadScanner::CSSPreloadScanner() {}
CSSPreloadScanner::~CSSPreloadScanner() {}
void CSSPreloadScanner::Reset() {
state_ = kInitial;
rule_.Clear();
rule_value_.Clear();
}
template <typename Char>
void CSSPreloadScanner::ScanCommon(const Char* begin,
const Char* end,
const SegmentedString& source,
PreloadRequestStream& requests,
const KURL& predicted_base_element_url) {
requests_ = &requests;
predicted_base_element_url_ = &predicted_base_element_url;
for (const Char* it = begin; it != end && state_ != kDoneParsingImportRules;
++it)
Tokenize(*it, source);
requests_ = nullptr;
predicted_base_element_url_ = nullptr;
}
void CSSPreloadScanner::Scan(const HTMLToken::DataVector& data,
const SegmentedString& source,
PreloadRequestStream& requests,
const KURL& predicted_base_element_url) {
ScanCommon(data.data(), data.data() + data.size(), source, requests,
predicted_base_element_url);
}
void CSSPreloadScanner::Scan(const String& tag_name,
const SegmentedString& source,
PreloadRequestStream& requests,
const KURL& predicted_base_element_url) {
if (tag_name.Is8Bit()) {
const LChar* begin = tag_name.Characters8();
ScanCommon(begin, begin + tag_name.length(), source, requests,
predicted_base_element_url);
return;
}
const UChar* begin = tag_name.Characters16();
ScanCommon(begin, begin + tag_name.length(), source, requests,
predicted_base_element_url);
}
void CSSPreloadScanner::SetReferrerPolicy(const ReferrerPolicy policy) {
referrer_policy_ = policy;
}
inline void CSSPreloadScanner::Tokenize(UChar c,
const SegmentedString& source) {
// We are just interested in @import rules, no need for real tokenization here
// Searching for other types of resources is probably low payoff.
// If we ever decide to preload fonts, we also need to change
// ResourceFetcher::resourceNeedsLoad to immediately load speculative font
// preloads.
switch (state_) {
case kInitial:
if (IsHTMLSpace<UChar>(c))
break;
if (c == '@')
state_ = kRuleStart;
else if (c == '/')
state_ = kMaybeComment;
else
state_ = kDoneParsingImportRules;
break;
case kMaybeComment:
if (c == '*')
state_ = kComment;
else
state_ = kInitial;
break;
case kComment:
if (c == '*')
state_ = kMaybeCommentEnd;
break;
case kMaybeCommentEnd:
if (c == '*')
break;
if (c == '/')
state_ = kInitial;
else
state_ = kComment;
break;
case kRuleStart:
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
rule_.Clear();
rule_value_.Clear();
rule_.Append(c);
state_ = kRule;
} else
state_ = kInitial;
break;
case kRule:
if (IsHTMLSpace<UChar>(c))
state_ = kAfterRule;
else if (c == ';')
state_ = kInitial;
else
rule_.Append(c);
break;
case kAfterRule:
if (IsHTMLSpace<UChar>(c))
break;
if (c == ';')
state_ = kInitial;
else if (c == '{')
state_ = kDoneParsingImportRules;
else {
state_ = kRuleValue;
rule_value_.Append(c);
}
break;
case kRuleValue:
if (IsHTMLSpace<UChar>(c))
state_ = kAfterRuleValue;
else if (c == ';')
EmitRule(source);
else
rule_value_.Append(c);
break;
case kAfterRuleValue:
if (IsHTMLSpace<UChar>(c))
break;
if (c == ';')
EmitRule(source);
else if (c == '{')
state_ = kDoneParsingImportRules;
else {
// FIXME: media rules
state_ = kInitial;
}
break;
case kDoneParsingImportRules:
NOTREACHED();
break;
}
}
static String ParseCSSStringOrURL(const String& string) {
size_t offset = 0;
size_t reduced_length = string.length();
while (reduced_length && IsHTMLSpace<UChar>(string[offset])) {
++offset;
--reduced_length;
}
while (reduced_length &&
IsHTMLSpace<UChar>(string[offset + reduced_length - 1]))
--reduced_length;
if (reduced_length >= 5 && (string[offset] == 'u' || string[offset] == 'U') &&
(string[offset + 1] == 'r' || string[offset + 1] == 'R') &&
(string[offset + 2] == 'l' || string[offset + 2] == 'L') &&
string[offset + 3] == '(' && string[offset + reduced_length - 1] == ')') {
offset += 4;
reduced_length -= 5;
}
while (reduced_length && IsHTMLSpace<UChar>(string[offset])) {
++offset;
--reduced_length;
}
while (reduced_length &&
IsHTMLSpace<UChar>(string[offset + reduced_length - 1]))
--reduced_length;
if (reduced_length < 2 ||
string[offset] != string[offset + reduced_length - 1] ||
!(string[offset] == '\'' || string[offset] == '"'))
return String();
offset++;
reduced_length -= 2;
while (reduced_length && IsHTMLSpace<UChar>(string[offset])) {
++offset;
--reduced_length;
}
while (reduced_length &&
IsHTMLSpace<UChar>(string[offset + reduced_length - 1]))
--reduced_length;
return string.Substring(offset, reduced_length);
}
void CSSPreloadScanner::EmitRule(const SegmentedString& source) {
if (DeprecatedEqualIgnoringCase(rule_, "import")) {
String url = ParseCSSStringOrURL(rule_value_.ToString());
TextPosition position =
TextPosition(source.CurrentLine(), source.CurrentColumn());
auto request = PreloadRequest::CreateIfNeeded(
FetchInitiatorTypeNames::css, position, url,
*predicted_base_element_url_, Resource::kCSSStyleSheet,
referrer_policy_, PreloadRequest::kBaseUrlIsReferrer,
ResourceFetcher::kImageNotImageSet);
if (request) {
// FIXME: Should this be including the charset in the preload request?
requests_->push_back(std::move(request));
}
state_ = kInitial;
} else if (DeprecatedEqualIgnoringCase(rule_, "charset"))
state_ = kInitial;
else
state_ = kDoneParsingImportRules;
rule_.Clear();
rule_value_.Clear();
}
CSSPreloaderResourceClient::CSSPreloaderResourceClient(
Resource* resource,
HTMLResourcePreloader* preloader)
: policy_(preloader->GetDocument()
->GetSettings()
->GetCSSExternalScannerPreload()
? kScanAndPreload
: kScanOnly),
preloader_(preloader),
resource_(ToCSSStyleSheetResource(resource)) {
resource_->AddClient(this);
}
CSSPreloaderResourceClient::~CSSPreloaderResourceClient() {}
void CSSPreloaderResourceClient::SetCSSStyleSheet(
const String& href,
const KURL& base_url,
ReferrerPolicy referrer_policy,
const WTF::TextEncoding&,
const CSSStyleSheetResource*) {
ClearResource();
}
// Only attach for one appendData call, as that's where most imports will likely
// be (according to spec).
void CSSPreloaderResourceClient::DidAppendFirstData(
const CSSStyleSheetResource* resource) {
if (preloader_)
ScanCSS(resource);
ClearResource();
}
void CSSPreloaderResourceClient::ScanCSS(
const CSSStyleSheetResource* resource) {
DCHECK(preloader_);
// Early abort if there is no document loader. Do this early to ensure that
// scan histograms and preload histograms do not count different quantities.
if (!preloader_->GetDocument()->Loader())
return;
// Passing an empty SegmentedString here results in PreloadRequest with no
// file/line information.
// TODO(csharrison): If this becomes an issue the CSSPreloadScanner may be
// augmented to take care of this case without performing an additional
// copy.
double start_time = MonotonicallyIncreasingTimeMS();
const String& chunk = resource->SheetText(nullptr);
if (chunk.IsNull())
return;
CSSPreloadScanner css_preload_scanner;
ReferrerPolicy referrer_policy = kReferrerPolicyDefault;
String referrer_policy_header =
resource->GetResponse().HttpHeaderField(HTTPNames::Referrer_Policy);
if (!referrer_policy_header.IsNull()) {
SecurityPolicy::ReferrerPolicyFromHeaderValue(
referrer_policy_header, kDoNotSupportReferrerPolicyLegacyKeywords,
&referrer_policy);
}
css_preload_scanner.SetReferrerPolicy(referrer_policy);
PreloadRequestStream preloads;
css_preload_scanner.Scan(chunk, SegmentedString(), preloads,
resource->GetResponse().Url());
DEFINE_STATIC_LOCAL(CustomCountHistogram, css_scan_time_histogram,
("PreloadScanner.ExternalCSS.ScanTime", 1, 1000000, 50));
css_scan_time_histogram.Count((MonotonicallyIncreasingTimeMS() - start_time) *
1000);
FetchPreloads(preloads);
}
void CSSPreloaderResourceClient::FetchPreloads(PreloadRequestStream& preloads) {
if (preloads.size()) {
preloader_->GetDocument()->Loader()->DidObserveLoadingBehavior(
WebLoadingBehaviorFlag::kWebLoadingBehaviorCSSPreloadFound);
}
if (policy_ == kScanAndPreload) {
int current_preload_count = preloader_->CountPreloads();
preloader_->TakeAndPreload(preloads);
DEFINE_STATIC_LOCAL(
CustomCountHistogram, css_import_histogram,
("PreloadScanner.ExternalCSS.PreloadCount", 1, 100, 50));
css_import_histogram.Count(preloader_->CountPreloads() -
current_preload_count);
}
}
void CSSPreloaderResourceClient::ClearResource() {
// Do not remove the client for unused, speculative markup preloads. This will
// trigger cancellation of the request and potential removal from memory
// cache. Link preloads are an exception because they support dynamic removal
// cancelling the request (and have their own passive resource client).
// Note: Speculative preloads which remain unused for their lifetime will
// never have this client removed. This should be fine because we only hold
// weak references to the resource.
if (resource_ && resource_->IsUnusedPreload() &&
!resource_->IsLinkPreload()) {
return;
}
if (resource_)
resource_->RemoveClient(this);
resource_.Clear();
}
void CSSPreloaderResourceClient::Trace(blink::Visitor* visitor) {
visitor->Trace(preloader_);
visitor->Trace(resource_);
StyleSheetResourceClient::Trace(visitor);
}
} // namespace blink