blob: a8048d6b3152dfe286542b046872cfd2b69e193a [file] [log] [blame]
// Copyright 2010 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "net/instaweb/rewriter/public/css_inline_filter.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "base/string_util.h"
#include "net/instaweb/htmlparse/public/html_element.h"
#include "net/instaweb/htmlparse/public/html_node.h"
#include "net/instaweb/htmlparse/public/html_parse.h"
#include "net/instaweb/rewriter/public/css_tag_scanner.h"
#include "net/instaweb/rewriter/public/resource_manager.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/util/public/google_url.h"
#include "net/instaweb/util/public/string_writer.h"
namespace net_instaweb {
CssInlineFilter::CssInlineFilter(HtmlParse* html_parse,
ResourceManager* resource_manager,
size_t size_threshold_bytes)
: CommonFilter(html_parse),
html_parse_(html_parse),
resource_manager_(resource_manager),
href_atom_(html_parse_->Intern("href")),
link_atom_(html_parse_->Intern("link")),
media_atom_(html_parse_->Intern("media")),
rel_atom_(html_parse_->Intern("rel")),
style_atom_(html_parse_->Intern("style")),
size_threshold_bytes_(size_threshold_bytes) {}
void CssInlineFilter::StartDocumentImpl() {
// TODO(sligocki): Domain lawyerify.
domain_ = html_parse_->gurl().host();
}
void CssInlineFilter::EndDocument() {
domain_.clear();
}
void CssInlineFilter::EndElementImpl(HtmlElement* element) {
if (element->tag() == link_atom_) {
const char* rel = element->AttributeValue(rel_atom_);
if (rel == NULL || strcmp(rel, "stylesheet")) {
return;
}
// If the link tag has a media attribute whose value isn't "all", don't
// inline. (Note that "all" is equivalent to having no media attribute;
// see http://www.w3.org/TR/html5/semantics.html#the-style-element)
const char* media = element->AttributeValue(media_atom_);
if (media != NULL && strcmp(media, "all") != 0) {
return;
}
// Get the URL where the external script is stored
const char* href = element->AttributeValue(href_atom_);
if (href == NULL) {
return; // We obviously can't inline if the URL isn't there.
}
// Make sure we're not moving across domains -- CSS can potentially contain
// Javascript expressions.
// TODO(jmaessen): Is the domain lawyer policy the appropriate one here?
// Or do we still have to check for strict domain equivalence?
// If so, add an inline-in-page policy to domainlawyer in some form,
// as we make a similar policy decision in js_inline_filter.
MessageHandler* message_handler = html_parse_->message_handler();
scoped_ptr<Resource> resource(
resource_manager_->CreateInputResourceAndReadIfCached(
base_gurl(), href, message_handler));
if (resource == NULL || !resource->ContentsValid()) {
return;
}
// Check that the file is small enough to inline.
StringPiece contents = resource->contents();
if (contents.size() > size_threshold_bytes_) {
return;
}
// Absolutify the URLs in the CSS -- relative URLs will break otherwise.
std::string rewritten;
StringWriter writer(&rewritten);
if (!CssTagScanner::AbsolutifyUrls(contents, resource->url(), &writer,
message_handler)) {
return;
}
// Inline the CSS.
HtmlElement* style_element =
html_parse_->NewElement(element->parent(), style_atom_);
if (html_parse_->ReplaceNode(element, style_element)) {
html_parse_->AppendChild(
style_element, html_parse_->NewCharactersNode(element, rewritten));
}
}
}
} // namespace net_instaweb