[webview_flutter] Add a backgroundColor option to the iOS webview (#4570)

This PR add an option to set the background color of the iOS webview.

Part of https://github.com/flutter/plugins/pull/3431
Part of https://github.com/flutter/flutter/issues/29300
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index e9c59c2..22f45f2 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.5.0
 
+* Adds an option to set the background color of the webview.
 * Migrates from `analysis_options_legacy.yaml` to `analysis_options.yaml`.
 * Integration test fixes.
 * Updates to webview_flutter_platform_interface version 1.5.2.
@@ -22,7 +23,7 @@
 
 ## 2.0.14
 
-* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). 
+* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package).
 
 ## 2.0.13
 
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m
index d193be7..e43fb97 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m
@@ -5,6 +5,25 @@
 @import XCTest;
 @import os.log;
 
+static UIColor* getPixelColorInImage(CGImageRef image, size_t x, size_t y) {
+  CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image));
+  const UInt8* data = CFDataGetBytePtr(pixelData);
+
+  size_t bytesPerRow = CGImageGetBytesPerRow(image);
+  size_t pixelInfo = (bytesPerRow * y) + (x * 4);  // 4 bytes per pixel
+
+  UInt8 red = data[pixelInfo + 0];
+  UInt8 green = data[pixelInfo + 1];
+  UInt8 blue = data[pixelInfo + 2];
+  UInt8 alpha = data[pixelInfo + 3];
+  CFRelease(pixelData);
+
+  return [UIColor colorWithRed:red / 255.0f
+                         green:green / 255.0f
+                          blue:blue / 255.0f
+                         alpha:alpha / 255.0f];
+}
+
 @interface FLTWebViewUITests : XCTestCase
 @property(nonatomic, strong) XCUIApplication* app;
 @end
@@ -18,6 +37,54 @@
   [self.app launch];
 }
 
+- (void)testTransparentBackground {
+  XCUIApplication* app = self.app;
+  XCUIElement* menu = app.buttons[@"Show menu"];
+  if (![menu waitForExistenceWithTimeout:30.0]) {
+    os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription);
+    XCTFail(@"Failed due to not able to find menu");
+  }
+  [menu tap];
+
+  XCUIElement* transparentBackground = app.buttons[@"Transparent background example"];
+  if (![transparentBackground waitForExistenceWithTimeout:30.0]) {
+    os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription);
+    XCTFail(@"Failed due to not able to find Transparent background example");
+  }
+  [transparentBackground tap];
+
+  XCUIElement* transparentBackgroundLoaded =
+      app.webViews.staticTexts[@"Transparent background test"];
+  if (![transparentBackgroundLoaded waitForExistenceWithTimeout:30.0]) {
+    os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription);
+    XCTFail(@"Failed due to not able to find Transparent background test");
+  }
+
+  XCUIScreenshot* screenshot = [[XCUIScreen mainScreen] screenshot];
+
+  UIImage* screenshotImage = screenshot.image;
+  CGImageRef screenshotCGImage = screenshotImage.CGImage;
+  UIColor* centerLeftColor =
+      getPixelColorInImage(screenshotCGImage, 0, CGImageGetHeight(screenshotCGImage) / 2);
+  UIColor* centerColor =
+      getPixelColorInImage(screenshotCGImage, CGImageGetWidth(screenshotCGImage) / 2,
+                           CGImageGetHeight(screenshotCGImage) / 2);
+
+  CGColorSpaceRef centerLeftColorSpace = CGColorGetColorSpace(centerLeftColor.CGColor);
+  // Flutter Colors.green color : 0xFF4CAF50 -> rgba(76, 175, 80, 1)
+  // https://github.com/flutter/flutter/blob/f4abaa0735eba4dfd8f33f73363911d63931fe03/packages/flutter/lib/src/material/colors.dart#L1208
+  // The background color of the webview is : rgba(0, 0, 0, 0.5)
+  // The expected color is : rgba(38, 87, 40, 1)
+  CGFloat flutterGreenColorComponents[] = {38.0f / 255.0f, 87.0f / 255.0f, 40.0f / 255.0f, 1.0f};
+  CGColorRef flutterGreenColor = CGColorCreate(centerLeftColorSpace, flutterGreenColorComponents);
+  CGFloat redColorComponents[] = {1.0f, 0.0f, 0.0f, 1.0f};
+  CGColorRef redColor = CGColorCreate(centerLeftColorSpace, redColorComponents);
+  CGColorSpaceRelease(centerLeftColorSpace);
+
+  XCTAssertTrue(CGColorEqualToColor(flutterGreenColor, centerLeftColor.CGColor));
+  XCTAssertTrue(CGColorEqualToColor(redColor, centerColor.CGColor));
+}
+
 - (void)testUserAgent {
   XCUIApplication* app = self.app;
   XCUIElement* menu = app.buttons[@"Show menu"];
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
index df85d42..41297bf 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
@@ -37,6 +37,27 @@
 </html>
 ''';
 
+const String kTransparentBackgroundPage = '''
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Transparent background test</title>
+</head>
+<style type="text/css">
+  body { background: transparent; margin: 0; padding: 0; }
+  #container { position: relative; margin: 0; padding: 0; width: 100vw; height: 100vh; }
+  #shape { background: #FF0000; width: 200px; height: 100%; margin: 0; padding: 0; position: absolute; top: 0; bottom: 0; left: calc(50% - 100px); }
+  p { text-align: center; }
+</style>
+<body>
+  <div id="container">
+    <p>Transparent background test</p>
+    <div id="shape"></div>
+  </div>
+</body>
+</html>
+''';
+
 const String kLocalFileExamplePage = '''
 <!DOCTYPE html>
 <html lang="en">
@@ -47,8 +68,8 @@
 
 <h1>Local demo page</h1>
 <p>
-  This is an example page used to demonstrate how to load a local file or HTML 
-  string using the <a href="https://pub.dev/packages/webview_flutter">Flutter 
+  This is an example page used to demonstrate how to load a local file or HTML
+  string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
   webview</a> plugin.
 </p>
 
@@ -70,6 +91,7 @@
   @override
   Widget build(BuildContext context) {
     return Scaffold(
+      backgroundColor: const Color(0xFF4CAF50),
       appBar: AppBar(
         title: const Text('Flutter WebView example'),
         // This drop down menu demonstrates that Flutter widgets can be shown over the web view.
@@ -96,6 +118,7 @@
             print('allowing navigation to $request');
             return NavigationDecision.navigate;
           },
+          backgroundColor: const Color(0x80000000),
         );
       }),
       floatingActionButton: favoriteButton(),
@@ -146,6 +169,7 @@
   loadLocalFile,
   loadHtmlString,
   doPostRequest,
+  transparentBackground,
 }
 
 class _SampleMenu extends StatelessWidget {
@@ -160,6 +184,7 @@
       builder:
           (BuildContext context, AsyncSnapshot<WebViewController> controller) {
         return PopupMenuButton<_MenuOptions>(
+          key: const ValueKey<String>('ShowPopupMenu'),
           onSelected: (_MenuOptions value) {
             switch (value) {
               case _MenuOptions.showUserAgent:
@@ -192,6 +217,9 @@
               case _MenuOptions.doPostRequest:
                 _onDoPostRequest(controller.data!, context);
                 break;
+              case _MenuOptions.transparentBackground:
+                _onTransparentBackground(controller.data!, context);
+                break;
             }
           },
           itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
@@ -236,6 +264,11 @@
               value: _MenuOptions.doPostRequest,
               child: Text('Post Request'),
             ),
+            const PopupMenuItem<_MenuOptions>(
+              key: ValueKey<String>('ShowTransparentBackgroundExample'),
+              value: _MenuOptions.transparentBackground,
+              child: Text('Transparent background example'),
+            ),
           ],
         );
       },
@@ -346,6 +379,11 @@
     );
   }
 
+  Future<void> _onTransparentBackground(
+      WebViewController controller, BuildContext context) async {
+    await controller.loadHtmlString(kTransparentBackgroundPage);
+  }
+
   static Future<String> _prepareLocalFile() async {
     final String tmpDir = (await getTemporaryDirectory()).path;
     final File indexFile = File('$tmpDir/www/index.html');
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
index ae540ae..69fa2bd 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
@@ -69,6 +69,7 @@
     this.initialMediaPlaybackPolicy =
         AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
     this.allowsInlineMediaPlayback = false,
+    this.backgroundColor,
   })  : assert(javascriptMode != null),
         assert(initialMediaPlaybackPolicy != null),
         assert(allowsInlineMediaPlayback != null),
@@ -227,6 +228,12 @@
   /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types].
   final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy;
 
+  /// The background color of the [WebView].
+  ///
+  /// When `null` the platform's webview default background color is used. By
+  /// default [backgroundColor] is `null`.
+  final Color? backgroundColor;
+
   @override
   _WebViewState createState() => _WebViewState();
 }
@@ -278,6 +285,7 @@
             _javascriptChannelRegistry.channels.keys.toSet(),
         autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
         userAgent: widget.userAgent,
+        backgroundColor: widget.backgroundColor,
       ),
       javascriptChannelRegistry: _javascriptChannelRegistry,
     );
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
index 2888cdd..2070b11 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
@@ -10,7 +10,7 @@
     sdk: flutter
 
   path_provider: ^2.0.6
-  
+
   webview_flutter_wkwebview:
     # When depending on this package from a real application you should use:
     #   webview_flutter: ^x.y.z
@@ -34,4 +34,3 @@
   assets:
     - assets/sample_audio.ogg
     - assets/sample_video.mp4
-    
\ No newline at end of file
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
index 351d1ae..25a93a6 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
@@ -96,6 +96,20 @@
                         inConfiguration:configuration];
 
     _webView = [[FLTWKWebView alloc] initWithFrame:frame configuration:configuration];
+
+    // Background color
+    NSNumber* backgroundColorNSNumber = args[@"backgroundColor"];
+    if ([backgroundColorNSNumber isKindOfClass:[NSNumber class]]) {
+      int backgroundColorInt = [backgroundColorNSNumber intValue];
+      UIColor* backgroundColor = [UIColor colorWithRed:(backgroundColorInt >> 16 & 0xff) / 255.0
+                                                 green:(backgroundColorInt >> 8 & 0xff) / 255.0
+                                                  blue:(backgroundColorInt & 0xff) / 255.0
+                                                 alpha:(backgroundColorInt >> 24 & 0xff) / 255.0];
+      _webView.opaque = NO;
+      _webView.backgroundColor = UIColor.clearColor;
+      _webView.scrollView.backgroundColor = backgroundColor;
+    }
+
     _navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:_channel];
     _webView.UIDelegate = self;
     _webView.navigationDelegate = _navigationDelegate;
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index a152e2b..7024733 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
 repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.4.0
+version: 2.5.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -18,11 +18,11 @@
 dependencies:
   flutter:
     sdk: flutter
-  webview_flutter_platform_interface: ^1.5.2
+  webview_flutter_platform_interface: ^1.7.0
 
 dev_dependencies:
   flutter_driver:
     sdk: flutter
   flutter_test:
     sdk: flutter
-  pedantic: ^1.10.0
\ No newline at end of file
+  pedantic: ^1.10.0