blob: d90d9f28a00da39f8fb221d0ea7e0d2788d0ffe7 [file] [log] [blame]
/**
* Copyright 2010 Google Inc.
*
* 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.
*/
// Author: sligocki@google.com (Shawn Ligocki)
#include "net/instaweb/rewriter/public/css_outline_filter.h"
#include "base/scoped_ptr.h"
#include "net/instaweb/rewriter/public/css_tag_scanner.h"
#include "net/instaweb/rewriter/public/output_resource.h"
#include "net/instaweb/rewriter/public/resource_manager.h"
#include "net/instaweb/htmlparse/public/html_parse.h"
#include "net/instaweb/htmlparse/public/html_element.h"
#include "net/instaweb/util/public/content_type.h"
#include "net/instaweb/util/public/simple_meta_data.h"
#include <string>
#include "net/instaweb/util/public/string_writer.h"
namespace net_instaweb {
const char kStylesheet[] = "stylesheet";
const char CssOutlineFilter::kFilterId[] = "co";
CssOutlineFilter::CssOutlineFilter(HtmlParse* html_parse,
ResourceManager* resource_manager,
size_t size_threshold_bytes)
: CommonFilter(html_parse),
inline_element_(NULL),
html_parse_(html_parse),
resource_manager_(resource_manager),
size_threshold_bytes_(size_threshold_bytes),
s_link_(html_parse->Intern("link")),
s_style_(html_parse->Intern("style")),
s_rel_(html_parse->Intern("rel")),
s_href_(html_parse->Intern("href")),
s_type_(html_parse->Intern("type")) { }
void CssOutlineFilter::StartDocumentImpl() {
inline_element_ = NULL;
buffer_.clear();
}
void CssOutlineFilter::StartElementImpl(HtmlElement* element) {
// No tags allowed inside style element.
if (inline_element_ != NULL) {
// TODO(sligocki): Add negative unit tests to hit these errors.
html_parse_->ErrorHere("Tag '%s' found inside style.",
element->tag().c_str());
inline_element_ = NULL; // Don't outline what we don't understand.
buffer_.clear();
}
if (element->tag() == s_style_) {
inline_element_ = element;
buffer_.clear();
}
}
void CssOutlineFilter::EndElementImpl(HtmlElement* element) {
if (inline_element_ != NULL) {
if (element != inline_element_) {
// No other tags allowed inside style element.
html_parse_->ErrorHere("Tag '%s' found inside style.",
element->tag().c_str());
} else if (buffer_.size() >= size_threshold_bytes_) {
OutlineStyle(inline_element_, buffer_);
} else {
html_parse_->InfoHere("Inline element not outlined because its size %d, "
"is below threshold %d",
static_cast<int>(buffer_.size()),
static_cast<int>(size_threshold_bytes_));
}
inline_element_ = NULL;
buffer_.clear();
}
}
void CssOutlineFilter::Flush() {
// If we were flushed in a style element, we cannot outline it.
inline_element_ = NULL;
buffer_.clear();
}
void CssOutlineFilter::Characters(HtmlCharactersNode* characters) {
if (inline_element_ != NULL) {
buffer_ += characters->contents();
}
}
void CssOutlineFilter::Comment(HtmlCommentNode* comment) {
if (inline_element_ != NULL) {
html_parse_->ErrorHere("Comment found inside style.");
inline_element_ = NULL; // Don't outline what we don't understand.
buffer_.clear();
}
}
void CssOutlineFilter::Cdata(HtmlCdataNode* cdata) {
if (inline_element_ != NULL) {
html_parse_->ErrorHere("CDATA found inside style.");
inline_element_ = NULL; // Don't outline what we don't understand.
buffer_.clear();
}
}
void CssOutlineFilter::IEDirective(HtmlIEDirectiveNode* directive) {
if (inline_element_ != NULL) {
html_parse_->ErrorHere("IE Directive found inside style.");
inline_element_ = NULL; // Don't outline what we don't understand.
buffer_.clear();
}
}
// Try to write content and possibly header to resource.
bool CssOutlineFilter::WriteResource(const std::string& content,
OutputResource* resource,
MessageHandler* handler) {
// We set the TTL of the origin->hashed_name map to 0 because this is
// derived from the inlined HTML.
int64 origin_expire_time_ms = 0;
return resource_manager_->Write(HttpStatus::kOK, content, resource,
origin_expire_time_ms, handler);
}
// Create file with style content and remove that element from DOM.
void CssOutlineFilter::OutlineStyle(HtmlElement* style_element,
const std::string& content) {
if (html_parse_->IsRewritable(style_element)) {
// Create style file from content.
const char* type = style_element->AttributeValue(s_type_);
// We only deal with CSS styles. If no type specified, CSS is assumed.
// See http://www.w3.org/TR/html5/semantics.html#the-style-element
if (type == NULL || strcmp(type, kContentTypeCss.mime_type()) == 0) {
MessageHandler* handler = html_parse_->message_handler();
// Create outline resource at the document location, not base URL location
scoped_ptr<OutputResource> resource(
resource_manager_->CreateOutputResourceWithPath(
GoogleUrl::AllExceptLeaf(html_parse_->gurl()), kFilterId, "_",
&kContentTypeCss, handler));
// Absolutify URLs in content.
std::string absolute_content;
StringWriter absolute_writer(&absolute_content);
// TODO(sligocki): Use CssParser instead of CssTagScanner hack.
// TODO(sligocki): Perhaps, only absolutify if base URL != dest path?
if (CssTagScanner::AbsolutifyUrls(content, GoogleUrl::Spec(base_gurl()),
&absolute_writer, handler) &&
WriteResource(absolute_content, resource.get(), handler)) {
HtmlElement* link_element = html_parse_->NewElement(
style_element->parent(), s_link_);
link_element->AddAttribute(s_rel_, kStylesheet, "'");
link_element->AddAttribute(s_href_, resource->url(), "'");
// Add all style atrributes to link.
for (int i = 0; i < style_element->attribute_size(); ++i) {
const HtmlElement::Attribute& attr = style_element->attribute(i);
link_element->AddAttribute(attr);
}
// Add link to DOM.
html_parse_->InsertElementAfterElement(style_element, link_element);
// Remove style element from DOM.
if (!html_parse_->DeleteElement(style_element)) {
html_parse_->FatalErrorHere("Failed to delete inline sytle element");
}
}
} else {
std::string element_string;
style_element->ToString(&element_string);
html_parse_->InfoHere("Cannot outline non-css stylesheet %s",
element_string.c_str());
}
}
}
} // namespace net_instaweb