blob: 6ec2991d017ff0694237e740981311bd7d8a8ed6 [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/js_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/resource_manager.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/util/public/google_url.h"
namespace net_instaweb {
JsInlineFilter::JsInlineFilter(HtmlParse* html_parse,
ResourceManager* resource_manager,
size_t size_threshold_bytes)
: CommonFilter(html_parse),
html_parse_(html_parse),
resource_manager_(resource_manager),
script_atom_(html_parse_->Intern("script")),
src_atom_(html_parse_->Intern("src")),
size_threshold_bytes_(size_threshold_bytes),
should_inline_(false) {}
JsInlineFilter::~JsInlineFilter() {}
void JsInlineFilter::StartDocumentImpl() {
// TODO(sligocki): This should go in the domain lawyer, right?
domain_ = html_parse_->gurl().host();
should_inline_ = false;
}
void JsInlineFilter::EndDocument() {
domain_.clear();
}
void JsInlineFilter::StartElementImpl(HtmlElement* element) {
DCHECK(!should_inline_);
if (element->tag() == script_atom_) {
const char* src = element->AttributeValue(src_atom_);
should_inline_ = (src != NULL);
}
}
void JsInlineFilter::EndElementImpl(HtmlElement* element) {
if (should_inline_) {
DCHECK(element->tag() == script_atom_);
const char* src = element->AttributeValue(src_atom_);
DCHECK(src != NULL);
should_inline_ = false;
MessageHandler* message_handler = html_parse_->message_handler();
scoped_ptr<Resource> resource(
resource_manager_->CreateInputResourceAndReadIfCached(
base_gurl(), src, message_handler));
// 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 css_inline_filter.
if (resource != NULL && resource->ContentsValid()) {
StringPiece contents = resource->contents();
if (contents.size() <= size_threshold_bytes_ &&
element->DeleteAttribute(src_atom_)) {
html_parse_->InsertElementBeforeCurrent(
html_parse_->NewCharactersNode(element, contents));
}
}
}
}
void JsInlineFilter::Characters(HtmlCharactersNode* characters) {
if (should_inline_) {
DCHECK(characters->parent() != NULL);
DCHECK(characters->parent()->tag() == script_atom_);
if (OnlyWhitespace(characters->contents())) {
// If it's just whitespace inside the script tag, it's (probably) safe to
// just remove it.
html_parse_->DeleteElement(characters);
} else {
// This script tag isn't empty, despite having a src field. The contents
// won't be executed by the browser, but will still be in the DOM; some
// external scripts like to use this as a place to store data. So, we'd
// better not try to inline in this case.
should_inline_ = false;
}
}
}
} // namespace net_instaweb