blob: 4cb4c76d8aa6171b86f1802266a653a1431b3091 [file] [log] [blame]
/*
* Copyright 2014 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: jmarantz@google.com (Joshua Marantz)
// Author: sligocki@google.com (Shawn Ligocki)
#ifndef NET_INSTAWEB_REWRITER_PUBLIC_RESPONSIVE_IMAGE_FILTER_H_
#define NET_INSTAWEB_REWRITER_PUBLIC_RESPONSIVE_IMAGE_FILTER_H_
#include <map>
#include <vector>
#include "net/instaweb/rewriter/public/common_filter.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_element.h"
#include "pagespeed/kernel/html/html_filter.h"
namespace net_instaweb {
struct ResponsiveImageCandidate {
ResponsiveImageCandidate(HtmlElement* element_arg, double resolution_arg)
: element(element_arg), resolution(resolution_arg) {}
ResponsiveImageCandidate() : element(NULL), resolution(0) {}
HtmlElement* element;
double resolution;
};
typedef std::vector<ResponsiveImageCandidate> ResponsiveImageCandidateVector;
// We insert several virtual <img> tags. This structure keeps track of all
// the virtual <img> inserted for a single original <img>.
struct ResponsiveVirtualImages {
int width;
int height;
// One non-inlinable virtual <img> for every resolution supported (2x, 4x).
// These will be used in the srcset (if inlinable_candidate is not actually
// inlined).
ResponsiveImageCandidateVector non_inlinable_candidates;
// One inlinable virtual <img> for the highest resolution. If this is
// inlined, we use that as the only version. Otherwise we discard it.
ResponsiveImageCandidate inlinable_candidate;
// One fullsized image. This will be used at the top end of the srcset in
// case users zoom in far enough.
ResponsiveImageCandidate fullsized_candidate;
};
typedef std::map<HtmlElement*, ResponsiveVirtualImages>
ResponsiveImageCandidateMap;
// Filter which converts <img> tags into responsive srcset= counterparts by
// rewriting the images at multiple resolutions.
// The filter actually runs twice. Once before rewrite_images to split an <img>
// element into multiple elements (one for each resolution). The second time,
// it runs after rewrite_images have been rendered and it combines the elements
// back together into a single <img> element with srcset=.
// It also adds JavaScript to polyfill and add zoom responsiveness to srcset.
class ResponsiveImageFirstFilter : public CommonFilter {
public:
// Labels for different images used by Responsive image filters.
static const char kOriginalImage[];
static const char kNonInlinableVirtualImage[];
static const char kInlinableVirtualImage[];
static const char kFullsizedVirtualImage[];
explicit ResponsiveImageFirstFilter(RewriteDriver* driver);
virtual ~ResponsiveImageFirstFilter();
virtual void StartElementImpl(HtmlElement* element) {}
virtual void StartDocumentImpl();
virtual void EndElementImpl(HtmlElement* element);
virtual const char* Name() const { return "ResponsiveImageFirst"; }
private:
void AddHiResImages(HtmlElement* element);
ResponsiveImageCandidate AddHiResVersion(
HtmlElement* img, const HtmlElement::Attribute& src_attr,
int orig_width, int orig_height, StringPiece responsive_attribute_value,
double resolution);
friend class ResponsiveImageSecondFilter;
std::vector<double> densities_;
ResponsiveImageCandidateMap candidate_map_;
DISALLOW_COPY_AND_ASSIGN(ResponsiveImageFirstFilter);
};
class ResponsiveImageSecondFilter : public CommonFilter {
public:
ResponsiveImageSecondFilter(
RewriteDriver* driver, const ResponsiveImageFirstFilter* first_filter);
virtual ~ResponsiveImageSecondFilter();
virtual void StartElementImpl(HtmlElement* element) {}
virtual void StartDocumentImpl();
virtual void EndElementImpl(HtmlElement* element);
virtual void EndDocument();
virtual const char* Name() const { return "ResponsiveImageSecond"; }
// Injects scripts only when option responsive_images_zoom is enabled, and
// the current document is not AMP.
ScriptUsage GetScriptUsage() const override { return kMayInjectScripts; }
private:
void CombineHiResImages(HtmlElement* orig_element,
const ResponsiveVirtualImages& candidates);
void Cleanup(HtmlElement* orig_element,
const ResponsiveVirtualImages& candidates);
void InsertPlaceholderDebugComment(
const ResponsiveImageCandidate& candidate, const char* qualifier);
const GoogleString responsive_js_url_;
const ResponsiveImageFirstFilter* first_filter_;
// Is the ResponsiveImagesZoom filter enabled?
bool zoom_filter_enabled_;
// Was at least one srcset added? If not we don't insert zoom script.
bool srcsets_added_;
DISALLOW_COPY_AND_ASSIGN(ResponsiveImageSecondFilter);
};
} // namespace net_instaweb
#endif // NET_INSTAWEB_REWRITER_PUBLIC_RESPONSIVE_IMAGE_FILTER_H_