blob: 60600d395ceae2d8f7bca4e6978b272db0cef1d8 [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: jmaessen@google.com (Jan Maessen)
#include "net/instaweb/rewriter/public/javascript_code_block.h"
#include "net/instaweb/util/public/message_handler.h"
#include "net/instaweb/util/public/statistics.h"
#include "net/instaweb/util/public/string_util.h"
#include "third_party/jsmin/cpp/jsmin.h"
namespace net_instaweb {
namespace {
// Statistics names
const char kJavascriptBlocksMinified[] = "javascript_blocks_minified";
const char kJavascriptBytesSaved[] = "javascript_bytes_saved";
const char kJavascriptMinificationFailures[] =
"javascript_minification_failures";
const char kJavascriptTotalBlocks[] = "javascript_total_blocks";
// If there's an SGML comment surrounding the string, remove it and return
// true; otherwise, return false. Also trim whitespace.
bool TrimWrappingSgmlComment(const StringPiece& input, std::string* output) {
TrimWhitespace(input, output);
StringPiece trimmed = *output;
if (trimmed.starts_with("<!--") && trimmed.ends_with("-->")) {
output->erase(output->size() - 3, 3); // Erase "-->" from end
output->erase(0, 4); // Erase "<!--" start
return true;
} else {
return false;
}
}
} // namespace
JavascriptRewriteConfig::JavascriptRewriteConfig(Statistics* stats)
: minify_(true),
redirect_(true),
blocks_minified_(NULL),
bytes_saved_(NULL),
minification_failures_(NULL),
total_blocks_(NULL) {
if (stats != NULL) {
blocks_minified_ = stats->GetVariable(kJavascriptBlocksMinified);
bytes_saved_ = stats->GetVariable(kJavascriptBytesSaved);
minification_failures_ =
stats->GetVariable(kJavascriptMinificationFailures);
total_blocks_ = stats->GetVariable(kJavascriptTotalBlocks);
}
}
void JavascriptRewriteConfig::Initialize(Statistics* statistics) {
statistics->AddVariable(kJavascriptBlocksMinified);
statistics->AddVariable(kJavascriptBytesSaved);
statistics->AddVariable(kJavascriptMinificationFailures);
statistics->AddVariable(kJavascriptTotalBlocks);
}
JavascriptCodeBlock::JavascriptCodeBlock(
const StringPiece& original_code, JavascriptRewriteConfig* config,
MessageHandler* handler)
: config_(config),
handler_(handler),
original_code_(original_code.data(), original_code.size()),
output_code_(original_code_),
rewritten_(false) { }
JavascriptCodeBlock::~JavascriptCodeBlock() { }
const JavascriptLibraryId JavascriptCodeBlock::ComputeJavascriptLibrary() {
// We always RewriteIfNecessary just to provide a degree of
// predictability to the rewrite flow.
RewriteIfNecessary();
if (!config_->redirect()) {
return JavascriptLibraryId();
}
return JavascriptLibraryId::Find(rewritten_code_);
}
void JavascriptCodeBlock::Rewrite() {
// Before attempting library identification, we minify. However, we only
// point output_code_ at the minified code if we actually want to serve it to
// the rest of the universe.
config_->AddBlock();
if ((config_->minify() || config_->redirect())) {
// If the script is wrapped in an SGML comment, remove it before running
// JSMin, and then put it back later.
// TODO(mdsteele): JSMin should really be dealing with this for us, but it
// doesn't. Someday soon, we should replace it with something better.
std::string sans_sgml_comment;
bool had_an_sgml_comment =
TrimWrappingSgmlComment(original_code_, &sans_sgml_comment);
std::string untrimmed;
if (!jsmin::MinifyJs(sans_sgml_comment, &untrimmed)) {
handler_->Message(kError, "Minification failed. Preserving old code.");
config_->AddMinificationFailure();
TrimWhitespace(sans_sgml_comment, &rewritten_code_);
} else {
TrimWhitespace(untrimmed, &rewritten_code_);
}
// If we removed an SGML comment before, put it back now.
if (had_an_sgml_comment) {
rewritten_code_.insert(0, "<!--\n");
rewritten_code_.append("\n//-->");
}
} else {
TrimWhitespace(original_code_, &rewritten_code_);
}
if (config_->minify()) {
output_code_ = rewritten_code_;
if (rewritten_code_.size() < original_code_.size()) {
size_t savings = original_code_.size() - rewritten_code_.size();
config_->AddBytesSaved(savings);
}
}
}
} // namespace net_instaweb