| /* |
| * 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/image_rewrite_filter.h" |
| |
| #include "net/instaweb/http/public/async_fetch.h" |
| #include "net/instaweb/http/public/counting_url_async_fetcher.h" |
| #include "net/instaweb/http/public/http_cache.h" |
| #include "net/instaweb/http/public/http_value.h" |
| #include "net/instaweb/http/public/log_record.h" |
| #include "net/instaweb/http/public/log_record_test_helper.h" |
| #include "net/instaweb/http/public/logging_proto.h" |
| #include "net/instaweb/http/public/logging_proto_impl.h" |
| #include "net/instaweb/http/public/mock_callback.h" |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/http/public/wait_url_async_fetcher.h" |
| #include "net/instaweb/rewriter/cached_result.pb.h" |
| #include "net/instaweb/rewriter/image_testing_peer.h" |
| #include "net/instaweb/rewriter/public/compatible_central_controller.h" |
| #include "net/instaweb/rewriter/public/dom_stats_filter.h" |
| #include "net/instaweb/rewriter/public/image.h" |
| #include "net/instaweb/rewriter/public/mock_critical_images_finder.h" |
| #include "net/instaweb/rewriter/public/resource.h" |
| #include "net/instaweb/rewriter/public/resource_namer.h" |
| #include "net/instaweb/rewriter/public/resource_tag_scanner.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_test_base.h" |
| #include "net/instaweb/rewriter/public/server_context.h" |
| #include "net/instaweb/rewriter/public/test_rewrite_driver_factory.h" |
| #include "net/instaweb/rewriter/rendered_image.pb.h" |
| #include "net/instaweb/util/public/mock_property_page.h" |
| #include "net/instaweb/util/public/property_cache.h" |
| #include "pagespeed/kernel/base/abstract_mutex.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/dynamic_annotations.h" // RunningOnValgrind |
| #include "pagespeed/kernel/base/gmock.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/md5_hasher.h" // for MD5Hasher |
| #include "pagespeed/kernel/base/mock_message_handler.h" |
| #include "pagespeed/kernel/base/null_thread_system.h" |
| #include "pagespeed/kernel/base/scoped_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/thread_system.h" |
| #include "pagespeed/kernel/base/timer.h" // for Timer, etc |
| #include "pagespeed/kernel/cache/lru_cache.h" |
| #include "pagespeed/kernel/html/empty_html_filter.h" |
| #include "pagespeed/kernel/html/html_element.h" |
| #include "pagespeed/kernel/html/html_parse.h" |
| #include "pagespeed/kernel/html/html_parse_test_base.h" |
| #include "pagespeed/kernel/http/content_type.h" |
| #include "pagespeed/kernel/http/google_url.h" |
| #include "pagespeed/kernel/http/http_names.h" |
| #include "pagespeed/kernel/http/http_options.h" |
| #include "pagespeed/kernel/http/response_headers.h" |
| #include "pagespeed/kernel/http/semantic_type.h" |
| #include "pagespeed/kernel/http/user_agent_matcher_test_base.h" |
| #include "pagespeed/kernel/image/test_utils.h" |
| #include "pagespeed/opt/logging/enums.pb.h" |
| |
| namespace net_instaweb { |
| |
| using net_instaweb::ImageRewriteFilter; |
| using pagespeed::image_compression::kMessagePatternPixelFormat; |
| using pagespeed::image_compression::kMessagePatternStats; |
| using pagespeed::image_compression::kMessagePatternWritingToWebp; |
| using ::testing::HasSubstr; |
| |
| namespace { |
| |
| // Filenames of resource files. |
| const char kAnimationGifFile[] = "PageSpeedAnimationSmall.gif"; |
| const char kBikePngFile[] = "BikeCrashIcn.png"; // photo; no alpha |
| const char kChromium24[] = "chromium-24.webp"; |
| const char kChefGifFile[] = "IronChef2.gif"; // photo; no alpha |
| const char kCradleAnimation[] = "CradleAnimation.gif"; |
| const char kCuppaPngFile[] = "Cuppa.png"; // graphic; no alpha |
| const char kCuppaOPngFile[] = "CuppaO.png"; // graphic; no alpha; no opt |
| const char kCuppaTPngFile[] = "CuppaT.png"; // graphic; alpha; no opt |
| const char kEmptyScreenGifFile[] = "red_empty_screen.gif"; // Empty screen |
| const char kLargePngFile[] = "Large.png"; // blank image; gray scale |
| const char kPuzzleJpgFile[] = "Puzzle.jpg"; // photo; no alpha |
| const char kRedbrushAlphaPngFile[] = "RedbrushAlpha-0.5.png"; // photo; alpha |
| const char kSmallDataFile[] = "small-data.png"; // not an image |
| const char k1x1GifFile[] = "o.gif"; // unoptimizable gif |
| const char kResolutionLimitPngFile[] = "ResolutionLimit.png"; |
| const char kResolutionLimitJpegFile[] = "ResolutionLimit.jpg"; |
| |
| // Both ResolutionLimit.png and ResolutionLimit.jpg have 4096 x 2048 pixels. |
| // We assume that each pixel has 4 bytes when we check whether the images are |
| // within the limit, so |
| // width * height * pixel_depth = 4096 x 2048 x 4 = 33554432 = |
| // kResolutionLimitBytes. |
| // 33554432 is also the default resolution limit (in bytes) in mod_pagespeed. |
| const int kResolutionLimitBytes = 33554432; |
| |
| const char kChefDims[] = " width=\"192\" height=\"256\""; |
| |
| // Size of a 1x1 image. |
| const char kPixelDims[] = " width='1' height='1'"; |
| |
| // If the expected value of a size is set to -1, this size will be ignored in |
| // the test. |
| const int kIgnoreSize = -1; |
| |
| const char kCriticalImagesCohort[] = "critical_images"; |
| |
| // Message to ignore. |
| const char kMessagePatternFailedToEncodeWebp[] = "*Could not encode webp data*"; |
| const char kMessagePatternRecompressing[] = "*Recompressing image*"; |
| const char kMessagePatternResizedImage[] = "*Resized image*"; |
| const char kMessagePatternShrinkingImage[] = "*Shrinking image*"; |
| const char kMessagePatternWebpTimeOut[] = "*WebP conversion timed out*"; |
| |
| struct OptimizedImageInfo { |
| const ContentType* content_type; |
| const char* vary_header; |
| int content_length; |
| }; |
| |
| struct OptimizedImageInfoList { |
| const struct OptimizedImageInfo with_via; |
| const struct OptimizedImageInfo with_none; |
| const struct OptimizedImageInfo with_savedata_via; |
| const struct OptimizedImageInfo with_savedata; |
| }; |
| |
| struct OptimizedImageInfoListInputs { |
| const char* user_agent; |
| const char* image_name; |
| const OptimizedImageInfoList* optimized_info; |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUa = { |
| // [Save-Data: no, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 33108}, |
| // [Save-Data: no, Via: no]: Convert to WebP mobile quality. |
| {&kContentTypeWebp, "User-Agent,Save-Data", 25774}, |
| // [Save-Data: yes, Via: yes]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 19124}, |
| // [Save-Data: yes, Via: no]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "User-Agent,Save-Data", 19124}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForSafariUa = { |
| // [Save-Data: no, Via: yes]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, "Accept,Save-Data", 73096}, |
| // [Save-Data: no, Via: no]: Convert to JPEG mobile quality. |
| {&kContentTypeJpeg, "User-Agent,Save-Data", 51452}, |
| // [Save-Data: yes, Via: yes]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "Accept,Save-Data", 38944}, |
| // [Save-Data: yes, Via: no]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "User-Agent,Save-Data", 38944}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForDesktopUa = { |
| // [Save-Data: no, Via: yes]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, "Accept,Save-Data", 73096}, |
| // [Save-Data: no, Via: no]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, "User-Agent,Save-Data", 73096}, |
| // [Save-Data: yes, Via: yes]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "Accept,Save-Data", 38944}, |
| // [Save-Data: yes, Via: no]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "User-Agent,Save-Data", 38944}, |
| }; |
| |
| const OptimizedImageInfoList kBikeOptimizedForWebpUa = { |
| // [Save-Data: no, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 2454}, |
| // [Save-Data: no, Via: no]: Convert to WebP mobile quality. |
| {&kContentTypeWebp, "User-Agent,Save-Data", 2014}, |
| // [Save-Data: yes, Via: yes]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 1476}, |
| // [Save-Data: yes, Via: no]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "User-Agent,Save-Data", 1476}, |
| }; |
| |
| const OptimizedImageInfoList kBikeOptimizedForSafariUa = { |
| // [Save-Data: no, Via: yes]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, "Accept,Save-Data", 3536}, |
| // [Save-Data: no, Via: no]: Convert to JPEG mobile quality. |
| {&kContentTypeJpeg, "User-Agent,Save-Data", 2606}, |
| // [Save-Data: yes, Via: yes]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "Accept,Save-Data", 2069}, |
| // [Save-Data: yes, Via: no]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "User-Agent,Save-Data", 2069}, |
| }; |
| |
| const OptimizedImageInfoList kBikeOptimizedForDesktopUa = { |
| // [Save-Data: no, Via: yes]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, "Accept,Save-Data", 3536}, |
| // [Save-Data: no, Via: no]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, "User-Agent,Save-Data", 3536}, |
| // [Save-Data: yes, Via: yes]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "Accept,Save-Data", 2069}, |
| // [Save-Data: yes, Via: no]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "User-Agent,Save-Data", 2069}, |
| }; |
| |
| const OptimizedImageInfoList kCuppaOptimizedForWebpUa = { |
| // [Save-Data: no, Via: yes]: Convert to PNG. |
| {&kContentTypePng, NULL, 770}, |
| // [Save-Data: no, Via: no]: Convert to WebP lossless. |
| {&kContentTypeWebp, "User-Agent", 694}, |
| // [Save-Data: yes, Via: yes]: Convert to PNG. |
| {&kContentTypePng, NULL, 770}, |
| // [Save-Data: yes, Via: no]: Convert to WebP lossless. |
| {&kContentTypeWebp, "User-Agent", 694}, |
| }; |
| |
| const OptimizedImageInfoList kCuppaOptimizedForDesktopUa = { |
| // [Save-Data: no, Via: yes]: Convert to PNG. |
| {&kContentTypePng, NULL, 770}, |
| // [Save-Data: no, Via: no]: Convert to PNG. |
| {&kContentTypePng, "User-Agent", 770}, |
| // [Save-Data: yes, Via: yes]: Convert to PNG. |
| {&kContentTypePng, NULL, 770}, |
| // [Save-Data: yes, Via: no]: Convert to PNG. |
| {&kContentTypePng, "User-Agent", 770}, |
| }; |
| |
| const OptimizedImageInfoList kAnimationOptimizedForWebpUa = { |
| // [Save-Data: no, Via: yes]: Cannot optimize. |
| {&kContentTypeGif, NULL, 26251}, |
| // [Save-Data: no, Via: no]: Convert to WebP desktop/mobile quality. |
| {&kContentTypeWebp, "User-Agent,Save-Data", 7232}, |
| // [Save-Data: yes, Via: yes]: Cannot optimize. |
| {&kContentTypeGif, NULL, 26251}, |
| // [Save-Data: yes, Via: no]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "User-Agent,Save-Data", 4312}, |
| }; |
| |
| const OptimizedImageInfoList kAnimationOptimizedForDesktopUa = { |
| // [Save-Data: no, Via: yes]: Cannot optimize. |
| {&kContentTypeGif, NULL, 26251}, |
| // [Save-Data: no, Via: no]: Cannot optimize. |
| {&kContentTypeGif, NULL, 26251}, |
| // [Save-Data: yes, Via: yes]: Cannot optimize. |
| {&kContentTypeGif, NULL, 26251}, |
| // [Save-Data: yes, Via: no]: Cannot optimize. |
| {&kContentTypeGif, NULL, 26251}, |
| }; |
| |
| const OptimizedImageInfoListInputs kOptimizedImageInfoList[] = { |
| // JPEG image, optimized for Chrome on Android. |
| {UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, kPuzzleJpgFile, |
| &kPuzzleOptimizedForWebpUa}, |
| // JPEG image, optimized for Safari on iOS. |
| {UserAgentMatcherTestBase::kCriOS31UserAgent, kPuzzleJpgFile, |
| &kPuzzleOptimizedForSafariUa}, |
| // JPEG image, optimized for Firefox on desktop. |
| {UserAgentMatcherTestBase::kFirefoxUserAgent, kPuzzleJpgFile, |
| &kPuzzleOptimizedForDesktopUa}, |
| // Photographic PNG image, optimized for Chrome on Android. |
| {UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, kBikePngFile, |
| &kBikeOptimizedForWebpUa}, |
| // Photographic PNG image, optimized for Safari on iOS. |
| {UserAgentMatcherTestBase::kCriOS31UserAgent, kBikePngFile, |
| &kBikeOptimizedForSafariUa}, |
| // Photographic PNG image, optimized for Firefox on desktop. |
| {UserAgentMatcherTestBase::kFirefoxUserAgent, kBikePngFile, |
| &kBikeOptimizedForDesktopUa}, |
| // Non-photographic PNG image, optimized for Chrome on Android. |
| {UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, kCuppaPngFile, |
| &kCuppaOptimizedForWebpUa}, |
| // Non-photographic PNG image, optimized for Safari on iOS. |
| {UserAgentMatcherTestBase::kCriOS31UserAgent, kCuppaPngFile, |
| &kCuppaOptimizedForDesktopUa}, |
| // Non-photographic PNG image, optimized for Firefox on desktop. |
| {UserAgentMatcherTestBase::kFirefoxUserAgent, kCuppaPngFile, |
| &kCuppaOptimizedForDesktopUa}, |
| // Animated GIF image, optimized for Chrome on Android. |
| {UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, kAnimationGifFile, |
| &kAnimationOptimizedForWebpUa}, |
| // Animated GIF image, optimized for Safari on iOS. |
| {UserAgentMatcherTestBase::kCriOS31UserAgent, kAnimationGifFile, |
| &kAnimationOptimizedForDesktopUa}, |
| // Animated GIF image, optimized for Firefox on desktop. |
| {UserAgentMatcherTestBase::kFirefoxUserAgent, kAnimationGifFile, |
| &kAnimationOptimizedForDesktopUa}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUaAllowSaveDataAccept = { |
| // [Save-Data: no, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 33108}, |
| // [Save-Data: no, Via: no]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 33108}, |
| // [Save-Data: yes, Via: yes]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 19124}, |
| // [Save-Data: yes, Via: no]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 19124}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUaAllowUserAgent = { |
| // [Save-Data: no, Via: yes]: Convert to WebP mobile quality. |
| {&kContentTypeWebp, "User-Agent", 25774}, |
| // [Save-Data: no, Via: no]: Convert to WebP mobile quality. |
| {&kContentTypeWebp, "User-Agent", 25774}, |
| // [Save-Data: yes, Via: yes]: Convert to WebP mobile quality. |
| {&kContentTypeWebp, "User-Agent", 25774}, |
| // [Save-Data: yes, Via: no]: Convert to WebP mobile quality. |
| {&kContentTypeWebp, "User-Agent", 25774}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUaAllowAccept = { |
| // [Save-Data: no, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept", 33108}, |
| // [Save-Data: no, Via: no]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept", 33108}, |
| // [Save-Data: yes, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept", 33108}, |
| // [Save-Data: yes, Via: no]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept", 33108}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUaAllowSaveData = { |
| // [Save-Data: no, Via: yes]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, "Save-Data", 73096}, |
| // [Save-Data: no, Via: no]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, "Save-Data", 73096}, |
| // [Save-Data: yes, Via: yes]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "Save-Data", 38944}, |
| // [Save-Data: yes, Via: no]: Convert to JPEG Save-Data quality. |
| {&kContentTypeJpeg, "Save-Data", 38944}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUaAllowNone = { |
| // [Save-Data: no, Via: yes]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, NULL, 73096}, |
| // [Save-Data: no, Via: no]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, NULL, 73096}, |
| // [Save-Data: yes, Via: yes]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, NULL, 73096}, |
| // [Save-Data: yes, Via: no]: Convert to JPEG desktop quality. |
| {&kContentTypeJpeg, NULL, 73096}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUaNoSaveDataQualities = { |
| // [Save-Data: no, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept", 33108}, |
| // [Save-Data: no, Via: no]: Convert to WebP mobile quality. |
| {&kContentTypeWebp, "User-Agent", 25774}, |
| // [Save-Data: yes, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept", 33108}, |
| // [Save-Data: yes, Via: no]: Convert to WebP mobile quality. |
| {&kContentTypeWebp, "User-Agent", 25774}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUaNoSmallScreenQualities = { |
| // [Save-Data: no, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 33108}, |
| // [Save-Data: no, Via: no]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "User-Agent,Save-Data", 33108}, |
| // [Save-Data: yes, Via: yes]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "Accept,Save-Data", 19124}, |
| // [Save-Data: yes, Via: no]: Convert to WebP Save-Data quality. |
| {&kContentTypeWebp, "User-Agent,Save-Data", 19124}, |
| }; |
| |
| const OptimizedImageInfoList kPuzzleOptimizedForWebpUaNoSpecialQualities = { |
| // [Save-Data: no, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept", 33108}, |
| // [Save-Data: no, Via: no]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "User-Agent", 33108}, |
| // [Save-Data: yes, Via: yes]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "Accept", 33108}, |
| // [Save-Data: yes, Via: no]: Convert to WebP desktop quality. |
| {&kContentTypeWebp, "User-Agent", 33108}, |
| }; |
| |
| // A callback for HTTP cache that stores body and string representation |
| // of headers into given strings. |
| class HTTPCacheStringCallback : public OptionsAwareHTTPCacheCallback { |
| public: |
| HTTPCacheStringCallback(const RewriteOptions* options, |
| const RequestContextPtr& request_ctx, |
| GoogleString* body_out, GoogleString* headers_out) |
| : OptionsAwareHTTPCacheCallback(options, request_ctx), |
| body_out_(body_out), |
| headers_out_(headers_out), found_(false) {} |
| |
| virtual ~HTTPCacheStringCallback() {} |
| |
| virtual void Done(HTTPCache::FindResult find_result) { |
| StringPiece contents; |
| if ((find_result.status == HTTPCache::kFound) && |
| http_value()->ExtractContents(&contents)) { |
| found_ = true; |
| contents.CopyToString(body_out_); |
| *headers_out_ = response_headers()->ToString(); |
| } |
| } |
| |
| void ExpectFound() { |
| EXPECT_TRUE(found_); |
| } |
| |
| private: |
| GoogleString* body_out_; |
| GoogleString* headers_out_; |
| bool found_; |
| DISALLOW_COPY_AND_ASSIGN(HTTPCacheStringCallback); |
| }; |
| |
| } // namespace |
| |
| // TODO(huibao): Move CopyOnWriteLogRecord and TestRequestContext to a shared |
| // file. |
| |
| // RequestContext that overrides NewSubordinateLogRecord to return a |
| // CopyOnWriteLogRecord that copies to a logging_info given at construction |
| // time. |
| class TestRequestContext : public RequestContext { |
| public: |
| TestRequestContext(LoggingInfo* logging_info, |
| AbstractMutex* mutex) |
| : RequestContext(kDefaultHttpOptionsForTests, mutex, NULL), |
| logging_info_copy_(logging_info) { |
| } |
| |
| virtual AbstractLogRecord* NewSubordinateLogRecord( |
| AbstractMutex* logging_mutex) { |
| return new CopyOnWriteLogRecord(logging_mutex, logging_info_copy_); |
| } |
| |
| private: |
| LoggingInfo* logging_info_copy_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestRequestContext); |
| }; |
| typedef RefCountedPtr<TestRequestContext> TestRequestContextPtr; |
| |
| class ImageRewriteTest : public RewriteTestBase { |
| protected: |
| ImageRewriteTest() |
| : test_request_context_(TestRequestContextPtr( |
| new TestRequestContext(&logging_info_, |
| factory()->thread_system()->NewMutex()))) { |
| } |
| |
| virtual void SetUp() { |
| PropertyCache* pcache = page_property_cache(); |
| server_context_->set_enable_property_cache(true); |
| const PropertyCache::Cohort* cohort = |
| SetupCohort(pcache, RewriteDriver::kDomCohort); |
| server_context()->set_dom_cohort(cohort); |
| RewriteTestBase::SetUp(); |
| MockPropertyPage* page = NewMockPage(kTestDomain); |
| pcache->set_enabled(true); |
| rewrite_driver()->set_property_page(page); |
| pcache->Read(page); |
| |
| // Ignore trivial message. |
| MockMessageHandler* handler = message_handler(); |
| handler->AddPatternToSkipPrinting(kMessagePatternFailedToEncodeWebp); |
| handler->AddPatternToSkipPrinting(kMessagePatternPixelFormat); |
| handler->AddPatternToSkipPrinting(kMessagePatternRecompressing); |
| handler->AddPatternToSkipPrinting(kMessagePatternResizedImage); |
| handler->AddPatternToSkipPrinting(kMessagePatternShrinkingImage); |
| handler->AddPatternToSkipPrinting(kMessagePatternStats); |
| handler->AddPatternToSkipPrinting(kMessagePatternWebpTimeOut); |
| handler->AddPatternToSkipPrinting(kMessagePatternWritingToWebp); |
| } |
| |
| void RewriteImageFromHtml(const GoogleString& tag_string, |
| const ContentType& content_type, |
| GoogleString* img_src) { |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| AddRecompressImageFilters(); |
| options()->set_image_inline_max_bytes(2000); |
| rewrite_driver()->AddFilters(); |
| |
| // URLs and content for HTML document and resources. |
| const GoogleUrl domain(EncodeWithBase("http://rewrite_image.test/", |
| "http://rewrite_image.test/", |
| "x", "0", "x", "x")); |
| const char html_url[] = "http://rewrite_image.test/RewriteImage.html"; |
| const char image_url[] = "http://rewrite_image.test/Puzzle.jpg"; |
| |
| const GoogleString image_html = |
| StrCat("<head/><body><", tag_string, " src=\"Puzzle.jpg\"/></body>"); |
| |
| // Store image contents into fetcher. |
| AddFileToMockFetcher(image_url, kPuzzleJpgFile, kContentTypeJpeg, 100); |
| ParseUrl(html_url, image_html); |
| StringVector img_srcs; |
| CollectImgSrcs("RewriteImage/collect_sources", output_buffer_, &img_srcs); |
| // output_buffer_ should have exactly one image file (Puzzle.jpg). |
| EXPECT_EQ(1UL, img_srcs.size()); |
| const GoogleUrl img_gurl(html_gurl(), img_srcs[0]); |
| EXPECT_TRUE(img_gurl.IsWebValid()); |
| EXPECT_EQ(domain.AllExceptLeaf(), img_gurl.AllExceptLeaf()); |
| EXPECT_TRUE(img_gurl.LeafSansQuery().ends_with( |
| content_type.file_extension())); |
| *img_src = img_srcs[0]; |
| } |
| |
| // Simple image rewrite test to check resource fetching functionality. |
| void RewriteImage(const GoogleString& tag_string, |
| const ContentType& content_type) { |
| static const char kCacheFragment[] = "a-cache-fragment"; |
| options()->set_cache_fragment(kCacheFragment); |
| |
| // Capture normal headers for comparison. We need to do it now |
| // since the clock -after- rewrite is non-deterministic, but it must be |
| // at the initial value at the time of the rewrite. |
| GoogleString expect_headers; |
| AppendDefaultHeadersWithCanonical(content_type, |
| "http://rewrite_image.test/Puzzle.jpg", |
| &expect_headers); |
| |
| GoogleString src_string; |
| |
| Histogram* rewrite_latency_ok = statistics()->GetHistogram( |
| ImageRewriteFilter::kImageRewriteLatencyOkMs); |
| Histogram* rewrite_latency_failed = statistics()->GetHistogram( |
| ImageRewriteFilter::kImageRewriteLatencyFailedMs); |
| rewrite_latency_ok->Clear(); |
| rewrite_latency_failed->Clear(); |
| |
| RewriteImageFromHtml(tag_string, content_type, &src_string); |
| |
| EXPECT_EQ(1, rewrite_latency_ok->Count()); |
| EXPECT_EQ(0, rewrite_latency_failed->Count()); |
| |
| const GoogleString expected_output = |
| StrCat("<head/><body><", tag_string, " src=\"", src_string, |
| "\" width=\"1023\" height=\"766\"/></body>"); |
| EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_); |
| |
| GoogleUrl img_gurl(html_gurl(), src_string); |
| |
| // Fetch the version we just put into the cache, so we can |
| // make sure we produce it consistently. |
| GoogleString rewritten_image; |
| GoogleString rewritten_headers; |
| HTTPCacheStringCallback cache_callback( |
| options(), rewrite_driver()->request_context(), |
| &rewritten_image, &rewritten_headers); |
| http_cache()->Find(img_gurl.Spec().as_string(), kCacheFragment, |
| message_handler(), &cache_callback); |
| cache_callback.ExpectFound(); |
| |
| // Make sure the headers produced make sense. |
| EXPECT_STREQ(expect_headers, rewritten_headers); |
| |
| // Also fetch the resource to ensure it can be created dynamically |
| ExpectStringAsyncFetch expect_callback(true, CreateRequestContext()); |
| lru_cache()->Clear(); |
| |
| // New time --- new timestamp. |
| expect_headers.clear(); |
| AppendDefaultHeadersWithCanonical(content_type, |
| "http://rewrite_image.test/Puzzle.jpg", |
| &expect_headers); |
| |
| EXPECT_TRUE(rewrite_driver()->FetchResource(img_gurl.Spec(), |
| &expect_callback)); |
| rewrite_driver()->WaitForCompletion(); |
| EXPECT_EQ(HttpStatus::kOK, |
| expect_callback.response_headers()->status_code()) << |
| "Looking for " << src_string; |
| EXPECT_STREQ(rewritten_image, expect_callback.buffer()); |
| EXPECT_STREQ(expect_headers, |
| expect_callback.response_headers()->ToString()); |
| // Try to fetch from an independent server. |
| ServeResourceFromManyContextsWithUA( |
| img_gurl.Spec().as_string(), rewritten_image, |
| rewrite_driver()->user_agent()); |
| |
| // Check that filter application was logged. |
| EXPECT_STREQ("ic", AppliedRewriterStringFromLog()); |
| } |
| |
| void TestInlining(bool convert_to_webp, const char* user_agent, |
| const StringPiece& file_name, const ContentType& input_type, |
| const ContentType& output_type, bool expect_inline) { |
| ClearRewriteDriver(); |
| |
| SetCurrentUserAgent(user_agent); |
| if (convert_to_webp) { |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpLossless); |
| AddRequestAttribute(HttpAttributes::kAccept, "image/webp"); |
| } |
| SetDriverRequestHeaders(); |
| |
| options()->set_image_inline_max_bytes(1000000); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| |
| TestSingleRewrite(file_name, input_type, output_type, "", "", |
| true /*expect_rewritten*/, expect_inline); |
| } |
| |
| void SetupIproTests(const char* allow_vary_on_string) { |
| EXPECT_TRUE(options()->EnableFiltersByCommaSeparatedList( |
| "recompress_images,convert_to_webp_lossless,convert_to_webp_animated," |
| "convert_png_to_jpeg,in_place_optimize_for_browser", |
| message_handler())); |
| |
| GoogleString puzzleUrl = StrCat(kTestDomain, kPuzzleJpgFile); |
| GoogleString bikeUrl = StrCat(kTestDomain, kBikePngFile); |
| GoogleString cuppaUrl = StrCat(kTestDomain, kCuppaPngFile); |
| GoogleString animationUrl = StrCat(kTestDomain, kAnimationGifFile); |
| AddFileToMockFetcher(puzzleUrl, kPuzzleJpgFile, kContentTypeJpeg, 100); |
| AddFileToMockFetcher(bikeUrl, kBikePngFile, kContentTypePng, 100); |
| AddFileToMockFetcher(cuppaUrl, kCuppaPngFile, kContentTypePng, 100); |
| AddFileToMockFetcher(animationUrl, kAnimationGifFile, kContentTypeGif, 100); |
| |
| UseMd5Hasher(); |
| options()->set_image_preserve_urls(true); |
| options()->set_in_place_rewriting_enabled(true); |
| options()->set_in_place_wait_for_optimized(true); |
| options()->set_image_recompress_quality(90); |
| options()->set_image_jpeg_recompress_quality(75); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(55); |
| options()->set_image_jpeg_quality_for_save_data(35); |
| options()->set_image_webp_recompress_quality(70); |
| options()->set_image_webp_recompress_quality_for_small_screens(50); |
| options()->set_image_webp_quality_for_save_data(30); |
| |
| RewriteOptions::AllowVaryOn allow_vary_on; |
| EXPECT_TRUE(RewriteOptions::ParseFromString(allow_vary_on_string, |
| &allow_vary_on)); |
| options()->set_allow_vary_on(allow_vary_on); |
| } |
| |
| void IproFetchAndValidateWithHeaders( |
| const char* image_name, const char* user_agent, |
| const OptimizedImageInfoList& optimized_info_list) { |
| IproFetchAndValidate(image_name, user_agent, |
| false /* save-data header */, |
| true /* via header */, |
| optimized_info_list.with_via); |
| |
| IproFetchAndValidate(image_name, user_agent, |
| false /* save-data header */, |
| false /* via header */, |
| optimized_info_list.with_none); |
| |
| IproFetchAndValidate(image_name, user_agent, |
| true /* save-data header */, |
| true /* via header */, |
| optimized_info_list.with_savedata_via); |
| |
| IproFetchAndValidate(image_name, user_agent, |
| true /* save-data header */, |
| false /* via header */, |
| optimized_info_list.with_savedata); |
| } |
| |
| // Helper class to collect image srcs. |
| class ImageCollector : public EmptyHtmlFilter { |
| public: |
| ImageCollector(HtmlParse* html_parse, StringVector* img_srcs) |
| : img_srcs_(img_srcs) { |
| } |
| |
| virtual void StartElement(HtmlElement* element) { |
| resource_tag_scanner::UrlCategoryVector attributes; |
| NullThreadSystem thread_system; |
| RewriteOptions options(&thread_system); |
| resource_tag_scanner::ScanElement(element, &options, &attributes); |
| for (int i = 0, n = attributes.size(); i < n; ++i) { |
| if (attributes[i].category == semantic_type::kImage) { |
| img_srcs_->push_back(attributes[i].url->DecodedValueOrNull()); |
| } |
| } |
| } |
| |
| virtual const char* Name() const { return "ImageCollector"; } |
| |
| private: |
| StringVector* img_srcs_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ImageCollector); |
| }; |
| |
| // Fills `img_srcs` with the urls in img src attributes in `html` |
| void CollectImgSrcs(const StringPiece& id, const StringPiece& html, |
| StringVector* img_srcs) { |
| HtmlParse html_parse(&message_handler_); |
| ImageCollector collector(&html_parse, img_srcs); |
| html_parse.AddFilter(&collector); |
| GoogleString dummy_url = StrCat("http://collect.css.links/", id, ".html"); |
| html_parse.StartParse(dummy_url); |
| html_parse.ParseText(html.data(), html.size()); |
| html_parse.FinishParse(); |
| } |
| |
| void DataUrlResource() { |
| static const char* kCuppaData = "data:image/png;base64," |
| "iVBORw0KGgoAAAANSUhEUgAAAEEAAABGCAAAAAC2maYhAAAC00lEQVQY0+3PTUhUYR" |
| "QG4HdmMhUaC6FaKSqEZS2MsEJEsaKSwMKgot2QkkKFUFBYWgSpGIhSZH+0yAgLDQ3p" |
| "ByoLRS2DjCjEfm0MzQhK08wZ5/Sde12kc8f5DrXLs3lfPs55uBf0t4MZ4X8QLjeY2X" |
| "C80cieUq9M6MB6I7tDcMgoRWgVCb5VyDLKFuCK8RCHMpFwEzjA+coGdHJ5COwRCSnA" |
| "Jc4cwOnlshs4KhFeA+jib48A1hovK4A6iXADiOB8oyQXF28Y0CIRKgDHsMoeJaTyw6" |
| "gDOC0RGtXlPS5RQOgAlwQgWSK4lZDDZacqxVyOqNIpECgSiBxTeVsdRo/z/9iBXImw" |
| "TV3eUemLU6WRXzYCziGB0KAOs7kUqLKZS40qVwVCr9qP4vJElblc3KocFAi+cMD2U5" |
| "VBdYhPqgyp3CcQKEYdDHCZDYT/mviYa5JvCANiubxTh2u4XAAcfQLhgzrM51KjSjmX" |
| "FGAvCYRTQGgvlwwggX/iGbDwm0RIAwo439tga+biAqpJIHy2I36Uyxkgl7MnBJkkEV" |
| "4AtUbJQvwP86/m94uE71juM8piPDayDOdJJNDKFjMzNpl5fcmYUPBMZIfbzBE3CQXB" |
| "TBIuHtaYwo5phHToTMk0QqaWUNxUUXrui7XggvZEFI9YCfu1AQeQbiWc0LrOe9D11Z" |
| "cNtFsIVVpCG696YrHVQqjVAezDxm4hEi2ElzpCvLl7EkkWwliIhrDD3K1EsoVASzWE" |
| "UnM1DbushO0aQpux2Qw8shJKggPzvLzYl4BYn5XQHVzI4r2Pi4CzZCVQUlChimi0cg" |
| "GQR9ZCRVDhbl1RtIoNngBC/yzozLJqLwUQqCjotTPR1fTnxVTBs3ra89T6/ikHfgK9" |
| "dQa+t1eS//gJVB8WUCgnLYHaYwIAeaQp0GC25S8cG9cWiOrm+AHrnhMJBLplmwLkE8" |
| "kEenp/8oyIBf2ZEWaEfyv8BsICdAZ/XeTCAAAAAElFTkSuQmCC"; |
| GoogleString cuppa_string(kCuppaData); |
| ResourcePtr cuppa_resource( |
| rewrite_driver()->CreateInputResourceAbsoluteUncheckedForTestsOnly( |
| cuppa_string)); |
| ASSERT_TRUE(cuppa_resource.get() != NULL); |
| EXPECT_TRUE(ReadIfCached(cuppa_resource)); |
| GoogleString cuppa_contents; |
| cuppa_resource->ExtractUncompressedContents().CopyToString(&cuppa_contents); |
| // Now make sure axing the original cuppa_string doesn't affect the |
| // internals of the cuppa_resource. |
| ResourcePtr other_resource( |
| rewrite_driver()->CreateInputResourceAbsoluteUncheckedForTestsOnly( |
| cuppa_string)); |
| ASSERT_TRUE(other_resource.get() != NULL); |
| cuppa_string.clear(); |
| EXPECT_TRUE(ReadIfCached(other_resource)); |
| GoogleString other_contents; |
| cuppa_resource->ExtractUncompressedContents().CopyToString(&other_contents); |
| ASSERT_EQ(cuppa_contents, other_contents); |
| } |
| |
| // Helper to test for how we handle trailing junk in URLs |
| void TestCorruptUrl(StringPiece junk, bool append_junk) { |
| const char kHtml[] = |
| "<img src=\"a.jpg\"><img src=\"b.png\"><img src=\"c.gif\">"; |
| AddFileToMockFetcher(StrCat(kTestDomain, "a.jpg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| |
| AddFileToMockFetcher(StrCat(kTestDomain, "b.png"), kBikePngFile, |
| kContentTypePng, 100); |
| |
| AddFileToMockFetcher(StrCat(kTestDomain, "c.gif"), kChefGifFile, |
| kContentTypeGif, 100); |
| |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| rewrite_driver()->AddFilters(); |
| |
| StringVector img_srcs; |
| ImageCollector image_collect(rewrite_driver(), &img_srcs); |
| rewrite_driver()->AddFilter(&image_collect); |
| |
| ParseUrl(kTestDomain, kHtml); |
| ASSERT_EQ(3, img_srcs.size()); |
| GoogleString normal_output = output_buffer_; |
| GoogleString url1 = img_srcs[0]; |
| GoogleString url2 = img_srcs[1]; |
| GoogleString url3 = img_srcs[2]; |
| |
| GoogleUrl gurl1(html_gurl(), url1); |
| GoogleUrl gurl2(html_gurl(), url2); |
| GoogleUrl gurl3(html_gurl(), url3); |
| |
| // Fetch messed up versions. Currently image rewriter doesn't actually |
| // fetch them. |
| GoogleString out; |
| EXPECT_TRUE(FetchResourceUrl(ChangeSuffix(gurl1.Spec(), append_junk, |
| ".jpg", junk), &out)); |
| EXPECT_TRUE(FetchResourceUrl(ChangeSuffix(gurl2.Spec(), append_junk, |
| ".png", junk), &out)); |
| // This actually has .png in the output since we convert gif -> png. |
| EXPECT_TRUE(FetchResourceUrl(ChangeSuffix(gurl3.Spec(), append_junk, |
| ".png", junk), &out)); |
| |
| // Now run through again to make sure we didn't cache the messed up URL |
| img_srcs.clear(); |
| ParseUrl(kTestDomain, kHtml); |
| EXPECT_EQ(normal_output, output_buffer_); |
| ASSERT_EQ(3, img_srcs.size()); |
| EXPECT_EQ(url1, img_srcs[0]); |
| EXPECT_EQ(url2, img_srcs[1]); |
| EXPECT_EQ(url3, img_srcs[2]); |
| } |
| |
| // Fetch a simple document referring to an image with filename "name" on a |
| // mock domain. Check that final dimensions are as expected, that rewriting |
| // occurred as expected, and that inlining occurred if that was anticipated. |
| // Assumes rewrite_driver has already been appropriately configured for the |
| // image rewrites under test. |
| void TestSingleRewrite(const StringPiece& name, |
| const ContentType& input_type, |
| const ContentType& output_type, |
| const char* initial_attributes, |
| const char* final_attributes, |
| bool expect_rewritten, |
| bool expect_inline) { |
| GoogleString initial_url = StrCat(kTestDomain, name); |
| TestSingleRewriteWithoutAbs(initial_url, name, input_type, output_type, |
| initial_attributes, final_attributes, expect_rewritten, expect_inline); |
| } |
| |
| void TestSingleRewriteWithoutAbs(const GoogleString& initial_url, |
| const StringPiece& name, |
| const ContentType& input_type, |
| const ContentType& output_type, |
| const char* initial_attributes, |
| const char* final_attributes, |
| bool expect_rewritten, |
| bool expect_inline) { |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| AddFileToMockFetcher(initial_url, name, input_type, 100); |
| |
| const char html_boilerplate[] = "<img src='%s'%s>"; |
| GoogleString html_input = |
| StringPrintf(html_boilerplate, initial_url.c_str(), initial_attributes); |
| |
| ParseUrl(page_url, html_input); |
| |
| // Check for single image file in the rewritten page. |
| StringVector image_urls; |
| CollectImgSrcs(initial_url, output_buffer_, &image_urls); |
| EXPECT_EQ(1, image_urls.size()); |
| const GoogleString& rewritten_url = image_urls[0]; |
| const GoogleUrl rewritten_gurl(rewritten_url); |
| EXPECT_TRUE(rewritten_gurl.IsWebOrDataValid()) << rewritten_url; |
| |
| if (expect_inline) { |
| EXPECT_TRUE(rewritten_gurl.SchemeIs("data")) |
| << rewritten_gurl.spec_c_str(); |
| GoogleString expected_start = |
| StrCat("data:", output_type.mime_type(), ";base64,"); |
| EXPECT_TRUE(rewritten_gurl.Spec().starts_with(expected_start)) |
| << "expected " << expected_start << " got " << rewritten_url; |
| } else if (expect_rewritten) { |
| EXPECT_NE(initial_url, rewritten_url); |
| EXPECT_TRUE(rewritten_gurl.LeafSansQuery().ends_with( |
| output_type.file_extension())) |
| << "expected end " << output_type.file_extension() |
| << " got " << rewritten_gurl.LeafSansQuery(); |
| } else { |
| EXPECT_EQ(initial_url, rewritten_url); |
| EXPECT_TRUE(rewritten_gurl.LeafSansQuery().ends_with( |
| output_type.file_extension())) |
| << "expected end " << output_type.file_extension() |
| << " got " << rewritten_gurl.LeafSansQuery(); |
| } |
| |
| GoogleString html_expected_output = |
| StringPrintf(html_boilerplate, rewritten_url.c_str(), final_attributes); |
| EXPECT_EQ(AddHtmlBody(html_expected_output), output_buffer_); |
| } |
| |
| // Returns the property cache value for kInlinableImageUrlsPropertyName, |
| // or NULL if it is not present. |
| const PropertyValue* FetchInlinablePropertyCacheValue() { |
| PropertyCache* pcache = page_property_cache(); |
| if (pcache == NULL) { |
| return NULL; |
| } |
| const PropertyCache::Cohort* cohort = pcache->GetCohort( |
| RewriteDriver::kDomCohort); |
| if (cohort == NULL) { |
| return NULL; |
| } |
| PropertyPage* property_page = rewrite_driver()->property_page(); |
| if (property_page == NULL) { |
| return NULL; |
| } |
| return property_page->GetProperty( |
| cohort, ImageRewriteFilter::kInlinableImageUrlsPropertyName); |
| } |
| |
| // Test dimensions of an optimized image by fetching it. |
| void TestDimensionRounding( |
| StringPiece leaf, int expected_width, int expected_height) { |
| GoogleString initial_url = StrCat(kTestDomain, kPuzzleJpgFile); |
| GoogleString fetch_url = StrCat(kTestDomain, leaf); |
| AddFileToMockFetcher(initial_url, kPuzzleJpgFile, kContentTypeJpeg, 100); |
| // Set up resizing |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| // Perform resource fetch |
| ExpectStringAsyncFetch expect_callback(true, CreateRequestContext()); |
| EXPECT_TRUE(rewrite_driver()->FetchResource(fetch_url, &expect_callback)); |
| rewrite_driver()->WaitForCompletion(); |
| EXPECT_EQ(HttpStatus::kOK, |
| expect_callback.response_headers()->status_code()) << |
| "Looking for " << fetch_url; |
| // Look up dimensions of resulting image |
| scoped_ptr<Image> image( |
| NewImage(expect_callback.buffer(), |
| fetch_url, server_context_->filename_prefix(), |
| new Image::CompressionOptions(), |
| timer(), &message_handler_)); |
| ImageDim image_dim; |
| image->Dimensions(&image_dim); |
| EXPECT_EQ(expected_width, image_dim.width()); |
| EXPECT_EQ(expected_height, image_dim.height()); |
| } |
| |
| void TestTranscodeAndOptimizePng(bool expect_rewritten, |
| const char* width_height_tags, |
| const ContentType& expected_type) { |
| // Make sure we convert png to jpeg if we requested that. |
| // We lower compression quality to ensure the jpeg is smaller. |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->set_image_jpeg_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, expected_type, |
| "", width_height_tags, expect_rewritten, false); |
| } |
| |
| void TestConversionVariables(int gif_webp_timeout, |
| int gif_webp_success, |
| int gif_webp_failure, |
| |
| int png_webp_timeout, |
| int png_webp_success, |
| int png_webp_failure, |
| |
| int jpeg_webp_timeout, |
| int jpeg_webp_success, |
| int jpeg_webp_failure, |
| |
| int gif_webp_animated_timeout, |
| int gif_webp_animated_success, |
| int gif_webp_animated_failure, |
| |
| bool is_opaque) { |
| EXPECT_EQ( |
| gif_webp_timeout, |
| statistics()->GetVariable( |
| ImageRewriteFilter::kImageWebpFromGifTimeouts)-> |
| Get()); |
| EXPECT_EQ( |
| gif_webp_success, |
| statistics()->GetHistogram( |
| ImageRewriteFilter::kImageWebpFromGifSuccessMs)-> |
| Count()); |
| EXPECT_EQ( |
| gif_webp_failure, |
| statistics()->GetHistogram( |
| ImageRewriteFilter::kImageWebpFromGifFailureMs)-> |
| Count()); |
| |
| EXPECT_EQ( |
| png_webp_timeout, |
| statistics()->GetVariable( |
| ImageRewriteFilter::kImageWebpFromPngTimeouts)-> |
| Get()); |
| EXPECT_EQ( |
| png_webp_success, |
| statistics()->GetHistogram( |
| ImageRewriteFilter::kImageWebpFromPngSuccessMs)-> |
| Count()); |
| EXPECT_EQ( |
| png_webp_failure, |
| statistics()->GetHistogram( |
| ImageRewriteFilter::kImageWebpFromPngFailureMs)-> |
| Count()); |
| |
| EXPECT_EQ( |
| jpeg_webp_timeout, |
| statistics()->GetVariable( |
| ImageRewriteFilter::kImageWebpFromJpegTimeouts)-> |
| Get()); |
| EXPECT_EQ( |
| jpeg_webp_success, |
| statistics()->GetHistogram( |
| ImageRewriteFilter::kImageWebpFromJpegSuccessMs)-> |
| Count()); |
| EXPECT_EQ( |
| jpeg_webp_failure, |
| statistics()->GetHistogram( |
| ImageRewriteFilter::kImageWebpFromJpegFailureMs)-> |
| Count()); |
| |
| EXPECT_EQ( |
| gif_webp_animated_timeout, |
| statistics()->GetVariable( |
| ImageRewriteFilter::kImageWebpFromGifAnimatedTimeouts)-> |
| Get()); |
| EXPECT_EQ( |
| gif_webp_animated_success, |
| statistics()->GetHistogram( |
| ImageRewriteFilter::kImageWebpFromGifAnimatedSuccessMs)-> |
| Count()); |
| EXPECT_EQ( |
| gif_webp_animated_failure, |
| statistics()->GetHistogram( |
| ImageRewriteFilter::kImageWebpFromGifAnimatedFailureMs)-> |
| Count()); |
| |
| int total_timeout = |
| gif_webp_timeout + |
| png_webp_timeout + |
| jpeg_webp_timeout + |
| gif_webp_animated_timeout; |
| int total_success = |
| gif_webp_success + |
| png_webp_success + |
| jpeg_webp_success + |
| gif_webp_animated_success; |
| int total_failure = |
| gif_webp_failure + |
| png_webp_failure + |
| jpeg_webp_failure + |
| gif_webp_animated_failure; |
| |
| EXPECT_EQ( |
| total_timeout, |
| statistics()->GetVariable( |
| is_opaque ? |
| ImageRewriteFilter::kImageWebpOpaqueTimeouts : |
| ImageRewriteFilter::kImageWebpWithAlphaTimeouts)->Get()); |
| EXPECT_EQ( |
| total_success, |
| statistics()->GetHistogram( |
| is_opaque ? |
| ImageRewriteFilter::kImageWebpOpaqueSuccessMs : |
| ImageRewriteFilter::kImageWebpWithAlphaSuccessMs)->Count()); |
| EXPECT_EQ( |
| total_failure, |
| statistics()->GetHistogram( |
| is_opaque ? |
| ImageRewriteFilter::kImageWebpOpaqueFailureMs : |
| ImageRewriteFilter::kImageWebpWithAlphaFailureMs)->Count()); |
| } |
| |
| // Verify log for background image rewriting. To skip url, pass in an empty |
| // string. To skip original_size or optimized_size, pass in kIgnoreSize. |
| void TestBackgroundRewritingLog( |
| int rewrite_info_size, |
| int rewrite_info_index, |
| RewriterApplication::Status status, |
| const char* id, |
| const GoogleString& url, |
| ImageType original_type, |
| ImageType optimized_type, |
| int original_size, |
| int optimized_size, |
| bool is_recompressed, |
| bool is_resized, |
| int original_width, |
| int original_height, |
| bool is_resized_using_rendered_dimensions, |
| int resized_width, |
| int resized_height) { |
| // Check URL. |
| net_instaweb::ResourceUrlInfo* url_info = |
| logging_info_.mutable_resource_url_info(); |
| if (!url.empty()) { |
| EXPECT_LT(0, url_info->url_size()); |
| if (url_info->url_size() > 0) { |
| EXPECT_EQ(url, url_info->url(0)); |
| } |
| } else { |
| EXPECT_EQ(0, url_info->url_size()); |
| } |
| |
| EXPECT_EQ(rewrite_info_size, logging_info_.rewriter_info_size()); |
| const RewriterInfo& rewriter_info = |
| logging_info_.rewriter_info(rewrite_info_index); |
| EXPECT_EQ(id, rewriter_info.id()); |
| EXPECT_EQ(status, rewriter_info.status()); |
| |
| ASSERT_TRUE(rewriter_info.has_rewrite_resource_info()); |
| const RewriteResourceInfo& resource_info = |
| rewriter_info.rewrite_resource_info(); |
| |
| if (original_size != kIgnoreSize) { |
| EXPECT_EQ(original_size, resource_info.original_size()); |
| } |
| if (optimized_size != kIgnoreSize) { |
| EXPECT_EQ(optimized_size, resource_info.optimized_size()); |
| } |
| EXPECT_EQ(is_recompressed, resource_info.is_recompressed()); |
| |
| ASSERT_TRUE(rewriter_info.has_image_rewrite_resource_info()); |
| const ImageRewriteResourceInfo& image_info = |
| rewriter_info.image_rewrite_resource_info(); |
| EXPECT_EQ(original_type, image_info.original_image_type()); |
| EXPECT_EQ(optimized_type, image_info.optimized_image_type()); |
| EXPECT_EQ(is_resized, image_info.is_resized()); |
| EXPECT_EQ(original_width, image_info.original_width()); |
| EXPECT_EQ(original_height, image_info.original_height()); |
| EXPECT_EQ(is_resized_using_rendered_dimensions, |
| image_info.is_resized_using_rendered_dimensions()); |
| EXPECT_EQ(resized_width, image_info.resized_width()); |
| EXPECT_EQ(resized_height, image_info.resized_height()); |
| } |
| |
| void TestForRenderedDimensions(MockCriticalImagesFinder* finder, |
| int width, int height, |
| int expected_width, int expected_height, |
| const char* dimensions_attribute, |
| const GoogleString& expected_rewritten_url, |
| int num_rewrites_using_rendered_dimensions) { |
| RenderedImages* rendered_images = new RenderedImages; |
| RenderedImages_Image* images = rendered_images->add_image(); |
| images->set_src(StrCat(kTestDomain, kChefGifFile)); |
| if (width != 0) { |
| images->set_rendered_width(width); |
| } |
| if (height != 0) { |
| images->set_rendered_height(height); |
| } |
| |
| // Original size of kChefGifFile is 192x256 |
| finder->set_rendered_images(rendered_images); |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypePng, |
| dimensions_attribute, dimensions_attribute, true, false); |
| |
| // Check for single image file in the rewritten page. |
| StringVector image_urls; |
| CollectImgSrcs(kChefGifFile, output_buffer_, &image_urls); |
| EXPECT_EQ(1, image_urls.size()); |
| const GoogleString& rewritten_url = image_urls[0]; |
| |
| EXPECT_STREQ(rewritten_url, expected_rewritten_url); |
| GoogleString output_png; |
| EXPECT_TRUE(FetchResourceUrl(rewritten_url, &output_png)); |
| // Check if we resized to rendered dimensions. |
| scoped_ptr<Image> image( |
| NewImage(output_png, rewritten_url, server_context_->filename_prefix(), |
| new Image::CompressionOptions(), |
| timer(), &message_handler_)); |
| ImageDim image_dim; |
| image->Dimensions(&image_dim); |
| EXPECT_EQ(expected_width, image_dim.width()); |
| EXPECT_EQ(expected_height, image_dim.height()); |
| Variable* resized_using_rendered_dimensions = statistics()->GetVariable( |
| ImageRewriteFilter::kImageResizedUsingRenderedDimensions); |
| EXPECT_EQ(num_rewrites_using_rendered_dimensions, |
| resized_using_rendered_dimensions->Get()); |
| resized_using_rendered_dimensions->Clear(); |
| } |
| |
| virtual RequestContextPtr CreateRequestContext() { |
| return RequestContextPtr(test_request_context_); |
| } |
| |
| // Fetches a URL for the given user-agent, returning success-status, |
| // and modifying content and response if successful. Statistics are |
| // cleared on each call. |
| bool FetchWebp(StringPiece url, StringPiece user_agent, |
| GoogleString* content, ResponseHeaders* response) { |
| content->clear(); |
| response->Clear(); |
| ClearStats(); |
| if (user_agent == "webp") { |
| ResetForWebp(); |
| } else { |
| ResetUserAgent(user_agent); |
| } |
| return FetchResourceUrl(url, content, response); |
| } |
| |
| void IProFetchAndValidate( |
| StringPiece url, StringPiece user_agent, StringPiece accept, |
| ResponseHeaders* response) { |
| ClearRewriteDriver(); |
| if (!user_agent.empty()) { |
| SetCurrentUserAgent(user_agent); |
| } |
| if (!accept.empty()) { |
| AddRequestAttribute(HttpAttributes::kAccept, accept); |
| } |
| GoogleString content_ignored; |
| response->Clear(); |
| EXPECT_TRUE(FetchResourceUrl(url, &content_ignored, response)); |
| const char* etag = response->Lookup1(HttpAttributes::kEtag); |
| EXPECT_EQ(0, GoogleString(etag).find("W/\"PSA-aj-")) << etag; |
| } |
| |
| void IproFetchAndValidate( |
| const char* image_name, StringPiece user_agent, bool has_save_data_header, |
| bool has_via_header, |
| const OptimizedImageInfo& expected_optimized_image_info) { |
| GoogleString url = StrCat(kTestDomain, image_name); |
| const ContentType* expected_content_type = |
| expected_optimized_image_info.content_type; |
| const char* expected_vary_header = |
| expected_optimized_image_info.vary_header; |
| int expected_content_length = expected_optimized_image_info.content_length; |
| |
| GoogleString response_content; |
| ResponseHeaders response_headers; |
| ClearRewriteDriver(); |
| if (!user_agent.empty()) { |
| SetCurrentUserAgent(user_agent); |
| } |
| if (user_agent.find("Chrome/") != StringPiece::npos) { |
| AddRequestAttribute(HttpAttributes::kAccept, "image/webp"); |
| } |
| if (has_save_data_header) { |
| AddRequestAttribute(HttpAttributes::kSaveData, "on"); |
| } |
| if (has_via_header) { |
| AddRequestAttribute(HttpAttributes::kVia, "proxy"); |
| } |
| |
| EXPECT_TRUE(FetchResourceUrl(url, &response_content, &response_headers)); |
| |
| EXPECT_EQ(expected_content_type->type(), |
| response_headers.DetermineContentType()->type()) << |
| response_headers.DetermineContentType()->mime_type(); |
| |
| if (expected_vary_header != NULL) { |
| ConstStringStarVector vary_header_vector; |
| EXPECT_TRUE(response_headers.Lookup(HttpAttributes::kVary, |
| &vary_header_vector)); |
| GoogleString vary_header = JoinStringStar(vary_header_vector, ","); |
| EXPECT_STREQ(expected_vary_header, vary_header); |
| } else { |
| EXPECT_FALSE(response_headers.Has(HttpAttributes::kVary)); |
| } |
| |
| // Because the image encoder may change behavior, content length of the |
| // optimized image may change value slightly. To be resistant to such |
| // change, we check the content size in a ragne, in stead of the exact |
| // value. The range is defined by variable "threshold". |
| const int threshold = 30; |
| int content_length = response_content.length(); |
| EXPECT_LE(expected_content_length - threshold, content_length) |
| << content_length; |
| EXPECT_GE(expected_content_length + threshold, content_length) |
| << content_length; |
| } |
| |
| void TestResolutionLimit(int resolution, const char* image_file, |
| const ContentType& content_type, bool try_webp, |
| bool try_resize, bool expect_rewritten) { |
| SetupForWebpLossless(); |
| options()->set_image_resolution_limit_bytes(resolution); |
| options()->set_image_jpeg_recompress_quality(85); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| |
| ContentType rewritten_type = content_type; |
| if (try_webp) { |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpLossless); |
| if (expect_rewritten) { |
| rewritten_type = kContentTypeWebp; |
| } |
| } |
| |
| const char* dimension = NULL; |
| if (try_resize) { |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| dimension = " width=4000 height=2000"; |
| } else { |
| dimension = ""; |
| } |
| rewrite_driver()->AddFilters(); |
| |
| TestSingleRewrite(image_file, content_type, rewritten_type, dimension, |
| dimension, expect_rewritten, false); |
| |
| Variable* image_rewrites = statistics()->GetVariable( |
| ImageRewriteFilter::kImageRewrites); |
| Variable* no_rewrites = statistics()->GetVariable( |
| ImageRewriteFilter::kImageNoRewritesHighResolution); |
| if (expect_rewritten) { |
| EXPECT_EQ(1, image_rewrites->Get()); |
| EXPECT_EQ(0, no_rewrites->Get()); |
| } else { |
| EXPECT_EQ(0, image_rewrites->Get()); |
| EXPECT_EQ(1, no_rewrites->Get()); |
| } |
| } |
| |
| void ResetUserAgent(StringPiece user_agent) { |
| ClearRewriteDriver(); |
| SetCurrentUserAgent(user_agent); |
| SetDriverRequestHeaders(); |
| } |
| |
| void ResetForWebp() { |
| ClearRewriteDriver(); |
| SetupForWebp(); |
| SetDriverRequestHeaders(); |
| } |
| |
| void MarkTooBusyToWork() { |
| // Set the current # of rewrites very high, so we stop doing more |
| // due to "load". |
| UpDownCounter* ongoing_rewrites = statistics()->GetUpDownCounter( |
| CompatibleCentralController::kCurrentExpensiveOperations); |
| ongoing_rewrites->Set(100); |
| } |
| |
| void UnMarkTooBusyToWork() { |
| UpDownCounter* ongoing_rewrites = statistics()->GetUpDownCounter( |
| CompatibleCentralController::kCurrentExpensiveOperations); |
| ongoing_rewrites->Set(0); |
| } |
| |
| private: |
| LoggingInfo logging_info_; |
| TestRequestContextPtr test_request_context_; |
| }; |
| |
| TEST_F(ImageRewriteTest, ImgTag) { |
| RewriteImage("img", kContentTypeJpeg); |
| } |
| |
| TEST_F(ImageRewriteTest, ImgTagWithComputeStatistics) { |
| options()->EnableFilter(RewriteOptions::kComputeStatistics); |
| RewriteImage("img", kContentTypeJpeg); |
| EXPECT_EQ(1, rewrite_driver()->dom_stats_filter()->num_img_tags()); |
| EXPECT_EQ(0, rewrite_driver()->dom_stats_filter()->num_inlined_img_tags()); |
| } |
| |
| TEST_F(ImageRewriteTest, ImgTagWebp) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| // We use the webp testing user agent; real webp-capable user agents are |
| // tested as part of user_agent_matcher_test and are likely to remain in flux |
| // over time. |
| SetupForWebp(); |
| RewriteImage("img", kContentTypeWebp); |
| } |
| |
| TEST_F(ImageRewriteTest, ImgTagWebpLa) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| // We use the webp testing user agent; real webp-capable user agents are |
| // tested as part of user_agent_matcher_test and are likely to remain in flux |
| // over time. |
| SetupForWebpLossless(); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpLossless); |
| |
| RewriteImage("img", kContentTypeWebp); |
| } |
| |
| TEST_F(ImageRewriteTest, InputTag) { |
| RewriteImage("input type=\"image\"", kContentTypeJpeg); |
| } |
| |
| TEST_F(ImageRewriteTest, InputTagWebp) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| // We use the webp testing user agent; real webp-capable user agents are |
| // tested as part of user_agent_matcher_test and are likely to remain in flux |
| // over time. |
| SetupForWebp(); |
| RewriteImage("input type=\"image\"", kContentTypeWebp); |
| } |
| |
| TEST_F(ImageRewriteTest, InputTagWebpLa) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| // We use the webp-la testing user agent; real webp-capable user agents are |
| // tested as part of user_agent_matcher_test and are likely to remain in flux |
| // over time. |
| SetupForWebpLossless(); |
| |
| // Note that, currently, images that are originally jpegs are |
| // converted to webp lossy regardless of this filter below. |
| options()->EnableFilter(RewriteOptions::kConvertToWebpLossless); |
| |
| RewriteImage("input type=\"image\"", kContentTypeWebp); |
| } |
| |
| TEST_F(ImageRewriteTest, DataUrlTest) { |
| DataUrlResource(); |
| } |
| |
| TEST_F(ImageRewriteTest, AddDimTest) { |
| Histogram* rewrite_latency_ok = statistics()->GetHistogram( |
| ImageRewriteFilter::kImageRewriteLatencyOkMs); |
| Histogram* rewrite_latency_failed = statistics()->GetHistogram( |
| ImageRewriteFilter::kImageRewriteLatencyFailedMs); |
| rewrite_latency_ok->Clear(); |
| rewrite_latency_failed->Clear(); |
| |
| // Make sure optimizable image isn't optimized, but |
| // dimensions are inserted. |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| "", " width=\"100\" height=\"100\"", false, false); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, rewrite_latency_ok->Count()); |
| EXPECT_EQ(1, rewrite_latency_failed->Count()); |
| |
| // Force any image read to be a fetch. |
| lru_cache()->Delete(HttpCacheKey(StrCat(kTestDomain, kBikePngFile))); |
| |
| // .. Now make sure we cached dimension insertion properly, and can do it |
| // without re-fetching the image. |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| "", " width=\"100\" height=\"100\"", false, false); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(ImageRewriteTest, NoDimsInNonImg) { |
| // As above, only with an icon. See: |
| // https://code.google.com/p/modpagespeed/issues/detail?id=629 |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| rewrite_driver()->AddFilters(); |
| GoogleString initial_url = StrCat(kTestDomain, kBikePngFile); |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| AddFileToMockFetcher(initial_url, kBikePngFile, kContentTypePng, 100); |
| |
| const char html_boilerplate[] = |
| "<link rel='apple-touch-icon-precomposed' sizes='100x100' href='%s'>"; |
| GoogleString html_input = |
| StringPrintf(html_boilerplate, initial_url.c_str()); |
| |
| ParseUrl(page_url, html_input); |
| |
| GoogleString html_expected_output = |
| StringPrintf(html_boilerplate, initial_url.c_str()); |
| EXPECT_EQ(AddHtmlBody(html_expected_output), output_buffer_); |
| } |
| |
| TEST_F(ImageRewriteTest, PngToJpeg) { |
| TestTranscodeAndOptimizePng(true, " width=\"100\" height=\"100\"", |
| kContentTypeJpeg); |
| } |
| |
| TEST_F(ImageRewriteTest, PngToJpegUnhealthy) { |
| lru_cache()->set_is_healthy(false); |
| TestTranscodeAndOptimizePng(false, "", kContentTypePng); |
| } |
| |
| TEST_F(ImageRewriteTest, PngToWebpWithWebpUa) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| // Make sure we convert png to webp if user agent permits. |
| // We lower compression quality to ensure the webp is smaller. |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| SetupForWebp(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypeWebp, |
| "", " width=\"100\" height=\"100\"", true, false); |
| TestConversionVariables(0, 0, 0, // gif |
| 0, 1, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageRewriteTest, PngToWebpWithWebpLaUa) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| // Make sure we convert png to webp if user agent permits. |
| // We lower compression quality to ensure the webp is smaller. |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpLossless(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypeWebp, |
| "", " width=\"100\" height=\"100\"", true, false); |
| TestConversionVariables(0, 0, 0, // gif |
| 0, 1, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageRewriteTest, PngToWebpWithWebpLaUaAndFlag) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| // Make sure we convert png to webp if user agent permits. |
| // We lower compression quality to ensure the webp is smaller. |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpLossless); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_allow_logging_urls_in_log_record(true); |
| options()->set_image_recompress_quality(85); |
| options()->set_log_background_rewrites(true); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpLossless(); |
| |
| TestSingleRewrite(kRedbrushAlphaPngFile, kContentTypePng, kContentTypeWebp, |
| "", " width=\"512\" height=\"480\"", true, false); |
| TestConversionVariables(0, 0, 0, // gif |
| 0, 1, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| false); |
| |
| // Imge is recompressed but not resized. |
| rewrite_driver()->Clear(); |
| TestBackgroundRewritingLog( |
| 1, /* rewrite_info_size */ |
| 0, /* rewrite_info_index */ |
| RewriterApplication::APPLIED_OK, /* status */ |
| "ic", /* rewrite ID */ |
| "", /* URL */ |
| IMAGE_PNG, /* original_type */ |
| IMAGE_WEBP_LOSSLESS_OR_ALPHA, /* optimized_type */ |
| 115870, /* original_size */ |
| kIgnoreSize, /* optimized_size */ |
| true, /* is_recompressed */ |
| false, /* is_resized */ |
| 512, /* original width */ |
| 480, /* original height */ |
| false, /* is_resized_using_rendered_dimensions */ |
| -1, /* resized_width */ |
| -1 /* resized_height */); |
| } |
| |
| // The settings are the same as "PngToWebpWithWebpLaUaAndFlag" except |
| // WebP lossless user agent. So conversion falls back to PNG. |
| TEST_F(ImageRewriteTest, PngFallbackToPngLackOfWebpLaUa) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpLossless); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_allow_logging_urls_in_log_record(true); |
| options()->set_image_recompress_quality(85); |
| options()->set_log_background_rewrites(true); |
| rewrite_driver()->AddFilters(); |
| |
| TestSingleRewrite(kRedbrushAlphaPngFile, kContentTypePng, kContentTypePng, |
| "", " width=\"512\" height=\"480\"", true, false); |
| TestConversionVariables(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| false); |
| } |
| |
| TEST_F(ImageRewriteTest, PngToWebpWithWebpLaUaAndFlagTimesOut) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| // Make sure we convert png to webp if user agent permits. |
| // We lower compression quality to ensure the webp is smaller. |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpLossless); |
| options()->set_image_recompress_quality(85); |
| options()->set_image_webp_timeout_ms(0); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpLossless(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypeJpeg, |
| "", " width=\"100\" height=\"100\"", true, false); |
| TestConversionVariables(0, 0, 0, // gif |
| 1, 0, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageRewriteTest, DistributedImageRewrite) { |
| // Distribute an image rewrite, make sure that the image is resized. |
| SetupSharedCache(); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options_->DistributeFilter(RewriteOptions::kImageCompressionId); |
| options_->set_distributed_rewrite_servers("example.com:80"); |
| options_->set_distributed_rewrite_key("1234123"); |
| other_options()->Merge(*options()); |
| rewrite_driver()->AddFilters(); |
| other_rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| " width=10 height=10", // initial_dims, |
| " width=10 height=10", // final_dims, |
| true, // expect_rewritten |
| false); // expect_inline |
| EXPECT_EQ(1, statistics()->GetVariable( |
| RewriteContext::kNumDistributedRewriteSuccesses)->Get()); |
| } |
| |
| TEST_F(ImageRewriteTest, DistributedImageInline) { |
| // Distribute an image rewrite, make sure that the inlined image is used from |
| // the returned metadata. |
| SetupSharedCache(); |
| options()->set_image_inline_max_bytes(1000000); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options_->DistributeFilter(RewriteOptions::kImageCompressionId); |
| options_->set_distributed_rewrite_servers("example.com:80"); |
| options_->set_distributed_rewrite_key("1234123"); |
| other_options()->Merge(*options()); |
| rewrite_driver()->AddFilters(); |
| other_rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, "", "", |
| true, // expect_rewritten |
| true); // expect_inline |
| EXPECT_EQ(1, statistics()->GetVariable( |
| RewriteContext::kNumDistributedRewriteSuccesses)->Get()); |
| GoogleString distributed_output = output_buffer_; |
| |
| // Run it again but this time without distributed rewriting, the output should |
| // be the same. |
| lru_cache()->Clear(); |
| ClearStats(); |
| // Clearing the distributed_rewrite_servers disables distribution. |
| options()->ClearSignatureForTesting(); |
| options_->set_distributed_rewrite_servers(""); |
| options()->ComputeSignature(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, "", "", |
| true, // expect_rewritten |
| true); // expect_inline |
| EXPECT_EQ(0, statistics()->GetVariable( |
| RewriteContext::kNumDistributedRewriteSuccesses)->Get()); |
| // Make sure we did a rewrite. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| |
| // Is the output from distributed rewriting and local rewriting the same? |
| EXPECT_STREQ(distributed_output, output_buffer_); |
| } |
| |
| TEST_F(ImageRewriteTest, ImageRewritePreserveURLsOnSoftEnable) { |
| // Make sure that the image URL stays the same when optimization is enabled |
| // due to core filters. |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRecompressPng); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kResizeImages); |
| options()->set_image_preserve_urls(true); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| " width=10 height=10", // initial_dims, |
| " width=10 height=10", // final_dims, |
| false, // expect_rewritten |
| false); // expect_inline |
| // The URL wasn't changed but the image should have been compressed and cached |
| // anyway (prefetching for IPRO). |
| ClearStats(); |
| GoogleString out_png_url(Encode(kTestDomain, "ic", "0", kBikePngFile, "png")); |
| GoogleString out_png; |
| EXPECT_TRUE(FetchResourceUrl(out_png_url, &out_png)); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, static_cast<int>(lru_cache()->num_hits())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts())); |
| |
| // Make sure that we didn't resize (original image is 100x100). |
| scoped_ptr<Image> image( |
| NewImage(out_png, out_png_url, server_context_->filename_prefix(), |
| new Image::CompressionOptions(), |
| timer(), &message_handler_)); |
| ImageDim image_dim; |
| image->Dimensions(&image_dim); |
| EXPECT_EQ(100, image_dim.width()); |
| EXPECT_EQ(100, image_dim.height()); |
| } |
| |
| TEST_F(ImageRewriteTest, ImageRewritePreserveURLsExplicitResizeOn) { |
| // Explicitly enabling resize_images is a strong signal from the user that |
| // it's OK to rename image URLs, so go ahead and do it in the image rewriter. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_image_preserve_urls(true); // Explicit filters override. |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| " width=10 height=10", // initial_dims, |
| " width=10 height=10", // final_dims, |
| true, // expect_rewritten: explicit cache_extend_images |
| false); // expect_inline |
| ClearStats(); |
| GoogleString out_png_url(StrCat( |
| kTestDomain, EncodeImage(10, 10, kBikePngFile, "0", "png"))); |
| |
| GoogleString out_png; |
| EXPECT_TRUE(FetchResourceUrl(out_png_url, &out_png)); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, static_cast<int>(lru_cache()->num_hits())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts())); |
| |
| // Make sure that we did the resize to 10x10 from 100x100. |
| scoped_ptr<Image> image( |
| NewImage(out_png, out_png_url, server_context_->filename_prefix(), |
| new Image::CompressionOptions(), |
| timer(), &message_handler_)); |
| ImageDim image_dim; |
| image->Dimensions(&image_dim); |
| EXPECT_EQ(10, image_dim.width()); |
| EXPECT_EQ(10, image_dim.height()); |
| } |
| |
| TEST_F(ImageRewriteTest, ImageRewritePreserveURLsDisablePreemptiveRewrite) { |
| // Make sure that the image URL stays the same. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->set_image_preserve_urls(true); |
| options()->set_in_place_preemptive_rewrite_images(false); |
| rewrite_driver()->AddFilters(); |
| ClearStats(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| " width=10 height=10", // initial_dims, |
| " width=10 height=10", // final_dims, |
| false, // expect_rewritten |
| false); // expect_inline |
| |
| // We should not have attempted any rewriting. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_hits())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts())); |
| |
| // But, a direct fetch should work. |
| ClearStats(); |
| GoogleString out_png_url(Encode(kTestDomain, "ic", "0", kBikePngFile, "png")); |
| GoogleString out_png; |
| EXPECT_TRUE(FetchResourceUrl(out_png_url, &out_png)); |
| // Make sure that we didn't resize (original image is 100x100). |
| scoped_ptr<Image> image( |
| NewImage(out_png, out_png_url, server_context_->filename_prefix(), |
| new Image::CompressionOptions(), |
| timer(), &message_handler_)); |
| ImageDim image_dim; |
| image->Dimensions(&image_dim); |
| EXPECT_EQ(100, image_dim.width()); |
| EXPECT_EQ(100, image_dim.height()); |
| } |
| |
| TEST_F(ImageRewriteTest, ImageRewriteInlinePreserveURLsOnSoftEnable) { |
| // Willing to inline large files. |
| options()->set_image_inline_max_bytes(1000000); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kInlineImages); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kInsertImageDimensions); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kConvertGifToPng); |
| options()->DisableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->set_image_preserve_urls(true); |
| rewrite_driver()->AddFilters(); |
| const char kResizedDims[] = " width=48 height=64"; |
| // File would be inlined without preserve urls, make sure it's not, |
| // because turning on image_preserve_urls overrides the implicit filter |
| // selection from Core filters. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kResizedDims, kResizedDims, |
| false, // expect_rewritten |
| false); // expect_inline |
| // The optimized file should be in the cache now. |
| ClearStats(); |
| GoogleString out_gif_url = Encode(kTestDomain, "ic", "0", kChefGifFile, |
| "png"); |
| GoogleString out_gif; |
| EXPECT_TRUE(FetchResourceUrl(out_gif_url, &out_gif)); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, static_cast<int>(lru_cache()->num_hits())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts())); |
| } |
| |
| TEST_F(ImageRewriteTest, ImageRewriteInlinePreserveURLsExplicit) { |
| // Willing to inline large files. |
| options()->set_image_inline_max_bytes(1000000); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->set_image_preserve_urls(true); |
| rewrite_driver()->AddFilters(); |
| const char kResizedDims[] = " width=48 height=64"; |
| // In this case, since we have explicitly requested inline images, |
| // we will get them despite the preserve URLs setting. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypePng, |
| kResizedDims, kResizedDims, |
| true, // expect_rewritten |
| true); // expect_inline |
| // The optimized file should be in the cache now. |
| ClearStats(); |
| GoogleString out_gif_url = Encode(kTestDomain, "ic", "0", kChefGifFile, |
| "png"); |
| GoogleString out_gif; |
| EXPECT_TRUE(FetchResourceUrl(out_gif_url, &out_gif)); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, static_cast<int>(lru_cache()->num_hits())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts())); |
| } |
| |
| TEST_F(ImageRewriteTest, NoTransform) { |
| // Make sure that the image stays the same and that the attribute is stripped. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| " pagespeed_no_transform", // initial attributes |
| "", // final attributes |
| false, // expect_rewritten |
| false); // expect_inline |
| } |
| |
| TEST_F(ImageRewriteTest, DataNoTransform) { |
| // Make sure that the image stays the same and that the attribute is stripped. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| " data-pagespeed-no-transform", // initial attributes |
| "", // final attributes |
| false, // expect_rewritten |
| false); // expect_inline |
| } |
| |
| TEST_F(ImageRewriteTest, NoTransformWithDims) { |
| // Make sure that the image stays the same and that the attribute is stripped. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| // initial attributes |
| " width=10 height=10 data-pagespeed-no-transform", |
| " width=10 height=10", // final attributes |
| false, // expect_rewritten |
| false); // expect_inline |
| } |
| |
| TEST_F(ImageRewriteTest, ImageRewriteDropAll) { |
| // Test that randomized optimization doesn't rewrite when drop % set to 100 |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_rewrite_random_drop_percentage(100); |
| rewrite_driver()->AddFilters(); |
| |
| for (int i = 0; i < 100; ++i) { |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| "", // initial attributes |
| "", // final attributes |
| false, // expect_rewritten |
| false); // expect_inline |
| lru_cache()->Clear(); |
| ClearStats(); |
| } |
| // Try some rewrites without clearing the cache to make sure that that |
| // works too. |
| for (int i = 0; i < 100; ++i) { |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| "", // initial attributes |
| "", // final attributes |
| false, // expect_rewritten |
| false); // expect_inline |
| } |
| } |
| |
| TEST_F(ImageRewriteTest, ImageRewriteDropNone) { |
| // Test that randomized optimization always rewrites when drop % set to 0. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_rewrite_random_drop_percentage(0); |
| rewrite_driver()->AddFilters(); |
| |
| for (int i = 0; i < 100; ++i) { |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| "", // initial attributes |
| "", // final attributes |
| true, // expect_rewritten |
| false); // expect_inline |
| lru_cache()->Clear(); |
| ClearStats(); |
| } |
| // Try some rewrites without clearing the cache to make sure that that |
| // works too. |
| for (int i = 0; i < 5; ++i) { |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, |
| "", // initial attributes |
| "", // final attributes |
| true, // expect_rewritten |
| false); // expect_inline |
| } |
| } |
| |
| TEST_F(ImageRewriteTest, ImageRewriteDropSometimes) { |
| // Test that randomized optimization sometimes rewrites and sometimes doesn't. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_rewrite_random_drop_percentage(50); |
| rewrite_driver()->AddFilters(); |
| |
| bool found_rewritten = false; |
| bool found_not_rewritten = false; |
| |
| // Boiler-plate fetching stuff. |
| GoogleString initial_url = StrCat(kTestDomain, kBikePngFile); |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| AddFileToMockFetcher(initial_url, kBikePngFile, kContentTypePng, 100); |
| const char html_boilerplate[] = "<img src='%s'%s>"; |
| GoogleString html_input = |
| StringPrintf(html_boilerplate, initial_url.c_str(), ""); |
| |
| // Note that this could flake, but for it to flake we'd have to have 100 |
| // heads or 100 tails in a row, a probability of 1.6e-30 when |
| // image_rewrite_percentage is 50. |
| for (int i = 0; i < 100; i++) { |
| ParseUrl(page_url, html_input); |
| |
| // Check for single image file in the rewritten page. |
| StringVector image_urls; |
| CollectImgSrcs(initial_url, output_buffer_, &image_urls); |
| EXPECT_EQ(1, image_urls.size()); |
| const GoogleString& rewritten_url = image_urls[0]; |
| const GoogleUrl rewritten_gurl(rewritten_url); |
| EXPECT_TRUE(rewritten_gurl.IsWebValid()); |
| |
| if (initial_url == rewritten_url) { |
| found_not_rewritten = true; |
| } else { |
| found_rewritten = true; |
| } |
| |
| if (found_rewritten && found_not_rewritten) { |
| break; |
| } |
| } |
| } |
| |
| // For Issue 748 where duplicate images in the same document with RandomDrop on |
| // caused the duplicate urls to be removed if the first image is not optimized. |
| // NOTE: This test only works if the first image is deterministically dropped. |
| // We set the drop_percentage to 100 to guarantee that. |
| TEST_F(ImageRewriteTest, ImageRewriteRandomDropRepeatedImages) { |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_rewrite_random_drop_percentage(100); |
| rewrite_driver()->AddFilters(); |
| GoogleString initial_url = StrCat(kTestDomain, kBikePngFile); |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| AddFileToMockFetcher(initial_url, kBikePngFile, kContentTypePng, 100); |
| const char html_boilerplate[] = |
| "<img src='%s'> <img src='%s'> <img src='%s'>"; |
| GoogleString html_input = |
| StringPrintf(html_boilerplate, initial_url.c_str(), initial_url.c_str(), |
| initial_url.c_str()); |
| ParseUrl(page_url, html_input); |
| StringVector image_urls; |
| CollectImgSrcs(initial_url, output_buffer_, &image_urls); |
| EXPECT_EQ(3, image_urls.size()); |
| EXPECT_EQ(initial_url, image_urls[0]); |
| EXPECT_EQ(initial_url, image_urls[1]); |
| EXPECT_EQ(initial_url, image_urls[2]); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeTest) { |
| // Make sure we resize images, but don't optimize them in place. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| // Without explicit resizing, we leave the image alone. |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| "", "", false, false); |
| // With resizing, we optimize. |
| const char kResizedDims[] = " width=\"256\" height=\"192\""; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedDims, kResizedDims, true, false); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeIsReallyPrefetch) { |
| // Make sure we don't resize a large image to 1x1, as it's |
| // really an image prefetch request. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kPixelDims, kPixelDims, false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, OptimizeRequestedPrefetch) { |
| // We shouldn't resize this image, but we should optimize it. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kPixelDims, kPixelDims, true, false); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeHigherDimensionTest) { |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| const char kOriginalDims[] = " width=\"100000\" height=\"100000\""; |
| TestSingleRewrite(kLargePngFile, kContentTypePng, kContentTypePng, |
| kOriginalDims, kOriginalDims, false, false); |
| Variable* no_rewrites = statistics()->GetVariable( |
| ImageRewriteFilter::kImageNoRewritesHighResolution); |
| EXPECT_EQ(1, no_rewrites->Get()); |
| } |
| |
| TEST_F(ImageRewriteTest, DimensionParsingOK) { |
| // First some tests that should succeed. |
| int value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("5", &value)); |
| EXPECT_EQ(value, 5); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute(" 341 ", &value)); |
| EXPECT_EQ(value, 341); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute(" 000743 ", &value)); |
| EXPECT_EQ(value, 743); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("\n\r\t \f62", |
| &value)); |
| EXPECT_EQ(value, 62); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("+40", &value)); |
| EXPECT_EQ(value, 40); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute(" +41", &value)); |
| EXPECT_EQ(value, 41); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("54px", &value)); |
| EXPECT_EQ(value, 54); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute(" 70.", &value)); |
| EXPECT_EQ(value, 70); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("71.3", &value)); |
| EXPECT_EQ(value, 71); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("71.523", &value)); |
| EXPECT_EQ(value, 72); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute( |
| "73.4999990982589729048572938579287459874", &value)); |
| EXPECT_EQ(value, 73); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("75.px", &value)); |
| EXPECT_EQ(value, 75); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("75.6 px", &value)); |
| EXPECT_EQ(value, 76); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("77.34px", &value)); |
| EXPECT_EQ(value, 77); |
| value = -34; |
| EXPECT_TRUE(ImageRewriteFilter::ParseDimensionAttribute("78px ", &value)); |
| EXPECT_EQ(value, 78); |
| } |
| |
| TEST_F(ImageRewriteTest, DimensionParsingFail) { |
| int value = -34; |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("0", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("+0", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "+0.9", &value)); // Bizarrely not allowed! |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute(" 0 ", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("junk5", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute(" junk10", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("junk 50", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("-43", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("+ 43", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("21px%", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("21px junk", |
| &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "9123948572038209720561049018365037891046", &value)); |
| EXPECT_EQ(-34, value); |
| // We don't handle percentages because we can't resize them. |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("73%", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("43.2 %", &value)); |
| EXPECT_EQ(-34, value); |
| // Trailing junk OK according to spec, but older browsers flunk / treat |
| // inconsistently |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "5junk", &value)); // Doesn't ignore |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "25p%x", &value)); // 25% on FF9! |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "26px%", &value)); // 25% on FF9! |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "45 643", &value)); // 45 today |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("21%px", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("59 .", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "60 . 9", &value)); // 60 today |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "+61. 9", &value)); // 61 today |
| EXPECT_EQ(-34, value); |
| // Some other units that some old browsers treat as px, but we just ignore |
| // to avoid confusion / inconsistency. |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("29in", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("30cm", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute("43pt", &value)); |
| EXPECT_EQ(-34, value); |
| EXPECT_FALSE(ImageRewriteFilter::ParseDimensionAttribute( |
| "99em", &value)); // FF9 screws this up |
| EXPECT_EQ(-34, value); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeWidthOnly) { |
| // Make sure we resize images, but don't optimize them in place. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| // Without explicit resizing, we leave the image alone. |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| "", "", false, false); |
| // With resizing, we optimize. |
| const char kResizedDims[] = " width=\"256\""; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedDims, kResizedDims, true, false); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeHeightOnly) { |
| // Make sure we resize images, but don't optimize them in place. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| // Without explicit resizing, we leave the image alone. |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| "", "", false, false); |
| // With resizing, we optimize. |
| const char kResizedDims[] = " height=\"192\""; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedDims, kResizedDims, true, false); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeHeightRounding) { |
| // Make sure fractional heights are rounded. We used to truncate, but this |
| // didn't match WebKit's behavior. To check this we need to fetch the resized |
| // image and verify its dimensions. The original image is 1023 x 766. |
| const char kLeafNoHeight[] = "256xNxPuzzle.jpg.pagespeed.ic.0.jpg"; |
| TestDimensionRounding(kLeafNoHeight, 256, 192); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeWidthRounding) { |
| // Make sure fractional widths are rounded, as above (with the same image). |
| const char kLeafNoWidth[] = "Nx383xPuzzle.jpg.pagespeed.ic.0.jpg"; |
| TestDimensionRounding(kLeafNoWidth, 512, 383); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeStyleTest) { |
| // Make sure we resize images, but don't optimize them in place. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| const char kResizedDims[] = " style=\"width:256px;height:192px;\""; |
| // Without explicit resizing, we leave the image alone. |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| "", "", false, false); |
| // With resizing, we optimize. |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedDims, kResizedDims, true, false); |
| |
| const char kMixedDims[] = " width=\"256\" style=\"height:192px;\""; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kMixedDims, kMixedDims, true, false); |
| |
| const char kMoreMixedDims[] = |
| " height=\"197\" style=\"width:256px;broken:true;\""; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kMoreMixedDims, kMoreMixedDims, true, false); |
| |
| const char kNonPixelDims[] = |
| " style=\"width:256cm;height:192cm;\""; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kNonPixelDims, kNonPixelDims, false, false); |
| |
| const char kNoDims[] = |
| " style=\"width:256;height:192;\""; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kNoDims, kNoDims, false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeWithPxInHtml) { |
| // Make sure we resize images if the html width and/or height specifies px. |
| // We rely on ImageRewriteTest.DimensionParsing above to test all the |
| // corner cases we might encounter and to cross-check the numbers. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| // Things that ought to work (ie result in resizing) |
| const char kResizedPx[] = " width='256px' height='192px'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedPx, kResizedPx, true, false); |
| const char kResizedWidthDot[] = " width='256.'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedWidthDot, kResizedWidthDot, true, false); |
| const char kResizedWidthDec[] = " width='255.536'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedWidthDec, kResizedWidthDec, true, false); |
| const char kResizedWidthPx[] = " width='256px'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedWidthPx, kResizedWidthPx, true, false); |
| const char kResizedWidthPxDot[] = " width='256.px'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedWidthPxDot, kResizedWidthPxDot, true, false); |
| const char kResizedWidthPxDec[] = " width='255.5px'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedWidthPxDec, kResizedWidthPxDec, true, false); |
| const char kResizedSpacePx[] = " width='256 px'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedSpacePx, kResizedSpacePx, true, false); |
| // Things that ought not to work (ie not result in resizing) |
| const char kResizedJunk[] = " width='256earths' height='192earths'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedJunk, kResizedJunk, false, false); |
| const char kResizedPercent[] = " width='20%' height='20%'"; |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| kResizedPercent, kResizedPercent, false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, NullResizeTest) { |
| // Make sure we don't crash on a value-less style attribute. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| " style", " style", false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, DebugResizeTest) { |
| options()->EnableFilter(RewriteOptions::kDebug); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| const char kResizedDims[] = " width=\"256\" height=\"192\""; |
| GoogleString initial_url = StrCat(kTestDomain, kPuzzleJpgFile); |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| AddFileToMockFetcher(initial_url, kPuzzleJpgFile, kContentTypeJpeg, 100); |
| const char html_boilerplate[] = "<img src='%s'%s>"; |
| GoogleString html_input = |
| StringPrintf(html_boilerplate, initial_url.c_str(), kResizedDims); |
| ParseUrl(page_url, html_input); |
| EXPECT_THAT( |
| output_buffer_, |
| testing::HasSubstr("<!--Resized image from 1023x766 to 256x192-->")); |
| } |
| |
| TEST_F(ImageRewriteTest, DebugNoResizeTest) { |
| options()->EnableFilter(RewriteOptions::kDebug); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| GoogleString initial_url = StrCat(kTestDomain, kPuzzleJpgFile); |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| AddFileToMockFetcher(initial_url, kPuzzleJpgFile, kContentTypeJpeg, 100); |
| const char html_boilerplate[] = "<img src='%s'>"; |
| GoogleString html_input = StringPrintf(html_boilerplate, initial_url.c_str()); |
| ParseUrl(page_url, html_input); |
| EXPECT_THAT( |
| output_buffer_, |
| testing::HasSubstr("<!--Image does not appear to need resizing.-->")); |
| } |
| |
| TEST_F(ImageRewriteTest, TestLoggingWithoutOptimize) { |
| // Make sure we don't resize, if we don't optimize. |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| rewrite_driver()->AddFilters(); |
| SetMockLogRecord(); |
| MockLogRecord* log = mock_log_record(); |
| EXPECT_CALL(*log, |
| MockLogImageRewriteActivity(LogImageRewriteActivityMatcher( |
| StrEq("ic"), |
| StrEq("http://test.com/IronChef2.gif"), |
| RewriterApplication::NOT_APPLIED, |
| false /* is_image_inlined */, |
| true /* is_critical_image */, |
| false /* is_url_rewritten */, |
| 24941 /* original size */, |
| false /* try_low_res_src_insertion */, |
| false /* low_res_src_inserted */, |
| IMAGE_UNKNOWN /* low res image type */, |
| _ /* low_res_data_size */))); |
| // Without resize, it's not optimizable. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| "", kChefDims, false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, TestLoggingWithOptimize) { |
| options()->set_image_inline_max_bytes(10000); |
| options()->set_log_url_indices(true); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->set_log_background_rewrites(true); |
| rewrite_driver()->AddFilters(); |
| const char kResizedDims[] = " width=48 height=64"; |
| SetMockLogRecord(); |
| MockLogRecord* log = mock_log_record(); |
| EXPECT_CALL(*log, |
| MockLogImageRewriteActivity(LogImageRewriteActivityMatcher( |
| StrEq("ic"), |
| StrEq("http://test.com/IronChef2.gif"), |
| RewriterApplication::APPLIED_OK, |
| true /* is_image_inlined */, |
| true /* is_critical_image */, |
| true /* is_url_rewritten */, |
| 5735 /* rewritten size */, |
| false /* try_low_res_src_insertion */, |
| false /* low_res_src_inserted */, |
| IMAGE_UNKNOWN /* low res image type */, |
| _ /* low_res_data_size */))); |
| // Without resize, it's not optimizable. |
| // With resize, the image shrinks quite a bit, and we can inline it |
| // given the 10K threshold explicitly set above. This also strips the |
| // size information, which is now embedded in the image itself anyway. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypePng, |
| kResizedDims, "", true, true); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineTestWithoutOptimize) { |
| // Make sure we don't resize, if we don't optimize. |
| options()->set_allow_logging_urls_in_log_record(true); |
| options()->set_image_inline_max_bytes(10000); |
| options()->set_log_background_rewrites(true); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| rewrite_driver()->AddFilters(); |
| // Without resize, it's not optimizable. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| "", kChefDims, false, false); |
| |
| // No optimization has been applied. Image type and size are not changed, |
| // so the optimzied image does not have these values logged. |
| rewrite_driver()->Clear(); |
| TestBackgroundRewritingLog( |
| 1, /* rewrite_info_size */ |
| 0, /* rewrite_info_index */ |
| RewriterApplication::NOT_APPLIED, /* status */ |
| "ic", /* ID */ |
| "http://test.com/IronChef2.gif", /* URL */ |
| IMAGE_GIF, /* original_type */ |
| IMAGE_UNKNOWN, /* optimized_type */ |
| 24941, /* original_size */ |
| 0, /* optimized_size */ |
| false, /* is_recompressed */ |
| false, /* is_resized */ |
| 192, /* original width */ |
| 256, /* original height */ |
| false, /* is_resized_using_rendered_dimensions */ |
| -1, /* resized_width */ |
| -1 /* resized_height */); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineTestWithResizeWithOptimize) { |
| options()->set_image_inline_max_bytes(10000); |
| options()->set_log_url_indices(true); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->set_log_background_rewrites(true); |
| rewrite_driver()->AddFilters(); |
| const char kResizedDims[] = " width=48 height=64"; |
| // Without resize, it's not optimizable. |
| // With resize, the image shrinks quite a bit, and we can inline it |
| // given the 10K threshold explicitly set above. This also strips the |
| // size information, which is now embedded in the image itself anyway. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypePng, |
| kResizedDims, "", true, true); |
| |
| // After optimization, the GIF image is converted to a PNG image. |
| rewrite_driver()->Clear(); |
| TestBackgroundRewritingLog( |
| 1, /* rewrite_info_size */ |
| 0, /* rewrite_info_index */ |
| RewriterApplication::APPLIED_OK, /* status */ |
| "ic", /* ID */ |
| "", /* URL */ |
| IMAGE_GIF, /* original_type */ |
| IMAGE_PNG, /* optimized_type */ |
| 24941, /* original_size */ |
| 5735, /* optimized_size */ |
| true, /* is_recompressed */ |
| true, /* is_resized */ |
| 192, /* original width */ |
| 256, /* original height */ |
| false, /* is_resized_using_rendered_dimensions */ |
| 48, /* resized_width */ |
| 64 /* resized_height */); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineTestWithResizeKeepDims) { |
| // their dimensions when we inline. |
| options()->set_image_inline_max_bytes(10000); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kDebug); |
| rewrite_driver()->AddFilters(); |
| |
| GoogleString initial_url = StrCat(kTestDomain, kChefGifFile); |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| AddFileToMockFetcher(initial_url, kChefGifFile, kContentTypeGif, 100); |
| const char kResizedDims[] = " width=48 height=64"; |
| const char html_boilerplate[] = "<td background='%s'%s></td>"; |
| GoogleString html_input = |
| StringPrintf(html_boilerplate, initial_url.c_str(), kResizedDims); |
| ParseUrl(page_url, html_input); |
| // Image should have been resized |
| EXPECT_THAT( |
| output_buffer_, |
| testing::HasSubstr("<!--Resized image from 192x256 to 48x64-->")); |
| // And inlined |
| EXPECT_THAT( |
| output_buffer_, |
| testing::HasSubstr("<td background='data:")); |
| // But dimensions should still be there. |
| EXPECT_THAT( |
| output_buffer_, |
| testing::HasSubstr(kResizedDims)); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineTestWithResizeWithOptimizeAndUrlLogging) { |
| options()->set_image_inline_max_bytes(10000); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->set_allow_logging_urls_in_log_record(true); |
| rewrite_driver()->AddFilters(); |
| |
| const char kResizedDims[] = " width=48 height=64"; |
| // Without resize, it's not optimizable. |
| // With resize, the image shrinks quite a bit, and we can inline it |
| // given the 10K threshold explicitly set above. This also strips the |
| // size information, which is now embedded in the image itself anyway. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypePng, |
| kResizedDims, "", true, true); |
| TestSingleRewriteWithoutAbs(kChefGifFile, kChefGifFile, kContentTypeGif, |
| kContentTypePng, kResizedDims, "", true, true); |
| } |
| |
| TEST_F(ImageRewriteTest, DimensionStripAfterInline) { |
| options()->set_image_inline_max_bytes(100000); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| rewrite_driver()->AddFilters(); |
| const char kChefWidth[] = " width=192"; |
| const char kChefHeight[] = " height=256"; |
| // With all specified dimensions matching, dims are stripped after inlining. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefDims, "", false, true); |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefWidth, "", false, true); |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefHeight, "", false, true); |
| // If we stretch the image in either dimension, we keep the dimensions. |
| const char kChefWider[] = " width=384 height=256"; |
| const char kChefTaller[] = " width=192 height=512"; |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefWider, kChefWider, false, true); |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefTaller, kChefTaller, false, true); |
| |
| const char kChefWidthWithPercentage[] = " width=100% height=1"; |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefWidthWithPercentage, kChefWidthWithPercentage, |
| false, true); |
| const char kChefHeightWithPercentage[] = " width=1 height=%"; |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefHeightWithPercentage, kChefHeightWithPercentage, |
| false, true); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineCriticalOnly) { |
| MockCriticalImagesFinder* finder = new MockCriticalImagesFinder(statistics()); |
| server_context()->set_critical_images_finder(finder); |
| options()->set_image_inline_max_bytes(30000); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| rewrite_driver()->AddFilters(); |
| // With no critical images registered, no images are candidates for inlining. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| "", "", false, false); |
| // Here and below, -1 results mean "no critical image data reported". |
| EXPECT_EQ(-1, logging_info()->num_html_critical_images()); |
| EXPECT_EQ(-1, logging_info()->num_css_critical_images()); |
| |
| // Image not present in critical set should not be inlined. |
| StringSet* critical_images = new StringSet; |
| critical_images->insert(StrCat(kTestDomain, "other_image.png")); |
| finder->set_critical_images(critical_images); |
| |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| "", "", false, false); |
| EXPECT_EQ(-1, logging_info()->num_html_critical_images()); |
| EXPECT_EQ(-1, logging_info()->num_css_critical_images()); |
| |
| // Image present in critical set should be inlined. |
| critical_images->insert(StrCat(kTestDomain, kChefGifFile)); |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| "", "", false, true); |
| EXPECT_EQ(-1, logging_info()->num_html_critical_images()); |
| EXPECT_EQ(-1, logging_info()->num_css_critical_images()); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineNoRewrite) { |
| // Make sure we inline an image that isn't otherwise altered in any way. |
| options()->set_image_inline_max_bytes(30000); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| rewrite_driver()->AddFilters(); |
| // This image is just small enough to inline, which also erases |
| // dimension information. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefDims, "", false, true); |
| // This image is too big to inline, and we don't insert missing |
| // dimension information because that is not explicitly enabled. |
| TestSingleRewrite(kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, |
| "", "", false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineNoResize) { |
| // Make sure we inline an image if it meets the inlining threshold but can't |
| // be resized. Make sure we retain sizing information when this happens. |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kRecompressWebp); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| const char kOrigDims[] = " width=24 height=24"; |
| const char kResizedDims[] = " width=20 height=12"; |
| // At natural size, we should inline and erase dimensions. |
| TestSingleRewrite(kChromium24, kContentTypeWebp, kContentTypeWebp, |
| kOrigDims, "", false, true); |
| // Image is inlined but not resized, so preserve dimensions. |
| TestSingleRewrite(kChromium24, kContentTypeWebp, kContentTypeWebp, |
| kResizedDims, kResizedDims, false, true); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineLargerResize) { |
| // Make sure we inline an image if it meets the inlining threshold before |
| // resize, resizing succeeds, but the resulting image is larger than the |
| // original. Make sure we retain sizing information when this happens. |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| const char kOrigDims[] = " width=65 height=70"; |
| const char kResizedDims[] = " width=64 height=69"; |
| // At natural size, we should inline and erase dimensions. |
| TestSingleRewrite(kCuppaOPngFile, kContentTypePng, kContentTypePng, |
| kOrigDims, "", false, true); |
| // Image is inlined but not resized, so preserve dimensions. |
| TestSingleRewrite(kCuppaOPngFile, kContentTypePng, kContentTypePng, |
| kResizedDims, kResizedDims, false, true); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeTransparentImage) { |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| const char kResizedDims[] = " width=26 height=28"; |
| // Image is resized and inlined. |
| TestSingleRewrite(kCuppaTPngFile, kContentTypePng, kContentTypePng, |
| kResizedDims, "", true, true); |
| } |
| |
| TEST_F(ImageRewriteTest, InlineEnlargedImage) { |
| // Make sure we inline an image that meets the inlining threshold, |
| // but retain its sizing information if the image has been enlarged. |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| const char kDoubledDims[] = " width=130 height=140"; |
| TestSingleRewrite(kCuppaOPngFile, kContentTypePng, kContentTypePng, |
| kDoubledDims, kDoubledDims, false, true); |
| } |
| |
| TEST_F(ImageRewriteTest, RespectsBaseUrl) { |
| // Put original files into our fetcher. |
| const char html_url[] = "http://image.test/base_url.html"; |
| const char png_url[] = "http://other_domain.test/foo/bar/a.png"; |
| const char jpeg_url[] = "http://other_domain.test/baz/b.jpeg"; |
| const char gif_url[] = "http://other_domain.test/foo/c.gif"; |
| |
| AddFileToMockFetcher(png_url, kBikePngFile, kContentTypePng, 100); |
| AddFileToMockFetcher(jpeg_url, kPuzzleJpgFile, kContentTypeJpeg, 100); |
| AddFileToMockFetcher(gif_url, kChefGifFile, kContentTypeGif, 100); |
| |
| // First two images are on base domain. Last is on origin domain. |
| const char html_format[] = |
| "<head>\n" |
| " <base href='http://other_domain.test/foo/'>\n" |
| "</head>\n" |
| "<body>\n" |
| " <img src='%s'>\n" |
| " <img src='%s'>\n" |
| " <img src='%s'>\n" |
| "</body>"; |
| |
| GoogleString html_input = |
| StringPrintf(html_format, "bar/a.png", "/baz/b.jpeg", "c.gif"); |
| |
| // Rewrite |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| ParseUrl(html_url, html_input); |
| |
| // Check for image files in the rewritten page. |
| StringVector image_urls; |
| CollectImgSrcs("base_url-links", output_buffer_, &image_urls); |
| EXPECT_EQ(3UL, image_urls.size()); |
| const GoogleString& new_png_url = image_urls[0]; |
| const GoogleString& new_jpeg_url = image_urls[1]; |
| const GoogleString& new_gif_url = image_urls[2]; |
| |
| // Sanity check that we changed the URL. |
| EXPECT_NE("bar/a.png", new_png_url); |
| EXPECT_NE("/baz/b.jpeg", new_jpeg_url); |
| EXPECT_NE("c.gif", new_gif_url); |
| |
| GoogleString expected_output = |
| StringPrintf(html_format, new_png_url.c_str(), |
| new_jpeg_url.c_str(), new_gif_url.c_str()); |
| |
| EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_); |
| |
| GoogleUrl base_gurl("http://other_domain.test/foo/"); |
| GoogleUrl new_png_gurl(base_gurl, new_png_url); |
| EXPECT_TRUE(new_png_gurl.IsWebValid()); |
| GoogleUrl encoded_png_gurl(EncodeWithBase("http://other_domain.test/", |
| "http://other_domain.test/foo/bar/", |
| "x", "0", "a.png", "x")); |
| EXPECT_EQ(encoded_png_gurl.AllExceptLeaf(), new_png_gurl.AllExceptLeaf()); |
| |
| GoogleUrl new_jpeg_gurl(base_gurl, new_jpeg_url); |
| EXPECT_TRUE(new_jpeg_gurl.IsWebValid()); |
| GoogleUrl encoded_jpeg_gurl(EncodeWithBase("http://other_domain.test/", |
| "http://other_domain.test/baz/", |
| "x", "0", "b.jpeg", "x")); |
| EXPECT_EQ(encoded_jpeg_gurl.AllExceptLeaf(), new_jpeg_gurl.AllExceptLeaf()); |
| |
| GoogleUrl new_gif_gurl(base_gurl, new_gif_url); |
| EXPECT_TRUE(new_gif_gurl.IsWebValid()); |
| GoogleUrl encoded_gif_gurl(EncodeWithBase("http://other_domain.test/", |
| "http://other_domain.test/foo/", |
| "x", "0", "c.gif", "x")); |
| EXPECT_EQ(encoded_gif_gurl.AllExceptLeaf(), new_gif_gurl.AllExceptLeaf()); |
| } |
| |
| TEST_F(ImageRewriteTest, FetchInvalid) { |
| // Make sure that fetching invalid URLs cleanly reports a problem by |
| // calling Done(false). |
| AddFilter(RewriteOptions::kRecompressJpeg); |
| GoogleString out; |
| |
| // We are trying to test with an invalid encoding. By construction, |
| // Encode cannot make an invalid encoding. However we can make one |
| // using a PlaceHolder string and then mutating it. |
| const char kPlaceholder[] = "PlaceHolder"; |
| GoogleString encoded_url = Encode("http://www.example.com/", "ic", |
| "ABCDEFGHIJ", kPlaceholder, "jpg"); |
| GlobalReplaceSubstring(kPlaceholder, "70x53x,", &encoded_url); |
| EXPECT_FALSE(FetchResourceUrl(encoded_url, &out)); |
| } |
| |
| TEST_F(ImageRewriteTest, Rewrite404) { |
| // Make sure we don't fail when rewriting with invalid input. |
| SetFetchResponse404("404.jpg"); |
| AddFilter(RewriteOptions::kRecompressJpeg); |
| DebugWithMessage("<!--4xx status code, preventing rewriting of %url%-->"); |
| for (int i = 0; i < 2; ++i) { |
| // Try twice to exercise the cached case. |
| ValidateExpected( |
| "404", |
| "<img src='404.jpg'>", |
| StrCat("<img src='404.jpg'>", DebugMessage("404.jpg"))); |
| } |
| } |
| |
| TEST_F(ImageRewriteTest, HonorNoTransform) { |
| // If cache-control: no-transform then we should serve the original URL |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| DebugWithMessage( |
| "<!--Cache-control: no-transform, preventing rewriting of %url%-->"); |
| |
| GoogleString url = StrCat(kTestDomain, "notransform.png"); |
| AddFileToMockFetcher(url, kBikePngFile, kContentTypePng, 100); |
| AddToResponse(url, HttpAttributes::kCacheControl, "no-transform"); |
| |
| for (int i = 0; i < 2; ++i) { |
| // Validate twice in case changes in cache from the first request alter the |
| // second. |
| ValidateExpected( |
| "NoTransform", |
| StrCat("<img src=", url, ">"), |
| StrCat("<img src=", url, ">", DebugMessage(url))); |
| } |
| } |
| |
| TEST_F(ImageRewriteTest, YesTransform) { |
| // Replicates above test but without no-transform to show that it works. We |
| // also verify that the data-pagespeed-no-defer attribute doesn't get removed |
| // when we rewrite images. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| |
| GoogleString url = StrCat(kTestDomain, "notransform.png"); |
| AddFileToMockFetcher(url, kBikePngFile, |
| kContentTypePng, 100); |
| ValidateExpected("YesTransform", |
| StrCat("<img src=", url, " data-pagespeed-no-defer>"), |
| StrCat("<img src=", |
| Encode("http://test.com/", "ic", "0", |
| "notransform.png", "png"), |
| " data-pagespeed-no-defer>")); |
| // Validate twice in case changes in cache from the first request alter the |
| // second. |
| ValidateExpected("YesTransform", StrCat("<img src=", url, ">"), |
| StrCat("<img src=", |
| Encode("http://test.com/", "ic", "0", |
| "notransform.png", "png"), |
| ">")); |
| } |
| |
| TEST_F(ImageRewriteTest, YesTransformWithOptionFalse) { |
| // Verify rewrite happens even when no-transform is set, if the option is |
| // set to false. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_disable_rewrite_on_no_transform(false); |
| rewrite_driver()->AddFilters(); |
| |
| GoogleString url = StrCat(kTestDomain, "notransform.png"); |
| AddFileToMockFetcher(url, kBikePngFile, kContentTypePng, 100); |
| AddToResponse(url, HttpAttributes::kCacheControl, "no-transform"); |
| ValidateExpected("YesTransform", StrCat("<img src=", url, ">"), |
| StrCat("<img src=", |
| Encode("http://test.com/", "ic", "0", |
| "notransform.png", "png"), |
| ">")); |
| // Validate twice in case changes in cache from the first request alter the |
| // second. |
| ValidateExpected("YesTransform", StrCat("<img src=", url, ">"), |
| StrCat("<img src=", |
| Encode("http://test.com/", "ic", "0", |
| "notransform.png", "png"), |
| ">")); |
| } |
| |
| TEST_F(ImageRewriteTest, NoExtensionCorruption) { |
| TestCorruptUrl("%22", true /* append %22 */); |
| } |
| |
| TEST_F(ImageRewriteTest, NoQueryCorruption) { |
| TestCorruptUrl("?query", true /* append ?query*/); |
| } |
| |
| TEST_F(ImageRewriteTest, NoWrongExtCorruption) { |
| TestCorruptUrl(".html", false /* replace ext with .html */); |
| } |
| |
| TEST_F(ImageRewriteTest, NoCrashOnInvalidDim) { |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| rewrite_driver()->AddFilters(); |
| AddFileToMockFetcher(StrCat(kTestDomain, "a.png"), kBikePngFile, |
| kContentTypePng, 100); |
| |
| ParseUrl(kTestDomain, "<img width=0 height=0 src=\"a.png\">"); |
| ParseUrl(kTestDomain, "<img width=0 height=42 src=\"a.png\">"); |
| ParseUrl(kTestDomain, "<img width=42 height=0 src=\"a.png\">"); |
| ParseUrl(kTestDomain, "<img width=\"-5\" height=\"5\" src=\"a.png\">"); |
| ParseUrl(kTestDomain, "<img width=\"-5\" height=\"0\" src=\"a.png\">"); |
| ParseUrl(kTestDomain, "<img width=\"-5\" height=\"-5\" src=\"a.png\">"); |
| ParseUrl(kTestDomain, "<img width=\"5\" height=\"-5\" src=\"a.png\">"); |
| } |
| |
| TEST_F(ImageRewriteTest, RewriteCacheExtendInteraction) { |
| // There was a bug in async mode where rewriting failing would prevent |
| // cache extension from working as well. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kExtendCacheImages); |
| rewrite_driver()->AddFilters(); |
| |
| // Provide a non-image file, so image rewrite fails (but cache extension |
| // works) |
| SetResponseWithDefaultHeaders("a.png", kContentTypePng, "Not a PNG", 600); |
| |
| ValidateExpected("cache_extend_fallback", "<img src=a.png>", |
| StrCat("<img src=", |
| Encode("", "ce", "0", "a.png", "png"), |
| ">")); |
| } |
| |
| // http://code.google.com/p/modpagespeed/issues/detail?id=324 |
| TEST_F(ImageRewriteTest, RetainExtraHeaders) { |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| rewrite_driver()->AddFilters(); |
| |
| // Store image contents into fetcher. |
| AddFileToMockFetcher(StrCat(kTestDomain, kPuzzleJpgFile), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| TestRetainExtraHeaders(kPuzzleJpgFile, "ic", "jpg"); |
| } |
| |
| TEST_F(ImageRewriteTest, NestedConcurrentRewritesLimit) { |
| // Make sure we're limiting # of concurrent rewrites properly even when we're |
| // nested inside another filter, and that we do not cache that outcome |
| // improperly. |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kRewriteCss); |
| options()->set_image_max_rewrites_at_once(1); |
| options()->set_always_rewrite_css(true); |
| rewrite_driver()->AddFilters(); |
| |
| const char kPngFile[] = "a.png"; |
| const char kCssFile[] = "a.css"; |
| const char kCssTemplate[] = "div{background-image:url(%s)}"; |
| AddFileToMockFetcher(StrCat(kTestDomain, kPngFile), kBikePngFile, |
| kContentTypePng, 100); |
| GoogleString in_css = StringPrintf(kCssTemplate, kPngFile); |
| SetResponseWithDefaultHeaders(kCssFile, kContentTypeCss, in_css, 100); |
| |
| GoogleString out_css_url = Encode("", "cf", "0", kCssFile, "css"); |
| GoogleString out_png_url = Encode("", "ic", "0", kPngFile, "png"); |
| |
| MarkTooBusyToWork(); |
| |
| // If the nested context is too busy, we don't want the parent to partially |
| // optimize. |
| ValidateNoChanges("img_in_css", CssLinkHref(kCssFile)); |
| |
| GoogleString out_css; |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, out_css_url), &out_css)); |
| // Nothing changes in the HTML and a dropped image rewrite should be recorded. |
| EXPECT_EQ(in_css, out_css); |
| TimedVariable* drops = statistics()->GetTimedVariable( |
| ImageRewriteFilter::kImageRewritesDroppedDueToLoad); |
| EXPECT_EQ(1, drops->Get(TimedVariable::START)); |
| |
| // Now rewrite it again w/o any load. We should get the image link |
| // changed. |
| UnMarkTooBusyToWork(); |
| ValidateExpected("img_in_css", CssLinkHref(kCssFile), |
| CssLinkHref(out_css_url)); |
| GoogleString expected_out_css = |
| StringPrintf(kCssTemplate, out_png_url.c_str()); |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, out_css_url), &out_css)); |
| // This time, however, CSS should be altered (and the drop count still be 1). |
| EXPECT_EQ(expected_out_css, out_css); |
| EXPECT_EQ(1, drops->Get(TimedVariable::START)); |
| } |
| |
| TEST_F(ImageRewriteTest, GifToPngTestWithResizeWithOptimize) { |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| rewrite_driver()->AddFilters(); |
| const char kResizedDims[] = " width=48 height=64"; |
| // With resize and optimization. Translating gif to png. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypePng, |
| kResizedDims, kResizedDims, true, false); |
| } |
| |
| TEST_F(ImageRewriteTest, GifToPngTestResizeEnableGifToPngDisabled) { |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| "", "", false, false); |
| const char kResizedDims[] = " width=48 height=64"; |
| // Not traslating gifs to pngs. |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kResizedDims, kResizedDims, false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, GifToPngTestWithoutResizeWithOptimize) { |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| rewrite_driver()->AddFilters(); |
| // Without resize and with optimization |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypePng, |
| "", "", true, false); |
| } |
| |
| // TODO(poojatandon): Add a test where .gif file size increases on optimization. |
| |
| TEST_F(ImageRewriteTest, GifToPngTestWithoutResizeWithoutOptimize) { |
| // Without resize and without optimization |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| "", "", false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, GifToJpegTestWithoutResizeWithOptimize) { |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| // Without resize and with optimization |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeJpeg, |
| "", "", true, false); |
| } |
| |
| TEST_F(ImageRewriteTest, GifToWebpTestWithResizeWithOptimize) { |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpLossless(); |
| const char kResizedDims[] = " width=48 height=64"; |
| // With resize and optimization |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeWebp, |
| kResizedDims, kResizedDims, true, false); |
| TestConversionVariables(0, 1, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageRewriteTest, GifToWebpTestWithoutResizeWithOptimize) { |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpLossless(); |
| // Without resize and with optimization |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeWebp, |
| "", "", true, false); |
| TestConversionVariables(0, 1, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageRewriteTest, InlinableImagesInsertedIntoPropertyCache) { |
| // If image_inlining_identify_and_cache_without_rewriting() is set in |
| // RewriteOptions, images that would have been inlined are instead inserted |
| // into the property cache. |
| options()->set_image_inline_max_bytes(30000); |
| options()->set_cache_small_images_unrewritten(true); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| rewrite_driver()->AddFilters(); |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeGif, |
| kChefDims, kChefDims, false, false); |
| EXPECT_STREQ("\"http://test.com/IronChef2.gif\"", |
| FetchInlinablePropertyCacheValue()->value()); |
| } |
| |
| TEST_F(ImageRewriteTest, InlinableCssImagesInsertedIntoPropertyCache) { |
| // If image_inlining_identify_and_cache_without_rewriting() is set in |
| // RewriteOptions, CSS images that would have been inlined are instead |
| // inserted into the property cache. |
| options()->set_image_inline_max_bytes(30000); |
| options()->set_cache_small_images_unrewritten(true); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| rewrite_driver()->AddFilters(); |
| const char kPngFile1[] = "a.png"; |
| const char kPngFile2[] = "b.png"; |
| AddFileToMockFetcher(StrCat(kTestDomain, kPngFile1), kBikePngFile, |
| kContentTypePng, 100); |
| AddFileToMockFetcher(StrCat(kTestDomain, kPngFile2), kCuppaTPngFile, |
| kContentTypePng, 100); |
| const char kCssFile[] = "a.css"; |
| // We include a duplicate image here to verify that duplicate suppression |
| // is working. |
| GoogleString css_contents = StringPrintf( |
| "div{background-image:url(%s)}" |
| "h1{background-image:url(%s)}" |
| "p{background-image:url(%s)}", kPngFile1, kPngFile1, kPngFile2); |
| SetResponseWithDefaultHeaders(kCssFile, kContentTypeCss, css_contents, 100); |
| // Parse the CSS and ensure contents are unchanged. |
| GoogleString out_css_url = Encode("", "cf", "0", kCssFile, "css"); |
| GoogleString out_css; |
| StringAsyncFetch async_fetch(RequestContext::NewTestRequestContext( |
| server_context()->thread_system()), &out_css); |
| ResponseHeaders response; |
| async_fetch.set_response_headers(&response); |
| EXPECT_TRUE(rewrite_driver_->FetchResource(StrCat(kTestDomain, out_css_url), |
| &async_fetch)); |
| rewrite_driver_->WaitForShutDown(); |
| EXPECT_TRUE(async_fetch.success()); |
| |
| // The CSS is unmodified and the image URL is stored in the property cache. |
| EXPECT_STREQ(css_contents, out_css); |
| // The expected URLs are present. |
| StringPieceVector urls; |
| StringSet expected_urls; |
| expected_urls.insert("\"http://test.com/a.png\""); |
| expected_urls.insert("\"http://test.com/b.png\""); |
| SplitStringPieceToVector(FetchInlinablePropertyCacheValue()->value(), ",", |
| &urls, false); |
| EXPECT_EQ(expected_urls.size(), urls.size()); |
| for (int i = 0; i < urls.size(); ++i) { |
| EXPECT_EQ(1, expected_urls.count(urls[i].as_string())); |
| } |
| } |
| |
| TEST_F(ImageRewriteTest, RewritesDroppedDueToNoSavingNoResizeTest) { |
| Histogram* rewrite_latency_ok = statistics()->GetHistogram( |
| ImageRewriteFilter::kImageRewriteLatencyOkMs); |
| Histogram* rewrite_latency_failed = statistics()->GetHistogram( |
| ImageRewriteFilter::kImageRewriteLatencyFailedMs); |
| rewrite_latency_ok->Clear(); |
| rewrite_latency_failed->Clear(); |
| |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| const char kOriginalDims[] = " width=65 height=70"; |
| TestSingleRewrite(kCuppaOPngFile, kContentTypePng, kContentTypePng, |
| kOriginalDims, kOriginalDims, false, false); |
| Variable* rewrites_drops = statistics()->GetVariable( |
| ImageRewriteFilter::kImageRewritesDroppedNoSavingNoResize); |
| EXPECT_EQ(1, rewrites_drops->Get()); |
| EXPECT_EQ(0, rewrite_latency_ok->Count()); |
| EXPECT_EQ(1, rewrite_latency_failed->Count()); |
| } |
| |
| TEST_F(ImageRewriteTest, RewritesDroppedDueToMIMETypeUnknownTest) { |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| const char kOriginalDims[] = " width=10 height=10"; |
| TestSingleRewrite(kSmallDataFile, kContentTypePng, kContentTypePng, |
| kOriginalDims, kOriginalDims, false, false); |
| Variable* rewrites_drops = statistics()->GetVariable( |
| ImageRewriteFilter::kImageRewritesDroppedMIMETypeUnknown); |
| EXPECT_EQ(1, rewrites_drops->Get()); |
| } |
| |
| TEST_F(ImageRewriteTest, JpegQualityForSmallScreens) { |
| ResetUserAgent("Mozilla/5.0 (Linux; U; Android 4.0.1; en-us; " |
| "Galaxy Nexus Build/ICL27) AppleWebKit/534.30 (KHTML, like Gecko) " |
| "Version/4.0 Mobile Safari/534.30"); |
| ImageRewriteFilter image_rewrite_filter(rewrite_driver()); |
| ResourceContext ctx; |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| const ResourcePtr res_ptr( |
| rewrite_driver()->CreateInputResourceAbsoluteUncheckedForTestsOnly( |
| "data:image/png;base64,test")); |
| scoped_ptr<Image::CompressionOptions> img_options( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| |
| // Neither option is set explicitly, default is 70. |
| EXPECT_EQ(70, img_options->jpeg_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base image quality is set, but for_small_screens is not, return base. |
| options()->ClearSignatureForTesting(); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(-1); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(85, img_options->jpeg_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base jpeg quality not set, but for_small_screens is, return small_screen. |
| options()->ClearSignatureForTesting(); |
| options()->set_image_recompress_quality(-1); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(20); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(20, img_options->jpeg_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Neither jpeg quality is set, return -1. |
| options()->ClearSignatureForTesting(); |
| options()->set_image_recompress_quality(-1); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(-1); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(-1, img_options->jpeg_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base and for_small_screen options are set; mobile |
| options()->ClearSignatureForTesting(); |
| options()->set_image_jpeg_recompress_quality(85); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(20); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(20, img_options->jpeg_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Non-mobile UA. |
| ResetUserAgent("Mozilla/5.0 (Windows; U; Windows NT 5.1; " |
| "en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C " |
| "Safari/525.13"); |
| ctx.Clear(); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(85, img_options->jpeg_quality); |
| EXPECT_FALSE(ctx.may_use_small_screen_quality()); |
| |
| // Mobile UA |
| ResetUserAgent("iPhone OS Safari"); |
| options()->ClearSignatureForTesting(); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(70); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(70, img_options->jpeg_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Although the regular (desktop) quality is smaller, it won't affect the |
| // quality used for mobile. |
| ResetUserAgent("iPhone OS Safari"); |
| options()->ClearSignatureForTesting(); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(70); |
| options()->set_image_jpeg_recompress_quality(60); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(70, img_options->jpeg_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| } |
| |
| TEST_F(ImageRewriteTest, WebPQualityForSmallScreens) { |
| ResetUserAgent("Mozilla/5.0 (Linux; U; Android 4.0.1; en-us; " |
| "Galaxy Nexus Build/ICL27) AppleWebKit/534.30 (KHTML, like Gecko) " |
| "Version/4.0 Mobile Safari/534.30"); |
| ImageRewriteFilter image_rewrite_filter(rewrite_driver()); |
| ResourceContext ctx; |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| const ResourcePtr res_ptr( |
| rewrite_driver()->CreateInputResourceAbsoluteUncheckedForTestsOnly( |
| "data:image/png;base64,test")); |
| scoped_ptr<Image::CompressionOptions> img_options( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| |
| // Neither option is set, default is 70. |
| EXPECT_EQ(70, img_options->webp_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base webp quality set, but for_small_screens is not, return base quality. |
| ctx.Clear(); |
| options()->ClearSignatureForTesting(); |
| options()->set_image_webp_recompress_quality(85); |
| options()->set_image_webp_recompress_quality_for_small_screens(-1); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(85, img_options->webp_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base webp quality not set, but for_small_screens is, return small_screen. |
| options()->ClearSignatureForTesting(); |
| options()->set_image_recompress_quality(-1); |
| options()->set_image_webp_recompress_quality(-1); |
| options()->set_image_webp_recompress_quality_for_small_screens(20); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(20, img_options->webp_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base and for_small_screen options are set; mobile |
| options()->ClearSignatureForTesting(); |
| options()->set_image_webp_recompress_quality(85); |
| options()->set_image_webp_recompress_quality_for_small_screens(20); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(20, img_options->webp_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Non-mobile UA. |
| ResetUserAgent("Mozilla/5.0 (Windows; U; Windows NT 5.1; " |
| "en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C " |
| "Safari/525.13"); |
| ctx.Clear(); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(85, img_options->webp_quality); |
| EXPECT_FALSE(ctx.may_use_small_screen_quality()); |
| |
| // Mobile UA |
| ResetUserAgent("iPhone OS Safari"); |
| ctx.Clear(); |
| options()->ClearSignatureForTesting(); |
| options()->set_image_webp_recompress_quality_for_small_screens(70); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(70, img_options->webp_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Although the regular (desktop) quality is smaller, it won't affect the |
| // quality used for mobile. |
| ResetUserAgent("iPhone OS Safari"); |
| ctx.Clear(); |
| options()->ClearSignatureForTesting(); |
| options()->set_image_webp_recompress_quality_for_small_screens(70); |
| options()->set_image_webp_recompress_quality(55); |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| img_options.reset( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| EXPECT_EQ(70, img_options->webp_quality); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| } |
| |
| void SetNumberOfScans(int num_scans, int num_scans_small_screen, |
| const ResourcePtr res_ptr, |
| RewriteOptions* options, |
| RewriteDriver* rewrite_driver, |
| ImageRewriteFilter* image_rewrite_filter, |
| ResourceContext* ctx, |
| scoped_ptr<Image::CompressionOptions>* img_options) { |
| static const int DO_NOT_SET = -10; |
| ctx->Clear(); |
| if ((num_scans != DO_NOT_SET) || |
| (num_scans_small_screen != DO_NOT_SET)) { |
| options->ClearSignatureForTesting(); |
| if (num_scans != DO_NOT_SET) { |
| options->set_image_jpeg_num_progressive_scans(num_scans); |
| } |
| if (num_scans_small_screen != DO_NOT_SET) { |
| options->set_image_jpeg_num_progressive_scans_for_small_screens( |
| num_scans_small_screen); |
| } |
| } |
| image_rewrite_filter->EncodeUserAgentIntoResourceContext(ctx); |
| img_options->reset( |
| image_rewrite_filter->ImageOptionsForLoadedResource( |
| *ctx, res_ptr)); |
| } |
| |
| TEST_F(ImageRewriteTest, JpegProgressiveScansForSmallScreens) { |
| static const int DO_NOT_SET = -10; |
| ResetUserAgent("Mozilla/5.0 (Linux; U; Android 4.0.1; en-us; " |
| "Galaxy Nexus Build/ICL27) AppleWebKit/534.30 (KHTML, like Gecko) " |
| "Version/4.0 Mobile Safari/534.30"); |
| ImageRewriteFilter image_rewrite_filter(rewrite_driver()); |
| ResourceContext ctx; |
| image_rewrite_filter.EncodeUserAgentIntoResourceContext(&ctx); |
| const ResourcePtr res_ptr( |
| rewrite_driver()->CreateInputResourceAbsoluteUncheckedForTestsOnly( |
| "data:image/png;base64,test")); |
| scoped_ptr<Image::CompressionOptions> img_options( |
| image_rewrite_filter.ImageOptionsForLoadedResource(ctx, res_ptr)); |
| |
| // Neither option is set, default is -1. |
| EXPECT_EQ(-1, img_options->jpeg_num_progressive_scans); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base jpeg num scans set, but for_small_screens is not, return |
| // base num scans. |
| SetNumberOfScans(8, -1, res_ptr, options(), rewrite_driver(), |
| &image_rewrite_filter, &ctx, &img_options); |
| EXPECT_EQ(8, img_options->jpeg_num_progressive_scans); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base jpeg quality not set, but for_small_screens is, return small_screen. |
| SetNumberOfScans(DO_NOT_SET, 2, res_ptr, options(), rewrite_driver(), |
| &image_rewrite_filter, &ctx, &img_options); |
| EXPECT_EQ(2, img_options->jpeg_num_progressive_scans); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Base and for_small_screen options are set; mobile. |
| SetNumberOfScans(8, 2, res_ptr, options(), rewrite_driver(), |
| &image_rewrite_filter, &ctx, &img_options); |
| EXPECT_EQ(2, img_options->jpeg_num_progressive_scans); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Non-mobile UA. |
| ResetUserAgent("Mozilla/5.0 (Windows; U; Windows NT 5.1; " |
| "en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C " |
| "Safari/525.13"); |
| SetNumberOfScans(DO_NOT_SET, DO_NOT_SET, res_ptr, options(), rewrite_driver(), |
| &image_rewrite_filter, &ctx, &img_options); |
| EXPECT_EQ(8, img_options->jpeg_num_progressive_scans); |
| EXPECT_FALSE(ctx.may_use_small_screen_quality()); |
| |
| // Mobile UA |
| ResetUserAgent("iPhone OS Safari"); |
| SetNumberOfScans(DO_NOT_SET, 2, res_ptr, options(), rewrite_driver(), |
| &image_rewrite_filter, &ctx, &img_options); |
| EXPECT_EQ(2, img_options->jpeg_num_progressive_scans); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| |
| // Although the regular (desktop) number of scans is smaller, it won't affect |
| // that used for mobile. |
| ResetUserAgent("iPhone OS Safari"); |
| SetNumberOfScans(2, 8, res_ptr, options(), rewrite_driver(), |
| &image_rewrite_filter, &ctx, &img_options); |
| EXPECT_EQ(8, img_options->jpeg_num_progressive_scans); |
| EXPECT_TRUE(ctx.may_use_small_screen_quality()); |
| } |
| |
| TEST_F(ImageRewriteTest, ProgressiveJpegThresholds) { |
| GoogleString image_data; |
| ASSERT_TRUE(LoadFile(kPuzzleJpgFile, &image_data)); |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| options->recompress_jpeg = true; |
| scoped_ptr<Image> image(NewImage(image_data, kPuzzleJpgFile, "", |
| options, timer(), message_handler())); |
| |
| // Since we haven't established a size, resizing won't happen. |
| ImageDim dims; |
| EXPECT_TRUE(ImageTestingPeer::ShouldConvertToProgressive(-1, image.get())); |
| |
| // Now provide a context, resizing the image to 10x10. Of course |
| // we should not convert that to progressive, because post-resizing |
| // the image will be tiny. |
| dims.set_width(10); |
| dims.set_height(10); |
| ImageTestingPeer::SetResizedDimensions(dims, image.get()); |
| EXPECT_FALSE(ImageTestingPeer::ShouldConvertToProgressive(-1, image.get())); |
| |
| // At 256x192, we are close to the tipping point, and whether we should |
| // convert to progressive or not is dependent on the compression |
| // level. |
| dims.set_width(256); |
| dims.set_height(192); |
| ImageTestingPeer::SetResizedDimensions(dims, image.get()); |
| EXPECT_TRUE(ImageTestingPeer::ShouldConvertToProgressive(-1, image.get())); |
| |
| // Setting compression to 90. The quality level is high, and our model |
| // says we'll wind up with an image >10204 bytes, which is still |
| // large enough to convert to progressive. |
| EXPECT_TRUE(ImageTestingPeer::ShouldConvertToProgressive(90, image.get())); |
| |
| // Now set the compression to 75, which shrinks our image to <10k so |
| // we should stop converting to progressive. |
| EXPECT_FALSE(ImageTestingPeer::ShouldConvertToProgressive(75, image.get())); |
| } |
| |
| TEST_F(ImageRewriteTest, CacheControlHeaderCheckForNonWebpUA) { |
| if (RunningOnValgrind()) { // Too slow under vg. |
| return; |
| } |
| |
| GoogleString initial_image_url = StrCat(kTestDomain, kPuzzleJpgFile); |
| const GoogleString kHtmlInput = |
| StrCat("<img src='", initial_image_url, "'>"); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| AddRecompressImageFilters(); |
| rewrite_driver()->AddFilters(); |
| ResetForWebp(); |
| |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| // Store image contents into fetcher. |
| AddFileToMockFetcher(initial_image_url, kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| int64 start_time_ms = timer()->NowMs(); |
| ParseUrl(page_url, kHtmlInput); |
| |
| StringVector image_urls; |
| CollectImgSrcs(initial_image_url, output_buffer_, &image_urls); |
| EXPECT_EQ(1, image_urls.size()); |
| const GoogleUrl image_gurl(image_urls[0]); |
| EXPECT_TRUE(image_gurl.LeafSansQuery().ends_with("webp")); |
| const GoogleString& src_string = image_urls[0]; |
| |
| ExpectStringAsyncFetch expect_callback(true, CreateRequestContext()); |
| EXPECT_TRUE(rewrite_driver()->FetchResource(src_string, &expect_callback)); |
| rewrite_driver()->WaitForCompletion(); |
| |
| ResponseHeaders* response_headers = expect_callback.response_headers(); |
| EXPECT_TRUE(response_headers->IsProxyCacheable()); |
| EXPECT_EQ(Timer::kYearMs, |
| response_headers->CacheExpirationTimeMs() - start_time_ms); |
| // Set a non-webp UA. |
| ResetUserAgent(""); |
| |
| GoogleString new_image_url = StrCat(kTestDomain, kPuzzleJpgFile); |
| page_url = StrCat(kTestDomain, "test.html"); |
| ParseUrl(page_url, kHtmlInput); |
| |
| CollectImgSrcs(new_image_url, output_buffer_, &image_urls); |
| EXPECT_EQ(2, image_urls.size()); |
| const GoogleString& rewritten_url = image_urls[1]; |
| const GoogleUrl rewritten_gurl(rewritten_url); |
| EXPECT_TRUE(rewritten_gurl.LeafSansQuery().ends_with("jpg")); |
| |
| GoogleString content; |
| ResponseHeaders response; |
| MD5Hasher hasher; |
| GoogleString new_hash = hasher.Hash(output_buffer_); |
| // Fetch a new rewritten url with a new hash so as to get a short cache |
| // time. |
| const GoogleString rewritten_url_new = |
| StrCat("http://test.com/x", kPuzzleJpgFile, ".pagespeed.ic.", |
| new_hash, ".jpg"); |
| ASSERT_TRUE(FetchResourceUrl(rewritten_url_new, &content, &response)); |
| EXPECT_FALSE(response.IsProxyCacheable()); |
| // TTL will be 100s since resource creation, because that is the input |
| // resource TTL and is lower than the 300s implicit cache TTL for hash |
| // mismatch. |
| EXPECT_EQ(100 * Timer::kSecondMs, |
| response.CacheExpirationTimeMs() - start_time_ms); |
| } |
| |
| TEST_F(ImageRewriteTest, RewriteImagesAddingOptionsToUrl) { |
| AddRecompressImageFilters(); |
| options()->set_add_options_to_urls(true); |
| options()->set_image_jpeg_recompress_quality(73); |
| GoogleString img_src; |
| RewriteImageFromHtml("img", kContentTypeJpeg, &img_src); |
| GoogleUrl img_gurl(html_gurl(), img_src); |
| EXPECT_STREQ("", img_gurl.Query()); |
| ResourceNamer namer; |
| EXPECT_TRUE(rewrite_driver()->Decode(img_gurl.LeafSansQuery(), &namer)); |
| EXPECT_STREQ("gp+jw+pj+rj+rp+rw+iq=73", namer.options()); |
| |
| // Serve this from rewrite_driver(), which has the same cache & the |
| // same options set so will have the canonical results. |
| GoogleString golden_content, remote_content; |
| ResponseHeaders golden_response, remote_response; |
| EXPECT_TRUE(FetchResourceUrl(img_gurl.Spec(), &golden_content, |
| &golden_response)); |
| // EXPECT_EQ(84204, golden_content.size()); |
| |
| // TODO(jmarantz): We cannot test fetches using a flow that |
| // resembles that of the server currently; we need a non-trivial |
| // refactor to put the query-param processing into BlockingFetch. |
| // |
| // In the meantime we rely on system-tests to make sure we can fetch |
| // what we rewrite. |
| /* |
| RewriteOptions* other_opts = other_server_context()->global_options(); |
| other_opts->ClearSignatureForTesting(); |
| other_opts->set_add_options_to_urls(true); |
| other_server_context()->ComputeSignature(other_opts); |
| ASSERT_TRUE(BlockingFetch(img_src, &remote_content, |
| other_server_context(), NULL)); |
| ASSERT_EQ(golden_content.size(), remote_content.size()); |
| EXPECT_EQ(golden_content, remote_content); // don't bother if sizes differ... |
| */ |
| } |
| |
| TEST_F(ImageRewriteTest, ServeWebpFromColdCache) { |
| const StringPiece kJpegMimeType = kContentTypeJpeg.mime_type(); |
| const StringPiece kWebpMimeType = kContentTypeWebp.mime_type(); |
| |
| // First rewrite an HTML file with an image for a webp-compatible browser, |
| // and collect the image URL. |
| UseMd5Hasher(); |
| AddRecompressImageFilters(); |
| options()->set_serve_rewritten_webp_urls_to_any_agent(true); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| GoogleString img_src; |
| ResetForWebp(); |
| Variable* webp_rewrite_count = statistics()->GetVariable( |
| ImageRewriteFilter::kImageWebpRewrites); |
| RewriteImageFromHtml("img", kContentTypeWebp, &img_src); |
| EXPECT_EQ(1, webp_rewrite_count->Get()); |
| GoogleUrl webp_gurl(html_gurl(), img_src); |
| |
| // Serve this image from cache. No further rewrites should be needed, since |
| // the image was optimized when serving HTML. |
| GoogleString golden_content, content; |
| ResponseHeaders response; |
| EXPECT_TRUE(FetchWebp(webp_gurl.Spec(), "webp", &golden_content, &response)); |
| EXPECT_STREQ("image/webp", response.Lookup1(HttpAttributes::kContentType)); |
| EXPECT_TRUE(response.IsProxyCacheable()); |
| EXPECT_EQ(0, webp_rewrite_count->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| |
| // Now clear the cache and fetch the resource again. We will need to |
| // reconstruct the image but we'll get the same result. |
| lru_cache()->Clear(); |
| EXPECT_TRUE(FetchWebp(webp_gurl.Spec(), "webp", &content, &response)); |
| EXPECT_STREQ(kWebpMimeType, response.Lookup1(HttpAttributes::kContentType)); |
| EXPECT_TRUE(response.IsProxyCacheable()); |
| EXPECT_EQ(1, webp_rewrite_count->Get()); // We had to reconstruct. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_TRUE(content == golden_content); |
| |
| // Do the same test again, but don't clear the cache. |
| EXPECT_TRUE(FetchWebp(webp_gurl.Spec(), "webp", &content, &response)); |
| EXPECT_STREQ(kWebpMimeType, response.Lookup1(HttpAttributes::kContentType)); |
| EXPECT_TRUE(response.IsProxyCacheable()); |
| EXPECT_EQ(0, webp_rewrite_count->Get()); // No need to reconstruct... |
| EXPECT_EQ(1, lru_cache()->num_hits()); // ...picked it up from cache. |
| EXPECT_TRUE(content == golden_content); |
| |
| // Now set the user-agent to something that does not support webp, |
| // and we should still reconstruct the webp when asked for it, since |
| // we have called options()->set_serve_rewritten_webp_urls_to_any_agent(true) |
| // above. |
| lru_cache()->Clear(); |
| EXPECT_TRUE(FetchWebp(webp_gurl.Spec(), "null", &content, &response)); |
| EXPECT_STREQ(kWebpMimeType, response.Lookup1(HttpAttributes::kContentType)); |
| EXPECT_TRUE(response.IsProxyCacheable()); |
| EXPECT_EQ(1, webp_rewrite_count->Get()); // We had to reconstruct. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_TRUE(content == golden_content); |
| |
| // Now turn off 'serve_rewritten_webp_urls_to_any_agent', and |
| // we will serve the original jpeg instead, privately cached. |
| options()->ClearSignatureForTesting(); |
| options()->set_serve_rewritten_webp_urls_to_any_agent(false); |
| server_context()->ComputeSignature(options()); |
| |
| // Don't clear the cache here, proving Issue 846 is fixed. |
| ClearStats(); |
| EXPECT_TRUE(FetchWebp(webp_gurl.Spec(), "null", &content, &response)); |
| EXPECT_STREQ(kJpegMimeType, response.Lookup1( |
| HttpAttributes::kContentType)); |
| EXPECT_FALSE(response.IsProxyCacheable()); |
| EXPECT_TRUE(response.IsBrowserCacheable()); |
| EXPECT_EQ(0, webp_rewrite_count->Get()); // Reconstruction not attempted. |
| EXPECT_EQ(2, lru_cache()->num_hits()); // Hits, but result is invalid. |
| EXPECT_FALSE(content == golden_content); |
| EXPECT_GT(content.size(), golden_content.size()); |
| |
| // All works fine anyway we if we clear the cache first. |
| lru_cache()->Clear(); |
| EXPECT_TRUE(FetchWebp(webp_gurl.Spec(), "null", &content, &response)); |
| EXPECT_STREQ(kJpegMimeType, response.Lookup1(HttpAttributes::kContentType)); |
| EXPECT_FALSE(response.IsProxyCacheable()); |
| EXPECT_TRUE(response.IsBrowserCacheable()); |
| EXPECT_EQ(0, webp_rewrite_count->Get()); // Reconstruction not attempted. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_FALSE(content == golden_content); |
| EXPECT_GT(content.size(), golden_content.size()); |
| |
| // But if any webp-enabled client asks for the resource, we will serve |
| // the webp to them. |
| EXPECT_TRUE(FetchWebp(webp_gurl.Spec(), "webp", &content, &response)); |
| EXPECT_STREQ(kWebpMimeType, response.Lookup1(HttpAttributes::kContentType)); |
| |
| // And we will continue to serve jpeg to other browsers. |
| EXPECT_TRUE(FetchWebp(webp_gurl.Spec(), "none", &content, &response)); |
| EXPECT_STREQ(kJpegMimeType, response.Lookup1(HttpAttributes::kContentType)); |
| } |
| |
| // If we drop a rewrite because of load, make sure it returns the original URL. |
| // This verifies that Issue 707 is fixed. |
| TEST_F(ImageRewriteTest, TooBusyReturnsOriginalResource) { |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->set_image_max_rewrites_at_once(1); |
| rewrite_driver()->AddFilters(); |
| |
| MarkTooBusyToWork(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, "", "", |
| false, false); |
| |
| UnMarkTooBusyToWork(); |
| TestSingleRewrite(kBikePngFile, kContentTypePng, kContentTypePng, "", "", |
| true, false); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeUsingRenderedDimensions) { |
| MockCriticalImagesFinder* finder = new MockCriticalImagesFinder(statistics()); |
| server_context()->set_critical_images_finder(finder); |
| options()->EnableFilter(RewriteOptions::kResizeToRenderedImageDimensions); |
| options()->set_log_background_rewrites(true); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| rewrite_driver()->AddFilters(); |
| |
| GoogleString expected_rewritten_url = |
| StrCat(kTestDomain, UintToString(100) , "x", UintToString(70), "x", |
| kChefGifFile, ".pagespeed.ic.0.png"); |
| TestForRenderedDimensions(finder, 100, 70, 100, 70, "", |
| expected_rewritten_url, 1); |
| TestBackgroundRewritingLog( |
| 1, /* rewrite_info_size */ |
| 0, /* rewrite_info_index */ |
| RewriterApplication::APPLIED_OK, /* status */ |
| "ic", /* ID */ |
| "", /* URL */ |
| IMAGE_GIF, /* original_type */ |
| IMAGE_PNG, /* optimized_type */ |
| 24941, /* original_size */ |
| 11489, /* optimized_size */ |
| true, /* is_recompressed */ |
| true, /* is_resized */ |
| 192, /* original width */ |
| 256, /* original height */ |
| true, /* is_resized_using_rendered_dimensions */ |
| 100, /* resized_width */ |
| 70 /* resized_height */); |
| |
| expected_rewritten_url = |
| StrCat(kTestDomain, "x", kChefGifFile, ".pagespeed.ic.0.png"); |
| TestForRenderedDimensions(finder, 100, 0, 192, 256, "", |
| expected_rewritten_url, 0); |
| TestForRenderedDimensions(finder, 0, 70, 192, 256, "", |
| expected_rewritten_url, 0); |
| TestForRenderedDimensions(finder, 0, 0, 192, 256, "", |
| expected_rewritten_url, 0); |
| |
| // Test if rendered dimensions is more than the width and height attribute, |
| // not to resize the image using rendered dimensions. |
| expected_rewritten_url = |
| StrCat(kTestDomain, UintToString(100), "x", UintToString(100), "x", |
| kChefGifFile, ".pagespeed.ic.0.png"); |
| TestForRenderedDimensions(finder, 400, 400, 100, 100, |
| " width=\"100\" height=\"100\"", |
| expected_rewritten_url, 0); |
| } |
| |
| TEST_F(ImageRewriteTest, ResizeEmptyImageUsingRenderedDimensions) { |
| MockCriticalImagesFinder* finder = new MockCriticalImagesFinder(statistics()); |
| server_context()->set_critical_images_finder(finder); |
| options()->EnableFilter(RewriteOptions::kResizeToRenderedImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| rewrite_driver()->AddFilters(); |
| |
| RenderedImages* rendered_images = new RenderedImages; |
| RenderedImages_Image* images = rendered_images->add_image(); |
| images->set_src(StrCat(kTestDomain, kEmptyScreenGifFile)); |
| images->set_rendered_width(1); // Only set width, but not height. |
| |
| finder->set_rendered_images(rendered_images); |
| |
| TestSingleRewrite(kEmptyScreenGifFile, kContentTypeGif, kContentTypeGif, |
| "", "", false, false); |
| } |
| |
| TEST_F(ImageRewriteTest, PreserveUrlRelativity) { |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| rewrite_driver()->AddFilters(); |
| AddFileToMockFetcher("a.jpg", kPuzzleJpgFile, kContentTypeJpeg, 100); |
| AddFileToMockFetcher("b.jpg", kPuzzleJpgFile, kContentTypeJpeg, 100); |
| ValidateExpected( |
| "single_attribute", |
| "<img src=a.jpg>" |
| "<img src=http://test.com/b.jpg>", |
| StrCat("<img src=", Encode("", "ic", "0", "a.jpg", "jpg"), ">" |
| "<img src=", Encode("http://test.com/", "ic", "0", "b.jpg", "jpg"), |
| ">")); |
| } |
| |
| TEST_F(ImageRewriteTest, RewriteMultipleAttributes) { |
| // Test a complex setup with both regular and custom image urls, including an |
| // invalid image which should only get cache-extended. |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| options()->EnableFilter(RewriteOptions::kExtendCacheImages); |
| |
| rewrite_driver()->AddFilters(); |
| |
| options()->ClearSignatureForTesting(); |
| options()->AddUrlValuedAttribute("img", "data-src", semantic_type::kImage); |
| server_context()->ComputeSignature(options()); |
| |
| // A, B, and D are real image files, so they should be properly rewritten. |
| AddFileToMockFetcher("a.jpg", kPuzzleJpgFile, kContentTypeJpeg, 100); |
| AddFileToMockFetcher("b.jpg", kPuzzleJpgFile, kContentTypeJpeg, 100); |
| AddFileToMockFetcher("d.jpg", kPuzzleJpgFile, kContentTypeJpeg, 100); |
| |
| // C is not an image file, so image rewrite fails (but cache extension works). |
| SetResponseWithDefaultHeaders("c.jpg", kContentTypeJpeg, "Not a JPG", 600); |
| |
| ValidateExpected( |
| "multiple_attributes", |
| "<img src=a.jpg data-src=b.jpg data-src=c.jpg data-src=d.jpg>", |
| StrCat( |
| "<img src=", Encode("", "ic", "0", "a.jpg", "jpg"), |
| " data-src=", Encode("", "ic", "0", "b.jpg", "jpg"), |
| " data-src=", Encode("", "ce", "0", "c.jpg", "jpg"), |
| " data-src=", Encode("", "ic", "0", "d.jpg", "jpg"), |
| ">")); |
| } |
| |
| TEST_F(ImageRewriteTest, IproCorrectVaryHeaders) { |
| // See https://code.google.com/p/modpagespeed/issues/detail?id=817 |
| // Here we're particularly looking for some issues that the ipro-specific |
| // testing doesn't catch because it uses a fake version of the image rewrite |
| // filter. |
| SetupIproTests("Accept"); |
| rewrite_driver()->AddFilters(); |
| GoogleString puzzleUrl = StrCat(kTestDomain, kPuzzleJpgFile); |
| GoogleString bikeUrl = StrCat(kTestDomain, kBikePngFile); |
| GoogleString cuppaUrl = StrCat(kTestDomain, kCuppaPngFile); |
| ResponseHeaders response_headers; |
| |
| // We test 3 kinds of image (photo, photographic png, non-photographic png) |
| // with two pairs of browsers: simple and maximally webp-capable (including |
| // Accept: image/webp). |
| |
| // puzzle is unconditionally webp-convertible and thus gets a vary: header. |
| IProFetchAndValidate(puzzleUrl, "webp-la", "image/webp", &response_headers); |
| EXPECT_EQ(&kContentTypeWebp, response_headers.DetermineContentType()) << |
| response_headers.DetermineContentType()->mime_type(); |
| EXPECT_STREQ(HttpAttributes::kAccept, |
| response_headers.Lookup1(HttpAttributes::kVary)); |
| IProFetchAndValidate(puzzleUrl, "", "", &response_headers); |
| EXPECT_EQ(&kContentTypeJpeg, response_headers.DetermineContentType()) << |
| response_headers.DetermineContentType()->mime_type(); |
| EXPECT_STREQ(HttpAttributes::kAccept, |
| response_headers.Lookup1(HttpAttributes::kVary)); |
| |
| // Similarly, bike is photographic and will be jpeg or webp-converted and have |
| // a Vary: header. |
| IProFetchAndValidate(bikeUrl, "webp-la", "image/webp", &response_headers); |
| EXPECT_EQ(&kContentTypeWebp, response_headers.DetermineContentType()) << |
| response_headers.DetermineContentType()->mime_type(); |
| EXPECT_STREQ(HttpAttributes::kAccept, |
| response_headers.Lookup1(HttpAttributes::kVary)); |
| IProFetchAndValidate(bikeUrl, "", "", &response_headers); |
| EXPECT_EQ(&kContentTypeJpeg, response_headers.DetermineContentType()) << |
| response_headers.DetermineContentType()->mime_type(); |
| EXPECT_STREQ(HttpAttributes::kAccept, |
| response_headers.Lookup1(HttpAttributes::kVary)); |
| |
| // Finally, cuppa has an alpha channel and is non-photographic, so it |
| // shouldn't be converted to webp and should remain a png. Thus it should |
| // lack a Vary: header. |
| IProFetchAndValidate(cuppaUrl, "webp-la", "image/webp", &response_headers); |
| EXPECT_EQ(&kContentTypePng, response_headers.DetermineContentType()) << |
| response_headers.DetermineContentType()->mime_type(); |
| EXPECT_FALSE(response_headers.Has(HttpAttributes::kVary)) << |
| response_headers.Lookup1(HttpAttributes::kVary); |
| IProFetchAndValidate(cuppaUrl, "", "", &response_headers); |
| EXPECT_EQ(&kContentTypePng, response_headers.DetermineContentType()) << |
| response_headers.DetermineContentType()->mime_type(); |
| EXPECT_FALSE(response_headers.Has(HttpAttributes::kVary)) << |
| response_headers.Lookup1(HttpAttributes::kVary); |
| } |
| |
| TEST_F(ImageRewriteTest, NoTransformOptimized) { |
| options()->set_no_transform_optimized_images(true); |
| AddRecompressImageFilters(); |
| rewrite_driver()->AddFilters(); |
| GoogleString initial_url = StrCat(kTestDomain, kBikePngFile); |
| AddFileToMockFetcher(initial_url, kBikePngFile, kContentTypePng, 100); |
| GoogleString out_jpg_url(Encode(kTestDomain, "ic", "0", kBikePngFile, "jpg")); |
| GoogleString out_jpg; |
| ResponseHeaders response_headers; |
| EXPECT_TRUE(FetchResourceUrl(out_jpg_url, &out_jpg, &response_headers)); |
| ConstStringStarVector values; |
| ASSERT_TRUE(response_headers.Lookup(HttpAttributes::kCacheControl, &values)); |
| bool found = false; |
| for (int i = 0, n = values.size(); i < n; ++i) { |
| found |= *(values[i]) == "no-transform"; |
| } |
| EXPECT_TRUE(found); |
| } |
| |
| TEST_F(ImageRewriteTest, ReportDimensionsToJs) { |
| options()->EnableFilter(RewriteOptions::kExperimentCollectMobImageInfo); |
| AddRecompressImageFilters(); |
| rewrite_driver()->AddFilters(); |
| AddFileToMockFetcher(StrCat(kTestDomain, "a.png"), kBikePngFile, |
| kContentTypePng, 100); |
| AddFileToMockFetcher(StrCat(kTestDomain, "b.jpeg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| const GoogleString kTest1Gif = StrCat(kTestDomain, k1x1GifFile); |
| AddFileToMockFetcher(kTest1Gif, k1x1GifFile, kContentTypeGif, 100); |
| |
| SetupWriter(); |
| rewrite_driver()->StartParse(StrCat(kTestDomain, "dims.html")); |
| rewrite_driver()->ParseText(StrCat("<img src=\"", kTestDomain, "a.png\">")); |
| rewrite_driver()->Flush(); |
| rewrite_driver()->ParseText(StrCat("<img src=\"", kTestDomain, "b.jpeg\">")); |
| rewrite_driver()->Flush(); |
| rewrite_driver()->ParseText(StrCat("<img src=\"", kTest1Gif, "\">")); |
| rewrite_driver()->FinishParse(); |
| |
| GoogleString out_png_url(Encode(kTestDomain, "ic", "0", "a.png", "jpg")); |
| GoogleString out_jpeg_url(Encode(kTestDomain, "ic", "0", "b.jpeg", "jpg")); |
| GoogleString js = StrCat( |
| "psMobStaticImageInfo = {" |
| "\"", kTest1Gif, "\":{w:1,h:1}," // not optimized. |
| "\"", out_png_url, "\":{w:100,h:100}," |
| "\"", out_jpeg_url, "\":{w:1023,h:766}," |
| "}"); |
| EXPECT_EQ(StrCat("<img src=\"", out_png_url, "\">" |
| "<img src=\"", out_jpeg_url, "\">" |
| "<img src=\"", kTest1Gif, "\">" |
| "<script>", js, "</script>"), |
| output_buffer_); |
| } |
| |
| TEST_F(ImageRewriteTest, ReportDimensionsToJsPartial) { |
| // Test where one image isn't loaded in time. We report partial info. |
| SetupWaitFetcher(); |
| options()->EnableFilter(RewriteOptions::kExperimentCollectMobImageInfo); |
| AddRecompressImageFilters(); |
| rewrite_driver()->AddFilters(); |
| AddFileToMockFetcher(StrCat(kTestDomain, "a.png"), kBikePngFile, |
| kContentTypePng, 100); |
| AddFileToMockFetcher(StrCat(kTestDomain, "b.jpeg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| factory()->wait_url_async_fetcher()->DoNotDelay(StrCat(kTestDomain, "a.png")); |
| |
| SetupWriter(); |
| rewrite_driver()->StartParse(StrCat(kTestDomain, "dims.html")); |
| rewrite_driver()->ParseText("<img src=\"a.png\"><img src=\"b.jpeg\">"); |
| rewrite_driver()->FinishParse(); |
| |
| GoogleString out_png_url(Encode("", "ic", "0", "a.png", "jpg")); |
| GoogleString out_jpeg_url(Encode("", "ic", "0", "b.jpeg", "jpg")); |
| GoogleString js1 = StrCat( |
| "psMobStaticImageInfo = {" |
| "\"", kTestDomain, out_png_url, "\":{w:100,h:100}," |
| "}"); |
| GoogleString js2 = StrCat( |
| "psMobStaticImageInfo = {" |
| "\"", kTestDomain, out_png_url, "\":{w:100,h:100}," |
| "\"", kTestDomain, out_jpeg_url, "\":{w:1023,h:766}," |
| "}"); |
| EXPECT_EQ(StrCat("<img src=\"", out_png_url, "\">", |
| "<img src=\"b.jpeg\">", |
| "<script>", js1, "</script>"), |
| output_buffer_); |
| |
| CallFetcherCallbacks(); |
| |
| // Next time all is available. |
| output_buffer_.clear(); |
| SetupWriter(); |
| rewrite_driver()->StartParse(StrCat(kTestDomain, "dims2.html")); |
| rewrite_driver()->ParseText("<img src=\"a.png\"><img src=\"b.jpeg\">"); |
| rewrite_driver()->FinishParse(); |
| EXPECT_EQ(StrCat("<img src=\"", out_png_url, "\">", |
| "<img src=\"", out_jpeg_url, "\">", |
| "<script>", js2, "</script>"), |
| output_buffer_); |
| } |
| |
| TEST_F(ImageRewriteTest, DebugMessageImageInfo) { |
| options()->EnableFilter(RewriteOptions::kDebug); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpAnimated); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| rewrite_driver()->AddFilters(); |
| AddFileToMockFetcher("photo_opaque.gif", kChefGifFile, kContentTypeGif, |
| 100); |
| AddFileToMockFetcher("graphic_transparent.png", kCuppaTPngFile, |
| kContentTypePng, 100); |
| AddFileToMockFetcher("animated.gif", kCradleAnimation, kContentTypeGif, 100); |
| |
| Parse("single_attribute", "<img src=photo_opaque.gif>" |
| "<img src=graphic_transparent.png><img src=animated.gif>"); |
| |
| const GoogleString expected = StrCat( |
| "<img src=", Encode("", "ic", "0", "photo_opaque.gif", "png"), ">" |
| "<!--Image does not appear to need resizing.-->" |
| "<!--Image has no transparent pixels, is not sensitive to compression " |
| "noise, and has no animation.-->" |
| "<img src=graphic_transparent.png>" |
| "<!--Image does not appear to need resizing.-->" |
| "<!--Image has transparent pixels, is sensitive to compression noise, " |
| "and has no animation.-->" |
| "<img src=animated.gif>" |
| "<!--Image does not appear to need resizing.-->" |
| "<!--Image has no transparent pixels, is sensitive to compression noise, " |
| "and has animation.-->"); |
| |
| EXPECT_THAT(output_buffer_, HasSubstr(expected)); |
| } |
| |
| TEST_F(ImageRewriteTest, DebugMessageInline) { |
| options()->set_image_inline_max_bytes(100); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kDebug); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| rewrite_driver()->AddFilters(); |
| |
| GoogleString initial_url = StrCat(kTestDomain, kChefGifFile); |
| GoogleString page_url = StrCat(kTestDomain, "test.html"); |
| AddFileToMockFetcher(initial_url, kChefGifFile, kContentTypeGif, 100); |
| const char html_boilerplate[] = "<img src='%s' width='10' height='12'>"; |
| GoogleString html_input = StringPrintf(html_boilerplate, initial_url.c_str()); |
| |
| ParseUrl(page_url, html_input); |
| |
| const char kInlineMessage[] = |
| "The image was not inlined because it has too many bytes."; |
| EXPECT_THAT(output_buffer_, HasSubstr(kInlineMessage)); |
| } |
| |
| TEST_F(ImageRewriteTest, DebugMessageUnauthorized) { |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kResizeImages); |
| options()->EnableFilter(RewriteOptions::kDebug); |
| rewrite_driver()->AddFilters(); |
| const char kAuthorizedPath[] = "http://test.com/photo_opaque.gif"; |
| const char kUnauthorizedPath[] = "http://unauth.com/photo_opaque.gif"; |
| AddFileToMockFetcher(kAuthorizedPath, kChefGifFile, kContentTypeGif, 100); |
| AddFileToMockFetcher(kUnauthorizedPath, kChefGifFile, kContentTypeGif, 100); |
| |
| Parse("unauthorized_domain", StrCat("<img src=", kAuthorizedPath, ">" |
| "<img src=", kUnauthorizedPath, ">")); |
| |
| GoogleUrl unauth_gurl(kUnauthorizedPath); |
| const GoogleString expected = StrCat( |
| "<img src=", Encode(kTestDomain, "ic", "0", "photo_opaque.gif", "png"), |
| ">" |
| "<!--Image does not appear to need resizing.-->" |
| "<!--Image has no transparent pixels, is not sensitive to compression " |
| "noise, and has no animation.-->" |
| "<img src=", kUnauthorizedPath, ">", |
| "<!--", |
| RewriteDriver::GenerateUnauthorizedDomainDebugComment(unauth_gurl), |
| "-->"); |
| |
| EXPECT_THAT(output_buffer_, HasSubstr(expected)); |
| } |
| |
| // Chrome on iPhone rewrites a photo-like GIF to lossy WebP but cannot inline |
| // it. |
| TEST_F(ImageRewriteTest, ChromeIphoneOutlinesWebP) { |
| TestInlining(true, UserAgentMatcherTestBase::kIPhoneChrome36UserAgent, |
| kChefGifFile, kContentTypeGif, kContentTypeWebp, false); |
| } |
| |
| // Chrome on iPad rewrites a graphics-like PNG to lossless WebP but cannot |
| // inline it. |
| TEST_F(ImageRewriteTest, ChromeIpadInlinesPng) { |
| TestInlining(true, UserAgentMatcherTestBase::kIPadChrome36UserAgent, |
| kCuppaTPngFile, kContentTypePng, kContentTypeWebp, false); |
| } |
| |
| // Chrome on iPad rewrites a JPEG to lossy WebP but cannot inline it. |
| TEST_F(ImageRewriteTest, ChromeIpadOutlinesWebp) { |
| TestInlining(true, UserAgentMatcherTestBase::kIPadChrome36UserAgent, |
| kPuzzleJpgFile, kContentTypeJpeg, kContentTypeWebp, false); |
| } |
| |
| // Chrome on iPhone rewrites a graphics-like PNG to another PNG and inlines it. |
| TEST_F(ImageRewriteTest, ChromeIphoneInlinesPng) { |
| TestInlining(false, UserAgentMatcherTestBase::kIPhoneChrome36UserAgent, |
| kCuppaPngFile, kContentTypePng, kContentTypePng, true); |
| } |
| |
| // Chrome on iPad rewrites a JPEG to another JPEG and inlines it. |
| TEST_F(ImageRewriteTest, ChromeIpadInlinesJpeg) { |
| TestInlining(false, UserAgentMatcherTestBase::kIPadChrome36UserAgent, |
| kPuzzleJpgFile, kContentTypeJpeg, kContentTypeJpeg, true); |
| } |
| |
| // Safari on iPhone rewrites a photo-like GIF to JPEG and inlines it. |
| TEST_F(ImageRewriteTest, SafariIphoneInlinesJpeg) { |
| TestInlining(false, UserAgentMatcherTestBase::kIPhone4Safari, |
| kChefGifFile, kContentTypeGif, kContentTypeJpeg, true); |
| } |
| |
| // Chrome on Android rewrites a photo-like PNG to lossy WebP and inlines it. |
| TEST_F(ImageRewriteTest, ChromeAndroidInlinesWebP) { |
| TestInlining(true, UserAgentMatcherTestBase::kAndroidChrome21UserAgent, |
| kChefGifFile, kContentTypeGif, kContentTypeWebp, true); |
| } |
| |
| // Chrome on desktop rewrites a JPEG to lossy WebP and inlines it. |
| TEST_F(ImageRewriteTest, ChromeDesktopInlinesWebp) { |
| TestInlining(true, UserAgentMatcherTestBase::kChrome18UserAgent, |
| kPuzzleJpgFile, kContentTypeJpeg, kContentTypeWebp, true); |
| } |
| |
| // Chrome on Android rewrites a graphics-like PNG to lossless WebP and |
| // inlines it. |
| TEST_F(ImageRewriteTest, ChromeAndroidInlinesLosslessWebp) { |
| TestInlining(true, UserAgentMatcherTestBase::kNexus10ChromeUserAgent, |
| kCuppaTPngFile, kContentTypePng, kContentTypeWebp, true); |
| } |
| |
| TEST_F(ImageRewriteTest, PngExceedResolutionLimit) { |
| TestResolutionLimit(kResolutionLimitBytes - 1, kResolutionLimitPngFile, |
| kContentTypePng, false /*try_webp*/, |
| false /*try_resize*/, false /*expect_rewritten*/); |
| } |
| |
| TEST_F(ImageRewriteTest, JpegExceedResolutionLimit) { |
| TestResolutionLimit(kResolutionLimitBytes - 1, kResolutionLimitJpegFile, |
| kContentTypeJpeg, false /*try_webp*/, |
| false /*try_resize*/, false /*expect_rewritten*/); |
| } |
| |
| TEST_F(ImageRewriteTest, PngInResolutionLimit) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| TestResolutionLimit(kResolutionLimitBytes, kResolutionLimitPngFile, |
| kContentTypePng, true /*try_webp*/, true /*try_resize*/, |
| true /*expect_rewritten*/); |
| } |
| |
| TEST_F(ImageRewriteTest, PngInResolutionLimitNoResizing) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| TestResolutionLimit(kResolutionLimitBytes, kResolutionLimitPngFile, |
| kContentTypePng, true /*try_webp*/, |
| false /*try_resize*/, true /*expect_rewritten*/); |
| } |
| |
| TEST_F(ImageRewriteTest, JpegInResolutionLimit) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| TestResolutionLimit(kResolutionLimitBytes, kResolutionLimitJpegFile, |
| kContentTypeJpeg, true /*try_webp*/, |
| true /*try_resize*/, true /*expect_rewritten*/); |
| } |
| |
| TEST_F(ImageRewriteTest, JpegInResolutionLimitNoResizing) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| TestResolutionLimit(kResolutionLimitBytes, kResolutionLimitJpegFile, |
| kContentTypeJpeg, true /*try_webp*/, |
| false /*try_resize*/, true /*expect_rewritten*/); |
| } |
| |
| TEST_F(ImageRewriteTest, AnimatedGifToWebpWithWebpAnimatedUa) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpAnimated); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpAnimated(); |
| TestSingleRewrite(kCradleAnimation, kContentTypeGif, kContentTypeWebp, |
| "", " width=\"200\" height=\"150\"", true, false); |
| |
| TestConversionVariables(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpg |
| 0, 1, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageRewriteTest, AnimatedGifToWebpWithWebpLaUa) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpAnimated); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpLossless(); |
| TestSingleRewrite(kCradleAnimation, kContentTypeGif, kContentTypeGif, |
| "", " width=\"200\" height=\"150\"", false, false); |
| TestConversionVariables(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| false); |
| } |
| |
| TEST_F(ImageRewriteTest, AnimatedGifToWebpNotEnabled) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpLossless); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpAnimated(); |
| TestSingleRewrite(kCradleAnimation, kContentTypeGif, kContentTypeGif, |
| "", " width=\"200\" height=\"150\"", false, false); |
| TestConversionVariables(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| false); |
| } |
| |
| TEST_F(ImageRewriteTest, GifToWebpLosslessWithWebpAnimatedUa) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| options()->EnableFilter(RewriteOptions::kInsertImageDimensions); |
| options()->EnableFilter(RewriteOptions::kConvertGifToPng); |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpAnimated); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| SetupForWebpAnimated(); |
| TestSingleRewrite(kChefGifFile, kContentTypeGif, kContentTypeWebp, |
| "", " width=\"192\" height=\"256\"", true, false); |
| TestConversionVariables(0, 1, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageRewriteTest, AnimatedNoCacheReuse) { |
| // Make sure we don't reuse results for animated webp-capable UAs for |
| // non-webp targets. |
| AddFileToMockFetcher(StrCat(kTestDomain, "a.jpeg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kConvertToWebpAnimated); |
| options()->set_image_recompress_quality(85); |
| rewrite_driver()->AddFilters(); |
| |
| // WebP capable browser --- made a WebP image. |
| SetupForWebpAnimated(); |
| ValidateExpected("webp broswer", "<img src=a.jpeg>", |
| "<img src=xa.jpeg.pagespeed.ic.0.webp>"); |
| ClearRewriteDriver(); |
| |
| // Not a WebP browser -- don't! |
| SetCurrentUserAgent("curl"); |
| ValidateNoChanges("non-webp broswer", "<img src=a.jpeg>"); |
| } |
| |
| // Make sure that we optimize images to the correct format and correct quality, |
| // and add the correct "Vary" response header. |
| // |
| // Test 4 images: |
| // - JPEG (optimized to lossy format) |
| // - PNG image with photographic content (optimized to lossy format) |
| // - PNG image with non-photographic content (optimized to lossless format) |
| // - Animated GIF (optimized to animated WebP) |
| // |
| // Use 3 user-agents: |
| // - Chrome on Android (mobile and supports all formats, including WebP) |
| // - Safari on iOS (mobile but doesn't support WebP) |
| // - Firefox (neither mobile nor supports WebP) |
| // |
| // Check 2 headers: |
| // - Save-Data header |
| // - Via header |
| // |
| // To make sure that we don't have cache collision, each image is fetched twice, |
| // with other image fetching in between. |
| TEST_F(ImageRewriteTest, IproAllowAuto) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("Auto"); |
| rewrite_driver()->AddFilters(); |
| |
| // Fetch each image twice, to make sure no cache collision. |
| for (int i = 0; i < 2; ++i) { |
| // Test the combination of 4 images and 3 user-agents. |
| for (int j = 0; j < 12; ++j) { |
| const char* image_name = kOptimizedImageInfoList[j].image_name; |
| const char* user_agent = kOptimizedImageInfoList[j].user_agent; |
| const OptimizedImageInfoList& optimized_info = |
| *kOptimizedImageInfoList[j].optimized_info; |
| // Test the combination of 2 headers (each header can be on or off). |
| IproFetchAndValidateWithHeaders(image_name, user_agent, optimized_info); |
| } |
| } |
| } |
| |
| // Test when we can vary on "Accept,Save-Data". |
| TEST_F(ImageRewriteTest, IproAllowSaveDataAccept) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("Accept,Save-Data"); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaAllowSaveDataAccept); |
| } |
| |
| // Test when we can vary on "User-Agent". |
| TEST_F(ImageRewriteTest, IproAllowUserAgent) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("User-Agent"); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaAllowUserAgent); |
| } |
| |
| // Test when we can vary on "Accept". |
| TEST_F(ImageRewriteTest, IproAllowAccept) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("Accept"); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaAllowAccept); |
| } |
| |
| // Test when we can vary on "Save-Data". |
| TEST_F(ImageRewriteTest, IproAllowSaveData) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("Save-Data"); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaAllowSaveData); |
| } |
| |
| // Test when we cannot vary on anything. |
| TEST_F(ImageRewriteTest, IproAllowNone) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("None"); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaAllowNone); |
| } |
| |
| // Test when the qualities for Save-Data are undefined. |
| TEST_F(ImageRewriteTest, IproAllowAutoNoSaveDataQualities) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("Auto"); |
| options()->set_image_jpeg_quality_for_save_data(-1); |
| options()->set_image_webp_quality_for_save_data(-1); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaNoSaveDataQualities); |
| } |
| |
| // Test when the qualities for Save-Data are the same as the regular ones. |
| TEST_F(ImageRewriteTest, IproAllowAutoUnusedSaveDataQualities) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("Auto"); |
| options()->set_image_jpeg_quality_for_save_data( |
| options()->ImageJpegQuality()); |
| options()->set_image_webp_quality_for_save_data( |
| options()->ImageWebpQuality()); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaNoSaveDataQualities); |
| } |
| |
| // Test when the qualities for small screen are undefined. |
| TEST_F(ImageRewriteTest, IproAllowAutoNoSmallScreenQualities) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("Auto"); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(-1); |
| options()->set_image_webp_recompress_quality_for_small_screens(-1); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaNoSmallScreenQualities); |
| } |
| |
| // Test when neither the qualities for Save-Data nor those for small screens |
| // are undefined. |
| TEST_F(ImageRewriteTest, IproAllowAutoNoSmallScreenSaveDataQualities) { |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| SetupIproTests("Auto"); |
| options()->set_image_jpeg_quality_for_save_data(-1); |
| options()->set_image_webp_quality_for_save_data(-1); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(-1); |
| options()->set_image_webp_recompress_quality_for_small_screens(-1); |
| rewrite_driver()->AddFilters(); |
| IproFetchAndValidateWithHeaders( |
| kPuzzleJpgFile, UserAgentMatcherTestBase::kNexus6Chrome44UserAgent, |
| kPuzzleOptimizedForWebpUaNoSpecialQualities); |
| } |
| |
| TEST_F(ImageRewriteTest, ContentTypeValidation) { |
| ValidateFallbackHeaderSanitization("ic"); |
| } |
| |
| } // namespace net_instaweb |