chromium/ios/chrome/browser/ui/integration_tests/pdf_egtest.mm

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>

#import "base/test/ios/wait_util.h"
#import "build/branding_buildflags.h"
#import "ios/chrome/test/earl_grey/chrome_actions.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "net/test/embedded_test_server/embedded_test_server.h"

using chrome_test_util::RegularTabGrid;
using chrome_test_util::ScrollToTop;
using chrome_test_util::TabGridCellAtIndex;

const char kPDFPath[] = "/complex_document.pdf";
const char kGreenPDFPath[] = "/green.pdf";

@interface PDFTestCase : ChromeTestCase
@end

@implementation PDFTestCase

// Regression test for crbug/981893. Repro steps: open a PDF in a new
// tab, switch back and forth betweeen the new tab and the old one by
// swiping in the toolbar. The regression is a crash.
- (void)testSwitchToAndFromPDF {
  // Compact width only.
  if (![ChromeEarlGrey isCompactWidth]) {
    EARL_GREY_TEST_DISABLED(@"Disabled on iPad -- depends on swiping in the "
                            @"toolbar to change tabs, which is a compact-"
                            @"only feature.");
  }

  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  // Load the first page.
  [ChromeEarlGrey loadURL:self.testServer->GetURL("/echo")];
  [ChromeEarlGrey waitForWebStateContainingText:"Echo"];

  // Open a new Tab to have a tab to switch to.
  [ChromeEarlGreyUI openNewTab];
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPDFPath)];

  id<GREYMatcher> toolbar = chrome_test_util::PrimaryToolbar();

  // Swipe to the first page.
  [[EarlGrey selectElementWithMatcher:toolbar]
      performAction:grey_swipeSlowInDirection(kGREYDirectionRight)];
  [ChromeEarlGrey waitForWebStateContainingText:"Echo"];

  // Swipe back and forth a few times. If this crashes, there may be a new
  // problem with how WKWebView snapshots PDFs.
  for (int i = 0; i < 3; i++) {
    [[EarlGrey selectElementWithMatcher:toolbar]
        performAction:grey_swipeFastInDirection(kGREYDirectionLeft)];
    [[EarlGrey selectElementWithMatcher:toolbar]
        performAction:grey_swipeFastInDirection(kGREYDirectionRight)];
  }

  [ChromeEarlGrey waitForWebStateContainingText:"Echo"];
}

// Regression test for crbug/981893. Repro steps: open PDFs in two tabs.
// Enter and leave the tab grid. Swipe back and forth repeatedly between
// the two tabs in the toolbar. The regressiom is a crash anywhere in this
// process.
- (void)testSwitchBetweenPDFs {
  // Compact width only.
  if (![ChromeEarlGrey isCompactWidth]) {
    EARL_GREY_TEST_DISABLED(@"Disabled on iPad -- depends on swiping in the "
                            @"toolbar to change tabs, which is a compact-"
                            @"only feature.");
  }

  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  // Load two PDFs in different tabs.
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPDFPath)];
  [ChromeEarlGreyUI openNewTab];
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPDFPath)];

  [ChromeEarlGreyUI openTabGrid];

  // Leave the tab grid.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];

  id<GREYMatcher> toolbar = chrome_test_util::PrimaryToolbar();
  // Swipe back and forth a few times. If this crashes, there may be a new
  // problem with how WKWebView snapshots PDFs.
  for (int i = 0; i < 3; i++) {
    [[EarlGrey selectElementWithMatcher:toolbar]
        performAction:grey_swipeFastInDirection(kGREYDirectionLeft)];
    [[EarlGrey selectElementWithMatcher:toolbar]
        performAction:grey_swipeFastInDirection(kGREYDirectionRight)];
  }
}

// Regression test for crbug/981893. Repro steps: Open a tab, then navigate
// to a PDF in that tab. Enter the tab grid. Wait five seconds. Exit the
// tab switcher. The regression is a crash anywhere in this process.
- (void)testPDFIntoTabGridAndWait {
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  // Load a page, then a PDF.
  [ChromeEarlGrey loadURL:self.testServer->GetURL("/echo")];
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPDFPath)];

  [ChromeEarlGreyUI openTabGrid];

  base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(5));

  // Leave the tab grid.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

// Tests the center color of the grid tab showing a PDF. (physical device only)
- (void)testCenterColorOfPDFTabGrid {
#if TARGET_IPHONE_SIMULATOR
  EARL_GREY_TEST_SKIPPED(@"The API to take a snapshot is not working correctly "
                         @"and it becomes black on simulator.");
#endif

  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  // Load a page, then a PDF.
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kGreenPDFPath)];

  // Open more than 6 pages to scroll down/up in a tab grid.
  for (int i = 0; i < 10; i++) {
    [ChromeEarlGreyUI openNewTab];
    [ChromeEarlGrey loadURL:self.testServer->GetURL("/echo")];
  }

  // Open a tab grid and then scroll up to make a tab grid cell recycled and
  // re-created.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:RegularTabGrid()]
      performAction:ScrollToTop()];

  // Take a snapshot of the tab grid showing a PDF.
  EDORemoteVariable<UIImage*>* tabGridSnapshot =
      [[EDORemoteVariable alloc] init];
  [[EarlGrey selectElementWithMatcher:TabGridCellAtIndex(0)]
      performAction:grey_snapshot(tabGridSnapshot)];

  // Get a color of the center position in a tab grid.
  CGFloat red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0;
  [self getCenterColor:tabGridSnapshot.object
                   red:&red
                 green:&green
                  blue:&blue
                 alpha:&alpha];

  // The values may not be exactly 0.0 or 1.0 due to the image compression. The
  // test allows more flexible values.
  GREYAssert(
      red < 0.1,
      @"A red value of the center color in a tab grid should be close to 0.");
  GREYAssert(
      green > 0.9,
      @"A green value of the center color in a tab grid should be close to 1.");
  GREYAssert(
      blue < 0.1,
      @"A blue value of the center color in a tab grid should be close to 0.");
  GREYAssert(
      alpha > 0.9,
      @"A alpha value of the center color in a tab grid should be close to 1.");
}

#pragma mark - Helper methods

- (void)getCenterColor:(UIImage*)image
                   red:(CGFloat*)red
                 green:(CGFloat*)green
                  blue:(CGFloat*)blue
                 alpha:(CGFloat*)alpha {
  CGImageRef imageRef = [image CGImage];

  NSUInteger width = CGImageGetWidth(imageRef);
  NSUInteger height = CGImageGetHeight(imageRef);
  NSUInteger x = width / 2;
  NSUInteger y = height / 2;

  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  unsigned char* data =
      (unsigned char*)calloc(height * width * 4, sizeof(unsigned char));
  NSUInteger bytesPerPixel = 4;
  NSUInteger bytesPerRow = bytesPerPixel * width;
  NSUInteger bitsPerComponent = 8;
  CGContextRef context =
      CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow,
                            colorSpace, kCGImageAlphaPremultipliedLast);
  CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);

  NSUInteger index = (bytesPerRow * y) + x * bytesPerPixel;
  *red = ((CGFloat)data[index]) / 255.0f;
  *green = ((CGFloat)data[index + 1]) / 255.0f;
  *blue = ((CGFloat)data[index + 2]) / 255.0f;
  *alpha = ((CGFloat)data[index + 3]) / 255.0f;

  CGColorSpaceRelease(colorSpace);
  CGContextRelease(context);
  free(data);
}

@end