├── .gitignore ├── LICENSE ├── README.md └── webview_popupauth ├── lib └── popupauth.dart ├── pubspec.yaml └── windows ├── CMakeLists.txt ├── include └── webviewpopupauth │ └── webview_popup_auth.h └── webviewpopupauth_plugin.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | build/ 7 | # If you're building an application, you may want to check-in your pubspec.lock 8 | pubspec.lock 9 | 10 | # Directory created by dartdoc 11 | # If you don't generate documentation locally you can remove this line. 12 | doc/api/ 13 | 14 | # Avoid committing generated Javascript files: 15 | *.dart.js 16 | *.info.json # Produced by the --dump-info flag. 17 | *.js # When generated by dart2js. Don't specify *.js if your 18 | # project includes source files written in JavaScript. 19 | *.js_ 20 | *.js.deps 21 | *.js.map 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, James Clarke 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_win_webview 2 | Prototype of a Webview2 based webview for flutter 3 | -------------------------------------------------------------------------------- /webview_popupauth/lib/popupauth.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | /// The result of a popup web authentication operation. 4 | class PopupAuthResult { 5 | /// Creates a new result object with the given state and paths. 6 | const PopupAuthResult({this.token, this.canceled}); 7 | 8 | /// Whether or not the authentication operation was canceled by the user. 9 | final bool canceled; 10 | 11 | /// A token returned from authentication request. If [canceled] is true, it 12 | /// should always be empty. 13 | final String token; 14 | } 15 | 16 | /// A singleton object that controls web authentication interactions with windows. 17 | class PopupAuthChannelController { 18 | PopupAuthChannelController._(); 19 | 20 | /// The platform channel used to manage popup auth 21 | final _channel = new MethodChannel("flutter/webviewpopupauth"); 22 | 23 | /// A reference to the singleton instance of the class. 24 | static final PopupAuthChannelController instance = 25 | new PopupAuthChannelController._(); 26 | 27 | /// Shows a Webauth popup, returning a 28 | /// [PopupAuthResult] when complete. 29 | Future show( 30 | String url, 31 | ) async { 32 | final methodName = "WebviewPopupauth.Show.Open"; 33 | final token = await _channel.invokeMethod(methodName, url); 34 | return PopupAuthResult(token: token ?? [], canceled: token == null); 35 | } 36 | } 37 | 38 | /// Shows a modal web authentication dialog. 39 | /// 40 | /// A number of configuration options are available: 41 | /// - [url] URL to point browser to. 42 | Future showOpenPanel({String url}) async { 43 | return PopupAuthChannelController.instance.show(url); 44 | } 45 | -------------------------------------------------------------------------------- /webview_popupauth/pubspec.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: webviewpopupauth 3 | description: Displays popup window containing a webview for use in authentication scenarios. 4 | version: 0.0.1 5 | 6 | publish_to: none 7 | 8 | flutter: 9 | plugin: 10 | platforms: 11 | windows: 12 | pluginClass: WebviewPopupAuth 13 | 14 | environment: 15 | sdk: ">=2.1.0 <3.0.0" 16 | 17 | dependencies: 18 | flutter: 19 | sdk: flutter -------------------------------------------------------------------------------- /webview_popupauth/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | set(PROJECT_NAME "webviewpopupauth") 3 | project(${PROJECT_NAME} LANGUAGES CXX) 4 | 5 | set(PLUGIN_NAME "${PROJECT_NAME}_plugin") 6 | 7 | find_program(NUGET_EXE NAMES nuget) 8 | if(NOT NUGET_EXE) 9 | message("NUGET.EXE not found.") 10 | message(FATAL_ERROR "Please install this executable, and run CMake again.") 11 | endif() 12 | 13 | exec_program(${NUGET_EXE} 14 | ARGS install "Microsoft.Web.WebView2" -Version 0.9.579 -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages) 15 | exec_program(${NUGET_EXE} 16 | ARGS install "Microsoft.Windows.ImplementationLibrary" -Version 1.0.200519.2 -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages) 17 | 18 | add_library(${PLUGIN_NAME} SHARED 19 | "${PLUGIN_NAME}.cpp" 20 | ) 21 | apply_standard_settings(${PLUGIN_NAME}) 22 | set_target_properties(${PLUGIN_NAME} PROPERTIES 23 | CXX_VISIBILITY_PRESET hidden) 24 | 25 | target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Web.WebView2/build/native/Microsoft.Web.WebView2.targets) 26 | target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.ImplementationLibrary/build/native/Microsoft.Windows.ImplementationLibrary.targets) 27 | 28 | target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) 29 | target_compile_definitions(${PLUGIN_NAME} PRIVATE _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING) 30 | target_include_directories(${PLUGIN_NAME} INTERFACE 31 | "${CMAKE_CURRENT_SOURCE_DIR}/include") 32 | target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) 33 | 34 | # List of absolute paths to libraries that should be bundled with the plugin 35 | set(webviewpopupauth_bundled_libraries 36 | "" 37 | PARENT_SCOPE 38 | ) -------------------------------------------------------------------------------- /webview_popupauth/windows/include/webviewpopupauth/webview_popup_auth.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGINS_WEBVIEW_POPUPAUTH_WINDOWS_WEBVIEW_POPUPAUTH_H_ 2 | #define PLUGINS_WEBVIEW_POPUPAUTH_WINDOWS_WEBVIEW_POPUPAUTH_H_ 3 | 4 | // A plugin to allow resizing the window. 5 | 6 | #include 7 | 8 | #ifdef FLUTTER_PLUGIN_IMPL 9 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) 10 | #else 11 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) 12 | #endif 13 | 14 | #if defined(__cplusplus) 15 | extern "C" { 16 | #endif 17 | 18 | FLUTTER_PLUGIN_EXPORT void WebviewPopupAuthRegisterWithRegistrar( 19 | FlutterDesktopPluginRegistrarRef registrar); 20 | 21 | #if defined(__cplusplus) 22 | } // extern "C" 23 | #endif 24 | 25 | #endif // PLUGINS_WEBVIEW_POPUPAUTH_WINDOWS_WEBVIEW_POPUPAUTH_H_ -------------------------------------------------------------------------------- /webview_popupauth/windows/webviewpopupauth_plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "include/webviewpopupauth/webview_popup_auth.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "WebView2.h" 15 | 16 | using namespace Microsoft::WRL; 17 | 18 | namespace { 19 | 20 | using flutter::EncodableList; 21 | using flutter::EncodableMap; 22 | using flutter::EncodableValue; 23 | 24 | // See channel_controller.dart for documentation. 25 | const char kChannelName[] = "flutter_webview_plugin"; 26 | const char kShowOpenAuthWindowMethod[] = "WebviewPopupauth.Show.Open"; 27 | 28 | // Pointer to WebViewController 29 | static wil::com_ptr webviewController; 30 | 31 | // Pointer to WebView window 32 | static wil::com_ptr webviewWindow; 33 | 34 | static EventRegistrationToken sourceChangedToken{}; 35 | 36 | // Returns the top-level window that owns |view|. 37 | HWND GetRootWindow(flutter::FlutterView *view) { 38 | return GetAncestor(view->GetNativeWindow(), GA_ROOT); 39 | } 40 | 41 | class WebviewPopupauthPlugin : public flutter::Plugin { 42 | public: 43 | static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); 44 | 45 | // Creates a plugin that communicates on the given channel. 46 | WebviewPopupauthPlugin(flutter::PluginRegistrarWindows *registrar); 47 | 48 | virtual ~WebviewPopupauthPlugin(); 49 | 50 | private: 51 | // Called when a method is called on the plugin channel; 52 | void HandleMethodCall( 53 | const flutter::MethodCall &method_call, 54 | std::unique_ptr> result); 55 | void WebviewInit(std::string url, bool hidden, bool clearCookies, std::unique_ptr> result); 56 | void OnUrlChanged(const std::string& newUri); 57 | 58 | // The registrar for this plugin, for accessing the window. 59 | flutter::PluginRegistrarWindows *registrar_; 60 | 61 | std::unique_ptr> channel; 62 | }; 63 | 64 | // static 65 | void WebviewPopupauthPlugin::RegisterWithRegistrar( 66 | flutter::PluginRegistrarWindows *registrar) { 67 | auto plugin = std::make_unique(registrar); 68 | 69 | plugin->channel = 70 | std::make_unique>( 71 | registrar->messenger(), kChannelName, 72 | &flutter::StandardMethodCodec::GetInstance()); 73 | 74 | plugin->channel->SetMethodCallHandler( 75 | [plugin_pointer = plugin.get()](const auto &call, auto result) { 76 | plugin_pointer->HandleMethodCall(call, std::move(result)); 77 | }); 78 | 79 | registrar->AddPlugin(std::move(plugin)); 80 | } 81 | 82 | WebviewPopupauthPlugin::WebviewPopupauthPlugin(flutter::PluginRegistrarWindows *registrar) 83 | : registrar_(registrar) {} 84 | 85 | WebviewPopupauthPlugin::~WebviewPopupauthPlugin() {} 86 | 87 | void WebviewPopupauthPlugin::HandleMethodCall( 88 | const flutter::MethodCall &method_call, 89 | std::unique_ptr> result) { 90 | 91 | OutputDebugStringA(method_call.method_name().c_str()); 92 | if (method_call.method_name().compare("launch") == 0) { 93 | if (!method_call.arguments() || !method_call.arguments()->IsMap() || method_call.arguments()->MapValue().empty()) { 94 | result->Error("Bad Arguments", "Argument map missing or malformed"); 95 | return; 96 | } 97 | auto map = method_call.arguments()->MapValue(); 98 | auto url = map.at(flutter::EncodableValue("url")).StringValue(); 99 | auto hidden = map.at(flutter::EncodableValue("hidden")).BoolValue(); 100 | auto clearCookies = map.at(flutter::EncodableValue("clearCookies")).BoolValue(); 101 | 102 | OutputDebugStringA(url.c_str()); 103 | 104 | WebviewInit(url, hidden, clearCookies, std::move(result)); 105 | } 106 | else if(method_call.method_name().compare("close") == 0) { 107 | webviewController->Close(); 108 | webviewController = nullptr; 109 | webviewWindow = nullptr; 110 | result->Success(nullptr); 111 | } 112 | else { 113 | result->NotImplemented(); 114 | } 115 | } 116 | 117 | } // namespace 118 | 119 | void WebviewPopupauthPlugin::OnUrlChanged(const std::string& newUri) { 120 | 121 | EncodableMap encodableMap = { { EncodableValue("url"), EncodableValue(newUri) } }; 122 | 123 | auto args = std::make_unique(encodableMap); 124 | 125 | channel->InvokeMethod("onUrlChanged", std::move(args)); 126 | } 127 | 128 | void WebviewPopupauthPlugin::WebviewInit(std::string url, bool hidden, bool clearCookies, std::unique_ptr> result) { 129 | auto hwnd = registrar_->GetView()->GetNativeWindow(); 130 | 131 | CreateCoreWebView2EnvironmentWithOptions(nullptr, nullptr, nullptr, 132 | Callback( 133 | [this, hwnd, url, hidden, clearCookies, result = std::move(result)](HRESULT r, ICoreWebView2Environment* env) mutable -> HRESULT { 134 | 135 | // Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window hWnd 136 | env->CreateCoreWebView2Controller(hwnd, Callback( 137 | [this, hwnd, url, hidden, clearCookies, result = std::move(result)](HRESULT r, ICoreWebView2Controller* controller) mutable -> HRESULT { 138 | if (controller != nullptr) { 139 | webviewController = controller; 140 | webviewController->get_CoreWebView2(&webviewWindow); 141 | } 142 | 143 | // Add a few settings for the webview 144 | // The demo step is redundant since the values are the default settings 145 | ICoreWebView2Settings* Settings; 146 | webviewWindow->get_Settings(&Settings); 147 | Settings->put_IsScriptEnabled(TRUE); 148 | Settings->put_AreDefaultScriptDialogsEnabled(TRUE); 149 | Settings->put_IsWebMessageEnabled(TRUE); 150 | 151 | webviewWindow->add_SourceChanged( 152 | Callback( 153 | [this](ICoreWebView2* sender, 154 | IUnknown* args) -> HRESULT 155 | { 156 | LPWSTR newUri{ nullptr }; 157 | webviewWindow->get_Source(&newUri); 158 | 159 | std::string newUriString = CW2A(newUri); 160 | 161 | OnUrlChanged(newUriString); 162 | 163 | return S_OK; 164 | }).Get(), &sourceChangedToken); 165 | 166 | // Resize WebView to fit the bounds of the parent window 167 | RECT bounds; 168 | GetClientRect(hwnd, &bounds); 169 | bounds.top += 50; 170 | bounds.left += 50; 171 | bounds.right -= 50; 172 | bounds.bottom -= 50; 173 | webviewController->put_Bounds(bounds); 174 | 175 | webviewController->put_IsVisible(!hidden); 176 | 177 | if (clearCookies) { 178 | webviewWindow->CallDevToolsProtocolMethod(L"Network.clearBrowserCookies", L"{}", nullptr); 179 | } 180 | 181 | std::wstring wstr(url.begin(), url.end()); 182 | webviewWindow->Navigate(wstr.c_str()); 183 | 184 | result->Success(nullptr); 185 | 186 | return S_OK; 187 | }).Get()); 188 | return S_OK; 189 | }).Get()); 190 | 191 | } 192 | 193 | void WebviewPopupAuthRegisterWithRegistrar( 194 | FlutterDesktopPluginRegistrarRef registrar) { 195 | WebviewPopupauthPlugin::RegisterWithRegistrar( 196 | flutter::PluginRegistrarManager::GetInstance() 197 | ->GetRegistrar(registrar)); 198 | } --------------------------------------------------------------------------------