[
  {
    "path": "LICENSE",
    "content": "Copyright 2018 The Chromium Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# 支持文件上传的flutter_webview\n"
  },
  {
    "path": "android/.classpath",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<classpath>\n\t<classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-10/\"/>\n\t<classpathentry kind=\"con\" path=\"org.eclipse.buildship.core.gradleclasspathcontainer\"/>\n\t<classpathentry kind=\"output\" path=\"bin/default\"/>\n</classpath>\n"
  },
  {
    "path": "android/.project",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<projectDescription>\n\t<name>webview_flutter</name>\n\t<comment>Project webview_flutter created by Buildship.</comment>\n\t<projects>\n\t</projects>\n\t<buildSpec>\n\t\t<buildCommand>\n\t\t\t<name>org.eclipse.jdt.core.javabuilder</name>\n\t\t\t<arguments>\n\t\t\t</arguments>\n\t\t</buildCommand>\n\t\t<buildCommand>\n\t\t\t<name>org.eclipse.buildship.core.gradleprojectbuilder</name>\n\t\t\t<arguments>\n\t\t\t</arguments>\n\t\t</buildCommand>\n\t</buildSpec>\n\t<natures>\n\t\t<nature>org.eclipse.jdt.core.javanature</nature>\n\t\t<nature>org.eclipse.buildship.core.gradleprojectnature</nature>\n\t</natures>\n</projectDescription>\n"
  },
  {
    "path": "android/.settings/org.eclipse.buildship.core.prefs",
    "content": "connection.project.dir=../../../../../../../Desktop/webview_flutter\\u7684\\u526F\\u672C/example/android\neclipse.preferences.version=1\n"
  },
  {
    "path": "android/build.gradle",
    "content": "group 'io.flutter.plugins.webviewflutter'\nversion '1.0-SNAPSHOT'\n\nbuildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.3.0'\n    }\n}\n\nrootProject.allprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n\napply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 28\n\n    defaultConfig {\n        minSdkVersion 16\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n    lintOptions {\n        disable 'InvalidPackage'\n    }\n\n    dependencies {\n        implementation 'androidx.annotation:annotation:1.0.0'\n        implementation 'androidx.webkit:webkit:1.2.0'\n    }\n}\n"
  },
  {
    "path": "android/settings.gradle",
    "content": "rootProject.name = 'webview_flutter'\n"
  },
  {
    "path": "android/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"io.flutter.plugins.webviewflutter\">\n</manifest>\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java",
    "content": "package io.flutter.plugins.webviewflutter;\n\nimport static android.hardware.display.DisplayManager.DisplayListener;\n\nimport android.annotation.TargetApi;\nimport android.hardware.display.DisplayManager;\nimport android.os.Build;\nimport android.util.Log;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\n\n/**\n * Works around an Android WebView bug by filtering some DisplayListener invocations.\n *\n * <p>Older Android WebView versions had assumed that when {@link DisplayListener#onDisplayChanged}\n * is invoked, the display ID it is provided is of a valid display. However it turns out that when a\n * display is removed Android may call onDisplayChanged with the ID of the removed display, in this\n * case the Android WebView code tries to fetch and use the display with this ID and crashes with an\n * NPE.\n *\n * <p>This issue was fixed in the Android WebView code in\n * https://chromium-review.googlesource.com/517913 which is available starting WebView version\n * 58.0.3029.125 however older webviews in the wild still have this issue.\n *\n * <p>Since Flutter removes virtual displays whenever a platform view is resized the webview crash\n * is more likely to happen than other apps. And users were reporting this issue see:\n * https://github.com/flutter/flutter/issues/30420\n *\n * <p>This class works around the webview bug by unregistering the WebView's DisplayListener, and\n * instead registering its own DisplayListener which delegates the callbacks to the WebView's\n * listener unless it's a onDisplayChanged for an invalid display.\n *\n * <p>I did not find a clean way to get a handle of the WebView's DisplayListener so I'm using\n * reflection to fetch all registered listeners before and after initializing a webview. In the\n * first initialization of a webview within the process the difference between the lists is the\n * webview's display listener.\n */\n@TargetApi(Build.VERSION_CODES.KITKAT)\nclass DisplayListenerProxy {\n  private static final String TAG = \"DisplayListenerProxy\";\n\n  private ArrayList<DisplayListener> listenersBeforeWebView;\n\n  /** Should be called prior to the webview's initialization. */\n  void onPreWebViewInitialization(DisplayManager displayManager) {\n    listenersBeforeWebView = yoinkDisplayListeners(displayManager);\n  }\n\n  /** Should be called after the webview's initialization. */\n  void onPostWebViewInitialization(final DisplayManager displayManager) {\n    final ArrayList<DisplayListener> webViewListeners = yoinkDisplayListeners(displayManager);\n    // We recorded the list of listeners prior to initializing webview, any new listeners we see\n    // after initializing the webview are listeners added by the webview.\n    webViewListeners.removeAll(listenersBeforeWebView);\n\n    if (webViewListeners.isEmpty()) {\n      // The Android WebView registers a single display listener per process (even if there\n      // are multiple WebView instances) so this list is expected to be non-empty only the\n      // first time a webview is initialized.\n      // Note that in an add2app scenario if the application had instantiated a non Flutter\n      // WebView prior to instantiating the Flutter WebView we are not able to get a reference\n      // to the WebView's display listener and can't work around the bug.\n      //\n      // This means that webview resizes in add2app Flutter apps with a non Flutter WebView\n      // running on a system with a webview prior to 58.0.3029.125 may crash (the Android's\n      // behavior seems to be racy so it doesn't always happen).\n      return;\n    }\n\n    for (DisplayListener webViewListener : webViewListeners) {\n      // Note that while DisplayManager.unregisterDisplayListener throws when given an\n      // unregistered listener, this isn't an issue as the WebView code never calls\n      // unregisterDisplayListener.\n      displayManager.unregisterDisplayListener(webViewListener);\n\n      // We never explicitly unregister this listener as the webview's listener is never\n      // unregistered (it's released when the process is terminated).\n      displayManager.registerDisplayListener(\n          new DisplayListener() {\n            @Override\n            public void onDisplayAdded(int displayId) {\n              for (DisplayListener webViewListener : webViewListeners) {\n                webViewListener.onDisplayAdded(displayId);\n              }\n            }\n\n            @Override\n            public void onDisplayRemoved(int displayId) {\n              for (DisplayListener webViewListener : webViewListeners) {\n                webViewListener.onDisplayRemoved(displayId);\n              }\n            }\n\n            @Override\n            public void onDisplayChanged(int displayId) {\n              if (displayManager.getDisplay(displayId) == null) {\n                return;\n              }\n              for (DisplayListener webViewListener : webViewListeners) {\n                webViewListener.onDisplayChanged(displayId);\n              }\n            }\n          },\n          null);\n    }\n  }\n\n  @SuppressWarnings({\"unchecked\", \"PrivateApi\"})\n  private static ArrayList<DisplayListener> yoinkDisplayListeners(DisplayManager displayManager) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n      // We cannot use reflection on Android P, but it shouldn't matter as it shipped\n      // with WebView 66.0.3359.158 and the WebView version the bug this code is working around was\n      // fixed in 61.0.3116.0.\n      return new ArrayList<>();\n    }\n    try {\n      Field displayManagerGlobalField = DisplayManager.class.getDeclaredField(\"mGlobal\");\n      displayManagerGlobalField.setAccessible(true);\n      Object displayManagerGlobal = displayManagerGlobalField.get(displayManager);\n      Field displayListenersField =\n          displayManagerGlobal.getClass().getDeclaredField(\"mDisplayListeners\");\n      displayListenersField.setAccessible(true);\n      ArrayList<Object> delegates =\n          (ArrayList<Object>) displayListenersField.get(displayManagerGlobal);\n\n      Field listenerField = null;\n      ArrayList<DisplayManager.DisplayListener> listeners = new ArrayList<>();\n      for (Object delegate : delegates) {\n        if (listenerField == null) {\n          listenerField = delegate.getClass().getField(\"mListener\");\n          listenerField.setAccessible(true);\n        }\n        DisplayManager.DisplayListener listener =\n            (DisplayManager.DisplayListener) listenerField.get(delegate);\n        listeners.add(listener);\n      }\n      return listeners;\n    } catch (NoSuchFieldException | IllegalAccessException e) {\n      Log.w(TAG, \"Could not extract WebView's display listeners. \" + e);\n      return new ArrayList<>();\n    }\n  }\n}\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\npackage io.flutter.plugins.webviewflutter;\n\nimport android.os.Build;\nimport android.os.Build.VERSION_CODES;\nimport android.webkit.CookieManager;\nimport android.webkit.ValueCallback;\nimport io.flutter.plugin.common.BinaryMessenger;\nimport io.flutter.plugin.common.MethodCall;\nimport io.flutter.plugin.common.MethodChannel;\nimport io.flutter.plugin.common.MethodChannel.MethodCallHandler;\nimport io.flutter.plugin.common.MethodChannel.Result;\n\nclass FlutterCookieManager implements MethodCallHandler {\n  private final MethodChannel methodChannel;\n\n  FlutterCookieManager(BinaryMessenger messenger) {\n    methodChannel = new MethodChannel(messenger, \"plugins.flutter.io/cookie_manager\");\n    methodChannel.setMethodCallHandler(this);\n  }\n\n  @Override\n  public void onMethodCall(MethodCall methodCall, Result result) {\n    switch (methodCall.method) {\n      case \"clearCookies\":\n        clearCookies(result);\n        break;\n      default:\n        result.notImplemented();\n    }\n  }\n\n  void dispose() {\n    methodChannel.setMethodCallHandler(null);\n  }\n\n  private static void clearCookies(final Result result) {\n    CookieManager cookieManager = CookieManager.getInstance();\n    final boolean hasCookies = cookieManager.hasCookies();\n    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {\n      cookieManager.removeAllCookies(\n          new ValueCallback<Boolean>() {\n            @Override\n            public void onReceiveValue(Boolean value) {\n              result.success(hasCookies);\n            }\n          });\n    } else {\n      cookieManager.removeAllCookie();\n      result.success(hasCookies);\n    }\n  }\n}\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\npackage io.flutter.plugins.webviewflutter;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.ClipData;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.hardware.display.DisplayManager;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.provider.MediaStore;\nimport android.os.Message;\nimport android.util.Log;\nimport android.view.View;\nimport android.webkit.ValueCallback;\nimport android.webkit.WebChromeClient;\nimport android.webkit.WebResourceRequest;\nimport android.webkit.WebStorage;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\nimport androidx.annotation.NonNull;\nimport io.flutter.plugin.common.BinaryMessenger;\nimport io.flutter.plugin.common.MethodCall;\nimport io.flutter.plugin.common.MethodChannel;\nimport io.flutter.plugin.common.MethodChannel.MethodCallHandler;\nimport io.flutter.plugin.common.MethodChannel.Result;\nimport io.flutter.plugin.platform.PlatformView;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\npublic class FlutterWebView implements PlatformView, MethodCallHandler {\n  private static final String TAG = \"FlutterWebView\";\n\n  private static final String JS_CHANNEL_NAMES_FIELD = \"javascriptChannelNames\";\n  private final InputAwareWebView webView;\n  private final MethodChannel methodChannel;\n  private final FlutterWebViewClient flutterWebViewClient;\n  private final Handler platformThreadHandler;\n\n  private Activity activity;\n  private Context context;\n  private ValueCallback<Uri> uploadMessage;\n  private ValueCallback<Uri[]> uploadMessageAboveL;\n  private final static int FILE_CHOOSER_RESULT_CODE = 10000;\n  public static final int RESULT_OK = -1;\n\n  // Verifies that a url opened by `Window.open` has a secure url.\n  private class FlutterWebChromeClient extends WebChromeClient {\n    @Override\n    public boolean onCreateWindow(\n        final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {\n      final WebViewClient webViewClient =\n          new WebViewClient() {\n            @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n            @Override\n            public boolean shouldOverrideUrlLoading(\n                @NonNull WebView view, @NonNull WebResourceRequest request) {\n              final String url = request.getUrl().toString();\n              if (!flutterWebViewClient.shouldOverrideUrlLoading(\n                  FlutterWebView.this.webView, request)) {\n                webView.loadUrl(url);\n              }\n              return true;\n            }\n\n            @Override\n            public boolean shouldOverrideUrlLoading(WebView view, String url) {\n              if (!flutterWebViewClient.shouldOverrideUrlLoading(\n                  FlutterWebView.this.webView, url)) {\n                webView.loadUrl(url);\n              }\n              return true;\n            }\n          };\n\n      final WebView newWebView = new WebView(view.getContext());\n      newWebView.setWebViewClient(webViewClient);\n\n      final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;\n      transport.setWebView(newWebView);\n      resultMsg.sendToTarget();\n\n      return true;\n    }\n    // For Android < 3.0\n    public void openFileChooser(ValueCallback<Uri> valueCallback) {\n      Log.v(TAG, \"openFileChooser Android < 3.0\");\n      uploadMessage = valueCallback;\n      openImageChooserActivity();\n    }\n\n    // For Android  >= 3.0\n    public void openFileChooser(ValueCallback valueCallback, String acceptType) {\n      Log.v(TAG, \"openFileChooser Android  >= 3.0\");\n      uploadMessage = valueCallback;\n      openImageChooserActivity();\n    }\n\n    //For Android  >= 4.1\n    public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {\n      Log.v(TAG, \"openFileChooser Android  >= 4.1\");\n      uploadMessage = valueCallback;\n      openImageChooserActivity();\n    }\n\n    // For Android >= 5.0\n    @Override\n    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {\n      Log.v(TAG, \"openFileChooser Android >= 5.0\");\n      uploadMessageAboveL = filePathCallback;\n      openImageChooserActivity();\n      return true;\n    }\n  }\n\n  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n  @SuppressWarnings(\"unchecked\")\n  FlutterWebView(\n      final Context context,\n      BinaryMessenger messenger,\n      int id,\n      Map<String, Object> params,\n      View containerView) {\n\n    DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy();\n    DisplayManager displayManager =\n        (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);\n    displayListenerProxy.onPreWebViewInitialization(displayManager);\n    webView = new InputAwareWebView(context, containerView);\n    displayListenerProxy.onPostWebViewInitialization(displayManager);\n\n    platformThreadHandler = new Handler(context.getMainLooper());\n    // Allow local storage.\n    webView.getSettings().setDomStorageEnabled(true);\n    webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);\n\n    // Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679.\n    webView.getSettings().setSupportMultipleWindows(true);\n    webView.setWebChromeClient(new FlutterWebChromeClient());\n\n    methodChannel = new MethodChannel(messenger, \"plugins.flutter.io/webview_\" + id);\n    methodChannel.setMethodCallHandler(this);\n\n    flutterWebViewClient = new FlutterWebViewClient(methodChannel);\n    Map<String, Object> settings = (Map<String, Object>) params.get(\"settings\");\n    if (settings != null) applySettings(settings);\n\n    if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) {\n      List<String> names = (List<String>) params.get(JS_CHANNEL_NAMES_FIELD);\n      if (names != null) registerJavaScriptChannelNames(names);\n    }\n\n    Integer autoMediaPlaybackPolicy = (Integer) params.get(\"autoMediaPlaybackPolicy\");\n    if (autoMediaPlaybackPolicy != null) updateAutoMediaPlaybackPolicy(autoMediaPlaybackPolicy);\n    if (params.containsKey(\"userAgent\")) {\n      String userAgent = (String) params.get(\"userAgent\");\n      updateUserAgent(userAgent);\n    }\n    if (params.containsKey(\"initialUrl\")) {\n      String url = (String) params.get(\"initialUrl\");\n      webView.loadUrl(url);\n    }\n  }\n\n  @Override\n  public View getView() {\n    return webView;\n  }\n\n  // @Override\n  // This is overriding a method that hasn't rolled into stable Flutter yet. Including the\n  // annotation would cause compile time failures in versions of Flutter too old to include the new\n  // method. However leaving it raw like this means that the method will be ignored in old versions\n  // of Flutter but used as an override anyway wherever it's actually defined.\n  // TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable.\n  public void onInputConnectionUnlocked() {\n    webView.unlockInputConnection();\n  }\n\n  // @Override\n  // This is overriding a method that hasn't rolled into stable Flutter yet. Including the\n  // annotation would cause compile time failures in versions of Flutter too old to include the new\n  // method. However leaving it raw like this means that the method will be ignored in old versions\n  // of Flutter but used as an override anyway wherever it's actually defined.\n  // TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable.\n  public void onInputConnectionLocked() {\n    webView.lockInputConnection();\n  }\n\n  // @Override\n  // This is overriding a method that hasn't rolled into stable Flutter yet. Including the\n  // annotation would cause compile time failures in versions of Flutter too old to include the new\n  // method. However leaving it raw like this means that the method will be ignored in old versions\n  // of Flutter but used as an override anyway wherever it's actually defined.\n  // TODO(mklim): Add the @Override annotation once stable passes v1.10.9.\n  public void onFlutterViewAttached(View flutterView) {\n    webView.setContainerView(flutterView);\n  }\n\n  // @Override\n  // This is overriding a method that hasn't rolled into stable Flutter yet. Including the\n  // annotation would cause compile time failures in versions of Flutter too old to include the new\n  // method. However leaving it raw like this means that the method will be ignored in old versions\n  // of Flutter but used as an override anyway wherever it's actually defined.\n  // TODO(mklim): Add the @Override annotation once stable passes v1.10.9.\n  public void onFlutterViewDetached() {\n    webView.setContainerView(null);\n  }\n\n  @Override\n  public void onMethodCall(MethodCall methodCall, Result result) {\n    switch (methodCall.method) {\n      case \"loadUrl\":\n        loadUrl(methodCall, result);\n        break;\n      case \"updateSettings\":\n        updateSettings(methodCall, result);\n        break;\n      case \"canGoBack\":\n        canGoBack(result);\n        break;\n      case \"canGoForward\":\n        canGoForward(result);\n        break;\n      case \"goBack\":\n        goBack(result);\n        break;\n      case \"goForward\":\n        goForward(result);\n        break;\n      case \"reload\":\n        reload(result);\n        break;\n      case \"currentUrl\":\n        currentUrl(result);\n        break;\n      case \"evaluateJavascript\":\n        evaluateJavaScript(methodCall, result);\n        break;\n      case \"addJavascriptChannels\":\n        addJavaScriptChannels(methodCall, result);\n        break;\n      case \"removeJavascriptChannels\":\n        removeJavaScriptChannels(methodCall, result);\n        break;\n      case \"clearCache\":\n        clearCache(result);\n        break;\n      case \"getTitle\":\n        getTitle(result);\n        break;\n      case \"scrollTo\":\n        scrollTo(methodCall, result);\n        break;\n      case \"scrollBy\":\n        scrollBy(methodCall, result);\n        break;\n      case \"getScrollX\":\n        getScrollX(result);\n        break;\n      case \"getScrollY\":\n        getScrollY(result);\n        break;\n      default:\n        result.notImplemented();\n    }\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void loadUrl(MethodCall methodCall, Result result) {\n    Map<String, Object> request = (Map<String, Object>) methodCall.arguments;\n    String url = (String) request.get(\"url\");\n    Map<String, String> headers = (Map<String, String>) request.get(\"headers\");\n    if (headers == null) {\n      headers = Collections.emptyMap();\n    }\n    webView.loadUrl(url, headers);\n    result.success(null);\n  }\n\n  private void canGoBack(Result result) {\n    result.success(webView.canGoBack());\n  }\n\n  private void canGoForward(Result result) {\n    result.success(webView.canGoForward());\n  }\n\n  private void goBack(Result result) {\n    if (webView.canGoBack()) {\n      webView.goBack();\n    }\n    result.success(null);\n  }\n\n  private void goForward(Result result) {\n    if (webView.canGoForward()) {\n      webView.goForward();\n    }\n    result.success(null);\n  }\n\n  private void reload(Result result) {\n    webView.reload();\n    result.success(null);\n  }\n\n  private void currentUrl(Result result) {\n    result.success(webView.getUrl());\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void updateSettings(MethodCall methodCall, Result result) {\n    applySettings((Map<String, Object>) methodCall.arguments);\n    result.success(null);\n  }\n\n  @TargetApi(Build.VERSION_CODES.KITKAT)\n  private void evaluateJavaScript(MethodCall methodCall, final Result result) {\n    String jsString = (String) methodCall.arguments;\n    if (jsString == null) {\n      throw new UnsupportedOperationException(\"JavaScript string cannot be null\");\n    }\n    webView.evaluateJavascript(\n        jsString,\n        new android.webkit.ValueCallback<String>() {\n          @Override\n          public void onReceiveValue(String value) {\n            result.success(value);\n          }\n        });\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void addJavaScriptChannels(MethodCall methodCall, Result result) {\n    List<String> channelNames = (List<String>) methodCall.arguments;\n    registerJavaScriptChannelNames(channelNames);\n    result.success(null);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private void removeJavaScriptChannels(MethodCall methodCall, Result result) {\n    List<String> channelNames = (List<String>) methodCall.arguments;\n    for (String channelName : channelNames) {\n      webView.removeJavascriptInterface(channelName);\n    }\n    result.success(null);\n  }\n\n  private void clearCache(Result result) {\n    webView.clearCache(true);\n    WebStorage.getInstance().deleteAllData();\n    result.success(null);\n  }\n\n  private void getTitle(Result result) {\n    result.success(webView.getTitle());\n  }\n\n  private void scrollTo(MethodCall methodCall, Result result) {\n    Map<String, Object> request = methodCall.arguments();\n    int x = (int) request.get(\"x\");\n    int y = (int) request.get(\"y\");\n\n    webView.scrollTo(x, y);\n\n    result.success(null);\n  }\n\n  private void scrollBy(MethodCall methodCall, Result result) {\n    Map<String, Object> request = methodCall.arguments();\n    int x = (int) request.get(\"x\");\n    int y = (int) request.get(\"y\");\n\n    webView.scrollBy(x, y);\n    result.success(null);\n  }\n\n  private void getScrollX(Result result) {\n    result.success(webView.getScrollX());\n  }\n\n  private void getScrollY(Result result) {\n    result.success(webView.getScrollY());\n  }\n\n  private void applySettings(Map<String, Object> settings) {\n    for (String key : settings.keySet()) {\n      switch (key) {\n        case \"jsMode\":\n          Integer mode = (Integer) settings.get(key);\n          if (mode != null) updateJsMode(mode);\n          break;\n        case \"hasNavigationDelegate\":\n          final boolean hasNavigationDelegate = (boolean) settings.get(key);\n\n          final WebViewClient webViewClient =\n              flutterWebViewClient.createWebViewClient(hasNavigationDelegate);\n\n          webView.setWebViewClient(webViewClient);\n          break;\n        case \"debuggingEnabled\":\n          final boolean debuggingEnabled = (boolean) settings.get(key);\n\n          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            webView.setWebContentsDebuggingEnabled(debuggingEnabled);\n          }\n          break;\n        case \"gestureNavigationEnabled\":\n          break;\n        case \"userAgent\":\n          updateUserAgent((String) settings.get(key));\n          break;\n        default:\n          throw new IllegalArgumentException(\"Unknown WebView setting: \" + key);\n      }\n    }\n  }\n\n  private void updateJsMode(int mode) {\n    switch (mode) {\n      case 0: // disabled\n        webView.getSettings().setJavaScriptEnabled(false);\n        break;\n      case 1: // unrestricted\n        webView.getSettings().setJavaScriptEnabled(true);\n        break;\n      default:\n        throw new IllegalArgumentException(\"Trying to set unknown JavaScript mode: \" + mode);\n    }\n  }\n\n  private void updateAutoMediaPlaybackPolicy(int mode) {\n    // This is the index of the AutoMediaPlaybackPolicy enum, index 1 is always_allow, for all\n    // other values we require a user gesture.\n    boolean requireUserGesture = mode != 1;\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n      webView.getSettings().setMediaPlaybackRequiresUserGesture(requireUserGesture);\n    }\n  }\n\n  private void registerJavaScriptChannelNames(List<String> channelNames) {\n    for (String channelName : channelNames) {\n      webView.addJavascriptInterface(\n          new JavaScriptChannel(methodChannel, channelName, platformThreadHandler), channelName);\n    }\n  }\n\n  private void updateUserAgent(String userAgent) {\n    webView.getSettings().setUserAgentString(userAgent);\n  }\n\n  @Override\n  public void dispose() {\n    methodChannel.setMethodCallHandler(null);\n    webView.dispose();\n    webView.destroy();\n  }\n\n  private void openImageChooserActivity() {\n    Log.v(TAG, \"openImageChooserActivity\");\n    Intent intent1 = new Intent(Intent.ACTION_PICK, null);\n    intent1.setDataAndType(\n            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, \"image/*\");\n    Intent chooser = new Intent(Intent.ACTION_CHOOSER);\n    chooser.putExtra(Intent.EXTRA_TITLE, \"选择图片\");\n    chooser.putExtra(Intent.EXTRA_INTENT,intent1);\n\n    if (WebViewFlutterPlugin.activity != null){\n      WebViewFlutterPlugin.activity.startActivityForResult(chooser, FILE_CHOOSER_RESULT_CODE);\n    } else {\n      Log.v(TAG, \"activity is null\");\n    }\n  }\n\n  public boolean activityResult(int requestCode, int resultCode, Intent data) {\n    if (requestCode == FILE_CHOOSER_RESULT_CODE) {\n      if (null == uploadMessage && null == uploadMessageAboveL) {\n        return false;\n      }\n      Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();\n      if (uploadMessageAboveL != null) {\n        onActivityResultAboveL(requestCode, resultCode, data);\n      } else if (uploadMessage != null && result != null) {\n        uploadMessage.onReceiveValue(result);\n        uploadMessage = null;\n      }\n    }\n    return false;\n  }\n\n  @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n  private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {\n    if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)\n    {\n      return;\n    }\n    Uri[] results = null;\n    if (resultCode == Activity.RESULT_OK) {\n      if (intent != null) {\n        String dataString = intent.getDataString();\n        ClipData clipData = intent.getClipData();\n        if (clipData != null) {\n          results = new Uri[clipData.getItemCount()];\n          for (int i = 0; i < clipData.getItemCount(); i++) {\n            ClipData.Item item = clipData.getItemAt(i);\n            results[i] = item.getUri();\n          }\n        }\n        if (dataString != null)\n        {\n          results = new Uri[]{Uri.parse(dataString)};\n        }\n      }\n    }\n    uploadMessageAboveL.onReceiveValue(results);\n    uploadMessageAboveL = null;\n  }\n}\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\npackage io.flutter.plugins.webviewflutter;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.webkit.WebResourceError;\nimport android.webkit.WebResourceRequest;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\nimport androidx.annotation.RequiresApi;\nimport androidx.webkit.WebResourceErrorCompat;\nimport androidx.webkit.WebViewClientCompat;\nimport io.flutter.plugin.common.MethodChannel;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\n\n// We need to use WebViewClientCompat to get\n// shouldOverrideUrlLoading(WebView view, WebResourceRequest request)\n// invoked by the webview on older Android devices, without it pages that use iframes will\n// be broken when a navigationDelegate is set on Android version earlier than N.\nclass FlutterWebViewClient {\n  private static final String TAG = \"FlutterWebViewClient\";\n  private final MethodChannel methodChannel;\n  private boolean hasNavigationDelegate;\n\n  FlutterWebViewClient(MethodChannel methodChannel) {\n    this.methodChannel = methodChannel;\n  }\n\n  private static String errorCodeToString(int errorCode) {\n    switch (errorCode) {\n      case WebViewClient.ERROR_AUTHENTICATION:\n        return \"authentication\";\n      case WebViewClient.ERROR_BAD_URL:\n        return \"badUrl\";\n      case WebViewClient.ERROR_CONNECT:\n        return \"connect\";\n      case WebViewClient.ERROR_FAILED_SSL_HANDSHAKE:\n        return \"failedSslHandshake\";\n      case WebViewClient.ERROR_FILE:\n        return \"file\";\n      case WebViewClient.ERROR_FILE_NOT_FOUND:\n        return \"fileNotFound\";\n      case WebViewClient.ERROR_HOST_LOOKUP:\n        return \"hostLookup\";\n      case WebViewClient.ERROR_IO:\n        return \"io\";\n      case WebViewClient.ERROR_PROXY_AUTHENTICATION:\n        return \"proxyAuthentication\";\n      case WebViewClient.ERROR_REDIRECT_LOOP:\n        return \"redirectLoop\";\n      case WebViewClient.ERROR_TIMEOUT:\n        return \"timeout\";\n      case WebViewClient.ERROR_TOO_MANY_REQUESTS:\n        return \"tooManyRequests\";\n      case WebViewClient.ERROR_UNKNOWN:\n        return \"unknown\";\n      case WebViewClient.ERROR_UNSAFE_RESOURCE:\n        return \"unsafeResource\";\n      case WebViewClient.ERROR_UNSUPPORTED_AUTH_SCHEME:\n        return \"unsupportedAuthScheme\";\n      case WebViewClient.ERROR_UNSUPPORTED_SCHEME:\n        return \"unsupportedScheme\";\n    }\n\n    final String message =\n        String.format(Locale.getDefault(), \"Could not find a string for errorCode: %d\", errorCode);\n    throw new IllegalArgumentException(message);\n  }\n\n  @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n  boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {\n    if (!hasNavigationDelegate) {\n      return false;\n    }\n    notifyOnNavigationRequest(\n        request.getUrl().toString(), request.getRequestHeaders(), view, request.isForMainFrame());\n    // We must make a synchronous decision here whether to allow the navigation or not,\n    // if the Dart code has set a navigation delegate we want that delegate to decide whether\n    // to navigate or not, and as we cannot get a response from the Dart delegate synchronously we\n    // return true here to block the navigation, if the Dart delegate decides to allow the\n    // navigation the plugin will later make an addition loadUrl call for this url.\n    //\n    // Since we cannot call loadUrl for a subframe, we currently only allow the delegate to stop\n    // navigations that target the main frame, if the request is not for the main frame\n    // we just return false to allow the navigation.\n    //\n    // For more details see: https://github.com/flutter/flutter/issues/25329#issuecomment-464863209\n    return request.isForMainFrame();\n  }\n\n  boolean shouldOverrideUrlLoading(WebView view, String url) {\n    if (!hasNavigationDelegate) {\n      return false;\n    }\n    // This version of shouldOverrideUrlLoading is only invoked by the webview on devices with\n    // webview versions  earlier than 67(it is also invoked when hasNavigationDelegate is false).\n    // On these devices we cannot tell whether the navigation is targeted to the main frame or not.\n    // We proceed assuming that the navigation is targeted to the main frame. If the page had any\n    // frames they will be loaded in the main frame instead.\n    Log.w(\n        TAG,\n        \"Using a navigationDelegate with an old webview implementation, pages with frames or iframes will not work\");\n    notifyOnNavigationRequest(url, null, view, true);\n    return true;\n  }\n\n  private void onPageStarted(WebView view, String url) {\n    Map<String, Object> args = new HashMap<>();\n    args.put(\"url\", url);\n    methodChannel.invokeMethod(\"onPageStarted\", args);\n  }\n\n  private void onPageFinished(WebView view, String url) {\n    Map<String, Object> args = new HashMap<>();\n    args.put(\"url\", url);\n    methodChannel.invokeMethod(\"onPageFinished\", args);\n  }\n\n  private void onWebResourceError(\n      final int errorCode, final String description, final String failingUrl) {\n    final Map<String, Object> args = new HashMap<>();\n    args.put(\"errorCode\", errorCode);\n    args.put(\"description\", description);\n    args.put(\"errorType\", FlutterWebViewClient.errorCodeToString(errorCode));\n    args.put(\"failingUrl\", failingUrl);\n    methodChannel.invokeMethod(\"onWebResourceError\", args);\n  }\n\n  private void notifyOnNavigationRequest(\n      String url, Map<String, String> headers, WebView webview, boolean isMainFrame) {\n    HashMap<String, Object> args = new HashMap<>();\n    args.put(\"url\", url);\n    args.put(\"isForMainFrame\", isMainFrame);\n    if (isMainFrame) {\n      methodChannel.invokeMethod(\n          \"navigationRequest\", args, new OnNavigationRequestResult(url, headers, webview));\n    } else {\n      methodChannel.invokeMethod(\"navigationRequest\", args);\n    }\n  }\n\n  // This method attempts to avoid using WebViewClientCompat due to bug\n  // https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see\n  // https://github.com/flutter/flutter/issues/29446.\n  WebViewClient createWebViewClient(boolean hasNavigationDelegate) {\n    this.hasNavigationDelegate = hasNavigationDelegate;\n\n    if (!hasNavigationDelegate || android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n      return internalCreateWebViewClient();\n    }\n\n    return internalCreateWebViewClientCompat();\n  }\n\n  private WebViewClient internalCreateWebViewClient() {\n    return new WebViewClient() {\n      @TargetApi(Build.VERSION_CODES.N)\n      @Override\n      public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {\n        return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request);\n      }\n\n      @Override\n      public void onPageStarted(WebView view, String url, Bitmap favicon) {\n        FlutterWebViewClient.this.onPageStarted(view, url);\n      }\n\n      @Override\n      public void onPageFinished(WebView view, String url) {\n        FlutterWebViewClient.this.onPageFinished(view, url);\n      }\n\n      @TargetApi(Build.VERSION_CODES.M)\n      @Override\n      public void onReceivedError(\n          WebView view, WebResourceRequest request, WebResourceError error) {\n        FlutterWebViewClient.this.onWebResourceError(\n            error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString());\n      }\n\n      @Override\n      public void onReceivedError(\n          WebView view, int errorCode, String description, String failingUrl) {\n        FlutterWebViewClient.this.onWebResourceError(errorCode, description, failingUrl);\n      }\n\n      @Override\n      public void onUnhandledKeyEvent(WebView view, KeyEvent event) {\n        // Deliberately empty. Occasionally the webview will mark events as having failed to be\n        // handled even though they were handled. We don't want to propagate those as they're not\n        // truly lost.\n      }\n    };\n  }\n\n  private WebViewClientCompat internalCreateWebViewClientCompat() {\n    return new WebViewClientCompat() {\n      @Override\n      public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {\n        return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request);\n      }\n\n      @Override\n      public boolean shouldOverrideUrlLoading(WebView view, String url) {\n        return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, url);\n      }\n\n      @Override\n      public void onPageStarted(WebView view, String url, Bitmap favicon) {\n        FlutterWebViewClient.this.onPageStarted(view, url);\n      }\n\n      @Override\n      public void onPageFinished(WebView view, String url) {\n        FlutterWebViewClient.this.onPageFinished(view, url);\n      }\n\n      // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is\n      // enabled. The deprecated method is called when a device doesn't support this.\n      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n      @SuppressLint(\"RequiresFeature\")\n      @Override\n      public void onReceivedError(\n          WebView view, WebResourceRequest request, WebResourceErrorCompat error) {\n        FlutterWebViewClient.this.onWebResourceError(\n            error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString());\n      }\n\n      @Override\n      public void onReceivedError(\n          WebView view, int errorCode, String description, String failingUrl) {\n        FlutterWebViewClient.this.onWebResourceError(errorCode, description, failingUrl);\n      }\n\n      @Override\n      public void onUnhandledKeyEvent(WebView view, KeyEvent event) {\n        // Deliberately empty. Occasionally the webview will mark events as having failed to be\n        // handled even though they were handled. We don't want to propagate those as they're not\n        // truly lost.\n      }\n    };\n  }\n\n  private static class OnNavigationRequestResult implements MethodChannel.Result {\n    private final String url;\n    private final Map<String, String> headers;\n    private final WebView webView;\n\n    private OnNavigationRequestResult(String url, Map<String, String> headers, WebView webView) {\n      this.url = url;\n      this.headers = headers;\n      this.webView = webView;\n    }\n\n    @Override\n    public void success(Object shouldLoad) {\n      Boolean typedShouldLoad = (Boolean) shouldLoad;\n      if (typedShouldLoad) {\n        loadUrl();\n      }\n    }\n\n    @Override\n    public void error(String errorCode, String s1, Object o) {\n      throw new IllegalStateException(\"navigationRequest calls must succeed\");\n    }\n\n    @Override\n    public void notImplemented() {\n      throw new IllegalStateException(\n          \"navigationRequest must be implemented by the webview method channel\");\n    }\n\n    private void loadUrl() {\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n        webView.loadUrl(url, headers);\n      } else {\n        webView.loadUrl(url);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\npackage io.flutter.plugins.webviewflutter;\n\nimport static android.content.Context.INPUT_METHOD_SERVICE;\n\nimport android.content.Context;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.inputmethod.InputMethodManager;\nimport android.webkit.WebView;\nimport android.widget.ListPopupWindow;\n\n/**\n * A WebView subclass that mirrors the same implementation hacks that the system WebView does in\n * order to correctly create an InputConnection.\n *\n * <p>These hacks are only needed in Android versions below N and exist to create an InputConnection\n * on the WebView's dedicated input, or IME, thread. The majority of this proxying logic is in\n * {@link #checkInputConnectionProxy}.\n *\n * <p>See also {@link ThreadedInputConnectionProxyAdapterView}.\n */\nfinal class InputAwareWebView extends WebView {\n  private static final String TAG = \"InputAwareWebView\";\n  private View threadedInputConnectionProxyView;\n  private ThreadedInputConnectionProxyAdapterView proxyAdapterView;\n  private View containerView;\n\n  InputAwareWebView(Context context, View containerView) {\n    super(context);\n    this.containerView = containerView;\n  }\n\n  void setContainerView(View containerView) {\n    this.containerView = containerView;\n\n    if (proxyAdapterView == null) {\n      return;\n    }\n\n    Log.w(TAG, \"The containerView has changed while the proxyAdapterView exists.\");\n    if (containerView != null) {\n      setInputConnectionTarget(proxyAdapterView);\n    }\n  }\n\n  /**\n   * Set our proxy adapter view to use its cached input connection instead of creating new ones.\n   *\n   * <p>This is used to avoid losing our input connection when the virtual display is resized.\n   */\n  void lockInputConnection() {\n    if (proxyAdapterView == null) {\n      return;\n    }\n\n    proxyAdapterView.setLocked(true);\n  }\n\n  /** Sets the proxy adapter view back to its default behavior. */\n  void unlockInputConnection() {\n    if (proxyAdapterView == null) {\n      return;\n    }\n\n    proxyAdapterView.setLocked(false);\n  }\n\n  /** Restore the original InputConnection, if needed. */\n  void dispose() {\n    resetInputConnection();\n  }\n\n  /**\n   * Creates an InputConnection from the IME thread when needed.\n   *\n   * <p>We only need to create a {@link ThreadedInputConnectionProxyAdapterView} and create an\n   * InputConnectionProxy on the IME thread when WebView is doing the same thing. So we rely on the\n   * system calling this method for WebView's proxy view in order to know when we need to create our\n   * own.\n   *\n   * <p>This method would normally be called for any View that used the InputMethodManager. We rely\n   * on flutter/engine filtering the calls we receive down to the ones in our hierarchy and the\n   * system WebView in order to know whether or not the system WebView expects an InputConnection on\n   * the IME thread.\n   */\n  @Override\n  public boolean checkInputConnectionProxy(final View view) {\n    // Check to see if the view param is WebView's ThreadedInputConnectionProxyView.\n    View previousProxy = threadedInputConnectionProxyView;\n    threadedInputConnectionProxyView = view;\n    if (previousProxy == view) {\n      // This isn't a new ThreadedInputConnectionProxyView. Ignore it.\n      return super.checkInputConnectionProxy(view);\n    }\n    if (containerView == null) {\n      Log.e(\n          TAG,\n          \"Can't create a proxy view because there's no container view. Text input may not work.\");\n      return super.checkInputConnectionProxy(view);\n    }\n\n    // We've never seen this before, so we make the assumption that this is WebView's\n    // ThreadedInputConnectionProxyView. We are making the assumption that the only view that could\n    // possibly be interacting with the IMM here is WebView's ThreadedInputConnectionProxyView.\n    proxyAdapterView =\n        new ThreadedInputConnectionProxyAdapterView(\n            /*containerView=*/ containerView,\n            /*targetView=*/ view,\n            /*imeHandler=*/ view.getHandler());\n    setInputConnectionTarget(/*targetView=*/ proxyAdapterView);\n    return super.checkInputConnectionProxy(view);\n  }\n\n  /**\n   * Ensure that input creation happens back on {@link #containerView}'s thread once this view no\n   * longer has focus.\n   *\n   * <p>The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's\n   * thread for all connections. We undo it here so users will be able to go back to typing in\n   * Flutter UIs as expected.\n   */\n  @Override\n  public void clearFocus() {\n    super.clearFocus();\n    resetInputConnection();\n  }\n\n  /**\n   * Ensure that input creation happens back on {@link #containerView}.\n   *\n   * <p>The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's\n   * thread for all connections. We undo it here so users will be able to go back to typing in\n   * Flutter UIs as expected.\n   */\n  private void resetInputConnection() {\n    if (proxyAdapterView == null) {\n      // No need to reset the InputConnection to the default thread if we've never changed it.\n      return;\n    }\n    if (containerView == null) {\n      Log.e(TAG, \"Can't reset the input connection to the container view because there is none.\");\n      return;\n    }\n    setInputConnectionTarget(/*targetView=*/ containerView);\n  }\n\n  /**\n   * This is the crucial trick that gets the InputConnection creation to happen on the correct\n   * thread pre Android N.\n   * https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java?l=169&rcl=f0698ee3e4483fad5b0c34159276f71cfaf81f3a\n   *\n   * <p>{@code targetView} should have a {@link View#getHandler} method with the thread that future\n   * InputConnections should be created on.\n   */\n  private void setInputConnectionTarget(final View targetView) {\n    if (containerView == null) {\n      Log.e(\n          TAG,\n          \"Can't set the input connection target because there is no containerView to use as a handler.\");\n      return;\n    }\n\n    targetView.requestFocus();\n    containerView.post(\n        new Runnable() {\n          @Override\n          public void run() {\n            InputMethodManager imm =\n                (InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE);\n            // This is a hack to make InputMethodManager believe that the target view now has focus.\n            // As a result, InputMethodManager will think that targetView is focused, and will call\n            // getHandler() of the view when creating input connection.\n\n            // Step 1: Set targetView as InputMethodManager#mNextServedView. This does not affect\n            // the real window focus.\n            targetView.onWindowFocusChanged(true);\n\n            // Step 2: Have InputMethodManager focus in on targetView. As a result, IMM will call\n            // onCreateInputConnection() on targetView on the same thread as\n            // targetView.getHandler(). It will also call subsequent InputConnection methods on this\n            // thread. This is the IME thread in cases where targetView is our proxyAdapterView.\n            imm.isActive(containerView);\n          }\n        });\n  }\n\n  @Override\n  protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {\n    // This works around a crash when old (<67.0.3367.0) Chromium versions are used.\n\n    // Prior to Chromium 67.0.3367 the following sequence happens when a select drop down is shown\n    // on tablets:\n    //\n    //  - WebView is calling ListPopupWindow#show\n    //  - buildDropDown is invoked, which sets mDropDownList to a DropDownListView.\n    //  - showAsDropDown is invoked - resulting in mDropDownList being added to the window and is\n    //    also synchronously performing the following sequence:\n    //    - WebView's focus change listener is loosing focus (as mDropDownList got it)\n    //    - WebView is hiding all popups (as it lost focus)\n    //    - WebView's SelectPopupDropDown#hide is invoked.\n    //    - DropDownPopupWindow#dismiss is invoked setting mDropDownList to null.\n    //  - mDropDownList#setSelection is invoked and is throwing a NullPointerException (as we just set mDropDownList to null).\n    //\n    // To workaround this, we drop the problematic focus lost call.\n    // See more details on: https://github.com/flutter/flutter/issues/54164\n    //\n    // We don't do this after Android P as it shipped with a new enough WebView version, and it's\n    // better to not do this on all future Android versions in case DropDownListView's code changes.\n    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P\n        && isCalledFromListPopupWindowShow()\n        && !focused) {\n      return;\n    }\n    super.onFocusChanged(focused, direction, previouslyFocusedRect);\n  }\n\n  private boolean isCalledFromListPopupWindowShow() {\n    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();\n    for (StackTraceElement stackTraceElement : stackTraceElements) {\n      if (stackTraceElement.getClassName().equals(ListPopupWindow.class.getCanonicalName())\n          && stackTraceElement.getMethodName().equals(\"show\")) {\n        return true;\n      }\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\npackage io.flutter.plugins.webviewflutter;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.webkit.JavascriptInterface;\nimport io.flutter.plugin.common.MethodChannel;\nimport java.util.HashMap;\n\n/**\n * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets\n * up.\n *\n * <p>Exposes a single method named `postMessage` to JavaScript, which sends a message over a method\n * channel to the Dart code.\n */\nclass JavaScriptChannel {\n  private final MethodChannel methodChannel;\n  private final String javaScriptChannelName;\n  private final Handler platformThreadHandler;\n\n  /**\n   * @param methodChannel the Flutter WebView method channel to which JS messages are sent\n   * @param javaScriptChannelName the name of the JavaScript channel, this is sent over the method\n   *     channel with each message to let the Dart code know which JavaScript channel the message\n   *     was sent through\n   */\n  JavaScriptChannel(\n      MethodChannel methodChannel, String javaScriptChannelName, Handler platformThreadHandler) {\n    this.methodChannel = methodChannel;\n    this.javaScriptChannelName = javaScriptChannelName;\n    this.platformThreadHandler = platformThreadHandler;\n  }\n\n  // Suppressing unused warning as this is invoked from JavaScript.\n  @SuppressWarnings(\"unused\")\n  @JavascriptInterface\n  public void postMessage(final String message) {\n    Runnable postMessageRunnable =\n        new Runnable() {\n          @Override\n          public void run() {\n            HashMap<String, String> arguments = new HashMap<>();\n            arguments.put(\"channel\", javaScriptChannelName);\n            arguments.put(\"message\", message);\n            methodChannel.invokeMethod(\"javascriptChannelMessage\", arguments);\n          }\n        };\n    if (platformThreadHandler.getLooper() == Looper.myLooper()) {\n      postMessageRunnable.run();\n    } else {\n      platformThreadHandler.post(postMessageRunnable);\n    }\n  }\n}\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/ThreadedInputConnectionProxyAdapterView.java",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\npackage io.flutter.plugins.webviewflutter;\n\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.view.View;\nimport android.view.inputmethod.EditorInfo;\nimport android.view.inputmethod.InputConnection;\n\n/**\n * A fake View only exposed to InputMethodManager.\n *\n * <p>This follows a similar flow to Chromium's WebView (see\n * https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionProxyView.java).\n * WebView itself bounces its InputConnection around several different threads. We follow its logic\n * here to get the same working connection.\n *\n * <p>This exists solely to forward input creation to WebView's ThreadedInputConnectionProxyView on\n * the IME thread. The way that this is created in {@link\n * InputAwareWebView#checkInputConnectionProxy} guarantees that we have a handle to\n * ThreadedInputConnectionProxyView and {@link #onCreateInputConnection} is always called on the IME\n * thread. We delegate to ThreadedInputConnectionProxyView there to get WebView's input connection.\n */\nfinal class ThreadedInputConnectionProxyAdapterView extends View {\n  final Handler imeHandler;\n  final IBinder windowToken;\n  final View containerView;\n  final View rootView;\n  final View targetView;\n\n  private boolean triggerDelayed = true;\n  private boolean isLocked = false;\n  private InputConnection cachedConnection;\n\n  ThreadedInputConnectionProxyAdapterView(View containerView, View targetView, Handler imeHandler) {\n    super(containerView.getContext());\n    this.imeHandler = imeHandler;\n    this.containerView = containerView;\n    this.targetView = targetView;\n    windowToken = containerView.getWindowToken();\n    rootView = containerView.getRootView();\n    setFocusable(true);\n    setFocusableInTouchMode(true);\n    setVisibility(VISIBLE);\n  }\n\n  /** Returns whether or not this is currently asynchronously acquiring an input connection. */\n  boolean isTriggerDelayed() {\n    return triggerDelayed;\n  }\n\n  /** Sets whether or not this should use its previously cached input connection. */\n  void setLocked(boolean locked) {\n    isLocked = locked;\n  }\n\n  /**\n   * This is expected to be called on the IME thread. See the setup required for this in {@link\n   * InputAwareWebView#checkInputConnectionProxy(View)}.\n   *\n   * <p>Delegates to ThreadedInputConnectionProxyView to get WebView's input connection.\n   */\n  @Override\n  public InputConnection onCreateInputConnection(final EditorInfo outAttrs) {\n    triggerDelayed = false;\n    InputConnection inputConnection =\n        (isLocked) ? cachedConnection : targetView.onCreateInputConnection(outAttrs);\n    triggerDelayed = true;\n    cachedConnection = inputConnection;\n    return inputConnection;\n  }\n\n  @Override\n  public boolean checkInputConnectionProxy(View view) {\n    return true;\n  }\n\n  @Override\n  public boolean hasWindowFocus() {\n    // None of our views here correctly report they have window focus because of how we're embedding\n    // the platform view inside of a virtual display.\n    return true;\n  }\n\n  @Override\n  public View getRootView() {\n    return rootView;\n  }\n\n  @Override\n  public boolean onCheckIsTextEditor() {\n    return true;\n  }\n\n  @Override\n  public boolean isFocused() {\n    return true;\n  }\n\n  @Override\n  public IBinder getWindowToken() {\n    return windowToken;\n  }\n\n  @Override\n  public Handler getHandler() {\n    return imeHandler;\n  }\n}\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFactory.java",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\npackage io.flutter.plugins.webviewflutter;\n\nimport android.content.Context;\nimport android.view.View;\nimport io.flutter.plugin.common.BinaryMessenger;\nimport io.flutter.plugin.common.StandardMessageCodec;\nimport io.flutter.plugin.platform.PlatformView;\nimport io.flutter.plugin.platform.PlatformViewFactory;\nimport java.util.Map;\n\npublic final class WebViewFactory extends PlatformViewFactory {\n  private final BinaryMessenger messenger;\n  private final View containerView;\n  private FlutterWebView flutterWebView;\n\n  WebViewFactory(BinaryMessenger messenger, View containerView) {\n    super(StandardMessageCodec.INSTANCE);\n    this.messenger = messenger;\n    this.containerView = containerView;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public PlatformView create(Context context, int id, Object args) {\n    Map<String, Object> params = (Map<String, Object>) args;\n    flutterWebView = new FlutterWebView(context, messenger, id, params, containerView);\n    return flutterWebView;\n  }\n\n  public FlutterWebView getFlutterWebView() {\n    return flutterWebView;\n  }\n}\n"
  },
  {
    "path": "android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\npackage io.flutter.plugins.webviewflutter;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\n\nimport android.util.Log;\nimport io.flutter.app.FlutterApplication;\nimport io.flutter.embedding.engine.plugins.FlutterPlugin;\nimport io.flutter.embedding.engine.plugins.activity.ActivityAware;\nimport io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;\nimport io.flutter.plugin.common.BinaryMessenger;\nimport io.flutter.plugin.common.PluginRegistry;\nimport io.flutter.plugin.common.PluginRegistry.Registrar;\n\n/**\n * Java platform implementation of the webview_flutter plugin.\n *\n * <p>Register this in an add to app scenario to gracefully handle activity and context changes.\n *\n * <p>Call {@link #registerWith(Registrar)} to use the stable {@code io.flutter.plugin.common}\n * package instead.\n */\npublic class WebViewFlutterPlugin implements FlutterPlugin, PluginRegistry.ActivityResultListener, ActivityAware {\n  private static final String TAG = \"WebViewFlutterPlugin\";\n\n  private FlutterCookieManager flutterCookieManager;\n  public static Activity activity;\n  private WebViewFactory factory;\n\n  /**\n   * Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to\n   * register it.\n   *\n   * <p>THIS PLUGIN CODE PATH DEPENDS ON A NEWER VERSION OF FLUTTER THAN THE ONE DEFINED IN THE\n   * PUBSPEC.YAML. Text input will fail on some Android devices unless this is used with at least\n   * flutter/flutter@1d4d63ace1f801a022ea9ec737bf8c15395588b9. Use the V1 embedding with {@link\n   * #registerWith(Registrar)} to use this plugin with older Flutter versions.\n   *\n   * <p>Registration should eventually be handled automatically by v2 of the\n   * GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694\n   */\n  public WebViewFlutterPlugin() {\n    Log.v(TAG,\"WebViewFlutterPlugin\");\n  }\n\n  /**\n   * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common}\n   * package.\n   *\n   * <p>Calling this automatically initializes the plugin. However plugins initialized this way\n   * won't react to changes in activity or context, unlike {@link CameraPlugin}.\n   */\n  @SuppressWarnings(\"deprecation\")\n  public static void registerWith(Registrar registrar) {\n    Log.v(TAG,\"registerWith\");\n    registrar\n        .platformViewRegistry()\n        .registerViewFactory(\n            \"plugins.flutter.io/webview\",\n            new WebViewFactory(registrar.messenger(), registrar.view()));\n    new FlutterCookieManager(registrar.messenger());\n  }\n\n  @Override\n  public void onAttachedToEngine(FlutterPluginBinding binding) {\n    Log.v(TAG,\"onAttachedToEngine\");\n    BinaryMessenger messenger = binding.getBinaryMessenger();\n    factory = new WebViewFactory(messenger, null);\n    binding\n            .getFlutterEngine()\n            .getPlatformViewsController()\n            .getRegistry()\n            .registerViewFactory(\n                    \"plugins.flutter.io/webview\", factory);\n    flutterCookieManager = new FlutterCookieManager(messenger);\n\n    Context appContext = binding.getApplicationContext();\n    if (appContext instanceof FlutterApplication) {\n      Activity currentActivity = ((FlutterApplication) appContext).getCurrentActivity();\n      if (currentActivity != null) {\n        activity = currentActivity;\n      }\n    }\n  }\n\n  @Override\n  public void onDetachedFromEngine(FlutterPluginBinding binding) {\n    Log.v(TAG,\"onDetachedFromEngine\");\n    if (flutterCookieManager == null) {\n      return;\n    }\n    activity = null;\n    flutterCookieManager.dispose();\n    flutterCookieManager = null;\n  }\n\n  @Override\n  public boolean onActivityResult(int requestCode, int resultCode, Intent data) {\n    Log.v(TAG,\"onActivityResult\");\n    if (factory != null && factory.getFlutterWebView() != null){\n      return factory.getFlutterWebView().activityResult(requestCode, resultCode, data);\n    }\n\n    return false;\n  }\n\n  @Override\n  public void onAttachedToActivity(ActivityPluginBinding binding) {\n    Log.v(TAG,\"onAttachedToActivity\");\n    activity = binding.getActivity();\n    binding.addActivityResultListener(this);\n  }\n\n  @Override\n  public void onDetachedFromActivityForConfigChanges() {\n    Log.v(TAG,\"onDetachedFromActivityForConfigChanges\");\n  }\n\n  @Override\n  public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {\n    Log.v(TAG,\"onReattachedToActivityForConfigChanges\");\n  }\n\n  @Override\n  public void onDetachedFromActivity() {\n    Log.v(TAG,\"onDetachedFromActivity\");\n  }\n}\n"
  },
  {
    "path": "ios/Assets/.gitkeep",
    "content": ""
  },
  {
    "path": "ios/Classes/FLTCookieManager.h",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import <Flutter/Flutter.h>\n#import <WebKit/WebKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface FLTCookieManager : NSObject <FlutterPlugin>\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "ios/Classes/FLTCookieManager.m",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import \"FLTCookieManager.h\"\n\n@implementation FLTCookieManager {\n}\n\n+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {\n  FLTCookieManager *instance = [[FLTCookieManager alloc] init];\n\n  FlutterMethodChannel *channel =\n      [FlutterMethodChannel methodChannelWithName:@\"plugins.flutter.io/cookie_manager\"\n                                  binaryMessenger:[registrar messenger]];\n  [registrar addMethodCallDelegate:instance channel:channel];\n}\n\n- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {\n  if ([[call method] isEqualToString:@\"clearCookies\"]) {\n    [self clearCookies:result];\n  } else {\n    result(FlutterMethodNotImplemented);\n  }\n}\n\n- (void)clearCookies:(FlutterResult)result {\n  if (@available(iOS 9.0, *)) {\n    NSSet<NSString *> *websiteDataTypes = [NSSet setWithObject:WKWebsiteDataTypeCookies];\n    WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];\n\n    void (^deleteAndNotify)(NSArray<WKWebsiteDataRecord *> *) =\n        ^(NSArray<WKWebsiteDataRecord *> *cookies) {\n          BOOL hasCookies = cookies.count > 0;\n          [dataStore removeDataOfTypes:websiteDataTypes\n                        forDataRecords:cookies\n                     completionHandler:^{\n                       result(@(hasCookies));\n                     }];\n        };\n\n    [dataStore fetchDataRecordsOfTypes:websiteDataTypes completionHandler:deleteAndNotify];\n  } else {\n    // support for iOS8 tracked in https://github.com/flutter/flutter/issues/27624.\n    NSLog(@\"Clearing cookies is not supported for Flutter WebViews prior to iOS 9.\");\n  }\n}\n\n@end\n"
  },
  {
    "path": "ios/Classes/FLTWKNavigationDelegate.h",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import <Flutter/Flutter.h>\n#import <WebKit/WebKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface FLTWKNavigationDelegate : NSObject <WKNavigationDelegate>\n\n- (instancetype)initWithChannel:(FlutterMethodChannel*)channel;\n\n/**\n * Whether to delegate navigation decisions over the method channel.\n */\n@property(nonatomic, assign) BOOL hasDartNavigationDelegate;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "ios/Classes/FLTWKNavigationDelegate.m",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import \"FLTWKNavigationDelegate.h\"\n\n@implementation FLTWKNavigationDelegate {\n  FlutterMethodChannel *_methodChannel;\n}\n\n- (instancetype)initWithChannel:(FlutterMethodChannel *)channel {\n  self = [super init];\n  if (self) {\n    _methodChannel = channel;\n  }\n  return self;\n}\n\n#pragma mark - WKNavigationDelegate conformance\n\n- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {\n  [_methodChannel invokeMethod:@\"onPageStarted\" arguments:@{@\"url\" : webView.URL.absoluteString}];\n}\n\n- (void)webView:(WKWebView *)webView\n    decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction\n                    decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {\n  if (!self.hasDartNavigationDelegate) {\n    decisionHandler(WKNavigationActionPolicyAllow);\n    return;\n  }\n  NSDictionary *arguments = @{\n    @\"url\" : navigationAction.request.URL.absoluteString,\n    @\"isForMainFrame\" : @(navigationAction.targetFrame.isMainFrame)\n  };\n  [_methodChannel invokeMethod:@\"navigationRequest\"\n                     arguments:arguments\n                        result:^(id _Nullable result) {\n                          if ([result isKindOfClass:[FlutterError class]]) {\n                            NSLog(@\"navigationRequest has unexpectedly completed with an error, \"\n                                  @\"allowing navigation.\");\n                            decisionHandler(WKNavigationActionPolicyAllow);\n                            return;\n                          }\n                          if (result == FlutterMethodNotImplemented) {\n                            NSLog(@\"navigationRequest was unexepectedly not implemented: %@, \"\n                                  @\"allowing navigation.\",\n                                  result);\n                            decisionHandler(WKNavigationActionPolicyAllow);\n                            return;\n                          }\n                          if (![result isKindOfClass:[NSNumber class]]) {\n                            NSLog(@\"navigationRequest unexpectedly returned a non boolean value: \"\n                                  @\"%@, allowing navigation.\",\n                                  result);\n                            decisionHandler(WKNavigationActionPolicyAllow);\n                            return;\n                          }\n                          NSNumber *typedResult = result;\n                          decisionHandler([typedResult boolValue] ? WKNavigationActionPolicyAllow\n                                                                  : WKNavigationActionPolicyCancel);\n                        }];\n}\n\n- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {\n  [_methodChannel invokeMethod:@\"onPageFinished\" arguments:@{@\"url\" : webView.URL.absoluteString}];\n}\n\n+ (id)errorCodeToString:(NSUInteger)code {\n  switch (code) {\n    case WKErrorUnknown:\n      return @\"unknown\";\n    case WKErrorWebContentProcessTerminated:\n      return @\"webContentProcessTerminated\";\n    case WKErrorWebViewInvalidated:\n      return @\"webViewInvalidated\";\n    case WKErrorJavaScriptExceptionOccurred:\n      return @\"javaScriptExceptionOccurred\";\n    case WKErrorJavaScriptResultTypeIsUnsupported:\n      return @\"javaScriptResultTypeIsUnsupported\";\n  }\n\n  return [NSNull null];\n}\n\n- (void)onWebResourceError:(NSError *)error {\n  [_methodChannel invokeMethod:@\"onWebResourceError\"\n                     arguments:@{\n                       @\"errorCode\" : @(error.code),\n                       @\"domain\" : error.domain,\n                       @\"description\" : error.description,\n                       @\"errorType\" : [FLTWKNavigationDelegate errorCodeToString:error.code],\n                     }];\n}\n\n- (void)webView:(WKWebView *)webView\n    didFailNavigation:(WKNavigation *)navigation\n            withError:(NSError *)error {\n  [self onWebResourceError:error];\n}\n\n- (void)webView:(WKWebView *)webView\n    didFailProvisionalNavigation:(WKNavigation *)navigation\n                       withError:(NSError *)error {\n  [self onWebResourceError:error];\n}\n@end\n"
  },
  {
    "path": "ios/Classes/FLTWebViewFlutterPlugin.h",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import <Flutter/Flutter.h>\n\n@interface FLTWebViewFlutterPlugin : NSObject <FlutterPlugin>\n@end\n"
  },
  {
    "path": "ios/Classes/FLTWebViewFlutterPlugin.m",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import \"FLTWebViewFlutterPlugin.h\"\n#import \"FLTCookieManager.h\"\n#import \"FlutterWebView.h\"\n\n@implementation FLTWebViewFlutterPlugin\n\n+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {\n  FLTWebViewFactory* webviewFactory =\n      [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger];\n  [registrar registerViewFactory:webviewFactory withId:@\"plugins.flutter.io/webview\"];\n  [FLTCookieManager registerWithRegistrar:registrar];\n}\n\n@end\n"
  },
  {
    "path": "ios/Classes/FlutterWebView.h",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import <Flutter/Flutter.h>\n#import <WebKit/WebKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface FLTWebViewController : NSObject <FlutterPlatformView, WKUIDelegate>\n\n- (instancetype)initWithFrame:(CGRect)frame\n               viewIdentifier:(int64_t)viewId\n                    arguments:(id _Nullable)args\n              binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;\n\n- (UIView*)view;\n@end\n\n@interface FLTWebViewFactory : NSObject <FlutterPlatformViewFactory>\n- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;\n@end\n\n/**\n * The WkWebView used for the plugin.\n *\n * This class overrides some methods in `WKWebView` to serve the needs for the plugin.\n */\n@interface FLTWKWebView : WKWebView\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "ios/Classes/FlutterWebView.m",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import \"FlutterWebView.h\"\n#import \"FLTWKNavigationDelegate.h\"\n#import \"JavaScriptChannelHandler.h\"\n\n@implementation FLTWebViewFactory {\n  NSObject<FlutterBinaryMessenger>* _messenger;\n}\n\n- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {\n  self = [super init];\n  if (self) {\n    _messenger = messenger;\n  }\n  return self;\n}\n\n- (NSObject<FlutterMessageCodec>*)createArgsCodec {\n  return [FlutterStandardMessageCodec sharedInstance];\n}\n\n- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame\n                                   viewIdentifier:(int64_t)viewId\n                                        arguments:(id _Nullable)args {\n  FLTWebViewController* webviewController = [[FLTWebViewController alloc] initWithFrame:frame\n                                                                         viewIdentifier:viewId\n                                                                              arguments:args\n                                                                        binaryMessenger:_messenger];\n  return webviewController;\n}\n\n@end\n\n@implementation FLTWKWebView\n\n- (void)setFrame:(CGRect)frame {\n  [super setFrame:frame];\n  self.scrollView.contentInset = UIEdgeInsetsZero;\n  // We don't want the contentInsets to be adjusted by iOS, flutter should always take control of\n  // webview's contentInsets.\n  // self.scrollView.contentInset = UIEdgeInsetsZero;\n  if (@available(iOS 11, *)) {\n    // Above iOS 11, adjust contentInset to compensate the adjustedContentInset so the sum will\n    // always be 0.\n    if (UIEdgeInsetsEqualToEdgeInsets(self.scrollView.adjustedContentInset, UIEdgeInsetsZero)) {\n      return;\n    }\n    UIEdgeInsets insetToAdjust = self.scrollView.adjustedContentInset;\n    self.scrollView.contentInset = UIEdgeInsetsMake(-insetToAdjust.top, -insetToAdjust.left,\n                                                    -insetToAdjust.bottom, -insetToAdjust.right);\n  }\n}\n\n@end\n\n@implementation FLTWebViewController {\n  FLTWKWebView* _webView;\n  int64_t _viewId;\n  FlutterMethodChannel* _channel;\n  NSString* _currentUrl;\n  // The set of registered JavaScript channel names.\n  NSMutableSet* _javaScriptChannelNames;\n  FLTWKNavigationDelegate* _navigationDelegate;\n}\n\n- (instancetype)initWithFrame:(CGRect)frame\n               viewIdentifier:(int64_t)viewId\n                    arguments:(id _Nullable)args\n              binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {\n  if (self = [super init]) {\n    _viewId = viewId;\n\n    NSString* channelName = [NSString stringWithFormat:@\"plugins.flutter.io/webview_%lld\", viewId];\n    _channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger];\n    _javaScriptChannelNames = [[NSMutableSet alloc] init];\n\n    WKUserContentController* userContentController = [[WKUserContentController alloc] init];\n    if ([args[@\"javascriptChannelNames\"] isKindOfClass:[NSArray class]]) {\n      NSArray* javaScriptChannelNames = args[@\"javascriptChannelNames\"];\n      [_javaScriptChannelNames addObjectsFromArray:javaScriptChannelNames];\n      [self registerJavaScriptChannels:_javaScriptChannelNames controller:userContentController];\n    }\n\n    NSDictionary<NSString*, id>* settings = args[@\"settings\"];\n\n    WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];\n    configuration.userContentController = userContentController;\n    [self updateAutoMediaPlaybackPolicy:args[@\"autoMediaPlaybackPolicy\"]\n                        inConfiguration:configuration];\n\n    _webView = [[FLTWKWebView alloc] initWithFrame:frame configuration:configuration];\n    _navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:_channel];\n    _webView.UIDelegate = self;\n    _webView.navigationDelegate = _navigationDelegate;\n    __weak __typeof__(self) weakSelf = self;\n    [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {\n      [weakSelf onMethodCall:call result:result];\n    }];\n\n    if (@available(iOS 11.0, *)) {\n      _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;\n      if (@available(iOS 13.0, *)) {\n        _webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = NO;\n      }\n    }\n\n    [self applySettings:settings];\n    // TODO(amirh): return an error if apply settings failed once it's possible to do so.\n    // https://github.com/flutter/flutter/issues/36228\n\n    NSString* initialUrl = args[@\"initialUrl\"];\n    if ([initialUrl isKindOfClass:[NSString class]]) {\n      [self loadUrl:initialUrl];\n    }\n  }\n  return self;\n}\n\n- (UIView*)view {\n  return _webView;\n}\n\n- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {\n  if ([[call method] isEqualToString:@\"updateSettings\"]) {\n    [self onUpdateSettings:call result:result];\n  } else if ([[call method] isEqualToString:@\"loadUrl\"]) {\n    [self onLoadUrl:call result:result];\n  } else if ([[call method] isEqualToString:@\"canGoBack\"]) {\n    [self onCanGoBack:call result:result];\n  } else if ([[call method] isEqualToString:@\"canGoForward\"]) {\n    [self onCanGoForward:call result:result];\n  } else if ([[call method] isEqualToString:@\"goBack\"]) {\n    [self onGoBack:call result:result];\n  } else if ([[call method] isEqualToString:@\"goForward\"]) {\n    [self onGoForward:call result:result];\n  } else if ([[call method] isEqualToString:@\"reload\"]) {\n    [self onReload:call result:result];\n  } else if ([[call method] isEqualToString:@\"currentUrl\"]) {\n    [self onCurrentUrl:call result:result];\n  } else if ([[call method] isEqualToString:@\"evaluateJavascript\"]) {\n    [self onEvaluateJavaScript:call result:result];\n  } else if ([[call method] isEqualToString:@\"addJavascriptChannels\"]) {\n    [self onAddJavaScriptChannels:call result:result];\n  } else if ([[call method] isEqualToString:@\"removeJavascriptChannels\"]) {\n    [self onRemoveJavaScriptChannels:call result:result];\n  } else if ([[call method] isEqualToString:@\"clearCache\"]) {\n    [self clearCache:result];\n  } else if ([[call method] isEqualToString:@\"getTitle\"]) {\n    [self onGetTitle:result];\n  } else if ([[call method] isEqualToString:@\"scrollTo\"]) {\n    [self onScrollTo:call result:result];\n  } else if ([[call method] isEqualToString:@\"scrollBy\"]) {\n    [self onScrollBy:call result:result];\n  } else if ([[call method] isEqualToString:@\"getScrollX\"]) {\n    [self getScrollX:call result:result];\n  } else if ([[call method] isEqualToString:@\"getScrollY\"]) {\n    [self getScrollY:call result:result];\n  } else {\n    result(FlutterMethodNotImplemented);\n  }\n}\n\n- (void)onUpdateSettings:(FlutterMethodCall*)call result:(FlutterResult)result {\n  NSString* error = [self applySettings:[call arguments]];\n  if (error == nil) {\n    result(nil);\n    return;\n  }\n  result([FlutterError errorWithCode:@\"updateSettings_failed\" message:error details:nil]);\n}\n\n- (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result {\n  if (![self loadRequest:[call arguments]]) {\n    result([FlutterError\n        errorWithCode:@\"loadUrl_failed\"\n              message:@\"Failed parsing the URL\"\n              details:[NSString stringWithFormat:@\"Request was: '%@'\", [call arguments]]]);\n  } else {\n    result(nil);\n  }\n}\n\n- (void)onCanGoBack:(FlutterMethodCall*)call result:(FlutterResult)result {\n  BOOL canGoBack = [_webView canGoBack];\n  result([NSNumber numberWithBool:canGoBack]);\n}\n\n- (void)onCanGoForward:(FlutterMethodCall*)call result:(FlutterResult)result {\n  BOOL canGoForward = [_webView canGoForward];\n  result([NSNumber numberWithBool:canGoForward]);\n}\n\n- (void)onGoBack:(FlutterMethodCall*)call result:(FlutterResult)result {\n  [_webView goBack];\n  result(nil);\n}\n\n- (void)onGoForward:(FlutterMethodCall*)call result:(FlutterResult)result {\n  [_webView goForward];\n  result(nil);\n}\n\n- (void)onReload:(FlutterMethodCall*)call result:(FlutterResult)result {\n  [_webView reload];\n  result(nil);\n}\n\n- (void)onCurrentUrl:(FlutterMethodCall*)call result:(FlutterResult)result {\n  _currentUrl = [[_webView URL] absoluteString];\n  result(_currentUrl);\n}\n\n- (void)onEvaluateJavaScript:(FlutterMethodCall*)call result:(FlutterResult)result {\n  NSString* jsString = [call arguments];\n  if (!jsString) {\n    result([FlutterError errorWithCode:@\"evaluateJavaScript_failed\"\n                               message:@\"JavaScript String cannot be null\"\n                               details:nil]);\n    return;\n  }\n  [_webView evaluateJavaScript:jsString\n             completionHandler:^(_Nullable id evaluateResult, NSError* _Nullable error) {\n               if (error) {\n                 result([FlutterError\n                     errorWithCode:@\"evaluateJavaScript_failed\"\n                           message:@\"Failed evaluating JavaScript\"\n                           details:[NSString stringWithFormat:@\"JavaScript string was: '%@'\\n%@\",\n                                                              jsString, error]]);\n               } else {\n                 result([NSString stringWithFormat:@\"%@\", evaluateResult]);\n               }\n             }];\n}\n\n- (void)onAddJavaScriptChannels:(FlutterMethodCall*)call result:(FlutterResult)result {\n  NSArray* channelNames = [call arguments];\n  NSSet* channelNamesSet = [[NSSet alloc] initWithArray:channelNames];\n  [_javaScriptChannelNames addObjectsFromArray:channelNames];\n  [self registerJavaScriptChannels:channelNamesSet\n                        controller:_webView.configuration.userContentController];\n  result(nil);\n}\n\n- (void)onRemoveJavaScriptChannels:(FlutterMethodCall*)call result:(FlutterResult)result {\n  // WkWebView does not support removing a single user script, so instead we remove all\n  // user scripts, all message handlers. And re-register channels that shouldn't be removed.\n  [_webView.configuration.userContentController removeAllUserScripts];\n  for (NSString* channelName in _javaScriptChannelNames) {\n    [_webView.configuration.userContentController removeScriptMessageHandlerForName:channelName];\n  }\n\n  NSArray* channelNamesToRemove = [call arguments];\n  for (NSString* channelName in channelNamesToRemove) {\n    [_javaScriptChannelNames removeObject:channelName];\n  }\n\n  [self registerJavaScriptChannels:_javaScriptChannelNames\n                        controller:_webView.configuration.userContentController];\n  result(nil);\n}\n\n- (void)clearCache:(FlutterResult)result {\n  if (@available(iOS 9.0, *)) {\n    NSSet* cacheDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];\n    WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore];\n    NSDate* dateFrom = [NSDate dateWithTimeIntervalSince1970:0];\n    [dataStore removeDataOfTypes:cacheDataTypes\n                   modifiedSince:dateFrom\n               completionHandler:^{\n                 result(nil);\n               }];\n  } else {\n    // support for iOS8 tracked in https://github.com/flutter/flutter/issues/27624.\n    NSLog(@\"Clearing cache is not supported for Flutter WebViews prior to iOS 9.\");\n  }\n}\n\n- (void)onGetTitle:(FlutterResult)result {\n  NSString* title = _webView.title;\n  result(title);\n}\n\n- (void)onScrollTo:(FlutterMethodCall*)call result:(FlutterResult)result {\n  NSDictionary* arguments = [call arguments];\n  int x = [arguments[@\"x\"] intValue];\n  int y = [arguments[@\"y\"] intValue];\n\n  _webView.scrollView.contentOffset = CGPointMake(x, y);\n  result(nil);\n}\n\n- (void)onScrollBy:(FlutterMethodCall*)call result:(FlutterResult)result {\n  CGPoint contentOffset = _webView.scrollView.contentOffset;\n\n  NSDictionary* arguments = [call arguments];\n  int x = [arguments[@\"x\"] intValue] + contentOffset.x;\n  int y = [arguments[@\"y\"] intValue] + contentOffset.y;\n\n  _webView.scrollView.contentOffset = CGPointMake(x, y);\n  result(nil);\n}\n\n- (void)getScrollX:(FlutterMethodCall*)call result:(FlutterResult)result {\n  int offsetX = _webView.scrollView.contentOffset.x;\n  result([NSNumber numberWithInt:offsetX]);\n}\n\n- (void)getScrollY:(FlutterMethodCall*)call result:(FlutterResult)result {\n  int offsetY = _webView.scrollView.contentOffset.y;\n  result([NSNumber numberWithInt:offsetY]);\n}\n\n// Returns nil when successful, or an error message when one or more keys are unknown.\n- (NSString*)applySettings:(NSDictionary<NSString*, id>*)settings {\n  NSMutableArray<NSString*>* unknownKeys = [[NSMutableArray alloc] init];\n  for (NSString* key in settings) {\n    if ([key isEqualToString:@\"jsMode\"]) {\n      NSNumber* mode = settings[key];\n      [self updateJsMode:mode];\n    } else if ([key isEqualToString:@\"hasNavigationDelegate\"]) {\n      NSNumber* hasDartNavigationDelegate = settings[key];\n      _navigationDelegate.hasDartNavigationDelegate = [hasDartNavigationDelegate boolValue];\n    } else if ([key isEqualToString:@\"debuggingEnabled\"]) {\n      // no-op debugging is always enabled on iOS.\n    } else if ([key isEqualToString:@\"gestureNavigationEnabled\"]) {\n      NSNumber* allowsBackForwardNavigationGestures = settings[key];\n      _webView.allowsBackForwardNavigationGestures =\n          [allowsBackForwardNavigationGestures boolValue];\n    } else if ([key isEqualToString:@\"userAgent\"]) {\n      NSString* userAgent = settings[key];\n      [self updateUserAgent:[userAgent isEqual:[NSNull null]] ? nil : userAgent];\n    } else {\n      [unknownKeys addObject:key];\n    }\n  }\n  if ([unknownKeys count] == 0) {\n    return nil;\n  }\n  return [NSString stringWithFormat:@\"webview_flutter: unknown setting keys: {%@}\",\n                                    [unknownKeys componentsJoinedByString:@\", \"]];\n}\n\n- (void)updateJsMode:(NSNumber*)mode {\n  WKPreferences* preferences = [[_webView configuration] preferences];\n  switch ([mode integerValue]) {\n    case 0:  // disabled\n      [preferences setJavaScriptEnabled:NO];\n      break;\n    case 1:  // unrestricted\n      [preferences setJavaScriptEnabled:YES];\n      break;\n    default:\n      NSLog(@\"webview_flutter: unknown JavaScript mode: %@\", mode);\n  }\n}\n\n- (void)updateAutoMediaPlaybackPolicy:(NSNumber*)policy\n                      inConfiguration:(WKWebViewConfiguration*)configuration {\n  switch ([policy integerValue]) {\n    case 0:  // require_user_action_for_all_media_types\n      if (@available(iOS 10.0, *)) {\n        configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeAll;\n      } else {\n        configuration.mediaPlaybackRequiresUserAction = true;\n      }\n      break;\n    case 1:  // always_allow\n      if (@available(iOS 10.0, *)) {\n        configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;\n      } else {\n        configuration.mediaPlaybackRequiresUserAction = false;\n      }\n      break;\n    default:\n      NSLog(@\"webview_flutter: unknown auto media playback policy: %@\", policy);\n  }\n}\n\n- (bool)loadRequest:(NSDictionary<NSString*, id>*)request {\n  if (!request) {\n    return false;\n  }\n\n  NSString* url = request[@\"url\"];\n  if ([url isKindOfClass:[NSString class]]) {\n    id headers = request[@\"headers\"];\n    if ([headers isKindOfClass:[NSDictionary class]]) {\n      return [self loadUrl:url withHeaders:headers];\n    } else {\n      return [self loadUrl:url];\n    }\n  }\n\n  return false;\n}\n\n- (bool)loadUrl:(NSString*)url {\n  return [self loadUrl:url withHeaders:[NSMutableDictionary dictionary]];\n}\n\n- (bool)loadUrl:(NSString*)url withHeaders:(NSDictionary<NSString*, NSString*>*)headers {\n  NSURL* nsUrl = [NSURL URLWithString:url];\n  if (!nsUrl) {\n    return false;\n  }\n  NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:nsUrl];\n  [request setAllHTTPHeaderFields:headers];\n  [_webView loadRequest:request];\n  return true;\n}\n\n- (void)registerJavaScriptChannels:(NSSet*)channelNames\n                        controller:(WKUserContentController*)userContentController {\n  for (NSString* channelName in channelNames) {\n    FLTJavaScriptChannel* channel =\n        [[FLTJavaScriptChannel alloc] initWithMethodChannel:_channel\n                                      javaScriptChannelName:channelName];\n    [userContentController addScriptMessageHandler:channel name:channelName];\n    NSString* wrapperSource = [NSString\n        stringWithFormat:@\"window.%@ = webkit.messageHandlers.%@;\", channelName, channelName];\n    WKUserScript* wrapperScript =\n        [[WKUserScript alloc] initWithSource:wrapperSource\n                               injectionTime:WKUserScriptInjectionTimeAtDocumentStart\n                            forMainFrameOnly:NO];\n    [userContentController addUserScript:wrapperScript];\n  }\n}\n\n- (void)updateUserAgent:(NSString*)userAgent {\n  if (@available(iOS 9.0, *)) {\n    [_webView setCustomUserAgent:userAgent];\n  } else {\n    NSLog(@\"Updating UserAgent is not supported for Flutter WebViews prior to iOS 9.\");\n  }\n}\n\n#pragma mark WKUIDelegate\n\n- (WKWebView*)webView:(WKWebView*)webView\n    createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration\n               forNavigationAction:(WKNavigationAction*)navigationAction\n                    windowFeatures:(WKWindowFeatures*)windowFeatures {\n  if (!navigationAction.targetFrame.isMainFrame) {\n    [webView loadRequest:navigationAction.request];\n  }\n\n  return nil;\n}\n\n@end\n"
  },
  {
    "path": "ios/Classes/JavaScriptChannelHandler.h",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import <Flutter/Flutter.h>\n#import <WebKit/WebKit.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface FLTJavaScriptChannel : NSObject <WKScriptMessageHandler>\n\n- (instancetype)initWithMethodChannel:(FlutterMethodChannel*)methodChannel\n                javaScriptChannelName:(NSString*)javaScriptChannelName;\n\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "ios/Classes/JavaScriptChannelHandler.m",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import \"JavaScriptChannelHandler.h\"\n\n@implementation FLTJavaScriptChannel {\n  FlutterMethodChannel* _methodChannel;\n  NSString* _javaScriptChannelName;\n}\n\n- (instancetype)initWithMethodChannel:(FlutterMethodChannel*)methodChannel\n                javaScriptChannelName:(NSString*)javaScriptChannelName {\n  self = [super init];\n  NSAssert(methodChannel != nil, @\"methodChannel must not be null.\");\n  NSAssert(javaScriptChannelName != nil, @\"javaScriptChannelName must not be null.\");\n  if (self) {\n    _methodChannel = methodChannel;\n    _javaScriptChannelName = javaScriptChannelName;\n  }\n  return self;\n}\n\n- (void)userContentController:(WKUserContentController*)userContentController\n      didReceiveScriptMessage:(WKScriptMessage*)message {\n  NSAssert(_methodChannel != nil, @\"Can't send a message to an unitialized JavaScript channel.\");\n  NSAssert(_javaScriptChannelName != nil,\n           @\"Can't send a message to an unitialized JavaScript channel.\");\n  NSDictionary* arguments = @{\n    @\"channel\" : _javaScriptChannelName,\n    @\"message\" : [NSString stringWithFormat:@\"%@\", message.body]\n  };\n  [_methodChannel invokeMethod:@\"javascriptChannelMessage\" arguments:arguments];\n}\n\n@end\n"
  },
  {
    "path": "ios/Tests/FLTWebViewTests.m",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n@import Flutter;\n@import XCTest;\n@import webview_flutter;\n\n// OCMock library doesn't generate a valid modulemap.\n#import <OCMock/OCMock.h>\n\nstatic bool feq(CGFloat a, CGFloat b) { return fabs(b - a) < FLT_EPSILON; }\n\n@interface FLTWebViewTests : XCTestCase\n\n@property(strong, nonatomic) NSObject<FlutterBinaryMessenger> *mockBinaryMessenger;\n\n@end\n\n@implementation FLTWebViewTests\n\n- (void)setUp {\n  [super setUp];\n  self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));\n}\n\n- (void)testCanInitFLTWebViewController {\n  FLTWebViewController *controller =\n      [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)\n                                   viewIdentifier:1\n                                        arguments:nil\n                                  binaryMessenger:self.mockBinaryMessenger];\n  XCTAssertNotNil(controller);\n}\n\n- (void)testCanInitFLTWebViewFactory {\n  FLTWebViewFactory *factory =\n      [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger];\n  XCTAssertNotNil(factory);\n}\n\n- (void)webViewContentInsetBehaviorShouldBeNeverOnIOS11 {\n  if (@available(iOS 11, *)) {\n    FLTWebViewController *controller =\n        [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)\n                                     viewIdentifier:1\n                                          arguments:nil\n                                    binaryMessenger:self.mockBinaryMessenger];\n    UIView *view = controller.view;\n    XCTAssertTrue([view isKindOfClass:WKWebView.class]);\n    WKWebView *webView = (WKWebView *)view;\n    XCTAssertEqual(webView.scrollView.contentInsetAdjustmentBehavior,\n                   UIScrollViewContentInsetAdjustmentNever);\n  }\n}\n\n- (void)testWebViewScrollIndicatorAticautomaticallyAdjustsScrollIndicatorInsetsShouldbeNoOnIOS13 {\n  if (@available(iOS 13, *)) {\n    FLTWebViewController *controller =\n        [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)\n                                     viewIdentifier:1\n                                          arguments:nil\n                                    binaryMessenger:self.mockBinaryMessenger];\n    UIView *view = controller.view;\n    XCTAssertTrue([view isKindOfClass:WKWebView.class]);\n    WKWebView *webView = (WKWebView *)view;\n    XCTAssertFalse(webView.scrollView.automaticallyAdjustsScrollIndicatorInsets);\n  }\n}\n\n- (void)testContentInsetsSumAlwaysZeroAfterSetFrame {\n  FLTWKWebView *webView = [[FLTWKWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 400)];\n  webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 300, 0);\n  XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero));\n  webView.frame = CGRectMake(0, 0, 300, 200);\n  XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero));\n  XCTAssertTrue(CGRectEqualToRect(webView.frame, CGRectMake(0, 0, 300, 200)));\n\n  if (@available(iOS 11, *)) {\n    // After iOS 11, we need to make sure the contentInset compensates the adjustedContentInset.\n    UIScrollView *partialMockScrollView = OCMPartialMock(webView.scrollView);\n    UIEdgeInsets insetToAdjust = UIEdgeInsetsMake(0, 0, 300, 0);\n    OCMStub(partialMockScrollView.adjustedContentInset).andReturn(insetToAdjust);\n    XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero));\n    webView.frame = CGRectMake(0, 0, 300, 100);\n    XCTAssertTrue(feq(webView.scrollView.contentInset.bottom, -insetToAdjust.bottom));\n    XCTAssertTrue(CGRectEqualToRect(webView.frame, CGRectMake(0, 0, 300, 100)));\n  }\n}\n\n@end\n"
  },
  {
    "path": "ios/webview_flutter.podspec",
    "content": "#\n# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html\n#\nPod::Spec.new do |s|\n  s.name             = 'webview_flutter'\n  s.version          = '0.0.1'\n  s.summary          = 'A WebView Plugin for Flutter.'\n  s.description      = <<-DESC\nA Flutter plugin that provides a WebView widget.\nDownloaded by pub (not CocoaPods).\n                       DESC\n  s.homepage         = 'https://github.com/flutter/plugins'\n  s.license          = { :type => 'BSD', :file => '../LICENSE' }\n  s.author           = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }\n  s.source           = { :http => 'https://github.com/flutter/plugins/tree/master/packages/webview_flutter' }\n  s.documentation_url = 'https://pub.dev/packages/webview_flutter'\n  s.source_files = 'Classes/**/*'\n  s.public_header_files = 'Classes/**/*.h'\n  s.dependency 'Flutter'\n\n  s.platform = :ios, '8.0'\n  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }\n\n  s.test_spec 'Tests' do |test_spec|\n    test_spec.source_files = 'Tests/**/*'\n    test_spec.dependency 'OCMock','3.5'\n  end\nend\n"
  },
  {
    "path": "lib/platform_interface.dart",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/gestures.dart';\nimport 'package:flutter/widgets.dart';\n\nimport 'webview_flutter.dart';\n\n/// Interface for callbacks made by [WebViewPlatformController].\n///\n/// The webview plugin implements this class, and passes an instance to the [WebViewPlatformController].\n/// [WebViewPlatformController] is notifying this handler on events that happened on the platform's webview.\nabstract class WebViewPlatformCallbacksHandler {\n  /// Invoked by [WebViewPlatformController] when a JavaScript channel message is received.\n  void onJavaScriptChannelMessage(String channel, String message);\n\n  /// Invoked by [WebViewPlatformController] when a navigation request is pending.\n  ///\n  /// If true is returned the navigation is allowed, otherwise it is blocked.\n  FutureOr<bool> onNavigationRequest({String url, bool isForMainFrame});\n\n  /// Invoked by [WebViewPlatformController] when a page has started loading.\n  void onPageStarted(String url);\n\n  /// Invoked by [WebViewPlatformController] when a page has finished loading.\n  void onPageFinished(String url);\n\n  /// Report web resource loading error to the host application.\n  void onWebResourceError(WebResourceError error);\n}\n\n/// Possible error type categorizations used by [WebResourceError].\nenum WebResourceErrorType {\n  /// User authentication failed on server.\n  authentication,\n\n  /// Malformed URL.\n  badUrl,\n\n  /// Failed to connect to the server.\n  connect,\n\n  /// Failed to perform SSL handshake.\n  failedSslHandshake,\n\n  /// Generic file error.\n  file,\n\n  /// File not found.\n  fileNotFound,\n\n  /// Server or proxy hostname lookup failed.\n  hostLookup,\n\n  /// Failed to read or write to the server.\n  io,\n\n  /// User authentication failed on proxy.\n  proxyAuthentication,\n\n  /// Too many redirects.\n  redirectLoop,\n\n  /// Connection timed out.\n  timeout,\n\n  /// Too many requests during this load.\n  tooManyRequests,\n\n  /// Generic error.\n  unknown,\n\n  /// Resource load was canceled by Safe Browsing.\n  unsafeResource,\n\n  /// Unsupported authentication scheme (not basic or digest).\n  unsupportedAuthScheme,\n\n  /// Unsupported URI scheme.\n  unsupportedScheme,\n\n  /// The web content process was terminated.\n  webContentProcessTerminated,\n\n  /// The web view was invalidated.\n  webViewInvalidated,\n\n  /// A JavaScript exception occurred.\n  javaScriptExceptionOccurred,\n\n  /// The result of JavaScript execution could not be returned.\n  javaScriptResultTypeIsUnsupported,\n}\n\n/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred.\nclass WebResourceError {\n  /// Creates a new [WebResourceError]\n  ///\n  /// A user should not need to instantiate this class, but will receive one in\n  /// [WebResourceErrorCallback].\n  WebResourceError({\n    @required this.errorCode,\n    @required this.description,\n    this.domain,\n    this.errorType,\n    this.failingUrl,\n  })  : assert(errorCode != null),\n        assert(description != null);\n\n  /// Raw code of the error from the respective platform.\n  ///\n  /// On Android, the error code will be a constant from a\n  /// [WebViewClient](https://developer.android.com/reference/android/webkit/WebViewClient#summary) and\n  /// will have a corresponding [errorType].\n  ///\n  /// On iOS, the error code will be a constant from `NSError.code` in\n  /// Objective-C. See\n  /// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html\n  /// for more information on error handling on iOS. Some possible error codes\n  /// can be found at https://developer.apple.com/documentation/webkit/wkerrorcode?language=objc.\n  final int errorCode;\n\n  /// The domain of where to find the error code.\n  ///\n  /// This field is only available on iOS and represents a \"domain\" from where\n  /// the [errorCode] is from. This value is taken directly from an `NSError`\n  /// in Objective-C. See\n  /// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html\n  /// for more information on error handling on iOS.\n  final String domain;\n\n  /// Description of the error that can be used to communicate the problem to the user.\n  final String description;\n\n  /// The type this error can be categorized as.\n  ///\n  /// This will never be `null` on Android, but can be `null` on iOS.\n  final WebResourceErrorType errorType;\n\n  /// Gets the URL for which the resource request was made.\n  ///\n  /// This value is not provided on iOS. Alternatively, you can keep track of\n  /// the last values provided to [WebViewPlatformController.loadUrl].\n  final String failingUrl;\n}\n\n/// Interface for talking to the webview's platform implementation.\n///\n/// An instance implementing this interface is passed to the `onWebViewPlatformCreated` callback that is\n/// passed to [WebViewPlatformBuilder#onWebViewPlatformCreated].\n///\n/// Platform implementations that live in a separate package should extend this class rather than\n/// implement it as webview_flutter does not consider newly added methods to be breaking changes.\n/// Extending this class (using `extends`) ensures that the subclass will get the default\n/// implementation, while platform implementations that `implements` this interface will be broken\n/// by newly added [WebViewPlatformController] methods.\nabstract class WebViewPlatformController {\n  /// Creates a new WebViewPlatform.\n  ///\n  /// Callbacks made by the WebView will be delegated to `handler`.\n  ///\n  /// The `handler` parameter must not be null.\n  WebViewPlatformController(WebViewPlatformCallbacksHandler handler);\n\n  /// Loads the specified URL.\n  ///\n  /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will\n  /// be added as key value pairs of HTTP headers for the request.\n  ///\n  /// `url` must not be null.\n  ///\n  /// Throws an ArgumentError if `url` is not a valid URL string.\n  Future<void> loadUrl(\n    String url,\n    Map<String, String> headers,\n  ) {\n    throw UnimplementedError(\n        \"WebView loadUrl is not implemented on the current platform\");\n  }\n\n  /// Updates the webview settings.\n  ///\n  /// Any non null field in `settings` will be set as the new setting value.\n  /// All null fields in `settings` are ignored.\n  Future<void> updateSettings(WebSettings setting) {\n    throw UnimplementedError(\n        \"WebView updateSettings is not implemented on the current platform\");\n  }\n\n  /// Accessor to the current URL that the WebView is displaying.\n  ///\n  /// If no URL was ever loaded, returns `null`.\n  Future<String> currentUrl() {\n    throw UnimplementedError(\n        \"WebView currentUrl is not implemented on the current platform\");\n  }\n\n  /// Checks whether there's a back history item.\n  Future<bool> canGoBack() {\n    throw UnimplementedError(\n        \"WebView canGoBack is not implemented on the current platform\");\n  }\n\n  /// Checks whether there's a forward history item.\n  Future<bool> canGoForward() {\n    throw UnimplementedError(\n        \"WebView canGoForward is not implemented on the current platform\");\n  }\n\n  /// Goes back in the history of this WebView.\n  ///\n  /// If there is no back history item this is a no-op.\n  Future<void> goBack() {\n    throw UnimplementedError(\n        \"WebView goBack is not implemented on the current platform\");\n  }\n\n  /// Goes forward in the history of this WebView.\n  ///\n  /// If there is no forward history item this is a no-op.\n  Future<void> goForward() {\n    throw UnimplementedError(\n        \"WebView goForward is not implemented on the current platform\");\n  }\n\n  /// Reloads the current URL.\n  Future<void> reload() {\n    throw UnimplementedError(\n        \"WebView reload is not implemented on the current platform\");\n  }\n\n  /// Clears all caches used by the [WebView].\n  ///\n  /// The following caches are cleared:\n  ///\t1. Browser HTTP Cache.\n  ///\t2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches.\n  ///    These are not yet supported in iOS WkWebView. Service workers tend to use this cache.\n  ///\t3. Application cache.\n  ///\t4. Local Storage.\n  Future<void> clearCache() {\n    throw UnimplementedError(\n        \"WebView clearCache is not implemented on the current platform\");\n  }\n\n  /// Evaluates a JavaScript expression in the context of the current page.\n  ///\n  /// The Future completes with an error if a JavaScript error occurred, or if the type of the\n  /// evaluated expression is not supported(e.g on iOS not all non primitive type can be evaluated).\n  Future<String> evaluateJavascript(String javascriptString) {\n    throw UnimplementedError(\n        \"WebView evaluateJavascript is not implemented on the current platform\");\n  }\n\n  /// Adds new JavaScript channels to the set of enabled channels.\n  ///\n  /// For each value in this list the platform's webview should make sure that a corresponding\n  /// property with a postMessage method is set on `window`. For example for a JavaScript channel\n  /// named `Foo` it should be possible for JavaScript code executing in the webview to do\n  ///\n  /// ```javascript\n  /// Foo.postMessage('hello');\n  /// ```\n  ///\n  /// See also: [CreationParams.javascriptChannelNames].\n  Future<void> addJavascriptChannels(Set<String> javascriptChannelNames) {\n    throw UnimplementedError(\n        \"WebView addJavascriptChannels is not implemented on the current platform\");\n  }\n\n  /// Removes JavaScript channel names from the set of enabled channels.\n  ///\n  /// This disables channels that were previously enabled by [addJavaScriptChannels] or through\n  /// [CreationParams.javascriptChannelNames].\n  Future<void> removeJavascriptChannels(Set<String> javascriptChannelNames) {\n    throw UnimplementedError(\n        \"WebView removeJavascriptChannels is not implemented on the current platform\");\n  }\n\n  /// Returns the title of the currently loaded page.\n  Future<String> getTitle() {\n    throw UnimplementedError(\n        \"WebView getTitle is not implemented on the current platform\");\n  }\n\n  /// Set the scrolled position of this view.\n  ///\n  /// The parameters `x` and `y` specify the position to scroll to in WebView pixels.\n  Future<void> scrollTo(int x, int y) {\n    throw UnimplementedError(\n        \"WebView scrollTo is not implemented on the current platform\");\n  }\n\n  /// Move the scrolled position of this view.\n  ///\n  /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by.\n  Future<void> scrollBy(int x, int y) {\n    throw UnimplementedError(\n        \"WebView scrollBy is not implemented on the current platform\");\n  }\n\n  /// Return the horizontal scroll position of this view.\n  ///\n  /// Scroll position is measured from left.\n  Future<int> getScrollX() {\n    throw UnimplementedError(\n        \"WebView getScrollX is not implemented on the current platform\");\n  }\n\n  /// Return the vertical scroll position of this view.\n  ///\n  /// Scroll position is measured from top.\n  Future<int> getScrollY() {\n    throw UnimplementedError(\n        \"WebView getScrollY is not implemented on the current platform\");\n  }\n}\n\n/// A single setting for configuring a WebViewPlatform which may be absent.\nclass WebSetting<T> {\n  /// Constructs an absent setting instance.\n  ///\n  /// The [isPresent] field for the instance will be false.\n  ///\n  /// Accessing [value] for an absent instance will throw.\n  WebSetting.absent()\n      : _value = null,\n        isPresent = false;\n\n  /// Constructs a setting of the given `value`.\n  ///\n  /// The [isPresent] field for the instance will be true.\n  WebSetting.of(T value)\n      : _value = value,\n        isPresent = true;\n\n  final T _value;\n\n  /// The setting's value.\n  ///\n  /// Throws if [WebSetting.isPresent] is false.\n  T get value {\n    if (!isPresent) {\n      throw StateError('Cannot access a value of an absent WebSetting');\n    }\n    assert(isPresent);\n    return _value;\n  }\n\n  /// True when this web setting instance contains a value.\n  ///\n  /// When false the [WebSetting.value] getter throws.\n  final bool isPresent;\n\n  @override\n  bool operator ==(Object other) {\n    if (other.runtimeType != runtimeType) return false;\n    final WebSetting<T> typedOther = other;\n    return typedOther.isPresent == isPresent && typedOther._value == _value;\n  }\n\n  @override\n  int get hashCode => hashValues(_value, isPresent);\n}\n\n/// Settings for configuring a WebViewPlatform.\n///\n/// Initial settings are passed as part of [CreationParams], settings updates are sent with\n/// [WebViewPlatform#updateSettings].\n///\n/// The `userAgent` parameter must not be null.\nclass WebSettings {\n  /// Construct an instance with initial settings. Future setting changes can be\n  /// sent with [WebviewPlatform#updateSettings].\n  ///\n  /// The `userAgent` parameter must not be null.\n  WebSettings({\n    this.javascriptMode,\n    this.hasNavigationDelegate,\n    this.debuggingEnabled,\n    this.gestureNavigationEnabled,\n    @required this.userAgent,\n  }) : assert(userAgent != null);\n\n  /// The JavaScript execution mode to be used by the webview.\n  final JavascriptMode javascriptMode;\n\n  /// Whether the [WebView] has a [NavigationDelegate] set.\n  final bool hasNavigationDelegate;\n\n  /// Whether to enable the platform's webview content debugging tools.\n  ///\n  /// See also: [WebView.debuggingEnabled].\n  final bool debuggingEnabled;\n\n  /// The value used for the HTTP `User-Agent:` request header.\n  ///\n  /// If [userAgent.value] is null the platform's default user agent should be used.\n  ///\n  /// An absent value ([userAgent.isPresent] is false) represents no change to this setting from the\n  /// last time it was set.\n  ///\n  /// See also [WebView.userAgent].\n  final WebSetting<String> userAgent;\n\n  /// Whether to allow swipe based navigation in iOS.\n  ///\n  /// See also: [WebView.gestureNavigationEnabled]\n  final bool gestureNavigationEnabled;\n\n  @override\n  String toString() {\n    return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent)';\n  }\n}\n\n/// Configuration to use when creating a new [WebViewPlatformController].\n///\n/// The `autoMediaPlaybackPolicy` parameter must not be null.\nclass CreationParams {\n  /// Constructs an instance to use when creating a new\n  /// [WebViewPlatformController].\n  ///\n  /// The `autoMediaPlaybackPolicy` parameter must not be null.\n  CreationParams({\n    this.initialUrl,\n    this.webSettings,\n    this.javascriptChannelNames,\n    this.userAgent,\n    this.autoMediaPlaybackPolicy =\n        AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,\n  }) : assert(autoMediaPlaybackPolicy != null);\n\n  /// The initialUrl to load in the webview.\n  ///\n  /// When null the webview will be created without loading any page.\n  final String initialUrl;\n\n  /// The initial [WebSettings] for the new webview.\n  ///\n  /// This can later be updated with [WebViewPlatformController.updateSettings].\n  final WebSettings webSettings;\n\n  /// The initial set of JavaScript channels that are configured for this webview.\n  ///\n  /// For each value in this set the platform's webview should make sure that a corresponding\n  /// property with a postMessage method is set on `window`. For example for a JavaScript channel\n  /// named `Foo` it should be possible for JavaScript code executing in the webview to do\n  ///\n  /// ```javascript\n  /// Foo.postMessage('hello');\n  /// ```\n  // TODO(amirh): describe what should happen when postMessage is called once that code is migrated\n  // to PlatformWebView.\n  final Set<String> javascriptChannelNames;\n\n  /// The value used for the HTTP User-Agent: request header.\n  ///\n  /// When null the platform's webview default is used for the User-Agent header.\n  final String userAgent;\n\n  /// Which restrictions apply on automatic media playback.\n  final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy;\n\n  @override\n  String toString() {\n    return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent)';\n  }\n}\n\n/// Signature for callbacks reporting that a [WebViewPlatformController] was created.\n///\n/// See also the `onWebViewPlatformCreated` argument for [WebViewPlatform.build].\ntypedef WebViewPlatformCreatedCallback = void Function(\n    WebViewPlatformController webViewPlatformController);\n\n/// Interface for a platform implementation of a WebView.\n///\n/// [WebView.platform] controls the builder that is used by [WebView].\n/// [AndroidWebViewPlatform] and [CupertinoWebViewPlatform] are the default implementations\n/// for Android and iOS respectively.\nabstract class WebViewPlatform {\n  /// Builds a new WebView.\n  ///\n  /// Returns a Widget tree that embeds the created webview.\n  ///\n  /// `creationParams` are the initial parameters used to setup the webview.\n  ///\n  /// `webViewPlatformHandler` will be used for handling callbacks that are made by the created\n  /// [WebViewPlatformController].\n  ///\n  /// `onWebViewPlatformCreated` will be invoked after the platform specific [WebViewPlatformController]\n  /// implementation is created with the [WebViewPlatformController] instance as a parameter.\n  ///\n  /// `gestureRecognizers` specifies which gestures should be consumed by the web view.\n  /// It is possible for other gesture recognizers to be competing with the web view on pointer\n  /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle\n  /// vertical drags. The web view will claim gestures that are recognized by any of the\n  /// recognizers on this list.\n  /// When `gestureRecognizers` is empty or null, the web view will only handle pointer events for gestures that\n  /// were not claimed by any other gesture recognizer.\n  ///\n  /// `webViewPlatformHandler` must not be null.\n  Widget build({\n    BuildContext context,\n    // TODO(amirh): convert this to be the actual parameters.\n    // I'm starting without it as the PR is starting to become pretty big.\n    // I'll followup with the conversion PR.\n    CreationParams creationParams,\n    @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,\n    WebViewPlatformCreatedCallback onWebViewPlatformCreated,\n    Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,\n  });\n\n  /// Clears all cookies for all [WebView] instances.\n  ///\n  /// Returns true if cookies were present before clearing, else false.\n  Future<bool> clearCookies() {\n    throw UnimplementedError(\n        \"WebView clearCookies is not implemented on the current platform\");\n  }\n}\n"
  },
  {
    "path": "lib/src/webview_android.dart",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/gestures.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter/widgets.dart';\n\nimport '../platform_interface.dart';\nimport 'webview_method_channel.dart';\n\n/// Builds an Android webview.\n///\n/// This is used as the default implementation for [WebView.platform] on Android. It uses\n/// an [AndroidView] to embed the webview in the widget hierarchy, and uses a method channel to\n/// communicate with the platform code.\nclass AndroidWebView implements WebViewPlatform {\n  @override\n  Widget build({\n    BuildContext context,\n    CreationParams creationParams,\n    @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,\n    WebViewPlatformCreatedCallback onWebViewPlatformCreated,\n    Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,\n  }) {\n    assert(webViewPlatformCallbacksHandler != null);\n    return GestureDetector(\n      // We prevent text selection by intercepting the long press event.\n      // This is a temporary stop gap due to issues with text selection on Android:\n      // https://github.com/flutter/flutter/issues/24585 - the text selection\n      // dialog is not responding to touch events.\n      // https://github.com/flutter/flutter/issues/24584 - the text selection\n      // handles are not showing.\n      // TODO(amirh): remove this when the issues above are fixed.\n      onLongPress: () {},\n      excludeFromSemantics: true,\n      child: AndroidView(\n        viewType: 'plugins.flutter.io/webview',\n        onPlatformViewCreated: (int id) {\n          if (onWebViewPlatformCreated == null) {\n            return;\n          }\n          onWebViewPlatformCreated(MethodChannelWebViewPlatform(\n              id, webViewPlatformCallbacksHandler));\n        },\n        gestureRecognizers: gestureRecognizers,\n        // WebView content is not affected by the Android view's layout direction,\n        // we explicitly set it here so that the widget doesn't require an ambient\n        // directionality.\n        layoutDirection: TextDirection.rtl,\n        creationParams:\n            MethodChannelWebViewPlatform.creationParamsToMap(creationParams),\n        creationParamsCodec: const StandardMessageCodec(),\n      ),\n    );\n  }\n\n  @override\n  Future<bool> clearCookies() => MethodChannelWebViewPlatform.clearCookies();\n}\n"
  },
  {
    "path": "lib/src/webview_cupertino.dart",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/gestures.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter/widgets.dart';\n\nimport '../platform_interface.dart';\nimport 'webview_method_channel.dart';\n\n/// Builds an iOS webview.\n///\n/// This is used as the default implementation for [WebView.platform] on iOS. It uses\n/// a [UiKitView] to embed the webview in the widget hierarchy, and uses a method channel to\n/// communicate with the platform code.\nclass CupertinoWebView implements WebViewPlatform {\n  @override\n  Widget build({\n    BuildContext context,\n    CreationParams creationParams,\n    @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,\n    WebViewPlatformCreatedCallback onWebViewPlatformCreated,\n    Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,\n  }) {\n    return UiKitView(\n      viewType: 'plugins.flutter.io/webview',\n      onPlatformViewCreated: (int id) {\n        if (onWebViewPlatformCreated == null) {\n          return;\n        }\n        onWebViewPlatformCreated(\n            MethodChannelWebViewPlatform(id, webViewPlatformCallbacksHandler));\n      },\n      gestureRecognizers: gestureRecognizers,\n      creationParams:\n          MethodChannelWebViewPlatform.creationParamsToMap(creationParams),\n      creationParamsCodec: const StandardMessageCodec(),\n    );\n  }\n\n  @override\n  Future<bool> clearCookies() => MethodChannelWebViewPlatform.clearCookies();\n}\n"
  },
  {
    "path": "lib/src/webview_method_channel.dart",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport 'dart:async';\n\nimport 'package:flutter/services.dart';\n\nimport '../platform_interface.dart';\n\n/// A [WebViewPlatformController] that uses a method channel to control the webview.\nclass MethodChannelWebViewPlatform implements WebViewPlatformController {\n  /// Constructs an instance that will listen for webviews broadcasting to the\n  /// given [id], using the given [WebViewPlatformCallbacksHandler].\n  MethodChannelWebViewPlatform(int id, this._platformCallbacksHandler)\n      : assert(_platformCallbacksHandler != null),\n        _channel = MethodChannel('plugins.flutter.io/webview_$id') {\n    _channel.setMethodCallHandler(_onMethodCall);\n  }\n\n  final WebViewPlatformCallbacksHandler _platformCallbacksHandler;\n\n  final MethodChannel _channel;\n\n  static const MethodChannel _cookieManagerChannel =\n      MethodChannel('plugins.flutter.io/cookie_manager');\n\n  Future<bool> _onMethodCall(MethodCall call) async {\n    switch (call.method) {\n      case 'javascriptChannelMessage':\n        final String channel = call.arguments['channel'];\n        final String message = call.arguments['message'];\n        _platformCallbacksHandler.onJavaScriptChannelMessage(channel, message);\n        return true;\n      case 'navigationRequest':\n        return await _platformCallbacksHandler.onNavigationRequest(\n          url: call.arguments['url'],\n          isForMainFrame: call.arguments['isForMainFrame'],\n        );\n      case 'onPageFinished':\n        _platformCallbacksHandler.onPageFinished(call.arguments['url']);\n        return null;\n      case 'onPageStarted':\n        _platformCallbacksHandler.onPageStarted(call.arguments['url']);\n        return null;\n      case 'onWebResourceError':\n        _platformCallbacksHandler.onWebResourceError(\n          WebResourceError(\n            errorCode: call.arguments['errorCode'],\n            description: call.arguments['description'],\n            domain: call.arguments['domain'],\n            failingUrl: call.arguments['failingUrl'],\n            errorType: call.arguments['errorType'] == null\n                ? null\n                : WebResourceErrorType.values.firstWhere(\n                    (WebResourceErrorType type) {\n                      return type.toString() ==\n                          '$WebResourceErrorType.${call.arguments['errorType']}';\n                    },\n                  ),\n          ),\n        );\n        return null;\n    }\n\n    throw MissingPluginException(\n      '${call.method} was invoked but has no handler',\n    );\n  }\n\n  @override\n  Future<void> loadUrl(\n    String url,\n    Map<String, String> headers,\n  ) async {\n    assert(url != null);\n    return _channel.invokeMethod<void>('loadUrl', <String, dynamic>{\n      'url': url,\n      'headers': headers,\n    });\n  }\n\n  @override\n  Future<String> currentUrl() => _channel.invokeMethod<String>('currentUrl');\n\n  @override\n  Future<bool> canGoBack() => _channel.invokeMethod<bool>(\"canGoBack\");\n\n  @override\n  Future<bool> canGoForward() => _channel.invokeMethod<bool>(\"canGoForward\");\n\n  @override\n  Future<void> goBack() => _channel.invokeMethod<void>(\"goBack\");\n\n  @override\n  Future<void> goForward() => _channel.invokeMethod<void>(\"goForward\");\n\n  @override\n  Future<void> reload() => _channel.invokeMethod<void>(\"reload\");\n\n  @override\n  Future<void> clearCache() => _channel.invokeMethod<void>(\"clearCache\");\n\n  @override\n  Future<void> updateSettings(WebSettings settings) {\n    final Map<String, dynamic> updatesMap = _webSettingsToMap(settings);\n    if (updatesMap.isEmpty) {\n      return null;\n    }\n    return _channel.invokeMethod<void>('updateSettings', updatesMap);\n  }\n\n  @override\n  Future<String> evaluateJavascript(String javascriptString) {\n    return _channel.invokeMethod<String>(\n        'evaluateJavascript', javascriptString);\n  }\n\n  @override\n  Future<void> addJavascriptChannels(Set<String> javascriptChannelNames) {\n    return _channel.invokeMethod<void>(\n        'addJavascriptChannels', javascriptChannelNames.toList());\n  }\n\n  @override\n  Future<void> removeJavascriptChannels(Set<String> javascriptChannelNames) {\n    return _channel.invokeMethod<void>(\n        'removeJavascriptChannels', javascriptChannelNames.toList());\n  }\n\n  @override\n  Future<String> getTitle() => _channel.invokeMethod<String>(\"getTitle\");\n\n  @override\n  Future<void> scrollTo(int x, int y) {\n    return _channel.invokeMethod<void>('scrollTo', <String, int>{\n      'x': x,\n      'y': y,\n    });\n  }\n\n  @override\n  Future<void> scrollBy(int x, int y) {\n    return _channel.invokeMethod<void>('scrollBy', <String, int>{\n      'x': x,\n      'y': y,\n    });\n  }\n\n  @override\n  Future<int> getScrollX() => _channel.invokeMethod<int>(\"getScrollX\");\n\n  @override\n  Future<int> getScrollY() => _channel.invokeMethod<int>(\"getScrollY\");\n\n  /// Method channel implementation for [WebViewPlatform.clearCookies].\n  static Future<bool> clearCookies() {\n    return _cookieManagerChannel\n        .invokeMethod<bool>('clearCookies')\n        .then<bool>((dynamic result) => result);\n  }\n\n  static Map<String, dynamic> _webSettingsToMap(WebSettings settings) {\n    final Map<String, dynamic> map = <String, dynamic>{};\n    void _addIfNonNull(String key, dynamic value) {\n      if (value == null) {\n        return;\n      }\n      map[key] = value;\n    }\n\n    void _addSettingIfPresent<T>(String key, WebSetting<T> setting) {\n      if (!setting.isPresent) {\n        return;\n      }\n      map[key] = setting.value;\n    }\n\n    _addIfNonNull('jsMode', settings.javascriptMode?.index);\n    _addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate);\n    _addIfNonNull('debuggingEnabled', settings.debuggingEnabled);\n    _addIfNonNull(\n        'gestureNavigationEnabled', settings.gestureNavigationEnabled);\n    _addSettingIfPresent('userAgent', settings.userAgent);\n    return map;\n  }\n\n  /// Converts a [CreationParams] object to a map as expected by `platform_views` channel.\n  ///\n  /// This is used for the `creationParams` argument of the platform views created by\n  /// [AndroidWebViewBuilder] and [CupertinoWebViewBuilder].\n  static Map<String, dynamic> creationParamsToMap(\n      CreationParams creationParams) {\n    return <String, dynamic>{\n      'initialUrl': creationParams.initialUrl,\n      'settings': _webSettingsToMap(creationParams.webSettings),\n      'javascriptChannelNames': creationParams.javascriptChannelNames.toList(),\n      'userAgent': creationParams.userAgent,\n      'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index,\n    };\n  }\n}\n"
  },
  {
    "path": "lib/webview_flutter.dart",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/gestures.dart';\nimport 'package:flutter/rendering.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter/widgets.dart';\n\nimport 'platform_interface.dart';\nimport 'src/webview_android.dart';\nimport 'src/webview_cupertino.dart';\nimport 'src/webview_method_channel.dart';\n\n/// Optional callback invoked when a web view is first created. [controller] is\n/// the [WebViewController] for the created web view.\ntypedef void WebViewCreatedCallback(WebViewController controller);\n\n/// Describes the state of JavaScript support in a given web view.\nenum JavascriptMode {\n  /// JavaScript execution is disabled.\n  disabled,\n\n  /// JavaScript execution is not restricted.\n  unrestricted,\n}\n\n/// A message that was sent by JavaScript code running in a [WebView].\nclass JavascriptMessage {\n  /// Constructs a JavaScript message object.\n  ///\n  /// The `message` parameter must not be null.\n  const JavascriptMessage(this.message) : assert(message != null);\n\n  /// The contents of the message that was sent by the JavaScript code.\n  final String message;\n}\n\n/// Callback type for handling messages sent from Javascript running in a web view.\ntypedef void JavascriptMessageHandler(JavascriptMessage message);\n\n/// Information about a navigation action that is about to be executed.\nclass NavigationRequest {\n  NavigationRequest._({this.url, this.isForMainFrame});\n\n  /// The URL that will be loaded if the navigation is executed.\n  final String url;\n\n  /// Whether the navigation request is to be loaded as the main frame.\n  final bool isForMainFrame;\n\n  @override\n  String toString() {\n    return '$runtimeType(url: $url, isForMainFrame: $isForMainFrame)';\n  }\n}\n\n/// A decision on how to handle a navigation request.\nenum NavigationDecision {\n  /// Prevent the navigation from taking place.\n  prevent,\n\n  /// Allow the navigation to take place.\n  navigate,\n}\n\n/// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget.\n///\n/// To use this, set [WebView.platform] to an instance of this class.\n///\n/// This implementation uses hybrid composition to render the [WebView] on\n/// Android. It solves multiple issues related to accessibility and interaction\n/// with the [WebView] at the cost of some performance on Android versions below\n/// 10. See https://github.com/flutter/flutter/wiki/Hybrid-Composition for more\n/// information.\nclass SurfaceAndroidWebView extends AndroidWebView {\n  @override\n  Widget build({\n    BuildContext context,\n    CreationParams creationParams,\n    WebViewPlatformCreatedCallback onWebViewPlatformCreated,\n    Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,\n    @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,\n  }) {\n    assert(webViewPlatformCallbacksHandler != null);\n    return PlatformViewLink(\n      viewType: 'plugins.flutter.io/webview',\n      surfaceFactory: (\n        BuildContext context,\n        PlatformViewController controller,\n      ) {\n        return AndroidViewSurface(\n          controller: controller,\n          gestureRecognizers: gestureRecognizers ??\n              const <Factory<OneSequenceGestureRecognizer>>{},\n          hitTestBehavior: PlatformViewHitTestBehavior.opaque,\n        );\n      },\n      onCreatePlatformView: (PlatformViewCreationParams params) {\n        return PlatformViewsService.initSurfaceAndroidView(\n          id: params.id,\n          viewType: 'plugins.flutter.io/webview',\n          // WebView content is not affected by the Android view's layout direction,\n          // we explicitly set it here so that the widget doesn't require an ambient\n          // directionality.\n          layoutDirection: TextDirection.rtl,\n          creationParams: MethodChannelWebViewPlatform.creationParamsToMap(\n            creationParams,\n          ),\n          creationParamsCodec: const StandardMessageCodec(),\n        )\n          ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)\n          ..addOnPlatformViewCreatedListener((int id) {\n            if (onWebViewPlatformCreated == null) {\n              return;\n            }\n            onWebViewPlatformCreated(\n              MethodChannelWebViewPlatform(id, webViewPlatformCallbacksHandler),\n            );\n          })\n          ..create();\n      },\n    );\n  }\n}\n\n/// Decides how to handle a specific navigation request.\n///\n/// The returned [NavigationDecision] determines how the navigation described by\n/// `navigation` should be handled.\n///\n/// See also: [WebView.navigationDelegate].\ntypedef FutureOr<NavigationDecision> NavigationDelegate(\n    NavigationRequest navigation);\n\n/// Signature for when a [WebView] has started loading a page.\ntypedef void PageStartedCallback(String url);\n\n/// Signature for when a [WebView] has finished loading a page.\ntypedef void PageFinishedCallback(String url);\n\n/// Signature for when a [WebView] has failed to load a resource.\ntypedef void WebResourceErrorCallback(WebResourceError error);\n\n/// Specifies possible restrictions on automatic media playback.\n///\n/// This is typically used in [WebView.initialMediaPlaybackPolicy].\n// The method channel implementation is marshalling this enum to the value's index, so the order\n// is important.\nenum AutoMediaPlaybackPolicy {\n  /// Starting any kind of media playback requires a user action.\n  ///\n  /// For example: JavaScript code cannot start playing media unless the code was executed\n  /// as a result of a user action (like a touch event).\n  require_user_action_for_all_media_types,\n\n  /// Starting any kind of media playback is always allowed.\n  ///\n  /// For example: JavaScript code that's triggered when the page is loaded can start playing\n  /// video or audio.\n  always_allow,\n}\n\nfinal RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9_]*\\$');\n\n/// A named channel for receiving messaged from JavaScript code running inside a web view.\nclass JavascriptChannel {\n  /// Constructs a Javascript channel.\n  ///\n  /// The parameters `name` and `onMessageReceived` must not be null.\n  JavascriptChannel({\n    @required this.name,\n    @required this.onMessageReceived,\n  })  : assert(name != null),\n        assert(onMessageReceived != null),\n        assert(_validChannelNames.hasMatch(name));\n\n  /// The channel's name.\n  ///\n  /// Passing this channel object as part of a [WebView.javascriptChannels] adds a channel object to\n  /// the Javascript window object's property named `name`.\n  ///\n  /// The name must start with a letter or underscore(_), followed by any combination of those\n  /// characters plus digits.\n  ///\n  /// Note that any JavaScript existing `window` property with this name will be overriden.\n  ///\n  /// See also [WebView.javascriptChannels] for more details on the channel registration mechanism.\n  final String name;\n\n  /// A callback that's invoked when a message is received through the channel.\n  final JavascriptMessageHandler onMessageReceived;\n}\n\n/// A web view widget for showing html content.\nclass WebView extends StatefulWidget {\n  /// Creates a new web view.\n  ///\n  /// The web view can be controlled using a `WebViewController` that is passed to the\n  /// `onWebViewCreated` callback once the web view is created.\n  ///\n  /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null.\n  const WebView({\n    Key key,\n    this.onWebViewCreated,\n    this.initialUrl,\n    this.javascriptMode = JavascriptMode.disabled,\n    this.javascriptChannels,\n    this.navigationDelegate,\n    this.gestureRecognizers,\n    this.onPageStarted,\n    this.onPageFinished,\n    this.onWebResourceError,\n    this.debuggingEnabled = false,\n    this.gestureNavigationEnabled = false,\n    this.userAgent,\n    this.initialMediaPlaybackPolicy =\n        AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,\n  })  : assert(javascriptMode != null),\n        assert(initialMediaPlaybackPolicy != null),\n        super(key: key);\n\n  static WebViewPlatform _platform;\n\n  /// Sets a custom [WebViewPlatform].\n  ///\n  /// This property can be set to use a custom platform implementation for WebViews.\n  ///\n  /// Setting `platform` doesn't affect [WebView]s that were already created.\n  ///\n  /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.\n  static set platform(WebViewPlatform platform) {\n    _platform = platform;\n  }\n\n  /// The WebView platform that's used by this WebView.\n  ///\n  /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.\n  static WebViewPlatform get platform {\n    if (_platform == null) {\n      switch (defaultTargetPlatform) {\n        case TargetPlatform.android:\n          _platform = AndroidWebView();\n          break;\n        case TargetPlatform.iOS:\n          _platform = CupertinoWebView();\n          break;\n        default:\n          throw UnsupportedError(\n              \"Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one\");\n      }\n    }\n    return _platform;\n  }\n\n  /// If not null invoked once the web view is created.\n  final WebViewCreatedCallback onWebViewCreated;\n\n  /// Which gestures should be consumed by the web view.\n  ///\n  /// It is possible for other gesture recognizers to be competing with the web view on pointer\n  /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle\n  /// vertical drags. The web view will claim gestures that are recognized by any of the\n  /// recognizers on this list.\n  ///\n  /// When this set is empty or null, the web view will only handle pointer events for gestures that\n  /// were not claimed by any other gesture recognizer.\n  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;\n\n  /// The initial URL to load.\n  final String initialUrl;\n\n  /// Whether Javascript execution is enabled.\n  final JavascriptMode javascriptMode;\n\n  /// The set of [JavascriptChannel]s available to JavaScript code running in the web view.\n  ///\n  /// For each [JavascriptChannel] in the set, a channel object is made available for the\n  /// JavaScript code in a window property named [JavascriptChannel.name].\n  /// The JavaScript code can then call `postMessage` on that object to send a message that will be\n  /// passed to [JavascriptChannel.onMessageReceived].\n  ///\n  /// For example for the following JavascriptChannel:\n  ///\n  /// ```dart\n  /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); });\n  /// ```\n  ///\n  /// JavaScript code can call:\n  ///\n  /// ```javascript\n  /// Print.postMessage('Hello');\n  /// ```\n  ///\n  /// To asynchronously invoke the message handler which will print the message to standard output.\n  ///\n  /// Adding a new JavaScript channel only takes affect after the next page is loaded.\n  ///\n  /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple\n  /// channels in the list.\n  ///\n  /// A null value is equivalent to an empty set.\n  final Set<JavascriptChannel> javascriptChannels;\n\n  /// A delegate function that decides how to handle navigation actions.\n  ///\n  /// When a navigation is initiated by the WebView (e.g when a user clicks a link)\n  /// this delegate is called and has to decide how to proceed with the navigation.\n  ///\n  /// See [NavigationDecision] for possible decisions the delegate can take.\n  ///\n  /// When null all navigation actions are allowed.\n  ///\n  /// Caveats on Android:\n  ///\n  ///   * Navigation actions targeted to the main frame can be intercepted,\n  ///     navigation actions targeted to subframes are allowed regardless of the value\n  ///     returned by this delegate.\n  ///   * Setting a navigationDelegate makes the WebView treat all navigations as if they were\n  ///     triggered by a user gesture, this disables some of Chromium's security mechanisms.\n  ///     A navigationDelegate should only be set when loading trusted content.\n  ///   * On Android WebView versions earlier than 67(most devices running at least Android L+ should have\n  ///     a later version):\n  ///     * When a navigationDelegate is set pages with frames are not properly handled by the\n  ///       webview, and frames will be opened in the main frame.\n  ///     * When a navigationDelegate is set HTTP requests do not include the HTTP referer header.\n  final NavigationDelegate navigationDelegate;\n\n  /// Invoked when a page starts loading.\n  final PageStartedCallback onPageStarted;\n\n  /// Invoked when a page has finished loading.\n  ///\n  /// This is invoked only for the main frame.\n  ///\n  /// When [onPageFinished] is invoked on Android, the page being rendered may\n  /// not be updated yet.\n  ///\n  /// When invoked on iOS or Android, any Javascript code that is embedded\n  /// directly in the HTML has been loaded and code injected with\n  /// [WebViewController.evaluateJavascript] can assume this.\n  final PageFinishedCallback onPageFinished;\n\n  /// Invoked when a web resource has failed to load.\n  ///\n  /// This can be called for any resource (iframe, image, etc.), not just for\n  /// the main page.\n  final WebResourceErrorCallback onWebResourceError;\n\n  /// Controls whether WebView debugging is enabled.\n  ///\n  /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/).\n  ///\n  /// WebView debugging is enabled by default in dev builds on iOS.\n  ///\n  /// To debug WebViews on iOS:\n  /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure \"Show Develop Menu in Menubar\" is on.)\n  /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> <your webview page>\n  ///\n  /// By default `debuggingEnabled` is false.\n  final bool debuggingEnabled;\n\n  /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations.\n  ///\n  /// This only works on iOS.\n  ///\n  /// By default `gestureNavigationEnabled` is false.\n  final bool gestureNavigationEnabled;\n\n  /// The value used for the HTTP User-Agent: request header.\n  ///\n  /// When null the platform's webview default is used for the User-Agent header.\n  ///\n  /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent.\n  ///\n  /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded.\n  ///\n  /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom\n  /// user agent.\n  ///\n  /// By default `userAgent` is null.\n  final String userAgent;\n\n  /// Which restrictions apply on automatic media playback.\n  ///\n  /// This initial value is applied to the platform's webview upon creation. Any following\n  /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved).\n  ///\n  /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types].\n  final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy;\n\n  @override\n  State<StatefulWidget> createState() => _WebViewState();\n}\n\nclass _WebViewState extends State<WebView> {\n  final Completer<WebViewController> _controller =\n      Completer<WebViewController>();\n\n  _PlatformCallbacksHandler _platformCallbacksHandler;\n\n  @override\n  Widget build(BuildContext context) {\n    return WebView.platform.build(\n      context: context,\n      onWebViewPlatformCreated: _onWebViewPlatformCreated,\n      webViewPlatformCallbacksHandler: _platformCallbacksHandler,\n      gestureRecognizers: widget.gestureRecognizers,\n      creationParams: _creationParamsfromWidget(widget),\n    );\n  }\n\n  @override\n  void initState() {\n    super.initState();\n    _assertJavascriptChannelNamesAreUnique();\n    _platformCallbacksHandler = _PlatformCallbacksHandler(widget);\n  }\n\n  @override\n  void didUpdateWidget(WebView oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    _assertJavascriptChannelNamesAreUnique();\n    _controller.future.then((WebViewController controller) {\n      _platformCallbacksHandler._widget = widget;\n      controller._updateWidget(widget);\n    });\n  }\n\n  void _onWebViewPlatformCreated(WebViewPlatformController webViewPlatform) {\n    final WebViewController controller =\n        WebViewController._(widget, webViewPlatform, _platformCallbacksHandler);\n    _controller.complete(controller);\n    if (widget.onWebViewCreated != null) {\n      widget.onWebViewCreated(controller);\n    }\n  }\n\n  void _assertJavascriptChannelNamesAreUnique() {\n    if (widget.javascriptChannels == null ||\n        widget.javascriptChannels.isEmpty) {\n      return;\n    }\n    assert(_extractChannelNames(widget.javascriptChannels).length ==\n        widget.javascriptChannels.length);\n  }\n}\n\nCreationParams _creationParamsfromWidget(WebView widget) {\n  return CreationParams(\n    initialUrl: widget.initialUrl,\n    webSettings: _webSettingsFromWidget(widget),\n    javascriptChannelNames: _extractChannelNames(widget.javascriptChannels),\n    userAgent: widget.userAgent,\n    autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,\n  );\n}\n\nWebSettings _webSettingsFromWidget(WebView widget) {\n  return WebSettings(\n    javascriptMode: widget.javascriptMode,\n    hasNavigationDelegate: widget.navigationDelegate != null,\n    debuggingEnabled: widget.debuggingEnabled,\n    gestureNavigationEnabled: widget.gestureNavigationEnabled,\n    userAgent: WebSetting<String>.of(widget.userAgent),\n  );\n}\n\n// This method assumes that no fields in `currentValue` are null.\nWebSettings _clearUnchangedWebSettings(\n    WebSettings currentValue, WebSettings newValue) {\n  assert(currentValue.javascriptMode != null);\n  assert(currentValue.hasNavigationDelegate != null);\n  assert(currentValue.debuggingEnabled != null);\n  assert(currentValue.userAgent.isPresent);\n  assert(newValue.javascriptMode != null);\n  assert(newValue.hasNavigationDelegate != null);\n  assert(newValue.debuggingEnabled != null);\n  assert(newValue.userAgent.isPresent);\n\n  JavascriptMode javascriptMode;\n  bool hasNavigationDelegate;\n  bool debuggingEnabled;\n  WebSetting<String> userAgent = WebSetting<String>.absent();\n  if (currentValue.javascriptMode != newValue.javascriptMode) {\n    javascriptMode = newValue.javascriptMode;\n  }\n  if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) {\n    hasNavigationDelegate = newValue.hasNavigationDelegate;\n  }\n  if (currentValue.debuggingEnabled != newValue.debuggingEnabled) {\n    debuggingEnabled = newValue.debuggingEnabled;\n  }\n  if (currentValue.userAgent != newValue.userAgent) {\n    userAgent = newValue.userAgent;\n  }\n\n  return WebSettings(\n    javascriptMode: javascriptMode,\n    hasNavigationDelegate: hasNavigationDelegate,\n    debuggingEnabled: debuggingEnabled,\n    userAgent: userAgent,\n  );\n}\n\nSet<String> _extractChannelNames(Set<JavascriptChannel> channels) {\n  final Set<String> channelNames = channels == null\n      ? <String>{}\n      : channels.map((JavascriptChannel channel) => channel.name).toSet();\n  return channelNames;\n}\n\nclass _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {\n  _PlatformCallbacksHandler(this._widget) {\n    _updateJavascriptChannelsFromSet(_widget.javascriptChannels);\n  }\n\n  WebView _widget;\n\n  // Maps a channel name to a channel.\n  final Map<String, JavascriptChannel> _javascriptChannels =\n      <String, JavascriptChannel>{};\n\n  @override\n  void onJavaScriptChannelMessage(String channel, String message) {\n    _javascriptChannels[channel].onMessageReceived(JavascriptMessage(message));\n  }\n\n  @override\n  FutureOr<bool> onNavigationRequest({String url, bool isForMainFrame}) async {\n    final NavigationRequest request =\n        NavigationRequest._(url: url, isForMainFrame: isForMainFrame);\n    final bool allowNavigation = _widget.navigationDelegate == null ||\n        await _widget.navigationDelegate(request) ==\n            NavigationDecision.navigate;\n    return allowNavigation;\n  }\n\n  @override\n  void onPageStarted(String url) {\n    if (_widget.onPageStarted != null) {\n      _widget.onPageStarted(url);\n    }\n  }\n\n  @override\n  void onPageFinished(String url) {\n    if (_widget.onPageFinished != null) {\n      _widget.onPageFinished(url);\n    }\n  }\n\n  @override\n  void onWebResourceError(WebResourceError error) {\n    if (_widget.onWebResourceError != null) {\n      _widget.onWebResourceError(error);\n    }\n  }\n\n  void _updateJavascriptChannelsFromSet(Set<JavascriptChannel> channels) {\n    _javascriptChannels.clear();\n    if (channels == null) {\n      return;\n    }\n    for (JavascriptChannel channel in channels) {\n      _javascriptChannels[channel.name] = channel;\n    }\n  }\n}\n\n/// Controls a [WebView].\n///\n/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated]\n/// callback for a [WebView] widget.\nclass WebViewController {\n  WebViewController._(\n    this._widget,\n    this._webViewPlatformController,\n    this._platformCallbacksHandler,\n  ) : assert(_webViewPlatformController != null) {\n    _settings = _webSettingsFromWidget(_widget);\n  }\n\n  final WebViewPlatformController _webViewPlatformController;\n\n  final _PlatformCallbacksHandler _platformCallbacksHandler;\n\n  WebSettings _settings;\n\n  WebView _widget;\n\n  /// Loads the specified URL.\n  ///\n  /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will\n  /// be added as key value pairs of HTTP headers for the request.\n  ///\n  /// `url` must not be null.\n  ///\n  /// Throws an ArgumentError if `url` is not a valid URL string.\n  Future<void> loadUrl(\n    String url, {\n    Map<String, String> headers,\n  }) async {\n    assert(url != null);\n    _validateUrlString(url);\n    return _webViewPlatformController.loadUrl(url, headers);\n  }\n\n  /// Accessor to the current URL that the WebView is displaying.\n  ///\n  /// If [WebView.initialUrl] was never specified, returns `null`.\n  /// Note that this operation is asynchronous, and it is possible that the\n  /// current URL changes again by the time this function returns (in other\n  /// words, by the time this future completes, the WebView may be displaying a\n  /// different URL).\n  Future<String> currentUrl() {\n    return _webViewPlatformController.currentUrl();\n  }\n\n  /// Checks whether there's a back history item.\n  ///\n  /// Note that this operation is asynchronous, and it is possible that the \"canGoBack\" state has\n  /// changed by the time the future completed.\n  Future<bool> canGoBack() {\n    return _webViewPlatformController.canGoBack();\n  }\n\n  /// Checks whether there's a forward history item.\n  ///\n  /// Note that this operation is asynchronous, and it is possible that the \"canGoForward\" state has\n  /// changed by the time the future completed.\n  Future<bool> canGoForward() {\n    return _webViewPlatformController.canGoForward();\n  }\n\n  /// Goes back in the history of this WebView.\n  ///\n  /// If there is no back history item this is a no-op.\n  Future<void> goBack() {\n    return _webViewPlatformController.goBack();\n  }\n\n  /// Goes forward in the history of this WebView.\n  ///\n  /// If there is no forward history item this is a no-op.\n  Future<void> goForward() {\n    return _webViewPlatformController.goForward();\n  }\n\n  /// Reloads the current URL.\n  Future<void> reload() {\n    return _webViewPlatformController.reload();\n  }\n\n  /// Clears all caches used by the [WebView].\n  ///\n  /// The following caches are cleared:\n  ///\t1. Browser HTTP Cache.\n  ///\t2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches.\n  ///    These are not yet supported in iOS WkWebView. Service workers tend to use this cache.\n  ///\t3. Application cache.\n  ///\t4. Local Storage.\n  ///\n  /// Note: Calling this method also triggers a reload.\n  Future<void> clearCache() async {\n    await _webViewPlatformController.clearCache();\n    return reload();\n  }\n\n  Future<void> _updateWidget(WebView widget) async {\n    _widget = widget;\n    await _updateSettings(_webSettingsFromWidget(widget));\n    await _updateJavascriptChannels(widget.javascriptChannels);\n  }\n\n  Future<void> _updateSettings(WebSettings newSettings) {\n    final WebSettings update =\n        _clearUnchangedWebSettings(_settings, newSettings);\n    _settings = newSettings;\n    return _webViewPlatformController.updateSettings(update);\n  }\n\n  Future<void> _updateJavascriptChannels(\n      Set<JavascriptChannel> newChannels) async {\n    final Set<String> currentChannels =\n        _platformCallbacksHandler._javascriptChannels.keys.toSet();\n    final Set<String> newChannelNames = _extractChannelNames(newChannels);\n    final Set<String> channelsToAdd =\n        newChannelNames.difference(currentChannels);\n    final Set<String> channelsToRemove =\n        currentChannels.difference(newChannelNames);\n    if (channelsToRemove.isNotEmpty) {\n      await _webViewPlatformController\n          .removeJavascriptChannels(channelsToRemove);\n    }\n    if (channelsToAdd.isNotEmpty) {\n      await _webViewPlatformController.addJavascriptChannels(channelsToAdd);\n    }\n    _platformCallbacksHandler._updateJavascriptChannelsFromSet(newChannels);\n  }\n\n  /// Evaluates a JavaScript expression in the context of the current page.\n  ///\n  /// On Android returns the evaluation result as a JSON formatted string.\n  ///\n  /// On iOS depending on the value type the return value would be one of:\n  ///\n  ///  - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100').\n  ///  - For JavaScript arrays of supported types: a string formatted NSArray(e.g '(1,2,3), note that the string for NSArray is formatted and might contain newlines and extra spaces.').\n  ///  - Other non-primitive types are not supported on iOS and will complete the Future with an error.\n  ///\n  /// The Future completes with an error if a JavaScript error occurred, or on iOS, if the type of the\n  /// evaluated expression is not supported as described above.\n  ///\n  /// When evaluating Javascript in a [WebView], it is best practice to wait for\n  /// the [WebView.onPageFinished] callback. This guarantees all the Javascript\n  /// embedded in the main frame HTML has been loaded.\n  Future<String> evaluateJavascript(String javascriptString) {\n    if (_settings.javascriptMode == JavascriptMode.disabled) {\n      return Future<String>.error(FlutterError(\n          'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.'));\n    }\n    if (javascriptString == null) {\n      return Future<String>.error(\n          ArgumentError('The argument javascriptString must not be null.'));\n    }\n    // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.\n    // https://github.com/flutter/flutter/issues/26431\n    // ignore: strong_mode_implicit_dynamic_method\n    return _webViewPlatformController.evaluateJavascript(javascriptString);\n  }\n\n  /// Returns the title of the currently loaded page.\n  Future<String> getTitle() {\n    return _webViewPlatformController.getTitle();\n  }\n\n  /// Sets the WebView's content scroll position.\n  ///\n  /// The parameters `x` and `y` specify the scroll position in WebView pixels.\n  Future<void> scrollTo(int x, int y) {\n    return _webViewPlatformController.scrollTo(x, y);\n  }\n\n  /// Move the scrolled position of this view.\n  ///\n  /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively.\n  Future<void> scrollBy(int x, int y) {\n    return _webViewPlatformController.scrollBy(x, y);\n  }\n\n  /// Return the horizontal scroll position, in WebView pixels, of this view.\n  ///\n  /// Scroll position is measured from left.\n  Future<int> getScrollX() {\n    return _webViewPlatformController.getScrollX();\n  }\n\n  /// Return the vertical scroll position, in WebView pixels, of this view.\n  ///\n  /// Scroll position is measured from top.\n  Future<int> getScrollY() {\n    return _webViewPlatformController.getScrollY();\n  }\n}\n\n/// Manages cookies pertaining to all [WebView]s.\nclass CookieManager {\n  /// Creates a [CookieManager] -- returns the instance if it's already been called.\n  factory CookieManager() {\n    return _instance ??= CookieManager._();\n  }\n\n  CookieManager._();\n\n  static CookieManager _instance;\n\n  /// Clears all cookies for all [WebView] instances.\n  ///\n  /// This is a no op on iOS version smaller than 9.\n  ///\n  /// Returns true if cookies were present before clearing, else false.\n  Future<bool> clearCookies() => WebView.platform.clearCookies();\n}\n\n// Throws an ArgumentError if `url` is not a valid URL string.\nvoid _validateUrlString(String url) {\n  try {\n    final Uri uri = Uri.parse(url);\n    if (uri.scheme.isEmpty) {\n      throw ArgumentError('Missing scheme in URL string: \"$url\"');\n    }\n  } on FormatException catch (e) {\n    throw ArgumentError(e);\n  }\n}\n"
  },
  {
    "path": "pubspec.yaml",
    "content": "name: webview_flutter\ndescription: A Flutter plugin that provides a WebView widget on Android and iOS.\nversion: 1.0.2\nhomepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter\n\nenvironment:\n  sdk: \">=2.7.0 <3.0.0\"\n  flutter: \">=1.22.0 <2.0.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  flutter_driver:\n    sdk: flutter\n  pedantic: ^1.8.0\n\nflutter:\n  plugin:\n    platforms:\n      android:\n        package: io.flutter.plugins.webviewflutter\n        pluginClass: WebViewFlutterPlugin\n      ios:\n        pluginClass: FLTWebViewFlutterPlugin\n"
  }
]