blob: 0823c6153e747ad25fbb61bd0bebe9c5688c0f3b [file] [log] [blame]
/*
* Copyright 2012 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: matterbury@google.com (Matt Atterbury)
#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_FLATTEN_IMPORTS_CONTEXT_H_
#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_FLATTEN_IMPORTS_CONTEXT_H_
#include "base/logging.h"
#include "net/instaweb/rewriter/cached_result.pb.h"
#include "net/instaweb/rewriter/public/css_filter.h"
#include "net/instaweb/rewriter/public/css_hierarchy.h"
#include "net/instaweb/rewriter/public/css_image_rewriter.h"
#include "net/instaweb/rewriter/public/css_tag_scanner.h"
#include "net/instaweb/rewriter/public/output_resource_kind.h"
#include "net/instaweb/rewriter/public/resource.h"
#include "net/instaweb/rewriter/public/rewrite_context.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_result.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "net/instaweb/rewriter/public/single_rewrite_context.h"
#include "net/instaweb/rewriter/public/url_namer.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/ref_counted_ptr.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/string_writer.h"
#include "pagespeed/kernel/http/content_type.h"
namespace net_instaweb {
// Context used by CssFilter under async flow that flattens @imports.
class CssFlattenImportsContext : public SingleRewriteContext {
public:
CssFlattenImportsContext(RewriteContext* parent,
CssFilter* filter,
CssFilter::Context* rewriter,
CssHierarchy* hierarchy)
: SingleRewriteContext(NULL, parent, NULL /* no resource_context */),
filter_(filter),
rewriter_(rewriter),
hierarchy_(hierarchy) {
}
virtual ~CssFlattenImportsContext() {}
virtual GoogleString CacheKeySuffix() const {
// We have to include the media that applies to this context in its key
// so that, if someone @import's the same file but with a different set
// of media on the @import rule, we don't fetch the cached file, since
// it has been minified based on the original set of applicable media.
GoogleString suffix;
if (hierarchy_->media().empty()) {
suffix = "all";
} else {
suffix = hierarchy_->media()[0];
for (int i = 1, n = hierarchy_->media().size(); i < n; ++i) {
StrAppend(&suffix, "_", hierarchy_->media()[i]);
}
}
return suffix;
}
virtual void RewriteSingle(const ResourcePtr& input_resource,
const OutputResourcePtr& output_resource) {
input_resource_ = input_resource;
output_resource_ = output_resource;
// We have to fix relative URLs in the CSS as they break if used in a
// CSS file that itself was loaded via a relative path from the base
// (for example, if styles/screen.css references ../images/icon.png, then
// the correct path for the image is /images/icon.png). We also need to
// absolutify or left-trim URLs in flattened CSS if no other rewriter is
// going to do it (cache extend, css image rewriter, etc), but it's
// hard to tell if that will happen so we transform URLs here regardless
// and note that for CssHierarchy::css_resolution_base().
RewriteDriver* driver = Driver();
RewriteDomainTransformer transformer(&hierarchy_->css_base_url(),
&hierarchy_->css_trim_url(),
driver->server_context(),
driver->options(),
driver->message_handler());
// If we rewrite the input resource's contents we need somewhere to store
// it; that's what the hierarchy's backing store is for.
StringWriter writer(hierarchy_->input_contents_backing_store());
// See RewriteDriver::ResolveCssUrls about why we disable trimming in
// proxy mode. We also disable it if trimming is not enabled.
if ( driver->server_context()->url_namer()->ProxyMode() ||
!driver->options()->trim_urls_in_css() ||
!driver->options()->Enabled(RewriteOptions::kLeftTrimUrls)) {
transformer.set_trim_urls(false);
}
if (CssTagScanner::TransformUrls(
input_resource_->ExtractUncompressedContents(), &writer,
&transformer, driver->message_handler())) {
hierarchy_->set_input_contents_to_backing_store();
hierarchy_->set_input_contents_resolved(true);
} else {
hierarchy_->set_input_contents(
input_resource_->ExtractUncompressedContents());
}
bool ok = true;
GoogleString failure_reason;
if (!hierarchy_->Parse()) {
// If we cannot parse the CSS then we cannot flatten it.
ok = false;
failure_reason = StrCat("Cannot parse the CSS in ",
hierarchy_->url_for_humans());
filter_->num_flatten_imports_minify_failed_->Add(1);
} else if (!hierarchy_->CheckCharsetOk(input_resource, &failure_reason)) {
ok = false;
filter_->num_flatten_imports_charset_mismatch_->Add(1);
} else {
rewriter_->RewriteCssFromNested(this, hierarchy_);
}
if (!ok) {
hierarchy_->set_flattening_succeeded(false);
hierarchy_->AddFlatteningFailureReason(failure_reason);
RewriteDone(kRewriteFailed, 0);
} else if (num_nested() > 0) {
StartNestedTasks(); // Initiates rewriting of @import'd files.
} else {
Harvest(); // Harvest centralizes all the output generation.
}
}
void Harvest() {
DCHECK_EQ(1, num_output_partitions());
// Propagate any info on images from child rewrites.
CssImageRewriter::InheritChildImageInfo(this);
// Roll up the rewritten CSS(s) regardless of success or failure.
// Failure means we can't flatten it for some reason, such as incompatible
// charsets or invalid CSS, but we still need to cache the unflattened
// version so we don't try to flatten it again and again, so even in that
// case we don't return kRewriteFailed.
hierarchy_->RollUpContents();
// Our result is the combination of all our imports and our own rules.
output_partition(0)->set_inlined_data(hierarchy_->minified_contents());
ServerContext* server_context = FindServerContext();
server_context->MergeNonCachingResponseHeaders(input_resource_,
output_resource_);
if (Driver()->Write(ResourceVector(1, input_resource_),
hierarchy_->minified_contents(),
&kContentTypeCss,
input_resource_->charset(),
output_resource_.get())) {
RewriteDone(kRewriteOk, 0);
} else {
RewriteDone(kRewriteFailed, 0);
}
}
virtual void Render() {
// If we have flattened the imported file ...
if (num_output_partitions() == 1 && output_partition(0)->optimizable()) {
// If Harvest() was called, directly or from RewriteSingle(), then the
// minified contents are already set as are the stylesheet and input
// contents - in that case we don't actually have to do anything. If
// they haven't been called then the minified contents are empty and
// the result was found in the cache, in which case we have to set the
// input and minified contents to this result; the minified because we
// know that cached values are minified (we only cache minified contents),
// the input because we will need that to generate the stylesheet from
// when RollUpStylesheets is eventually called.
if (hierarchy_->minified_contents().empty()) {
hierarchy_->set_minified_contents(
output_partition(0)->inlined_data());
hierarchy_->set_input_contents(hierarchy_->minified_contents());
// Parse() will compute flattening_succeeded_, which needs to be
// restored. See https://github.com/pagespeed/mod_pagespeed/issues/1092
hierarchy_->Parse();
}
} else {
// Something has gone wrong earlier. It could be that the resource is
// not valid and cacheable (see SingleRewriteContext::Partition) or it
// could be that we're handling a cached failure, but it's hard to tell.
// So, mark flattening as failed but don't record a failure statistic
// nor a failure reason.
hierarchy_->set_flattening_succeeded(false);
}
}
virtual const char* id() const {
return RewriteOptions::kCssImportFlattenerId;
}
virtual OutputResourceKind kind() const { return kRewrittenResource; }
private:
CssFilter* filter_;
CssFilter::Context* rewriter_;
CssHierarchy* hierarchy_;
ResourcePtr input_resource_;
OutputResourcePtr output_resource_;
DISALLOW_COPY_AND_ASSIGN(CssFlattenImportsContext);
};
} // namespace net_instaweb
#endif // NET_INSTAWEB_REWRITER_PUBLIC_CSS_FLATTEN_IMPORTS_CONTEXT_H_