blob: 091b1961c9710f1a2ec3971d2dfc6d679d36d8ef [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 "third_party/blink/renderer/core/html/parser/css_preload_scanner.h"
#include <memory>
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/network/http_names.h"
#include "third_party/blink/renderer/platform/text/segmented_string.h"
namespace blink {
CSSPreloadScanner::CSSPreloadScanner() = default;
CSSPreloadScanner::~CSSPreloadScanner() = default;
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);
// When reading the rule and hitting ')', which signifies the URL end,
// emit the rule.
if (c == ')') {
EmitRule(source);
}
}
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) {
wtf_size_t offset = 0;
wtf_size_t reduced_length = string.length();
// Remove whitespace from the rule start
while (reduced_length && IsHTMLSpace<UChar>(string[offset])) {
++offset;
--reduced_length;
}
// Remove whitespace from the rule end
// TODO(yoav): Evaluate performance benefits of using raw string operations.
// TODO(yoav): Look into moving parsing to use better parsing primitives.
while (reduced_length &&
IsHTMLSpace<UChar>(string[offset + reduced_length - 1])) {
--reduced_length;
}
// Skip the "url(" prefix and the ")" suffix
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;
}
// Skip whitespace before and after the URL inside the "url()" parenthesis.
while (reduced_length && IsHTMLSpace<UChar>(string[offset])) {
++offset;
--reduced_length;
}
while (reduced_length &&
IsHTMLSpace<UChar>(string[offset + reduced_length - 1])) {
--reduced_length;
}
// Remove single-quotes or double-quotes from the URL
if ((reduced_length >= 2) &&
(string[offset] == string[offset + reduced_length - 1]) &&
(string[offset] == '\'' || string[offset] == '"')) {
offset++;
reduced_length -= 2;
}
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(
fetch_initiator_type_names::kCSS, position, url,
*predicted_base_element_url_, ResourceType::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();
}
} // namespace blink