blob: 872988a1e81fa301f9c98bb2c4dd0c37d288e925 [file] [log] [blame]
/*
* Copyright 2016 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: morlovich@google.com (Maksim Orlovich)
//
// Contains special slots that help rewrite images inside srcset attributes.
#include "net/instaweb/rewriter/public/srcset_slot.h"
#include <vector>
#include "net/instaweb/rewriter/public/common_filter.h"
#include "net/instaweb/rewriter/public/resource.h"
#include "net/instaweb/rewriter/public/resource_slot.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "pagespeed/kernel/base/ref_counted_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_element.h"
#include "pagespeed/kernel/http/google_url.h"
namespace net_instaweb {
SrcSetSlotCollection::SrcSetSlotCollection(
RewriteDriver* driver, HtmlElement* element,
HtmlElement::Attribute* attribute)
: driver_(driver), element_(element), attribute_(attribute),
filter_(nullptr),
// Note: these need to be deep-copied in case we run as a detached
// rewrite, in which case element_ may be dead.
begin_line_number_(element->begin_line_number()),
end_line_number_(element->end_line_number()) {
}
void SrcSetSlotCollection::Initialize(CommonFilter* filter) {
filter_ = filter;
StringPiece input(attribute_->DecodedValueOrNull());
ParseSrcSet(input, &candidates_);
for (int i = 0, n = candidates_.size(); i < n; ++i) {
GoogleString url = candidates_[i].url;
if (!url.empty()) {
// TODO(morlovich): Different filters may different policy wrt to
// inlining unknown; make it explicit somewhere that this relies on
// them being consistent about it if shared between filters.
ResourcePtr resource(filter->CreateInputResourceOrInsertDebugComment(
candidates_[i].url, RewriteDriver::InputRole::kImg, element_));
if (resource.get() != nullptr) {
candidates_[i].slot.reset(new SrcSetSlot(resource, this, i));
}
}
}
}
void SrcSetSlotCollection::ParseSrcSet(
StringPiece input, std::vector<ImageCandidate>* out) {
out->clear();
// ref: https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-srcset-attribute
while (true) {
// Strip leading whitespace, commas.
while (!input.empty() &&
(IsHtmlSpace(input[0]) || input[0] == ',')) {
input.remove_prefix(1);
}
if (input.empty()) {
break;
}
// Find where the URL ends --- it's WS terminated.
stringpiece_ssize_type url_end = input.find_first_of(" \f\n\r\t");
StringPiece url;
if (url_end == StringPiece::npos) {
url = input;
input = StringPiece();
} else {
url = input.substr(0, url_end);
input = input.substr(url_end);
}
// URL may have trailing commas, which also means there is no
// descriptor.
bool expect_descriptor = true;
while (url.ends_with(",")) {
url.remove_suffix(1);
expect_descriptor = false;
}
StringPiece descriptor;
if (expect_descriptor) {
bool inside_paren = false;
int pos, n;
for (pos = 0, n = input.size(); pos < n; ++pos) {
if (input[pos] == '(') {
inside_paren = true;
} else if (input[pos] == ')' && inside_paren) {
inside_paren = false;
} else if (input[pos] == ',' && !inside_paren) {
break;
}
}
descriptor = input.substr(0, pos);
input = input.substr(pos);
TrimWhitespace(&descriptor);
}
ImageCandidate cand;
url.CopyToString(&cand.url);
descriptor.CopyToString(&cand.descriptor);
out->push_back(cand);
}
}
GoogleString SrcSetSlotCollection::Serialize(
const std::vector<ImageCandidate>& in) {
GoogleString result;
for (int i = 0, n = in.size(); i < n; ++i) {
if (i != 0) {
StrAppend(&result, ", ");
}
StrAppend(&result, in[i].url);
if (!in[i].descriptor.empty()) {
StrAppend(&result, " ", in[i].descriptor);
}
}
return result;
}
void SrcSetSlotCollection::Commit() {
// Note that if slots don't want to render, they simply end up not changing
// things in candidates_
attribute_->SetValue(Serialize(candidates_));
}
SrcSetSlot::SrcSetSlot(const ResourcePtr& resource,
SrcSetSlotCollection* parent,
int index)
: ResourceSlot(resource), parent_(parent), index_(index),
url_relativity_(
GoogleUrl::FindRelativity(parent_->url(index))) {
}
SrcSetSlot::~SrcSetSlot() {}
void SrcSetSlot::Render() {
if (disable_rendering() || preserve_urls()) {
return;
}
parent_->set_url(
index_,
RelativizeOrPassthrough(
parent_->driver()->options(), resource()->url(),
url_relativity_, parent_->driver()->base_url()));
parent_->Commit();
}
GoogleString SrcSetSlot::LocationString() const {
GoogleString loc = StrCat(parent_->driver()->id(), ":",
IntegerToString(parent_->begin_line_number()));
if (parent_->end_line_number() != parent_->begin_line_number()) {
StrAppend(&loc, "-", IntegerToString(parent_->end_line_number()));
}
StrAppend(&loc, " srcset entry for ", parent_->descriptor(index_));
return loc;
}
bool SrcSetSlotCollectionComparator::operator()(
const SrcSetSlotCollectionPtr& p, const SrcSetSlotCollectionPtr& q) const {
// Note: The ordering depends on pointer comparison and so is arbitrary
// and non-deterministic.
if (p->element() < q->element()) {
return true;
} else if (p->element() > q->element()) {
return false;
}
return (p->attribute() < q->attribute());
}
} // namespace net_instaweb