├── .clang-format ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .idea ├── libraries │ └── Dart_SDK.xml ├── modules.xml ├── runConfigurations │ └── example_lib_main_dart.xml └── workspace.xml ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml ├── test │ └── widget_test.dart └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ └── CMakeLists.txt │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── lib ├── src │ ├── cursor.dart │ ├── enums.dart │ └── webview.dart └── webview_windows.dart ├── pubspec.yaml ├── test └── webview_windows_test.dart ├── webview_windows.iml └── windows ├── .gitignore ├── CMakeLists.txt ├── graphics_context.cc ├── graphics_context.h ├── include └── webview_windows │ └── webview_windows_plugin.h ├── texture_bridge.cc ├── texture_bridge.h ├── texture_bridge_gpu.cc ├── texture_bridge_gpu.h ├── util ├── composition.desktop.interop.h ├── d3dutil.h ├── direct3d11.interop.cc ├── direct3d11.interop.h ├── rohelper.cc ├── rohelper.h ├── string_converter.cc └── string_converter.h ├── webview.cc ├── webview.h ├── webview_bridge.cc ├── webview_bridge.h ├── webview_host.cc ├── webview_host.h ├── webview_platform.cc ├── webview_platform.h └── webview_windows_plugin.cc /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | DerivePointerAlignment: false 3 | PointerAlignment: Left -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.buymeacoffee.com/jnschulze'] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | defaults: 5 | run: 6 | working-directory: example 7 | 8 | jobs: 9 | build_windows: 10 | name: Build on Windows 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | submodules: true 16 | - uses: subosito/flutter-action@v1 17 | with: 18 | channel: "beta" 19 | - run: flutter config --enable-windows-desktop 20 | - run: flutter pub get 21 | - run: flutter build windows 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | pubspec.lock 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnschulze/flutter-webview-windows/5e156e4d616ab901ef5b386f01b4a57f03449634/.gitmodules -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 3955ef0d0204a9db4c35019e601c88cf7cc63582 8 | channel: master 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.4.0 2 | 3 | * Enable MSVC coroutine support ([#278](https://github.com/jnschulze/flutter-webview-windows/pull/278)) 4 | * Enable scrolling with trackpad ([#274](https://github.com/jnschulze/flutter-webview-windows/pull/274)) 5 | 6 | ## 0.3.0 7 | 8 | * Add full-screen support ([#189](https://github.com/jnschulze/flutter-webview-windows/pull/189)) 9 | * Make `loadingState` a broadcast stream ([#193](https://github.com/jnschulze/flutter-webview-windows/pull/193)) 10 | * Add `getWebViewVersion()` method ([#197](https://github.com/jnschulze/flutter-webview-windows/pull/197)) 11 | * Fix string casting of paths that may contain Unicode characters ([#199](https://github.com/jnschulze/flutter-webview-windows/pull/199)) 12 | * Add high-DPI screen support ([#203](https://github.com/jnschulze/flutter-webview-windows/pull/203)) 13 | * Add `setZoomFactor` ([#214](https://github.com/jnschulze/flutter-webview-windows/pull/214)) 14 | * Fix example ([#215](https://github.com/jnschulze/flutter-webview-windows/pull/215)) 15 | * Fix Visual Studio 17.6 builds ([#252](https://github.com/jnschulze/flutter-webview-windows/pull/252)) 16 | 17 | ## 0.2.2 18 | 19 | * Remove `libfmt` dependency in favor of C++20 `std::format` 20 | * Enable D3D texture bridge by default 21 | * Make `executeScript` return the script's result 22 | 23 | ## 0.2.1 24 | 25 | * Add `WebviewController.addScriptToExecuteOnDocumentCreated` and `WebviewController.removeScriptToExecuteOnDocumentCreated` 26 | * Add `WebviewController.onLoadError` stream 27 | * Change `WebviewController.webMessage` stream type from `Map` to `dynamic` 28 | * Add virtual hostname mapping support 29 | * Add multi-touch support 30 | 31 | ## 0.2.0 32 | 33 | * Fix Flutter 3.0 null safety warning in example 34 | * Bump WebView2 SDK version to `1.0.1210.3` 35 | * Add an option for limiting the FPS 36 | * Change data directory base path from `RoamingAppData` to `LocalAppData` 37 | 38 | ## 0.1.9 39 | 40 | * Fix Flutter 3.0 compatibility 41 | 42 | ## 0.1.8 43 | 44 | * Prefix CMake build target names to prevent collisions with other plugins 45 | 46 | ## 0.1.7 47 | 48 | * Add method for opening DevTools 49 | * Update `TextureBridgeGpu` 50 | * Update `libfmt` dependency 51 | 52 | ## 0.1.7-dev.2 53 | 54 | * Ensure Flutter apps referencing `webview_windows` still work on Windows 8. 55 | 56 | ## 0.1.7-dev.1 57 | 58 | * Remove windowsapp.lib dependency 59 | 60 | ## 0.1.6 61 | 62 | * Improve WebView creation error handling 63 | 64 | ## 0.1.5 65 | 66 | * Fix a potential crash during WebView creation 67 | 68 | ## 0.1.4 69 | 70 | * Improve error handling for Webview environment creation 71 | 72 | ## 0.1.3 73 | 74 | * Stability fixes 75 | 76 | ## 0.1.2 77 | 78 | * Unregister method channel handlers upon WebView destruction 79 | 80 | ## 0.1.1 81 | 82 | * Fix unicode string conversion in ExecuteScript and LoadStringContent 83 | * Load CoreMessaging.dll on demand 84 | 85 | ## 0.1.0 86 | 87 | * Fix a string conversion issue 88 | * Add an option for controlling popup window behavior 89 | * Update Microsoft.Web.WebView2 and Microsoft.Windows.ImplementationLibrary 90 | 91 | ## 0.0.9 92 | 93 | * Fix resizing issues 94 | * Add preliminary GpuSurfaceTexture support 95 | 96 | ## 0.0.8 97 | 98 | * Don't rely on AVX2 support 99 | * Add history controls 100 | * Add suspend/resume support 101 | * Add support for disabling cache, clearing cookies etc. 102 | 103 | ## 0.0.7 104 | 105 | * Add support for handling permission requests 106 | * Allow setting the background color 107 | * Automatically download nuget 108 | 109 | ## 0.0.6 110 | 111 | * Fix mousewheel event handling 112 | * Make text selection work 113 | * Add method for setting the user agent 114 | * Add support for JavaScript injection 115 | * Add support for JSON message passing between Dart and JS 116 | * Fix WebView disposal 117 | 118 | ## 0.0.5 119 | 120 | * Fix input field focus issue 121 | 122 | ## 0.0.4 123 | 124 | * Minor cleanup 125 | 126 | ## 0.0.3 127 | 128 | * Add support for additional cursor types 129 | 130 | ## 0.0.2 131 | 132 | * Add support for loading string content 133 | 134 | ## 0.0.1 135 | 136 | * Initial release 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021 Niklas Schulze 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webview_windows 2 | 3 | [![CI](https://github.com/jnschulze/flutter-webview-windows/actions/workflows/ci.yml/badge.svg)](https://github.com/jnschulze/flutter-webview-windows/actions/workflows/ci.yml) 4 | [![Pub](https://img.shields.io/pub/v/webview_windows.svg)](https://pub.dartlang.org/packages/webview_windows) 5 | 6 | A [Flutter](https://flutter.dev/) WebView plugin for Windows built on [Microsoft Edge WebView2](https://docs.microsoft.com/en-us/microsoft-edge/webview2/). 7 | 8 | 9 | ### Target platform requirements 10 | - [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) 11 | Before initializing the webview, call `getWebViewVersion()` to check whether the required **WebView2 Runtime** is installed or not on the current system. If `getWebViewVersion()` returns null, guide your user to install **WebView2 Runtime** from this [page](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). 12 | - Windows 10 1809+ 13 | 14 | ### Development platform requirements 15 | - Visual Studio 2019 or higher 16 | - Windows 11 SDK (10.0.22000.194 or higher) 17 | - (recommended) nuget.exe in your $PATH *(The makefile attempts to download nuget if it's not installed, however, this fallback might not work in China)* 18 | 19 | ## Demo 20 | ![image](https://user-images.githubusercontent.com/720469/116823636-d8b9fe00-ab85-11eb-9f91-b7bc819615ed.png) 21 | 22 | https://user-images.githubusercontent.com/720469/116716747-66f08180-a9d8-11eb-86ca-63ad5c24f07b.mp4 23 | 24 | 25 | 26 | ## Limitations 27 | This plugin provides seamless composition of web-based contents with other Flutter widgets by rendering off-screen. 28 | 29 | Unfortunately, [Microsoft Edge WebView2](https://docs.microsoft.com/en-us/microsoft-edge/webview2/) doesn't currently have an explicit API for offscreen rendering. 30 | In order to still be able to obtain a pixel buffer upon rendering a new frame, this plugin currently relies on the `Windows.Graphics.Capture` API provided by Windows 10. 31 | The downside is that older Windows versions aren't currently supported. 32 | 33 | Older Windows versions might still be targeted by using `BitBlt` for the time being. 34 | 35 | See: 36 | - https://github.com/MicrosoftEdge/WebView2Feedback/issues/20 37 | - https://github.com/MicrosoftEdge/WebView2Feedback/issues/526 38 | - https://github.com/MicrosoftEdge/WebView2Feedback/issues/547 39 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file 2 | # for details. All rights reserved. Use of this source code is governed by a 3 | # BSD-style license that can be found in the LICENSE file. 4 | # 5 | # Google internally enforced rules. See README.md for more information, 6 | # including a list of lints that are intentionally _not_ enforced. 7 | 8 | linter: 9 | rules: 10 | - always_declare_return_types 11 | - always_require_non_null_named_parameters 12 | - annotate_overrides 13 | - avoid_init_to_null 14 | - avoid_null_checks_in_equality_operators 15 | - avoid_relative_lib_imports 16 | - avoid_return_types_on_setters 17 | - avoid_shadowing_type_parameters 18 | - avoid_single_cascade_in_expression_statements 19 | - avoid_types_as_parameter_names 20 | - await_only_futures 21 | - camel_case_extensions 22 | - curly_braces_in_flow_control_structures 23 | - empty_catches 24 | - empty_constructor_bodies 25 | - library_names 26 | - library_prefixes 27 | - no_duplicate_case_values 28 | - null_closures 29 | - omit_local_variable_types 30 | - prefer_adjacent_string_concatenation 31 | - prefer_collection_literals 32 | - prefer_conditional_assignment 33 | - prefer_contains 34 | - prefer_equal_for_default_values 35 | - prefer_final_fields 36 | - prefer_for_elements_to_map_fromIterable 37 | - prefer_generic_function_type_aliases 38 | - prefer_if_null_operators 39 | - prefer_inlined_adds 40 | - prefer_is_empty 41 | - prefer_is_not_empty 42 | - prefer_iterable_whereType 43 | - prefer_single_quotes 44 | - prefer_spread_collections 45 | - recursive_getters 46 | - slash_for_doc_comments 47 | - sort_child_properties_last 48 | - type_init_formals 49 | - unawaited_futures 50 | - unnecessary_brace_in_string_interps 51 | - unnecessary_const 52 | - unnecessary_getters_setters 53 | - unnecessary_new 54 | - unnecessary_null_in_if_null_operators 55 | - unnecessary_this 56 | - unrelated_type_equality_checks 57 | - unsafe_html 58 | - use_full_hex_values_for_flutter_colors 59 | - use_function_type_syntax_for_parameters 60 | - use_rethrow_when_possible 61 | - valid_regexps -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | generated_plugin_registrant.* 49 | GeneratedPluginRegistrant.* 50 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 3955ef0d0204a9db4c35019e601c88cf7cc63582 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # webview_windows_example 2 | 3 | Demonstrates how to use the webview_windows plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'dart:async'; 4 | 5 | import 'package:webview_windows/webview_windows.dart'; 6 | import 'package:window_manager/window_manager.dart'; 7 | 8 | final navigatorKey = GlobalKey(); 9 | 10 | void main() async { 11 | // For full-screen example 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | await windowManager.ensureInitialized(); 14 | 15 | runApp(MyApp()); 16 | } 17 | 18 | class MyApp extends StatelessWidget { 19 | @override 20 | Widget build(BuildContext context) { 21 | return MaterialApp(navigatorKey: navigatorKey, home: ExampleBrowser()); 22 | } 23 | } 24 | 25 | class ExampleBrowser extends StatefulWidget { 26 | @override 27 | State createState() => _ExampleBrowser(); 28 | } 29 | 30 | class _ExampleBrowser extends State { 31 | final _controller = WebviewController(); 32 | final _textController = TextEditingController(); 33 | final List _subscriptions = []; 34 | bool _isWebviewSuspended = false; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | initPlatformState(); 40 | } 41 | 42 | Future initPlatformState() async { 43 | // Optionally initialize the webview environment using 44 | // a custom user data directory 45 | // and/or a custom browser executable directory 46 | // and/or custom chromium command line flags 47 | //await WebviewController.initializeEnvironment( 48 | // additionalArguments: '--show-fps-counter'); 49 | 50 | try { 51 | await _controller.initialize(); 52 | _subscriptions.add(_controller.url.listen((url) { 53 | _textController.text = url; 54 | })); 55 | 56 | _subscriptions 57 | .add(_controller.containsFullScreenElementChanged.listen((flag) { 58 | debugPrint('Contains fullscreen element: $flag'); 59 | windowManager.setFullScreen(flag); 60 | })); 61 | 62 | await _controller.setBackgroundColor(Colors.transparent); 63 | await _controller.setPopupWindowPolicy(WebviewPopupWindowPolicy.deny); 64 | await _controller.loadUrl('https://flutter.dev'); 65 | 66 | if (!mounted) return; 67 | setState(() {}); 68 | } on PlatformException catch (e) { 69 | WidgetsBinding.instance.addPostFrameCallback((_) { 70 | showDialog( 71 | context: context, 72 | builder: (_) => AlertDialog( 73 | title: Text('Error'), 74 | content: Column( 75 | mainAxisSize: MainAxisSize.min, 76 | crossAxisAlignment: CrossAxisAlignment.start, 77 | children: [ 78 | Text('Code: ${e.code}'), 79 | Text('Message: ${e.message}'), 80 | ], 81 | ), 82 | actions: [ 83 | TextButton( 84 | child: Text('Continue'), 85 | onPressed: () { 86 | Navigator.of(context).pop(); 87 | }, 88 | ) 89 | ], 90 | )); 91 | }); 92 | } 93 | } 94 | 95 | Widget compositeView() { 96 | if (!_controller.value.isInitialized) { 97 | return const Text( 98 | 'Not Initialized', 99 | style: TextStyle( 100 | fontSize: 24.0, 101 | fontWeight: FontWeight.w900, 102 | ), 103 | ); 104 | } else { 105 | return Padding( 106 | padding: EdgeInsets.all(20), 107 | child: Column( 108 | children: [ 109 | Card( 110 | elevation: 0, 111 | child: Row(children: [ 112 | Expanded( 113 | child: TextField( 114 | decoration: InputDecoration( 115 | hintText: 'URL', 116 | contentPadding: EdgeInsets.all(10.0), 117 | ), 118 | textAlignVertical: TextAlignVertical.center, 119 | controller: _textController, 120 | onSubmitted: (val) { 121 | _controller.loadUrl(val); 122 | }, 123 | ), 124 | ), 125 | IconButton( 126 | icon: Icon(Icons.refresh), 127 | splashRadius: 20, 128 | onPressed: () { 129 | _controller.reload(); 130 | }, 131 | ), 132 | IconButton( 133 | icon: Icon(Icons.developer_mode), 134 | tooltip: 'Open DevTools', 135 | splashRadius: 20, 136 | onPressed: () { 137 | _controller.openDevTools(); 138 | }, 139 | ) 140 | ]), 141 | ), 142 | Expanded( 143 | child: Card( 144 | color: Colors.transparent, 145 | elevation: 0, 146 | clipBehavior: Clip.antiAliasWithSaveLayer, 147 | child: Stack( 148 | children: [ 149 | Webview( 150 | _controller, 151 | permissionRequested: _onPermissionRequested, 152 | ), 153 | StreamBuilder( 154 | stream: _controller.loadingState, 155 | builder: (context, snapshot) { 156 | if (snapshot.hasData && 157 | snapshot.data == LoadingState.loading) { 158 | return LinearProgressIndicator(); 159 | } else { 160 | return SizedBox(); 161 | } 162 | }), 163 | ], 164 | ))), 165 | ], 166 | ), 167 | ); 168 | } 169 | } 170 | 171 | @override 172 | Widget build(BuildContext context) { 173 | return Scaffold( 174 | floatingActionButton: FloatingActionButton( 175 | tooltip: _isWebviewSuspended ? 'Resume webview' : 'Suspend webview', 176 | onPressed: () async { 177 | if (_isWebviewSuspended) { 178 | await _controller.resume(); 179 | } else { 180 | await _controller.suspend(); 181 | } 182 | setState(() { 183 | _isWebviewSuspended = !_isWebviewSuspended; 184 | }); 185 | }, 186 | child: Icon(_isWebviewSuspended ? Icons.play_arrow : Icons.pause), 187 | ), 188 | appBar: AppBar( 189 | title: StreamBuilder( 190 | stream: _controller.title, 191 | builder: (context, snapshot) { 192 | return Text( 193 | snapshot.hasData ? snapshot.data! : 'WebView (Windows) Example'); 194 | }, 195 | )), 196 | body: Center( 197 | child: compositeView(), 198 | ), 199 | ); 200 | } 201 | 202 | Future _onPermissionRequested( 203 | String url, WebviewPermissionKind kind, bool isUserInitiated) async { 204 | final decision = await showDialog( 205 | context: navigatorKey.currentContext!, 206 | builder: (BuildContext context) => AlertDialog( 207 | title: const Text('WebView permission requested'), 208 | content: Text('WebView has requested permission \'$kind\''), 209 | actions: [ 210 | TextButton( 211 | onPressed: () => 212 | Navigator.pop(context, WebviewPermissionDecision.deny), 213 | child: const Text('Deny'), 214 | ), 215 | TextButton( 216 | onPressed: () => 217 | Navigator.pop(context, WebviewPermissionDecision.allow), 218 | child: const Text('Allow'), 219 | ), 220 | ], 221 | ), 222 | ); 223 | 224 | return decision ?? WebviewPermissionDecision.none; 225 | } 226 | 227 | @override 228 | void dispose() { 229 | _subscriptions.forEach((s) => s.cancel()); 230 | _controller.dispose(); 231 | super.dispose(); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: webview_windows_example 2 | description: Demonstrates how to use the webview_windows plugin. 3 | 4 | publish_to: 'none' 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | webview_windows: 14 | path: ../ 15 | window_manager: ^0.2.7 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | flutter: 22 | uses-material-design: true 23 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(CMAKE_SYSTEM_VERSION 10.0.22000 CACHE STRING INTERNAL FORCE) 4 | 5 | project(webview_windows_example LANGUAGES CXX) 6 | 7 | set(BINARY_NAME "webview_windows_example") 8 | 9 | cmake_policy(SET CMP0063 NEW) 10 | 11 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 12 | 13 | # Configure build options. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | function(APPLY_STANDARD_SETTINGS TARGET) 37 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 38 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 39 | target_compile_options(${TARGET} PRIVATE /EHsc) 40 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 41 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 42 | endfunction() 43 | 44 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 45 | 46 | # Flutter library and tool build rules. 47 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 48 | 49 | # Application build 50 | add_subdirectory("runner") 51 | 52 | # Generated plugin build rules, which manage building the plugins and adding 53 | # them to the application. 54 | include(flutter/generated_plugins.cmake) 55 | 56 | 57 | # === Installation === 58 | # Support files are copied into place next to the executable, so that it can 59 | # run in place. This is done instead of making a separate bundle (as on Linux) 60 | # so that building and running from within Visual Studio will work. 61 | set(BUILD_BUNDLE_DIR "$") 62 | # Make the "install" step default, as it's required to run. 63 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 64 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 65 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 66 | endif() 67 | 68 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 69 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 70 | 71 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 75 | COMPONENT Runtime) 76 | 77 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 78 | COMPONENT Runtime) 79 | 80 | if(PLUGIN_BUNDLED_LIBRARIES) 81 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 82 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 83 | COMPONENT Runtime) 84 | endif() 85 | 86 | # Fully re-copy the assets directory on each build to avoid having stale files 87 | # from a previous install. 88 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 89 | install(CODE " 90 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 91 | " COMPONENT Runtime) 92 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 93 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 94 | 95 | # Install the AOT library on non-Debug builds only. 96 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 97 | CONFIGURATIONS Profile;Release 98 | COMPONENT Runtime) 99 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | "flutter_texture_registrar.h" 27 | ) 28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 29 | add_library(flutter INTERFACE) 30 | target_include_directories(flutter INTERFACE 31 | "${EPHEMERAL_DIR}" 32 | ) 33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 34 | add_dependencies(flutter flutter_assemble) 35 | 36 | # === Wrapper === 37 | list(APPEND CPP_WRAPPER_SOURCES_CORE 38 | "core_implementations.cc" 39 | "standard_codec.cc" 40 | ) 41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 43 | "plugin_registrar.cc" 44 | ) 45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 46 | list(APPEND CPP_WRAPPER_SOURCES_APP 47 | "flutter_engine.cc" 48 | "flutter_view_controller.cc" 49 | ) 50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 51 | 52 | # Wrapper sources needed for a plugin. 53 | add_library(flutter_wrapper_plugin STATIC 54 | ${CPP_WRAPPER_SOURCES_CORE} 55 | ${CPP_WRAPPER_SOURCES_PLUGIN} 56 | ) 57 | apply_standard_settings(flutter_wrapper_plugin) 58 | set_target_properties(flutter_wrapper_plugin PROPERTIES 59 | POSITION_INDEPENDENT_CODE ON) 60 | set_target_properties(flutter_wrapper_plugin PROPERTIES 61 | CXX_VISIBILITY_PRESET hidden) 62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 63 | target_include_directories(flutter_wrapper_plugin PUBLIC 64 | "${WRAPPER_ROOT}/include" 65 | ) 66 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 67 | 68 | # Wrapper sources needed for the runner. 69 | add_library(flutter_wrapper_app STATIC 70 | ${CPP_WRAPPER_SOURCES_CORE} 71 | ${CPP_WRAPPER_SOURCES_APP} 72 | ) 73 | apply_standard_settings(flutter_wrapper_app) 74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 75 | target_include_directories(flutter_wrapper_app PUBLIC 76 | "${WRAPPER_ROOT}/include" 77 | ) 78 | add_dependencies(flutter_wrapper_app flutter_assemble) 79 | 80 | # === Flutter tool backend === 81 | # _phony_ is a non-existent file to force this command to run every time, 82 | # since currently there's no way to get a full input/output list from the 83 | # flutter tool. 84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 86 | add_custom_command( 87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 89 | ${CPP_WRAPPER_SOURCES_APP} 90 | ${PHONY_OUTPUT} 91 | COMMAND ${CMAKE_COMMAND} -E env 92 | ${FLUTTER_TOOL_ENVIRONMENT} 93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 94 | windows-x64 $ 95 | VERBATIM 96 | ) 97 | add_custom_target(flutter_assemble DEPENDS 98 | "${FLUTTER_LIBRARY}" 99 | ${FLUTTER_LIBRARY_HEADERS} 100 | ${CPP_WRAPPER_SOURCES_CORE} 101 | ${CPP_WRAPPER_SOURCES_PLUGIN} 102 | ${CPP_WRAPPER_SOURCES_APP} 103 | ) 104 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(runner LANGUAGES CXX) 4 | 5 | add_executable(${BINARY_NAME} WIN32 6 | "flutter_window.cpp" 7 | "main.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "Demonstrates how to use the webview_windows plugin." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "webview_windows_example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "webview_windows_example.exe" "\0" 98 | VALUE "ProductName", "webview_windows_example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"webview_windows_example", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnschulze/flutter-webview-windows/5e156e4d616ab901ef5b386f01b4a57f03449634/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 123 | nullptr, nullptr, GetModuleHandle(nullptr), this); 124 | 125 | if (!window) { 126 | return false; 127 | } 128 | 129 | return OnCreate(); 130 | } 131 | 132 | // static 133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 134 | UINT const message, 135 | WPARAM const wparam, 136 | LPARAM const lparam) noexcept { 137 | if (message == WM_NCCREATE) { 138 | auto window_struct = reinterpret_cast(lparam); 139 | SetWindowLongPtr(window, GWLP_USERDATA, 140 | reinterpret_cast(window_struct->lpCreateParams)); 141 | 142 | auto that = static_cast(window_struct->lpCreateParams); 143 | EnableFullDpiSupportIfAvailable(window); 144 | that->window_handle_ = window; 145 | } else if (Win32Window* that = GetThisFromHandle(window)) { 146 | return that->MessageHandler(window, message, wparam, lparam); 147 | } 148 | 149 | return DefWindowProc(window, message, wparam, lparam); 150 | } 151 | 152 | LRESULT 153 | Win32Window::MessageHandler(HWND hwnd, 154 | UINT const message, 155 | WPARAM const wparam, 156 | LPARAM const lparam) noexcept { 157 | switch (message) { 158 | case WM_DESTROY: 159 | window_handle_ = nullptr; 160 | Destroy(); 161 | if (quit_on_close_) { 162 | PostQuitMessage(0); 163 | } 164 | return 0; 165 | 166 | case WM_DPICHANGED: { 167 | auto newRectSize = reinterpret_cast(lparam); 168 | LONG newWidth = newRectSize->right - newRectSize->left; 169 | LONG newHeight = newRectSize->bottom - newRectSize->top; 170 | 171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 173 | 174 | return 0; 175 | } 176 | case WM_SIZE: { 177 | RECT rect = GetClientArea(); 178 | if (child_content_ != nullptr) { 179 | // Size and position the child window. 180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 181 | rect.bottom - rect.top, TRUE); 182 | } 183 | return 0; 184 | } 185 | 186 | case WM_ACTIVATE: 187 | if (child_content_ != nullptr) { 188 | SetFocus(child_content_); 189 | } 190 | return 0; 191 | } 192 | 193 | return DefWindowProc(window_handle_, message, wparam, lparam); 194 | } 195 | 196 | void Win32Window::Destroy() { 197 | OnDestroy(); 198 | 199 | if (window_handle_) { 200 | DestroyWindow(window_handle_); 201 | window_handle_ = nullptr; 202 | } 203 | if (g_active_window_count == 0) { 204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 205 | } 206 | } 207 | 208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 209 | return reinterpret_cast( 210 | GetWindowLongPtr(window, GWLP_USERDATA)); 211 | } 212 | 213 | void Win32Window::SetChildContent(HWND content) { 214 | child_content_ = content; 215 | SetParent(content, window_handle_); 216 | RECT frame = GetClientArea(); 217 | 218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 219 | frame.bottom - frame.top, true); 220 | 221 | SetFocus(child_content_); 222 | } 223 | 224 | RECT Win32Window::GetClientArea() { 225 | RECT frame; 226 | GetClientRect(window_handle_, &frame); 227 | return frame; 228 | } 229 | 230 | HWND Win32Window::GetHandle() { 231 | return window_handle_; 232 | } 233 | 234 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 235 | quit_on_close_ = quit_on_close; 236 | } 237 | 238 | bool Win32Window::OnCreate() { 239 | // No-op; provided for subclasses. 240 | return true; 241 | } 242 | 243 | void Win32Window::OnDestroy() { 244 | // No-op; provided for subclasses. 245 | } 246 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /lib/src/cursor.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | const Map _cursors = { 4 | 'none': SystemMouseCursors.none, 5 | 'basic': SystemMouseCursors.basic, 6 | 'click': SystemMouseCursors.click, 7 | 'forbidden': SystemMouseCursors.forbidden, 8 | 'wait': SystemMouseCursors.wait, 9 | 'progress': SystemMouseCursors.progress, 10 | 'contextMenu': SystemMouseCursors.contextMenu, 11 | 'help': SystemMouseCursors.help, 12 | 'text': SystemMouseCursors.text, 13 | 'verticalText': SystemMouseCursors.verticalText, 14 | 'cell': SystemMouseCursors.cell, 15 | 'precise': SystemMouseCursors.precise, 16 | 'move': SystemMouseCursors.move, 17 | 'grab': SystemMouseCursors.grab, 18 | 'grabbing': SystemMouseCursors.grabbing, 19 | 'noDrop': SystemMouseCursors.noDrop, 20 | 'alias': SystemMouseCursors.alias, 21 | 'copy': SystemMouseCursors.copy, 22 | 'disappearing': SystemMouseCursors.disappearing, 23 | 'allScroll': SystemMouseCursors.allScroll, 24 | 'resizeLeftRight': SystemMouseCursors.resizeLeftRight, 25 | 'resizeUpDown': SystemMouseCursors.resizeUpDown, 26 | 'resizeUpLeftDownRight': SystemMouseCursors.resizeUpLeftDownRight, 27 | 'resizeUpRightDownLeft': SystemMouseCursors.resizeUpRightDownLeft, 28 | 'resizeUp': SystemMouseCursors.resizeUp, 29 | 'resizeDown': SystemMouseCursors.resizeDown, 30 | 'resizeLeft': SystemMouseCursors.resizeLeft, 31 | 'resizeRight': SystemMouseCursors.resizeRight, 32 | 'resizeUpLeft': SystemMouseCursors.resizeUpLeft, 33 | 'resizeUpRight': SystemMouseCursors.resizeUpRight, 34 | 'resizeDownLeft': SystemMouseCursors.resizeDownLeft, 35 | 'resizeDownRight': SystemMouseCursors.resizeDownRight, 36 | 'resizeColumn': SystemMouseCursors.resizeColumn, 37 | 'resizeRow': SystemMouseCursors.resizeRow, 38 | 'zoomIn': SystemMouseCursors.zoomIn, 39 | 'zoomOut': SystemMouseCursors.zoomOut, 40 | }; 41 | 42 | SystemMouseCursor getCursorByName(String name) => 43 | _cursors[name] ?? SystemMouseCursors.basic; 44 | -------------------------------------------------------------------------------- /lib/src/enums.dart: -------------------------------------------------------------------------------- 1 | /// Loading state 2 | // Order must match WebviewLoadingState (see webview.h) 3 | enum LoadingState { none, loading, navigationCompleted } 4 | 5 | /// Pointer button type 6 | // Order must match WebviewPointerButton (see webview.h) 7 | enum PointerButton { none, primary, secondary, tertiary } 8 | 9 | enum WebviewDownloadEventKind { 10 | downloadStarted, 11 | downloadCompleted, 12 | downloadProgress 13 | } 14 | 15 | /// Pointer Event kind 16 | // Order must match WebviewPointerEventKind (see webview.h) 17 | enum WebviewPointerEventKind { activate, down, enter, leave, up, update } 18 | 19 | /// Permission kind 20 | // Order must match WebviewPermissionKind (see webview.h) 21 | enum WebviewPermissionKind { 22 | unknown, 23 | microphone, 24 | camera, 25 | geoLocation, 26 | notifications, 27 | otherSensors, 28 | clipboardRead 29 | } 30 | 31 | enum WebviewPermissionDecision { none, allow, deny } 32 | 33 | /// The policy for popup requests. 34 | /// 35 | /// [allow] allows popups and will create new windows. 36 | /// [deny] suppresses popups. 37 | /// [sameWindow] displays popup contents in the current WebView. 38 | enum WebviewPopupWindowPolicy { allow, deny, sameWindow } 39 | 40 | /// The kind of cross origin resource access for virtual hosts 41 | /// 42 | /// [deny] all cross origin requests are denied. 43 | /// [allow] all cross origin requests are allowed. 44 | /// [denyCors] sub resource cross origin requests are allowed, otherwise denied. 45 | /// 46 | /// For more detailed information, please refer to 47 | /// [Microsofts](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2#corewebview2_host_resource_access_kind) 48 | /// documentation. 49 | // Order must match WebviewHostResourceAccessKind (see webview.h) 50 | enum WebviewHostResourceAccessKind { deny, allow, denyCors } 51 | 52 | enum WebErrorStatus { 53 | WebErrorStatusUnknown, 54 | WebErrorStatusCertificateCommonNameIsIncorrect, 55 | WebErrorStatusCertificateExpired, 56 | WebErrorStatusClientCertificateContainsErrors, 57 | WebErrorStatusCertificateRevoked, 58 | WebErrorStatusCertificateIsInvalid, 59 | WebErrorStatusServerUnreachable, 60 | WebErrorStatusTimeout, 61 | WebErrorStatusErrorHTTPInvalidServerResponse, 62 | WebErrorStatusConnectionAborted, 63 | WebErrorStatusConnectionReset, 64 | WebErrorStatusDisconnected, 65 | WebErrorStatusCannotConnect, 66 | WebErrorStatusHostNameNotResolved, 67 | WebErrorStatusOperationCanceled, 68 | WebErrorStatusRedirectFailed, 69 | WebErrorStatusUnexpectedError, 70 | WebErrorStatusValidAuthenticationCredentialsRequired, 71 | WebErrorStatusValidProxyAuthenticationRequired, 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/webview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:ui'; 4 | 5 | import 'package:flutter/gestures.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:flutter/widgets.dart'; 9 | 10 | import 'cursor.dart'; 11 | import 'enums.dart'; 12 | 13 | class HistoryChanged { 14 | final bool canGoBack; 15 | final bool canGoForward; 16 | const HistoryChanged(this.canGoBack, this.canGoForward); 17 | } 18 | 19 | class WebviewDownloadEvent { 20 | final WebviewDownloadEventKind kind; 21 | final String url; 22 | final String resultFilePath; 23 | final int bytesReceived; 24 | final int totalBytesToReceive; 25 | const WebviewDownloadEvent( 26 | this.kind, 27 | this.url, 28 | this.resultFilePath, 29 | this.bytesReceived, 30 | this.totalBytesToReceive, 31 | ); 32 | } 33 | 34 | typedef PermissionRequestedDelegate 35 | = FutureOr Function( 36 | String url, WebviewPermissionKind permissionKind, bool isUserInitiated); 37 | 38 | typedef ScriptID = String; 39 | 40 | /// Attempts to translate a button constant such as [kPrimaryMouseButton] 41 | /// to a [PointerButton] 42 | PointerButton getButton(int value) { 43 | switch (value) { 44 | case kPrimaryMouseButton: 45 | return PointerButton.primary; 46 | case kSecondaryMouseButton: 47 | return PointerButton.secondary; 48 | case kTertiaryButton: 49 | return PointerButton.tertiary; 50 | default: 51 | return PointerButton.none; 52 | } 53 | } 54 | 55 | const String _pluginChannelPrefix = 'io.jns.webview.win'; 56 | const MethodChannel _pluginChannel = MethodChannel(_pluginChannelPrefix); 57 | 58 | class WebviewValue { 59 | const WebviewValue({ 60 | required this.isInitialized, 61 | }); 62 | 63 | final bool isInitialized; 64 | 65 | WebviewValue copyWith({ 66 | bool? isInitialized, 67 | }) { 68 | return WebviewValue( 69 | isInitialized: isInitialized ?? this.isInitialized, 70 | ); 71 | } 72 | 73 | WebviewValue.uninitialized() 74 | : this( 75 | isInitialized: false, 76 | ); 77 | } 78 | 79 | /// Controls a WebView and provides streams for various change events. 80 | class WebviewController extends ValueNotifier { 81 | /// Explicitly initializes the underlying WebView environment 82 | /// using an optional [browserExePath], an optional [userDataPath] 83 | /// and optional Chromium command line arguments [additionalArguments]. 84 | /// 85 | /// The environment is shared between all WebviewController instances and 86 | /// can be initialized only once. Initialization must take place before any 87 | /// WebviewController is created/initialized. 88 | /// 89 | /// Throws [PlatformException] if the environment was initialized before. 90 | static Future initializeEnvironment( 91 | {String? userDataPath, 92 | String? browserExePath, 93 | String? additionalArguments}) async { 94 | return _pluginChannel 95 | .invokeMethod('initializeEnvironment', { 96 | 'userDataPath': userDataPath, 97 | 'browserExePath': browserExePath, 98 | 'additionalArguments': additionalArguments 99 | }); 100 | } 101 | 102 | /// Get the browser version info including channel name if it is not the 103 | /// WebView2 Runtime. 104 | /// Returns [null] if the webview2 runtime is not installed. 105 | static Future getWebViewVersion() async { 106 | return _pluginChannel.invokeMethod('getWebViewVersion'); 107 | } 108 | 109 | late Completer _creatingCompleter; 110 | int _textureId = 0; 111 | bool _isDisposed = false; 112 | 113 | Future get ready => _creatingCompleter.future; 114 | 115 | PermissionRequestedDelegate? _permissionRequested; 116 | 117 | late MethodChannel _methodChannel; 118 | late EventChannel _eventChannel; 119 | StreamSubscription? _eventStreamSubscription; 120 | 121 | final StreamController _urlStreamController = 122 | StreamController(); 123 | 124 | /// A stream reflecting the current URL. 125 | Stream get url => _urlStreamController.stream; 126 | 127 | final StreamController _loadingStateStreamController = 128 | StreamController.broadcast(); 129 | 130 | final StreamController _downloadEventStreamController = 131 | StreamController.broadcast(); 132 | 133 | final StreamController _onLoadErrorStreamController = 134 | StreamController(); 135 | 136 | /// A stream reflecting the current loading state. 137 | Stream get loadingState => _loadingStateStreamController.stream; 138 | 139 | Stream get onDownloadEvent => 140 | _downloadEventStreamController.stream; 141 | 142 | /// A stream reflecting the navigation error when navigation completed with an error. 143 | Stream get onLoadError => _onLoadErrorStreamController.stream; 144 | 145 | final StreamController _historyChangedStreamController = 146 | StreamController(); 147 | 148 | /// A stream reflecting the current history state. 149 | Stream get historyChanged => 150 | _historyChangedStreamController.stream; 151 | 152 | final StreamController _securityStateChangedStreamController = 153 | StreamController(); 154 | 155 | /// A stream reflecting the current security state. 156 | Stream get securityStateChanged => 157 | _securityStateChangedStreamController.stream; 158 | 159 | final StreamController _titleStreamController = 160 | StreamController(); 161 | 162 | /// A stream reflecting the current document title. 163 | Stream get title => _titleStreamController.stream; 164 | 165 | final StreamController _cursorStreamController = 166 | StreamController.broadcast(); 167 | 168 | /// A stream reflecting the current cursor style. 169 | Stream get _cursor => _cursorStreamController.stream; 170 | 171 | final StreamController _webMessageStreamController = 172 | StreamController(); 173 | 174 | Stream get webMessage => _webMessageStreamController.stream; 175 | 176 | final StreamController 177 | _containsFullScreenElementChangedStreamController = 178 | StreamController.broadcast(); 179 | 180 | /// A stream reflecting whether the document currently contains full-screen elements. 181 | Stream get containsFullScreenElementChanged => 182 | _containsFullScreenElementChangedStreamController.stream; 183 | 184 | WebviewController() : super(WebviewValue.uninitialized()); 185 | 186 | /// Initializes the underlying platform view. 187 | Future initialize() async { 188 | if (_isDisposed) { 189 | return Future.value(); 190 | } 191 | _creatingCompleter = Completer(); 192 | try { 193 | final reply = 194 | await _pluginChannel.invokeMapMethod('initialize'); 195 | 196 | _textureId = reply!['textureId']; 197 | _methodChannel = MethodChannel('$_pluginChannelPrefix/$_textureId'); 198 | _eventChannel = EventChannel('$_pluginChannelPrefix/$_textureId/events'); 199 | _eventStreamSubscription = 200 | _eventChannel.receiveBroadcastStream().listen((event) { 201 | final map = event as Map; 202 | switch (map['type']) { 203 | case 'urlChanged': 204 | _urlStreamController.add(map['value']); 205 | break; 206 | case 'onLoadError': 207 | final value = WebErrorStatus.values[map['value']]; 208 | _onLoadErrorStreamController.add(value); 209 | break; 210 | case 'loadingStateChanged': 211 | final value = LoadingState.values[map['value']]; 212 | _loadingStateStreamController.add(value); 213 | break; 214 | case 'downloadEvent': 215 | final value = WebviewDownloadEvent( 216 | WebviewDownloadEventKind.values[map['value']['kind']], 217 | map['value']['url'], 218 | map['value']['resultFilePath'], 219 | map['value']['bytesReceived'], 220 | map['value']['totalBytesToReceive'], 221 | ); 222 | _downloadEventStreamController.add(value); 223 | break; 224 | case 'historyChanged': 225 | final value = HistoryChanged( 226 | map['value']['canGoBack'], map['value']['canGoForward']); 227 | _historyChangedStreamController.add(value); 228 | break; 229 | case 'securityStateChanged': 230 | _securityStateChangedStreamController.add(map['value']); 231 | break; 232 | case 'titleChanged': 233 | _titleStreamController.add(map['value']); 234 | break; 235 | case 'cursorChanged': 236 | _cursorStreamController.add(getCursorByName(map['value'])); 237 | break; 238 | case 'webMessageReceived': 239 | try { 240 | final message = json.decode(map['value']); 241 | _webMessageStreamController.add(message); 242 | } catch (ex) { 243 | _webMessageStreamController.addError(ex); 244 | } 245 | break; 246 | case 'containsFullScreenElementChanged': 247 | _containsFullScreenElementChangedStreamController.add(map['value']); 248 | break; 249 | } 250 | }); 251 | 252 | _methodChannel.setMethodCallHandler((call) { 253 | if (call.method == 'permissionRequested') { 254 | return _onPermissionRequested( 255 | call.arguments as Map); 256 | } 257 | 258 | throw MissingPluginException('Unknown method ${call.method}'); 259 | }); 260 | 261 | value = value.copyWith(isInitialized: true); 262 | _creatingCompleter.complete(); 263 | } on PlatformException catch (e) { 264 | _creatingCompleter.completeError(e); 265 | } 266 | 267 | return _creatingCompleter.future; 268 | } 269 | 270 | Future _onPermissionRequested(Map args) async { 271 | if (_permissionRequested == null) { 272 | return null; 273 | } 274 | 275 | final url = args['url'] as String?; 276 | final permissionKindIndex = args['permissionKind'] as int?; 277 | final isUserInitiated = args['isUserInitiated'] as bool?; 278 | 279 | if (url != null && permissionKindIndex != null && isUserInitiated != null) { 280 | final permissionKind = WebviewPermissionKind.values[permissionKindIndex]; 281 | final decision = 282 | await _permissionRequested!(url, permissionKind, isUserInitiated); 283 | 284 | switch (decision) { 285 | case WebviewPermissionDecision.allow: 286 | return true; 287 | case WebviewPermissionDecision.deny: 288 | return false; 289 | default: 290 | return null; 291 | } 292 | } 293 | 294 | return null; 295 | } 296 | 297 | @override 298 | Future dispose() async { 299 | await _creatingCompleter.future; 300 | if (!_isDisposed) { 301 | _isDisposed = true; 302 | await _eventStreamSubscription?.cancel(); 303 | await _pluginChannel.invokeMethod('dispose', _textureId); 304 | } 305 | super.dispose(); 306 | } 307 | 308 | /// Loads the given [url]. 309 | Future loadUrl(String url) async { 310 | if (_isDisposed) { 311 | return; 312 | } 313 | assert(value.isInitialized); 314 | return _methodChannel.invokeMethod('loadUrl', url); 315 | } 316 | 317 | /// Loads a document from the given string. 318 | Future loadStringContent(String content) async { 319 | if (_isDisposed) { 320 | return; 321 | } 322 | assert(value.isInitialized); 323 | return _methodChannel.invokeMethod('loadStringContent', content); 324 | } 325 | 326 | /// Reloads the current document. 327 | Future reload() async { 328 | if (_isDisposed) { 329 | return; 330 | } 331 | assert(value.isInitialized); 332 | return _methodChannel.invokeMethod('reload'); 333 | } 334 | 335 | /// Stops all navigations and pending resource fetches. 336 | Future stop() async { 337 | if (_isDisposed) { 338 | return; 339 | } 340 | assert(value.isInitialized); 341 | return _methodChannel.invokeMethod('stop'); 342 | } 343 | 344 | /// Navigates the WebView to the previous page in the navigation history. 345 | Future goBack() async { 346 | if (_isDisposed) { 347 | return; 348 | } 349 | assert(value.isInitialized); 350 | return _methodChannel.invokeMethod('goBack'); 351 | } 352 | 353 | /// Navigates the WebView to the next page in the navigation history. 354 | Future goForward() async { 355 | if (_isDisposed) { 356 | return; 357 | } 358 | assert(value.isInitialized); 359 | return _methodChannel.invokeMethod('goForward'); 360 | } 361 | 362 | /// Adds the provided JavaScript [script] to a list of scripts that should be run after the global 363 | /// object has been created, but before the HTML document has been parsed and before any 364 | /// other script included by the HTML document is run. 365 | /// 366 | /// Returns a [ScriptID] on success which can be used for [removeScriptToExecuteOnDocumentCreated]. 367 | /// 368 | /// see https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.1264.42#addscripttoexecuteondocumentcreated 369 | Future addScriptToExecuteOnDocumentCreated(String script) async { 370 | if (_isDisposed) { 371 | return null; 372 | } 373 | assert(value.isInitialized); 374 | return _methodChannel.invokeMethod( 375 | 'addScriptToExecuteOnDocumentCreated', script); 376 | } 377 | 378 | /// Removes the script identified by [scriptId] from the list of registered scripts. 379 | /// 380 | /// see https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.1264.42#removescripttoexecuteondocumentcreated 381 | Future removeScriptToExecuteOnDocumentCreated(ScriptID scriptId) async { 382 | if (_isDisposed) { 383 | return null; 384 | } 385 | assert(value.isInitialized); 386 | return _methodChannel.invokeMethod( 387 | 'removeScriptToExecuteOnDocumentCreated', scriptId); 388 | } 389 | 390 | /// Runs the JavaScript [script] in the current top-level document rendered in 391 | /// the WebView and returns its result. 392 | /// 393 | /// see https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.1264.42#executescript 394 | Future executeScript(String script) async { 395 | if (_isDisposed) { 396 | return; 397 | } 398 | assert(value.isInitialized); 399 | 400 | final data = await _methodChannel.invokeMethod('executeScript', script); 401 | if (data == null) return null; 402 | return jsonDecode(data as String); 403 | } 404 | 405 | /// Posts the given JSON-formatted message to the current document. 406 | Future postWebMessage(String message) async { 407 | if (_isDisposed) { 408 | return; 409 | } 410 | assert(value.isInitialized); 411 | return _methodChannel.invokeMethod('postWebMessage', message); 412 | } 413 | 414 | /// Sets the user agent value. 415 | Future setUserAgent(String userAgent) async { 416 | if (_isDisposed) { 417 | return; 418 | } 419 | assert(value.isInitialized); 420 | return _methodChannel.invokeMethod('setUserAgent', userAgent); 421 | } 422 | 423 | /// Clears browser cookies. 424 | Future clearCookies() async { 425 | if (_isDisposed) { 426 | return; 427 | } 428 | assert(value.isInitialized); 429 | return _methodChannel.invokeMethod('clearCookies'); 430 | } 431 | 432 | /// Clears browser cache. 433 | Future clearCache() async { 434 | if (_isDisposed) { 435 | return; 436 | } 437 | assert(value.isInitialized); 438 | return _methodChannel.invokeMethod('clearCache'); 439 | } 440 | 441 | /// Toggles ignoring cache for each request. If true, cache will not be used. 442 | Future setCacheDisabled(bool disabled) async { 443 | if (_isDisposed) { 444 | return; 445 | } 446 | assert(value.isInitialized); 447 | return _methodChannel.invokeMethod('setCacheDisabled', disabled); 448 | } 449 | 450 | /// Opens the Browser DevTools in a separate window 451 | Future openDevTools() async { 452 | if (_isDisposed) { 453 | return; 454 | } 455 | assert(value.isInitialized); 456 | return _methodChannel.invokeMethod('openDevTools'); 457 | } 458 | 459 | /// Sets the background color to the provided [color]. 460 | /// 461 | /// Due to a limitation of the underlying WebView implementation, 462 | /// semi-transparent values are not supported. 463 | /// Any non-zero alpha value will be considered as opaque (0xff). 464 | Future setBackgroundColor(Color color) async { 465 | if (_isDisposed) { 466 | return; 467 | } 468 | assert(value.isInitialized); 469 | return _methodChannel.invokeMethod( 470 | 'setBackgroundColor', color.value.toSigned(32)); 471 | } 472 | 473 | /// Sets the zoom factor. 474 | Future setZoomFactor(double zoomFactor) async { 475 | if (_isDisposed) { 476 | return; 477 | } 478 | assert(value.isInitialized); 479 | return _methodChannel.invokeMethod('setZoomFactor', zoomFactor); 480 | } 481 | 482 | /// Sets the [WebviewPopupWindowPolicy]. 483 | Future setPopupWindowPolicy( 484 | WebviewPopupWindowPolicy popupPolicy) async { 485 | if (_isDisposed) { 486 | return; 487 | } 488 | assert(value.isInitialized); 489 | return _methodChannel.invokeMethod( 490 | 'setPopupWindowPolicy', popupPolicy.index); 491 | } 492 | 493 | /// Suspends the web view. 494 | Future suspend() async { 495 | if (_isDisposed) { 496 | return; 497 | } 498 | assert(value.isInitialized); 499 | return _methodChannel.invokeMethod('suspend'); 500 | } 501 | 502 | /// Resumes the web view. 503 | Future resume() async { 504 | if (_isDisposed) { 505 | return; 506 | } 507 | assert(value.isInitialized); 508 | return _methodChannel.invokeMethod('resume'); 509 | } 510 | 511 | /// Adds a Virtual Host Name Mapping. 512 | /// 513 | /// Please refer to 514 | /// [Microsofts](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_3#setvirtualhostnametofoldermapping) 515 | /// documentation for more details. 516 | Future addVirtualHostNameMapping(String hostName, String folderPath, 517 | WebviewHostResourceAccessKind accessKind) async { 518 | if (_isDisposed) { 519 | return; 520 | } 521 | 522 | return _methodChannel.invokeMethod( 523 | 'setVirtualHostNameMapping', [hostName, folderPath, accessKind.index]); 524 | } 525 | 526 | /// Removes a Virtual Host Name Mapping. 527 | /// 528 | /// Please refer to 529 | /// [Microsofts](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_3#clearvirtualhostnametofoldermapping) 530 | /// documentation for more details. 531 | Future removeVirtualHostNameMapping(String hostName) async { 532 | if (_isDisposed) { 533 | return; 534 | } 535 | return _methodChannel.invokeMethod('clearVirtualHostNameMapping', hostName); 536 | } 537 | 538 | /// Limits the number of frames per second to the given value. 539 | Future setFpsLimit([int? maxFps = 0]) async { 540 | if (_isDisposed) { 541 | return; 542 | } 543 | assert(value.isInitialized); 544 | return _methodChannel.invokeMethod('setFpsLimit', maxFps); 545 | } 546 | 547 | /// Sends a Pointer (Touch) update 548 | Future _setPointerUpdate(WebviewPointerEventKind kind, int pointer, 549 | Offset position, double size, double pressure) async { 550 | if (_isDisposed) { 551 | return; 552 | } 553 | assert(value.isInitialized); 554 | return _methodChannel.invokeMethod('setPointerUpdate', 555 | [pointer, kind.index, position.dx, position.dy, size, pressure]); 556 | } 557 | 558 | /// Moves the virtual cursor to [position]. 559 | Future _setCursorPos(Offset position) async { 560 | if (_isDisposed) { 561 | return; 562 | } 563 | assert(value.isInitialized); 564 | return _methodChannel 565 | .invokeMethod('setCursorPos', [position.dx, position.dy]); 566 | } 567 | 568 | /// Indicates whether the specified [button] is currently down. 569 | Future _setPointerButtonState(PointerButton button, bool isDown) async { 570 | if (_isDisposed) { 571 | return; 572 | } 573 | assert(value.isInitialized); 574 | return _methodChannel.invokeMethod('setPointerButton', 575 | {'button': button.index, 'isDown': isDown}); 576 | } 577 | 578 | /// Sets the horizontal and vertical scroll delta. 579 | Future _setScrollDelta(double dx, double dy) async { 580 | if (_isDisposed) { 581 | return; 582 | } 583 | assert(value.isInitialized); 584 | return _methodChannel.invokeMethod('setScrollDelta', [dx, dy]); 585 | } 586 | 587 | /// Sets the surface size to the provided [size]. 588 | Future _setSize(Size size, double scaleFactor) async { 589 | if (_isDisposed) { 590 | return; 591 | } 592 | assert(value.isInitialized); 593 | return _methodChannel 594 | .invokeMethod('setSize', [size.width, size.height, scaleFactor]); 595 | } 596 | } 597 | 598 | class Webview extends StatefulWidget { 599 | final WebviewController controller; 600 | final PermissionRequestedDelegate? permissionRequested; 601 | final double? width; 602 | final double? height; 603 | 604 | /// An optional scale factor. Defaults to [FlutterView.devicePixelRatio] for 605 | /// rendering in native resolution. 606 | /// Setting this to 1.0 will disable high-DPI support. 607 | /// This should only be needed to mimic old behavior before high-DPI support 608 | /// was available. 609 | final double? scaleFactor; 610 | 611 | /// The [FilterQuality] used for scaling the texture's contents. 612 | /// Defaults to [FilterQuality.none] as this renders in native resolution 613 | /// unless specifying a [scaleFactor]. 614 | final FilterQuality filterQuality; 615 | 616 | const Webview(this.controller, 617 | {this.width, 618 | this.height, 619 | this.permissionRequested, 620 | this.scaleFactor, 621 | this.filterQuality = FilterQuality.none}); 622 | 623 | @override 624 | _WebviewState createState() => _WebviewState(); 625 | } 626 | 627 | class _WebviewState extends State { 628 | final GlobalKey _key = GlobalKey(); 629 | final _downButtons = {}; 630 | 631 | PointerDeviceKind _pointerKind = PointerDeviceKind.unknown; 632 | 633 | MouseCursor _cursor = SystemMouseCursors.basic; 634 | 635 | WebviewController get _controller => widget.controller; 636 | 637 | StreamSubscription? _cursorSubscription; 638 | 639 | @override 640 | void initState() { 641 | super.initState(); 642 | 643 | // TODO: Refactor callback and event handling and 644 | // remove this line 645 | _controller._permissionRequested = widget.permissionRequested; 646 | 647 | // Report initial surface size 648 | WidgetsBinding.instance.addPostFrameCallback((_) => _reportSurfaceSize()); 649 | 650 | _cursorSubscription = _controller._cursor.listen((cursor) { 651 | setState(() { 652 | _cursor = cursor; 653 | }); 654 | }); 655 | } 656 | 657 | @override 658 | Widget build(BuildContext context) { 659 | return (widget.height != null && widget.width != null) 660 | ? SizedBox( 661 | key: _key, 662 | width: widget.width, 663 | height: widget.height, 664 | child: _buildInner()) 665 | : SizedBox.expand(key: _key, child: _buildInner()); 666 | } 667 | 668 | Widget _buildInner() { 669 | return NotificationListener( 670 | onNotification: (notification) { 671 | _reportSurfaceSize(); 672 | return true; 673 | }, 674 | child: SizeChangedLayoutNotifier( 675 | child: _controller.value.isInitialized 676 | ? Listener( 677 | onPointerHover: (ev) { 678 | // ev.kind is for whatever reason not set to touch 679 | // even on touch input 680 | if (_pointerKind == PointerDeviceKind.touch) { 681 | // Ignoring hover events on touch for now 682 | return; 683 | } 684 | _controller._setCursorPos(ev.localPosition); 685 | }, 686 | onPointerDown: (ev) { 687 | _pointerKind = ev.kind; 688 | if (ev.kind == PointerDeviceKind.touch) { 689 | _controller._setPointerUpdate( 690 | WebviewPointerEventKind.down, 691 | ev.pointer, 692 | ev.localPosition, 693 | ev.size, 694 | ev.pressure); 695 | return; 696 | } 697 | final button = getButton(ev.buttons); 698 | _downButtons[ev.pointer] = button; 699 | _controller._setPointerButtonState(button, true); 700 | }, 701 | onPointerUp: (ev) { 702 | _pointerKind = ev.kind; 703 | if (ev.kind == PointerDeviceKind.touch) { 704 | _controller._setPointerUpdate( 705 | WebviewPointerEventKind.up, 706 | ev.pointer, 707 | ev.localPosition, 708 | ev.size, 709 | ev.pressure); 710 | return; 711 | } 712 | final button = _downButtons.remove(ev.pointer); 713 | if (button != null) { 714 | _controller._setPointerButtonState(button, false); 715 | } 716 | }, 717 | onPointerCancel: (ev) { 718 | _pointerKind = ev.kind; 719 | final button = _downButtons.remove(ev.pointer); 720 | if (button != null) { 721 | _controller._setPointerButtonState(button, false); 722 | } 723 | }, 724 | onPointerMove: (ev) { 725 | _pointerKind = ev.kind; 726 | if (ev.kind == PointerDeviceKind.touch) { 727 | _controller._setPointerUpdate( 728 | WebviewPointerEventKind.update, 729 | ev.pointer, 730 | ev.localPosition, 731 | ev.size, 732 | ev.pressure); 733 | } else { 734 | _controller._setCursorPos(ev.localPosition); 735 | } 736 | }, 737 | onPointerSignal: (signal) { 738 | if (signal is PointerScrollEvent) { 739 | _controller._setScrollDelta( 740 | -signal.scrollDelta.dx, -signal.scrollDelta.dy); 741 | } 742 | }, 743 | onPointerPanZoomUpdate: (signal) { 744 | if (signal.panDelta.dx.abs() > signal.panDelta.dy.abs()) { 745 | _controller._setScrollDelta(-signal.panDelta.dx, 0); 746 | } else { 747 | _controller._setScrollDelta(0, signal.panDelta.dy); 748 | } 749 | }, 750 | child: MouseRegion( 751 | cursor: _cursor, 752 | child: Texture( 753 | textureId: _controller._textureId, 754 | filterQuality: widget.filterQuality, 755 | )), 756 | ) 757 | : const SizedBox())); 758 | } 759 | 760 | void _reportSurfaceSize() async { 761 | final box = _key.currentContext?.findRenderObject() as RenderBox?; 762 | if (box != null) { 763 | await _controller.ready; 764 | unawaited(_controller._setSize( 765 | box.size, widget.scaleFactor ?? window.devicePixelRatio)); 766 | } 767 | } 768 | 769 | @override 770 | void dispose() { 771 | super.dispose(); 772 | _cursorSubscription?.cancel(); 773 | } 774 | } 775 | -------------------------------------------------------------------------------- /lib/webview_windows.dart: -------------------------------------------------------------------------------- 1 | export 'src/enums.dart'; 2 | export 'src/webview.dart'; 3 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: webview_windows 2 | description: A WebView2-powered webview implementation for the Windows platform. 3 | version: 0.4.0 4 | repository: https://github.com/jnschulze/flutter-webview-windows 5 | homepage: https://jns.io 6 | 7 | environment: 8 | sdk: ">=2.13.0 <3.0.0" 9 | flutter: ">=3.3.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | flutter: 20 | plugin: 21 | platforms: 22 | windows: 23 | pluginClass: WebviewWindowsPlugin 24 | -------------------------------------------------------------------------------- /test/webview_windows_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | -------------------------------------------------------------------------------- /webview_windows.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | set(PROJECT_NAME "webview_windows") 3 | 4 | set(WIL_VERSION "1.0.220914.1") 5 | set(WEBVIEW_VERSION "1.0.1210.39") 6 | 7 | message(VERBOSE "CMake system version is ${CMAKE_SYSTEM_VERSION} (using SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION})") 8 | 9 | project(${PROJECT_NAME} LANGUAGES CXX) 10 | 11 | # This value is used when generating builds using this plugin, so it must 12 | # not be changed 13 | set(PLUGIN_NAME "webview_windows_plugin") 14 | 15 | set(NUGET_URL https://dist.nuget.org/win-x86-commandline/v5.10.0/nuget.exe) 16 | set(NUGET_SHA256 852b71cc8c8c2d40d09ea49d321ff56fd2397b9d6ea9f96e532530307bbbafd3) 17 | 18 | find_program(NUGET nuget) 19 | if(NOT NUGET) 20 | message(NOTICE "Nuget is not installed.") 21 | set(NUGET ${CMAKE_BINARY_DIR}/nuget.exe) 22 | if (NOT EXISTS ${NUGET}) 23 | message(NOTICE "Attempting to download nuget.") 24 | file(DOWNLOAD ${NUGET_URL} ${NUGET}) 25 | endif() 26 | 27 | file(SHA256 ${NUGET} NUGET_DL_HASH) 28 | if (NOT NUGET_DL_HASH STREQUAL NUGET_SHA256) 29 | message(FATAL_ERROR "Integrity check for ${NUGET} failed.") 30 | endif() 31 | endif() 32 | 33 | add_custom_target(${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD ALL) 34 | add_custom_command( 35 | TARGET ${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD PRE_BUILD 36 | COMMAND ${NUGET} install Microsoft.Windows.ImplementationLibrary -Version ${WIL_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages 37 | COMMAND ${NUGET} install Microsoft.Web.WebView2 -Version ${WEBVIEW_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages 38 | DEPENDS ${NUGET} 39 | ) 40 | 41 | add_library(${PLUGIN_NAME} SHARED 42 | "webview_windows_plugin.cc" 43 | "webview_platform.cc" 44 | "webview.cc" 45 | "webview_host.cc" 46 | "webview_bridge.cc" 47 | "texture_bridge.cc" 48 | "texture_bridge_gpu.cc" 49 | "graphics_context.cc" 50 | "util/direct3d11.interop.cc" 51 | "util/rohelper.cc" 52 | "util/string_converter.cc" 53 | ) 54 | 55 | if(MSVC) 56 | target_compile_options(${PLUGIN_NAME} PRIVATE "/await") 57 | endif() 58 | 59 | apply_standard_settings(${PLUGIN_NAME}) 60 | target_compile_features(${PLUGIN_NAME} PUBLIC cxx_std_20) # For std::format support 61 | 62 | set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) 63 | 64 | target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Web.WebView2/build/native/Microsoft.Web.WebView2.targets) 65 | target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.ImplementationLibrary/build/native/Microsoft.Windows.ImplementationLibrary.targets) 66 | 67 | target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) 68 | target_include_directories(${PLUGIN_NAME} INTERFACE 69 | "${CMAKE_CURRENT_SOURCE_DIR}/include" 70 | ) 71 | 72 | target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) 73 | 74 | set(webview_windows_bundled_libraries 75 | PARENT_SCOPE 76 | ) 77 | -------------------------------------------------------------------------------- /windows/graphics_context.cc: -------------------------------------------------------------------------------- 1 | #include "graphics_context.h" 2 | 3 | #include "util/d3dutil.h" 4 | #include "util/direct3d11.interop.h" 5 | 6 | GraphicsContext::GraphicsContext(rx::RoHelper* rohelper) : rohelper_(rohelper) { 7 | device_ = CreateD3DDevice(); 8 | if (!device_) { 9 | return; 10 | } 11 | 12 | device_->GetImmediateContext(device_context_.put()); 13 | if (FAILED(util::CreateDirect3D11DeviceFromDXGIDevice( 14 | device_.try_as().get(), 15 | (IInspectable**)device_winrt_.put()))) { 16 | return; 17 | } 18 | 19 | valid_ = true; 20 | } 21 | 22 | winrt::com_ptr 23 | GraphicsContext::CreateCompositor() { 24 | HSTRING className; 25 | HSTRING_HEADER classNameHeader; 26 | 27 | if (FAILED(rohelper_->GetStringReference( 28 | RuntimeClass_Windows_UI_Composition_Compositor, &className, 29 | &classNameHeader))) { 30 | return nullptr; 31 | } 32 | 33 | winrt::com_ptr af; 34 | if (FAILED(rohelper_->GetActivationFactory( 35 | className, __uuidof(IActivationFactory), af.put_void()))) { 36 | return nullptr; 37 | } 38 | 39 | winrt::com_ptr compositor; 40 | if (FAILED(af->ActivateInstance( 41 | reinterpret_cast(compositor.put())))) { 42 | return nullptr; 43 | } 44 | 45 | return compositor; 46 | } 47 | 48 | winrt::com_ptr 49 | GraphicsContext::CreateGraphicsCaptureItemFromVisual( 50 | ABI::Windows::UI::Composition::IVisual* visual) const { 51 | HSTRING className; 52 | HSTRING_HEADER classNameHeader; 53 | 54 | if (FAILED(rohelper_->GetStringReference( 55 | RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem, &className, 56 | &classNameHeader))) { 57 | return nullptr; 58 | } 59 | 60 | ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics* 61 | capture_item_statics; 62 | if (FAILED(rohelper_->GetActivationFactory( 63 | className, 64 | __uuidof( 65 | ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics), 66 | (void**)&capture_item_statics))) { 67 | return nullptr; 68 | } 69 | 70 | winrt::com_ptr 71 | capture_item; 72 | if (FAILED( 73 | capture_item_statics->CreateFromVisual(visual, capture_item.put()))) { 74 | return nullptr; 75 | } 76 | 77 | return capture_item; 78 | } 79 | 80 | winrt::com_ptr 81 | GraphicsContext::CreateCaptureFramePool( 82 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, 83 | ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, 84 | INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const { 85 | HSTRING className; 86 | HSTRING_HEADER classNameHeader; 87 | 88 | if (FAILED(rohelper_->GetStringReference( 89 | RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, 90 | &className, &classNameHeader))) { 91 | return nullptr; 92 | } 93 | 94 | ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics* 95 | capture_frame_pool_statics; 96 | if (FAILED(rohelper_->GetActivationFactory( 97 | className, 98 | __uuidof(ABI::Windows::Graphics::Capture:: 99 | IDirect3D11CaptureFramePoolStatics), 100 | (void**)&capture_frame_pool_statics))) { 101 | return nullptr; 102 | } 103 | 104 | winrt::com_ptr 105 | capture_frame_pool; 106 | 107 | if (FAILED(capture_frame_pool_statics->Create(device, pixelFormat, 108 | numberOfBuffers, size, 109 | capture_frame_pool.put()))) { 110 | return nullptr; 111 | } 112 | 113 | return capture_frame_pool; 114 | } 115 | 116 | winrt::com_ptr 117 | GraphicsContext::CreateFreeThreadedCaptureFramePool( 118 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, 119 | ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, 120 | INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const { 121 | HSTRING className; 122 | HSTRING_HEADER classNameHeader; 123 | 124 | if (FAILED(rohelper_->GetStringReference( 125 | RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, 126 | &className, &classNameHeader))) { 127 | return nullptr; 128 | } 129 | 130 | ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics2* 131 | capture_frame_pool_statics; 132 | if (FAILED(rohelper_->GetActivationFactory( 133 | className, 134 | __uuidof(ABI::Windows::Graphics::Capture:: 135 | IDirect3D11CaptureFramePoolStatics2), 136 | (void**)&capture_frame_pool_statics))) { 137 | return nullptr; 138 | } 139 | 140 | winrt::com_ptr 141 | capture_frame_pool; 142 | 143 | if (FAILED(capture_frame_pool_statics->CreateFreeThreaded( 144 | device, pixelFormat, numberOfBuffers, size, 145 | capture_frame_pool.put()))) { 146 | return nullptr; 147 | } 148 | 149 | return capture_frame_pool; 150 | } 151 | -------------------------------------------------------------------------------- /windows/graphics_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "util/rohelper.h" 9 | 10 | class GraphicsContext { 11 | public: 12 | GraphicsContext(rx::RoHelper* rohelper); 13 | 14 | inline bool IsValid() const { return valid_; } 15 | 16 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device() const { 17 | return device_winrt_.get(); 18 | } 19 | ID3D11Device* d3d_device() const { return device_.get(); } 20 | ID3D11DeviceContext* d3d_device_context() const { 21 | return device_context_.get(); 22 | } 23 | 24 | winrt::com_ptr CreateCompositor(); 25 | 26 | winrt::com_ptr 27 | CreateGraphicsCaptureItemFromVisual( 28 | ABI::Windows::UI::Composition::IVisual* visual) const; 29 | 30 | winrt::com_ptr 31 | CreateCaptureFramePool( 32 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, 33 | ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, 34 | INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const; 35 | 36 | winrt::com_ptr 37 | CreateFreeThreadedCaptureFramePool( 38 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, 39 | ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, 40 | INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const; 41 | 42 | private: 43 | bool valid_ = false; 44 | rx::RoHelper* rohelper_; 45 | winrt::com_ptr 46 | device_winrt_; 47 | winrt::com_ptr device_{nullptr}; 48 | winrt::com_ptr device_context_{nullptr}; 49 | }; 50 | -------------------------------------------------------------------------------- /windows/include/webview_windows/webview_windows_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_WEBVIEW_WINDOWS_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_WEBVIEW_WINDOWS_PLUGIN_H_ 3 | 4 | #include 5 | 6 | #ifdef FLUTTER_PLUGIN_IMPL 7 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) 8 | #else 9 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) 10 | #endif 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | FLUTTER_PLUGIN_EXPORT void WebviewWindowsPluginRegisterWithRegistrar( 17 | FlutterDesktopPluginRegistrarRef registrar); 18 | 19 | #if defined(__cplusplus) 20 | } // extern "C" 21 | #endif 22 | 23 | #endif // FLUTTER_PLUGIN_WEBVIEW_WINDOWS_PLUGIN_H_ 24 | -------------------------------------------------------------------------------- /windows/texture_bridge.cc: -------------------------------------------------------------------------------- 1 | #include "texture_bridge.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "util/direct3d11.interop.h" 11 | 12 | namespace { 13 | const int kNumBuffers = 1; 14 | } // namespace 15 | 16 | TextureBridge::TextureBridge(GraphicsContext* graphics_context, 17 | ABI::Windows::UI::Composition::IVisual* visual) 18 | : graphics_context_(graphics_context) { 19 | capture_item_ = 20 | graphics_context_->CreateGraphicsCaptureItemFromVisual(visual); 21 | assert(capture_item_); 22 | 23 | capture_item_->add_Closed( 24 | Microsoft::WRL::Callback>( 27 | [](ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* item, 28 | IInspectable* args) -> HRESULT { 29 | std::cerr << "Capture item was closed." << std::endl; 30 | return S_OK; 31 | }) 32 | .Get(), 33 | &on_closed_token_); 34 | } 35 | 36 | TextureBridge::~TextureBridge() { 37 | const std::lock_guard lock(mutex_); 38 | StopInternal(); 39 | if (capture_item_) { 40 | capture_item_->remove_Closed(on_closed_token_); 41 | } 42 | } 43 | 44 | bool TextureBridge::Start() { 45 | const std::lock_guard lock(mutex_); 46 | if (is_running_ || !capture_item_) { 47 | return false; 48 | } 49 | 50 | ABI::Windows::Graphics::SizeInt32 size; 51 | capture_item_->get_Size(&size); 52 | 53 | frame_pool_ = graphics_context_->CreateCaptureFramePool( 54 | graphics_context_->device(), 55 | static_cast( 56 | kPixelFormat), 57 | kNumBuffers, size); 58 | assert(frame_pool_); 59 | 60 | frame_pool_->add_FrameArrived( 61 | Microsoft::WRL::Callback>( 64 | [this](ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool* 65 | pool, 66 | IInspectable* args) -> HRESULT { 67 | OnFrameArrived(); 68 | return S_OK; 69 | }) 70 | .Get(), 71 | &on_frame_arrived_token_); 72 | 73 | if (FAILED(frame_pool_->CreateCaptureSession(capture_item_.get(), 74 | capture_session_.put()))) { 75 | std::cerr << "Creating capture session failed." << std::endl; 76 | return false; 77 | } 78 | 79 | if (SUCCEEDED(capture_session_->StartCapture())) { 80 | is_running_ = true; 81 | return true; 82 | } 83 | 84 | return false; 85 | } 86 | 87 | void TextureBridge::Stop() { 88 | const std::lock_guard lock(mutex_); 89 | StopInternal(); 90 | } 91 | 92 | void TextureBridge::StopInternal() { 93 | if (is_running_) { 94 | is_running_ = false; 95 | frame_pool_->remove_FrameArrived(on_frame_arrived_token_); 96 | auto closable = 97 | capture_session_.try_as(); 98 | assert(closable); 99 | closable->Close(); 100 | capture_session_ = nullptr; 101 | } 102 | } 103 | 104 | void TextureBridge::OnFrameArrived() { 105 | const std::lock_guard lock(mutex_); 106 | if (!is_running_) { 107 | return; 108 | } 109 | 110 | bool has_frame = false; 111 | 112 | winrt::com_ptr 113 | frame; 114 | auto hr = frame_pool_->TryGetNextFrame(frame.put()); 115 | if (SUCCEEDED(hr) && frame) { 116 | winrt::com_ptr< 117 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface> 118 | frame_surface; 119 | 120 | if (SUCCEEDED(frame->get_Surface(frame_surface.put()))) { 121 | last_frame_ = 122 | util::TryGetDXGIInterfaceFromObject(frame_surface); 123 | has_frame = !ShouldDropFrame(); 124 | } 125 | } 126 | 127 | if (needs_update_) { 128 | ABI::Windows::Graphics::SizeInt32 size; 129 | capture_item_->get_Size(&size); 130 | frame_pool_->Recreate( 131 | graphics_context_->device(), 132 | static_cast( 133 | kPixelFormat), 134 | kNumBuffers, size); 135 | needs_update_ = false; 136 | } 137 | 138 | if (has_frame && frame_available_) { 139 | frame_available_(); 140 | } 141 | } 142 | 143 | bool TextureBridge::ShouldDropFrame() { 144 | if (!frame_duration_.has_value()) { 145 | return false; 146 | } 147 | auto now = std::chrono::high_resolution_clock::now(); 148 | 149 | bool should_drop_frame = false; 150 | if (last_frame_timestamp_.has_value()) { 151 | auto diff = std::chrono::duration_cast( 152 | now - last_frame_timestamp_.value()); 153 | should_drop_frame = diff < frame_duration_.value(); 154 | } 155 | 156 | if (!should_drop_frame) { 157 | last_frame_timestamp_ = now; 158 | } 159 | return should_drop_frame; 160 | } 161 | 162 | void TextureBridge::NotifySurfaceSizeChanged() { 163 | const std::lock_guard lock(mutex_); 164 | needs_update_ = true; 165 | } 166 | 167 | void TextureBridge::SetFpsLimit(std::optional max_fps) { 168 | const std::lock_guard lock(mutex_); 169 | auto value = max_fps.value_or(0); 170 | if (value != 0) { 171 | frame_duration_ = FrameDuration(1000.0 / value); 172 | } else { 173 | frame_duration_.reset(); 174 | last_frame_timestamp_.reset(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /windows/texture_bridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "graphics_context.h" 13 | 14 | typedef struct { 15 | size_t width; 16 | size_t height; 17 | } Size; 18 | 19 | class TextureBridge { 20 | public: 21 | typedef std::function FrameAvailableCallback; 22 | typedef std::function SurfaceSizeChangedCallback; 23 | typedef std::chrono::duration FrameDuration; 24 | 25 | TextureBridge(GraphicsContext* graphics_context, 26 | ABI::Windows::UI::Composition::IVisual* visual); 27 | virtual ~TextureBridge(); 28 | 29 | bool Start(); 30 | void Stop(); 31 | 32 | void SetOnFrameAvailable(FrameAvailableCallback callback) { 33 | frame_available_ = std::move(callback); 34 | } 35 | 36 | void SetOnSurfaceSizeChanged(SurfaceSizeChangedCallback callback) { 37 | surface_size_changed_ = std::move(callback); 38 | } 39 | 40 | void NotifySurfaceSizeChanged(); 41 | void SetFpsLimit(std::optional max_fps); 42 | 43 | protected: 44 | bool is_running_ = false; 45 | 46 | const GraphicsContext* graphics_context_; 47 | std::mutex mutex_; 48 | std::optional frame_duration_ = std::nullopt; 49 | 50 | FrameAvailableCallback frame_available_; 51 | SurfaceSizeChangedCallback surface_size_changed_; 52 | std::atomic needs_update_ = false; 53 | winrt::com_ptr last_frame_; 54 | std::optional 55 | last_frame_timestamp_; 56 | 57 | winrt::com_ptr 58 | capture_item_; 59 | winrt::com_ptr 60 | frame_pool_; 61 | winrt::com_ptr 62 | capture_session_; 63 | 64 | EventRegistrationToken on_closed_token_ = {}; 65 | EventRegistrationToken on_frame_arrived_token_ = {}; 66 | 67 | virtual void StopInternal(); 68 | void OnFrameArrived(); 69 | bool ShouldDropFrame(); 70 | 71 | // corresponds to DXGI_FORMAT_B8G8R8A8_UNORM 72 | static constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX:: 73 | DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized; 74 | }; 75 | -------------------------------------------------------------------------------- /windows/texture_bridge_gpu.cc: -------------------------------------------------------------------------------- 1 | #include "texture_bridge_gpu.h" 2 | 3 | #include 4 | 5 | #include "util/direct3d11.interop.h" 6 | 7 | TextureBridgeGpu::TextureBridgeGpu( 8 | GraphicsContext* graphics_context, 9 | ABI::Windows::UI::Composition::IVisual* visual) 10 | : TextureBridge(graphics_context, visual) { 11 | surface_descriptor_.struct_size = sizeof(FlutterDesktopGpuSurfaceDescriptor); 12 | surface_descriptor_.format = 13 | kFlutterDesktopPixelFormatNone; // no format required for DXGI surfaces 14 | } 15 | 16 | void TextureBridgeGpu::ProcessFrame( 17 | winrt::com_ptr src_texture) { 18 | D3D11_TEXTURE2D_DESC desc; 19 | src_texture->GetDesc(&desc); 20 | 21 | const auto width = desc.Width; 22 | const auto height = desc.Height; 23 | 24 | EnsureSurface(width, height); 25 | 26 | auto device_context = graphics_context_->d3d_device_context(); 27 | 28 | device_context->CopyResource(surface_.get(), src_texture.get()); 29 | device_context->Flush(); 30 | } 31 | 32 | void TextureBridgeGpu::EnsureSurface(uint32_t width, uint32_t height) { 33 | if (!surface_ || surface_size_.width != width || 34 | surface_size_.height != height) { 35 | D3D11_TEXTURE2D_DESC dstDesc = {}; 36 | dstDesc.ArraySize = 1; 37 | dstDesc.MipLevels = 1; 38 | dstDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; 39 | dstDesc.CPUAccessFlags = 0; 40 | dstDesc.Format = static_cast(kPixelFormat); 41 | dstDesc.Width = width; 42 | dstDesc.Height = height; 43 | dstDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; 44 | dstDesc.SampleDesc.Count = 1; 45 | dstDesc.SampleDesc.Quality = 0; 46 | dstDesc.Usage = D3D11_USAGE_DEFAULT; 47 | 48 | surface_ = nullptr; 49 | if (!SUCCEEDED(graphics_context_->d3d_device()->CreateTexture2D( 50 | &dstDesc, nullptr, surface_.put()))) { 51 | std::cerr << "Creating intermediate texture failed" << std::endl; 52 | return; 53 | } 54 | 55 | HANDLE shared_handle; 56 | surface_.try_as(dxgi_surface_); 57 | assert(dxgi_surface_); 58 | dxgi_surface_->GetSharedHandle(&shared_handle); 59 | 60 | surface_descriptor_.handle = shared_handle; 61 | surface_descriptor_.width = surface_descriptor_.visible_width = width; 62 | surface_descriptor_.height = surface_descriptor_.visible_height = height; 63 | surface_descriptor_.release_context = surface_.get(); 64 | surface_descriptor_.release_callback = [](void* release_context) { 65 | auto texture = reinterpret_cast(release_context); 66 | texture->Release(); 67 | }; 68 | 69 | surface_size_ = {width, height}; 70 | } 71 | } 72 | 73 | const FlutterDesktopGpuSurfaceDescriptor* 74 | TextureBridgeGpu::GetSurfaceDescriptor(size_t width, size_t height) { 75 | const std::lock_guard lock(mutex_); 76 | 77 | if (!is_running_) { 78 | return nullptr; 79 | } 80 | 81 | if (last_frame_) { 82 | ProcessFrame(last_frame_); 83 | } 84 | 85 | if (surface_) { 86 | // Gets released in the SurfaceDescriptor's release callback. 87 | surface_->AddRef(); 88 | } 89 | 90 | return &surface_descriptor_; 91 | } 92 | 93 | void TextureBridgeGpu::StopInternal() { 94 | TextureBridge::StopInternal(); 95 | 96 | // For some reason, the destination surface needs to be recreated upon 97 | // resuming. Force |EnsureSurface| to create a new one by resetting it here. 98 | surface_ = nullptr; 99 | } 100 | -------------------------------------------------------------------------------- /windows/texture_bridge_gpu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "texture_bridge.h" 6 | 7 | class TextureBridgeGpu : public TextureBridge { 8 | public: 9 | TextureBridgeGpu(GraphicsContext* graphics_context, 10 | ABI::Windows::UI::Composition::IVisual* visual); 11 | 12 | const FlutterDesktopGpuSurfaceDescriptor* GetSurfaceDescriptor(size_t width, 13 | size_t height); 14 | 15 | protected: 16 | void StopInternal() override; 17 | 18 | private: 19 | FlutterDesktopGpuSurfaceDescriptor surface_descriptor_ = {}; 20 | Size surface_size_ = {0, 0}; 21 | winrt::com_ptr surface_{nullptr}; 22 | winrt::com_ptr dxgi_surface_; 23 | 24 | void ProcessFrame(winrt::com_ptr src_texture); 25 | void EnsureSurface(uint32_t width, uint32_t height); 26 | }; 27 | -------------------------------------------------------------------------------- /windows/util/composition.desktop.interop.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace util { 6 | 7 | winrt::com_ptr 8 | TryCreateDesktopWindowTarget( 9 | const winrt::com_ptr& 10 | compositor, 11 | HWND window) { 12 | namespace abi = ABI::Windows::UI::Composition::Desktop; 13 | auto interop = compositor.try_as(); 14 | 15 | winrt::com_ptr target; 16 | interop->CreateDesktopWindowTarget(window, true, target.put()); 17 | return target; 18 | } 19 | 20 | } // namespace util 21 | -------------------------------------------------------------------------------- /windows/util/d3dutil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | inline auto CreateD3DDevice(D3D_DRIVER_TYPE const type, 8 | winrt::com_ptr& device) { 9 | WINRT_ASSERT(!device); 10 | 11 | UINT flags = 12 | D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_VIDEO_SUPPORT; 13 | 14 | //#ifdef _DEBUG 15 | // flags |= D3D11_CREATE_DEVICE_DEBUG; 16 | //#endif 17 | 18 | return D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0, 19 | D3D11_SDK_VERSION, device.put(), nullptr, nullptr); 20 | } 21 | 22 | inline auto CreateD3DDevice() { 23 | winrt::com_ptr device; 24 | HRESULT hr = CreateD3DDevice(D3D_DRIVER_TYPE_HARDWARE, device); 25 | 26 | if (DXGI_ERROR_UNSUPPORTED == hr) { 27 | CreateD3DDevice(D3D_DRIVER_TYPE_WARP, device); 28 | } 29 | 30 | return device; 31 | } 32 | -------------------------------------------------------------------------------- /windows/util/direct3d11.interop.cc: -------------------------------------------------------------------------------- 1 | #include "direct3d11.interop.h" 2 | 3 | namespace util { 4 | 5 | namespace { 6 | 7 | typedef HRESULT(WINAPI* CreateDirect3D11DeviceFromDXGIDeviceFn)(IDXGIDevice*, 8 | LPVOID*); 9 | 10 | struct D3DFuncs { 11 | CreateDirect3D11DeviceFromDXGIDeviceFn CreateDirect3D11DeviceFromDXGIDevice = 12 | nullptr; 13 | 14 | D3DFuncs() { 15 | auto handle = GetModuleHandle(L"d3d11.dll"); 16 | if (!handle) { 17 | return; 18 | } 19 | 20 | CreateDirect3D11DeviceFromDXGIDevice = 21 | reinterpret_cast( 22 | GetProcAddress(handle, "CreateDirect3D11DeviceFromDXGIDevice")); 23 | } 24 | 25 | static const D3DFuncs& instance() { 26 | static D3DFuncs funcs; 27 | return funcs; 28 | } 29 | }; 30 | 31 | } // namespace 32 | 33 | HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice, 34 | IInspectable** graphicsDevice) { 35 | auto ptr = D3DFuncs::instance().CreateDirect3D11DeviceFromDXGIDevice; 36 | if (ptr) { 37 | return ptr(dxgiDevice, reinterpret_cast(graphicsDevice)); 38 | } 39 | 40 | return E_NOTIMPL; 41 | } 42 | 43 | } // namespace util 44 | -------------------------------------------------------------------------------- /windows/util/direct3d11.interop.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "dxgi.h" 9 | 10 | namespace Windows { 11 | namespace Graphics { 12 | namespace DirectX { 13 | namespace Direct3D11 { 14 | struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) 15 | IDirect3DDxgiInterfaceAccess : ::IUnknown { 16 | virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0; 17 | }; 18 | 19 | } // namespace Direct3D11 20 | } // namespace DirectX 21 | } // namespace Graphics 22 | } // namespace Windows 23 | 24 | namespace util { 25 | 26 | HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice, 27 | IInspectable** graphicsDevice); 28 | 29 | template 30 | auto GetDXGIInterfaceFromObject( 31 | winrt::Windows::Foundation::IInspectable const& object) { 32 | auto access = object.as< 33 | Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); 34 | winrt::com_ptr result; 35 | winrt::check_hresult( 36 | access->GetInterface(winrt::guid_of(), result.put_void())); 37 | return result; 38 | } 39 | 40 | template 41 | auto TryGetDXGIInterfaceFromObject(const winrt::com_ptr& object) { 42 | auto access = object.try_as< 43 | Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); 44 | winrt::com_ptr result; 45 | access->GetInterface(winrt::guid_of(), result.put_void()); 46 | return result; 47 | } 48 | 49 | } // namespace util 50 | -------------------------------------------------------------------------------- /windows/util/rohelper.cc: -------------------------------------------------------------------------------- 1 | // Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h}) 2 | // - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h 3 | // - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp 4 | // - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f 5 | // 6 | // Copyright 2018 The ANGLE Project Authors. 7 | // All rights reserved. 8 | // 9 | // Redistribution and use in source and binary forms, with or without 10 | // modification, are permitted provided that the following conditions 11 | // are met: 12 | // 13 | // Redistributions of source code must retain the above copyright 14 | // notice, this list of conditions and the following disclaimer. 15 | // 16 | // Redistributions in binary form must reproduce the above 17 | // copyright notice, this list of conditions and the following 18 | // disclaimer in the documentation and/or other materials provided 19 | // with the distribution. 20 | // 21 | // Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. 22 | // Ltd., nor the names of their contributors may be used to endorse 23 | // or promote products derived from this software without specific 24 | // prior written permission. 25 | // 26 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 29 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 30 | // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 31 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 33 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 36 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 | // POSSIBILITY OF SUCH DAMAGE. 38 | 39 | #include "rohelper.h" 40 | 41 | #include 42 | #include 43 | 44 | namespace rx { 45 | template 46 | bool AssignProcAddress(HMODULE comBaseModule, const char* name, T*& outProc) { 47 | outProc = reinterpret_cast(GetProcAddress(comBaseModule, name)); 48 | return *outProc != nullptr; 49 | } 50 | 51 | RoHelper::RoHelper(RO_INIT_TYPE init_type) 52 | : mFpWindowsCreateStringReference(nullptr), 53 | mFpGetActivationFactory(nullptr), 54 | mFpWindowsCompareStringOrdinal(nullptr), 55 | mFpCreateDispatcherQueueController(nullptr), 56 | mFpWindowsDeleteString(nullptr), 57 | mFpRoInitialize(nullptr), 58 | mFpRoUninitialize(nullptr), 59 | mWinRtAvailable(false), 60 | mComBaseModule(nullptr), 61 | mCoreMessagingModule(nullptr) { 62 | #ifdef WINUWP 63 | mFpWindowsCreateStringReference = &::WindowsCreateStringReference; 64 | mFpRoInitialize = &::RoInitialize; 65 | mFpRoUninitialize = &::RoUninitialize; 66 | mFpWindowsDeleteString = &::WindowsDeleteString; 67 | mFpGetActivationFactory = &::RoGetActivationFactory; 68 | mFpWindowsCompareStringOrdinal = &::WindowsCompareStringOrdinal; 69 | mFpCreateDispatcherQueueController = &::CreateDispatcherQueueController; 70 | mWinRtAvailable = true; 71 | #else 72 | 73 | mComBaseModule = LoadLibraryA("ComBase.dll"); 74 | 75 | if (mComBaseModule == nullptr) { 76 | return; 77 | } 78 | 79 | if (!AssignProcAddress(mComBaseModule, "WindowsCreateStringReference", 80 | mFpWindowsCreateStringReference)) { 81 | return; 82 | } 83 | 84 | if (!AssignProcAddress(mComBaseModule, "RoGetActivationFactory", 85 | mFpGetActivationFactory)) { 86 | return; 87 | } 88 | 89 | if (!AssignProcAddress(mComBaseModule, "WindowsCompareStringOrdinal", 90 | mFpWindowsCompareStringOrdinal)) { 91 | return; 92 | } 93 | 94 | if (!AssignProcAddress(mComBaseModule, "WindowsDeleteString", 95 | mFpWindowsDeleteString)) { 96 | return; 97 | } 98 | 99 | if (!AssignProcAddress(mComBaseModule, "RoInitialize", mFpRoInitialize)) { 100 | return; 101 | } 102 | 103 | if (!AssignProcAddress(mComBaseModule, "RoUninitialize", mFpRoUninitialize)) { 104 | return; 105 | } 106 | 107 | mCoreMessagingModule = LoadLibraryA("coremessaging.dll"); 108 | 109 | if (mCoreMessagingModule == nullptr) { 110 | return; 111 | } 112 | 113 | if (!AssignProcAddress(mCoreMessagingModule, 114 | "CreateDispatcherQueueController", 115 | mFpCreateDispatcherQueueController)) { 116 | return; 117 | } 118 | 119 | auto result = RoInitialize(init_type); 120 | 121 | if (SUCCEEDED(result) || result == S_FALSE || result == RPC_E_CHANGED_MODE) { 122 | mWinRtAvailable = true; 123 | } 124 | #endif 125 | } 126 | 127 | RoHelper::~RoHelper() { 128 | #ifndef WINUWP 129 | if (mWinRtAvailable) { 130 | RoUninitialize(); 131 | } 132 | 133 | if (mCoreMessagingModule != nullptr) { 134 | FreeLibrary(mCoreMessagingModule); 135 | mCoreMessagingModule = nullptr; 136 | } 137 | 138 | if (mComBaseModule != nullptr) { 139 | FreeLibrary(mComBaseModule); 140 | mComBaseModule = nullptr; 141 | } 142 | #endif 143 | } 144 | 145 | bool RoHelper::WinRtAvailable() const { return mWinRtAvailable; } 146 | 147 | bool RoHelper::SupportedWindowsRelease() { 148 | if (!mWinRtAvailable) { 149 | return false; 150 | } 151 | 152 | HSTRING className, contractName; 153 | HSTRING_HEADER classNameHeader, contractNameHeader; 154 | boolean isSupported = false; 155 | 156 | HRESULT hr = GetStringReference( 157 | RuntimeClass_Windows_Foundation_Metadata_ApiInformation, &className, 158 | &classNameHeader); 159 | 160 | if (FAILED(hr)) { 161 | return !!isSupported; 162 | } 163 | 164 | Microsoft::WRL::ComPtr< 165 | ABI::Windows::Foundation::Metadata::IApiInformationStatics> 166 | api; 167 | 168 | hr = GetActivationFactory( 169 | className, 170 | __uuidof(ABI::Windows::Foundation::Metadata::IApiInformationStatics), 171 | &api); 172 | 173 | if (FAILED(hr)) { 174 | return !!isSupported; 175 | } 176 | 177 | hr = GetStringReference(L"Windows.Foundation.UniversalApiContract", 178 | &contractName, &contractNameHeader); 179 | if (FAILED(hr)) { 180 | return !!isSupported; 181 | } 182 | 183 | api->IsApiContractPresentByMajor(contractName, 6, &isSupported); 184 | 185 | return !!isSupported; 186 | } 187 | 188 | HRESULT RoHelper::GetStringReference(PCWSTR source, HSTRING* act, 189 | HSTRING_HEADER* header) { 190 | if (!mWinRtAvailable) { 191 | return E_FAIL; 192 | } 193 | 194 | const wchar_t* str = static_cast(source); 195 | 196 | unsigned int length; 197 | HRESULT hr = SizeTToUInt32(::wcslen(str), &length); 198 | if (FAILED(hr)) { 199 | return hr; 200 | } 201 | 202 | return mFpWindowsCreateStringReference(source, length, header, act); 203 | } 204 | 205 | HRESULT RoHelper::GetActivationFactory(const HSTRING act, 206 | const IID& interfaceId, void** fac) { 207 | if (!mWinRtAvailable) { 208 | return E_FAIL; 209 | } 210 | auto hr = mFpGetActivationFactory(act, interfaceId, fac); 211 | return hr; 212 | } 213 | 214 | HRESULT RoHelper::WindowsCompareStringOrdinal(HSTRING one, HSTRING two, 215 | int* result) { 216 | if (!mWinRtAvailable) { 217 | return E_FAIL; 218 | } 219 | return mFpWindowsCompareStringOrdinal(one, two, result); 220 | } 221 | 222 | HRESULT RoHelper::CreateDispatcherQueueController( 223 | DispatcherQueueOptions options, 224 | ABI::Windows::System::IDispatcherQueueController** 225 | dispatcherQueueController) { 226 | if (!mWinRtAvailable) { 227 | return E_FAIL; 228 | } 229 | return mFpCreateDispatcherQueueController(options, dispatcherQueueController); 230 | } 231 | 232 | HRESULT RoHelper::WindowsDeleteString(HSTRING one) { 233 | if (!mWinRtAvailable) { 234 | return E_FAIL; 235 | } 236 | return mFpWindowsDeleteString(one); 237 | } 238 | 239 | HRESULT RoHelper::RoInitialize(RO_INIT_TYPE type) { 240 | return mFpRoInitialize(type); 241 | } 242 | 243 | void RoHelper::RoUninitialize() { mFpRoUninitialize(); } 244 | } // namespace rx 245 | -------------------------------------------------------------------------------- /windows/util/rohelper.h: -------------------------------------------------------------------------------- 1 | // Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h}) 2 | // - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h 3 | // - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp 4 | // - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f 5 | // 6 | // Copyright 2018 The ANGLE Project Authors. 7 | // All rights reserved. 8 | // 9 | // Redistribution and use in source and binary forms, with or without 10 | // modification, are permitted provided that the following conditions 11 | // are met: 12 | // 13 | // Redistributions of source code must retain the above copyright 14 | // notice, this list of conditions and the following disclaimer. 15 | // 16 | // Redistributions in binary form must reproduce the above 17 | // copyright notice, this list of conditions and the following 18 | // disclaimer in the documentation and/or other materials provided 19 | // with the distribution. 20 | // 21 | // Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. 22 | // Ltd., nor the names of their contributors may be used to endorse 23 | // or promote products derived from this software without specific 24 | // prior written permission. 25 | // 26 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 29 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 30 | // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 31 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 33 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 36 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 | // POSSIBILITY OF SUCH DAMAGE. 38 | 39 | #pragma once 40 | 41 | #include 42 | #include 43 | #include 44 | 45 | namespace rx { 46 | class RoHelper { 47 | public: 48 | RoHelper(RO_INIT_TYPE init_type); 49 | ~RoHelper(); 50 | bool WinRtAvailable() const; 51 | bool SupportedWindowsRelease(); 52 | HRESULT GetStringReference(PCWSTR source, HSTRING* act, 53 | HSTRING_HEADER* header); 54 | HRESULT GetActivationFactory(const HSTRING act, const IID& interfaceId, 55 | void** fac); 56 | HRESULT WindowsCompareStringOrdinal(HSTRING one, HSTRING two, int* result); 57 | HRESULT CreateDispatcherQueueController( 58 | DispatcherQueueOptions options, 59 | ABI::Windows::System::IDispatcherQueueController** 60 | dispatcherQueueController); 61 | HRESULT WindowsDeleteString(HSTRING one); 62 | HRESULT RoInitialize(RO_INIT_TYPE type); 63 | void RoUninitialize(); 64 | 65 | private: 66 | using WindowsCreateStringReference_ = HRESULT __stdcall(PCWSTR, UINT32, 67 | HSTRING_HEADER*, 68 | HSTRING*); 69 | 70 | using GetActivationFactory_ = HRESULT __stdcall(HSTRING, REFIID, void**); 71 | 72 | using WindowsCompareStringOrginal_ = HRESULT __stdcall(HSTRING, HSTRING, 73 | int*); 74 | 75 | using WindowsDeleteString_ = HRESULT __stdcall(HSTRING); 76 | 77 | using CreateDispatcherQueueController_ = 78 | HRESULT __stdcall(DispatcherQueueOptions, 79 | ABI::Windows::System::IDispatcherQueueController**); 80 | 81 | using RoInitialize_ = HRESULT __stdcall(RO_INIT_TYPE); 82 | using RoUninitialize_ = void __stdcall(); 83 | 84 | WindowsCreateStringReference_* mFpWindowsCreateStringReference; 85 | GetActivationFactory_* mFpGetActivationFactory; 86 | WindowsCompareStringOrginal_* mFpWindowsCompareStringOrdinal; 87 | CreateDispatcherQueueController_* mFpCreateDispatcherQueueController; 88 | WindowsDeleteString_* mFpWindowsDeleteString; 89 | RoInitialize_* mFpRoInitialize; 90 | RoUninitialize_* mFpRoUninitialize; 91 | 92 | bool mWinRtAvailable; 93 | 94 | HMODULE mComBaseModule; 95 | HMODULE mCoreMessagingModule; 96 | }; 97 | } // namespace rx 98 | -------------------------------------------------------------------------------- /windows/util/string_converter.cc: -------------------------------------------------------------------------------- 1 | #include "string_converter.h" 2 | 3 | #include 4 | 5 | namespace util { 6 | std::string Utf8FromUtf16(std::wstring_view utf16_string) { 7 | if (utf16_string.empty()) { 8 | return std::string(); 9 | } 10 | 11 | auto src_length = static_cast(utf16_string.size()); 12 | int target_length = 13 | ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), 14 | src_length, nullptr, 0, nullptr, nullptr); 15 | 16 | std::string utf8_string; 17 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 18 | return utf8_string; 19 | } 20 | utf8_string.resize(target_length); 21 | int converted_length = ::WideCharToMultiByte( 22 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), src_length, 23 | utf8_string.data(), target_length, nullptr, nullptr); 24 | if (converted_length == 0) { 25 | return std::string(); 26 | } 27 | return utf8_string; 28 | } 29 | 30 | std::wstring Utf16FromUtf8(std::string_view utf8_string) { 31 | if (utf8_string.empty()) { 32 | return std::wstring(); 33 | } 34 | 35 | auto src_length = static_cast(utf8_string.size()); 36 | int target_length = 37 | ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), 38 | src_length, nullptr, 0); 39 | 40 | std::wstring utf16_string; 41 | if (target_length <= 0 || target_length > utf16_string.max_size()) { 42 | return utf16_string; 43 | } 44 | utf16_string.resize(target_length); 45 | int converted_length = 46 | ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), 47 | src_length, utf16_string.data(), target_length); 48 | if (converted_length == 0) { 49 | return std::wstring(); 50 | } 51 | return utf16_string; 52 | } 53 | 54 | } // namespace util 55 | -------------------------------------------------------------------------------- /windows/util/string_converter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace util { 6 | std::string Utf8FromUtf16(std::wstring_view utf16_string); 7 | std::wstring Utf16FromUtf8(std::string_view utf8_string); 8 | } // namespace util 9 | -------------------------------------------------------------------------------- /windows/webview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | class WebviewHost; 12 | 13 | enum class WebviewLoadingState { None, Loading, NavigationCompleted }; 14 | 15 | enum class WebviewPointerButton { None, Primary, Secondary, Tertiary }; 16 | 17 | enum class WebviewPointerEventKind { Activate, Down, Enter, Leave, Up, Update }; 18 | 19 | enum class WebviewDownloadEventKind { 20 | DownloadStarted, 21 | DownloadCompleted, 22 | DownloadProgress 23 | }; 24 | 25 | enum class WebviewPermissionKind { 26 | Unknown, 27 | Microphone, 28 | Camera, 29 | GeoLocation, 30 | Notifications, 31 | OtherSensors, 32 | ClipboardRead 33 | }; 34 | 35 | enum class WebviewPermissionState { Default, Allow, Deny }; 36 | 37 | enum class WebviewPopupWindowPolicy { Allow, Deny, ShowInSameWindow }; 38 | 39 | enum class WebviewHostResourceAccessKind { Deny, Allow, DenyCors }; 40 | 41 | struct WebviewHistoryChanged { 42 | BOOL can_go_back; 43 | BOOL can_go_forward; 44 | }; 45 | 46 | struct WebviewDownloadEvent { 47 | WebviewDownloadEventKind kind; 48 | std::string url; 49 | std::string resultFilePath; 50 | INT64 bytesReceived; 51 | INT64 totalBytesToReceive; 52 | }; 53 | 54 | struct VirtualKeyState { 55 | public: 56 | inline void set_isLeftButtonDown(bool is_down) { 57 | set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: 58 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON, 59 | is_down); 60 | } 61 | 62 | inline void set_isRightButtonDown(bool is_down) { 63 | set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: 64 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON, 65 | is_down); 66 | } 67 | 68 | inline void set_isMiddleButtonDown(bool is_down) { 69 | set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: 70 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_MIDDLE_BUTTON, 71 | is_down); 72 | } 73 | 74 | inline COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state() const { return state_; } 75 | 76 | private: 77 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state_ = 78 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: 79 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; 80 | 81 | inline void set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS key, bool flag) { 82 | if (flag) { 83 | state_ |= key; 84 | } else { 85 | state_ &= ~key; 86 | } 87 | } 88 | }; 89 | 90 | struct EventRegistrations { 91 | EventRegistrationToken source_changed_token_{}; 92 | EventRegistrationToken content_loading_token_{}; 93 | EventRegistrationToken navigation_completed_token_{}; 94 | EventRegistrationToken history_changed_token_{}; 95 | EventRegistrationToken document_title_changed_token_{}; 96 | EventRegistrationToken cursor_changed_token_{}; 97 | EventRegistrationToken got_focus_token_{}; 98 | EventRegistrationToken lost_focus_token_{}; 99 | EventRegistrationToken web_message_received_token_{}; 100 | EventRegistrationToken permission_requested_token_{}; 101 | EventRegistrationToken devtools_protocol_event_token_{}; 102 | EventRegistrationToken new_windows_requested_token_{}; 103 | EventRegistrationToken contains_fullscreen_element_changed_token_{}; 104 | EventRegistrationToken download_starting_token_{}; 105 | EventRegistrationToken download_bytes_received_token_{}; 106 | EventRegistrationToken download_state_changed_token_{}; 107 | }; 108 | 109 | class Webview { 110 | public: 111 | friend class WebviewHost; 112 | 113 | typedef std::function UrlChangedCallback; 114 | typedef std::function LoadingStateChangedCallback; 115 | typedef std::function 116 | OnLoadErrorCallback; 117 | typedef std::function HistoryChangedCallback; 118 | typedef std::function DevtoolsProtocolEventCallback; 119 | typedef std::function DocumentTitleChangedCallback; 120 | typedef std::function 121 | SurfaceSizeChangedCallback; 122 | typedef std::function CursorChangedCallback; 123 | typedef std::function FocusChangedCallback; 124 | typedef std::function 125 | AddScriptToExecuteOnDocumentCreatedCallback; 126 | typedef std::function ScriptExecutedCallback; 127 | typedef std::function WebMessageReceivedCallback; 128 | typedef std::function 129 | WebviewPermissionRequestedCompleter; 130 | typedef std::function 133 | PermissionRequestedCallback; 134 | typedef std::function 135 | ContainsFullScreenElementChangedCallback; 136 | typedef std::function DownloadEventCallback; 137 | 138 | ~Webview(); 139 | 140 | ABI::Windows::UI::Composition::IVisual* const surface() { 141 | return surface_.get(); 142 | } 143 | 144 | bool IsValid() { return is_valid_; } 145 | 146 | void SetSurfaceSize(size_t width, size_t height, float scale_factor); 147 | void SetCursorPos(double x, double y); 148 | void SetPointerUpdate(int32_t pointer, WebviewPointerEventKind eventKind, 149 | double x, double y, double size, double pressure); 150 | void SetPointerButtonState(WebviewPointerButton button, bool isDown); 151 | void SetScrollDelta(double delta_x, double delta_y); 152 | void LoadUrl(const std::string& url); 153 | void LoadStringContent(const std::string& content); 154 | bool Stop(); 155 | bool Reload(); 156 | bool GoBack(); 157 | bool GoForward(); 158 | void AddScriptToExecuteOnDocumentCreated( 159 | const std::string& script, 160 | AddScriptToExecuteOnDocumentCreatedCallback callback); 161 | void RemoveScriptToExecuteOnDocumentCreated(const std::string& script_id); 162 | void ExecuteScript(const std::string& script, 163 | ScriptExecutedCallback callback); 164 | bool PostWebMessage(const std::string& json); 165 | bool ClearCookies(); 166 | bool ClearCache(); 167 | bool SetCacheDisabled(bool disabled); 168 | void SetPopupWindowPolicy(WebviewPopupWindowPolicy policy); 169 | bool SetUserAgent(const std::string& user_agent); 170 | bool OpenDevTools(); 171 | bool SetBackgroundColor(int32_t color); 172 | bool SetZoomFactor(double factor); 173 | bool Suspend(); 174 | bool Resume(); 175 | 176 | bool SetVirtualHostNameMapping(const std::string& hostName, 177 | const std::string& path, 178 | WebviewHostResourceAccessKind accessKind); 179 | bool ClearVirtualHostNameMapping(const std::string& hostName); 180 | 181 | void UpdateDownloadProgress(ICoreWebView2DownloadOperation* download); 182 | 183 | void OnUrlChanged(UrlChangedCallback callback) { 184 | url_changed_callback_ = std::move(callback); 185 | } 186 | 187 | void OnLoadError(OnLoadErrorCallback callback) { 188 | on_load_error_callback_ = std::move(callback); 189 | } 190 | 191 | void OnLoadingStateChanged(LoadingStateChangedCallback callback) { 192 | loading_state_changed_callback_ = std::move(callback); 193 | } 194 | 195 | void OnDownloadEvent(DownloadEventCallback callback) { 196 | download_event_callback_ = std::move(callback); 197 | } 198 | 199 | void OnHistoryChanged(HistoryChangedCallback callback) { 200 | history_changed_callback_ = std::move(callback); 201 | } 202 | 203 | void OnSurfaceSizeChanged(SurfaceSizeChangedCallback callback) { 204 | surface_size_changed_callback_ = std::move(callback); 205 | } 206 | 207 | void OnDocumentTitleChanged(DocumentTitleChangedCallback callback) { 208 | document_title_changed_callback_ = std::move(callback); 209 | } 210 | 211 | void OnCursorChanged(CursorChangedCallback callback) { 212 | cursor_changed_callback_ = std::move(callback); 213 | } 214 | 215 | void OnFocusChanged(FocusChangedCallback callback) { 216 | focus_changed_callback_ = std::move(callback); 217 | } 218 | 219 | void OnWebMessageReceived(WebMessageReceivedCallback callback) { 220 | web_message_received_callback_ = std::move(callback); 221 | } 222 | 223 | void OnPermissionRequested(PermissionRequestedCallback callback) { 224 | permission_requested_callback_ = std::move(callback); 225 | } 226 | 227 | void OnDevtoolsProtocolEvent(DevtoolsProtocolEventCallback callback) { 228 | devtools_protocol_event_callback_ = std::move(callback); 229 | } 230 | 231 | void OnContainsFullScreenElementChanged( 232 | ContainsFullScreenElementChangedCallback callback) { 233 | contains_fullscreen_element_changed_callback_ = std::move(callback); 234 | } 235 | 236 | private: 237 | HWND hwnd_; 238 | bool owns_window_; 239 | bool is_valid_ = false; 240 | float scale_factor_ = 1.0; 241 | wil::com_ptr composition_controller_; 242 | wil::com_ptr webview_controller_; 243 | wil::com_ptr webview_; 244 | wil::com_ptr 245 | devtools_protocol_event_receiver_; 246 | wil::com_ptr settings2_; 247 | POINT last_cursor_pos_ = {0, 0}; 248 | VirtualKeyState virtual_keys_; 249 | WebviewPopupWindowPolicy popup_window_policy_ = 250 | WebviewPopupWindowPolicy::Allow; 251 | 252 | winrt::com_ptr surface_; 253 | winrt::com_ptr 254 | window_target_; 255 | 256 | WebviewHost* host_; 257 | EventRegistrations event_registrations_{}; 258 | 259 | UrlChangedCallback url_changed_callback_; 260 | LoadingStateChangedCallback loading_state_changed_callback_; 261 | DownloadEventCallback download_event_callback_; 262 | OnLoadErrorCallback on_load_error_callback_; 263 | HistoryChangedCallback history_changed_callback_; 264 | DocumentTitleChangedCallback document_title_changed_callback_; 265 | SurfaceSizeChangedCallback surface_size_changed_callback_; 266 | CursorChangedCallback cursor_changed_callback_; 267 | FocusChangedCallback focus_changed_callback_; 268 | WebMessageReceivedCallback web_message_received_callback_; 269 | PermissionRequestedCallback permission_requested_callback_; 270 | DevtoolsProtocolEventCallback devtools_protocol_event_callback_; 271 | ContainsFullScreenElementChangedCallback 272 | contains_fullscreen_element_changed_callback_; 273 | 274 | Webview( 275 | wil::com_ptr composition_controller, 276 | WebviewHost* host, HWND hwnd, bool owns_window, bool offscreen_only); 277 | 278 | bool CreateSurface( 279 | winrt::com_ptr compositor, 280 | HWND hwnd, bool offscreen_only); 281 | void RegisterEventHandlers(); 282 | void EnableSecurityUpdates(); 283 | void SendScroll(double offset, bool horizontal); 284 | }; 285 | -------------------------------------------------------------------------------- /windows/webview_bridge.cc: -------------------------------------------------------------------------------- 1 | #include "webview_bridge.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "texture_bridge_gpu.h" 9 | 10 | namespace { 11 | constexpr auto kErrorInvalidArgs = "invalidArguments"; 12 | 13 | constexpr auto kMethodLoadUrl = "loadUrl"; 14 | constexpr auto kMethodLoadStringContent = "loadStringContent"; 15 | constexpr auto kMethodReload = "reload"; 16 | constexpr auto kMethodStop = "stop"; 17 | constexpr auto kMethodGoBack = "goBack"; 18 | constexpr auto kMethodGoForward = "goForward"; 19 | constexpr auto kMethodAddScriptToExecuteOnDocumentCreated = 20 | "addScriptToExecuteOnDocumentCreated"; 21 | constexpr auto kMethodRemoveScriptToExecuteOnDocumentCreated = 22 | "removeScriptToExecuteOnDocumentCreated"; 23 | constexpr auto kMethodExecuteScript = "executeScript"; 24 | constexpr auto kMethodPostWebMessage = "postWebMessage"; 25 | constexpr auto kMethodSetSize = "setSize"; 26 | constexpr auto kMethodSetCursorPos = "setCursorPos"; 27 | constexpr auto kMethodSetPointerUpdate = "setPointerUpdate"; 28 | constexpr auto kMethodSetPointerButton = "setPointerButton"; 29 | constexpr auto kMethodSetScrollDelta = "setScrollDelta"; 30 | constexpr auto kMethodSetUserAgent = "setUserAgent"; 31 | constexpr auto kMethodSetBackgroundColor = "setBackgroundColor"; 32 | constexpr auto kMethodSetZoomFactor = "setZoomFactor"; 33 | constexpr auto kMethodOpenDevTools = "openDevTools"; 34 | constexpr auto kMethodSuspend = "suspend"; 35 | constexpr auto kMethodResume = "resume"; 36 | constexpr auto kMethodSetVirtualHostNameMapping = "setVirtualHostNameMapping"; 37 | constexpr auto kMethodClearVirtualHostNameMapping = 38 | "clearVirtualHostNameMapping"; 39 | constexpr auto kMethodClearCookies = "clearCookies"; 40 | constexpr auto kMethodClearCache = "clearCache"; 41 | constexpr auto kMethodSetCacheDisabled = "setCacheDisabled"; 42 | constexpr auto kMethodSetPopupWindowPolicy = "setPopupWindowPolicy"; 43 | constexpr auto kMethodSetFpsLimit = "setFpsLimit"; 44 | 45 | constexpr auto kEventType = "type"; 46 | constexpr auto kEventValue = "value"; 47 | 48 | constexpr auto kErrorNotSupported = "not_supported"; 49 | constexpr auto kScriptFailed = "script_failed"; 50 | constexpr auto kMethodFailed = "method_failed"; 51 | 52 | static const std::optional> GetPointFromArgs( 53 | const flutter::EncodableValue* args) { 54 | const flutter::EncodableList* list = 55 | std::get_if(args); 56 | if (!list || list->size() != 2) { 57 | return std::nullopt; 58 | } 59 | const auto x = std::get_if(&(*list)[0]); 60 | const auto y = std::get_if(&(*list)[1]); 61 | if (!x || !y) { 62 | return std::nullopt; 63 | } 64 | return std::make_pair(*x, *y); 65 | } 66 | 67 | static const std::optional> 68 | GetPointAndScaleFactorFromArgs(const flutter::EncodableValue* args) { 69 | const flutter::EncodableList* list = 70 | std::get_if(args); 71 | if (!list || list->size() != 3) { 72 | return std::nullopt; 73 | } 74 | const auto x = std::get_if(&(*list)[0]); 75 | const auto y = std::get_if(&(*list)[1]); 76 | const auto z = std::get_if(&(*list)[2]); 77 | if (!x || !y || !z) { 78 | return std::nullopt; 79 | } 80 | return std::make_tuple(*x, *y, *z); 81 | } 82 | 83 | static const std::string& GetCursorName(const HCURSOR cursor) { 84 | // The cursor names correspond to the Flutter Engine names: 85 | // in shell/platform/windows/flutter_window_win32.cc 86 | static const std::string kDefaultCursorName = "basic"; 87 | static const std::pair mappings[] = { 88 | {"allScroll", IDC_SIZEALL}, 89 | {kDefaultCursorName, IDC_ARROW}, 90 | {"click", IDC_HAND}, 91 | {"forbidden", IDC_NO}, 92 | {"help", IDC_HELP}, 93 | {"move", IDC_SIZEALL}, 94 | {"none", nullptr}, 95 | {"noDrop", IDC_NO}, 96 | {"precise", IDC_CROSS}, 97 | {"progress", IDC_APPSTARTING}, 98 | {"text", IDC_IBEAM}, 99 | {"resizeColumn", IDC_SIZEWE}, 100 | {"resizeDown", IDC_SIZENS}, 101 | {"resizeDownLeft", IDC_SIZENESW}, 102 | {"resizeDownRight", IDC_SIZENWSE}, 103 | {"resizeLeft", IDC_SIZEWE}, 104 | {"resizeLeftRight", IDC_SIZEWE}, 105 | {"resizeRight", IDC_SIZEWE}, 106 | {"resizeRow", IDC_SIZENS}, 107 | {"resizeUp", IDC_SIZENS}, 108 | {"resizeUpDown", IDC_SIZENS}, 109 | {"resizeUpLeft", IDC_SIZENWSE}, 110 | {"resizeUpRight", IDC_SIZENESW}, 111 | {"resizeUpLeftDownRight", IDC_SIZENWSE}, 112 | {"resizeUpRightDownLeft", IDC_SIZENESW}, 113 | {"wait", IDC_WAIT}, 114 | }; 115 | 116 | static std::map cursors; 117 | static bool initialized = false; 118 | 119 | if (!initialized) { 120 | initialized = true; 121 | for (const auto& pair : mappings) { 122 | HCURSOR cursor_handle = LoadCursor(nullptr, pair.second); 123 | if (cursor_handle) { 124 | cursors[cursor_handle] = pair.first; 125 | } 126 | } 127 | } 128 | 129 | const auto it = cursors.find(cursor); 130 | if (it != cursors.end()) { 131 | return it->second; 132 | } 133 | return kDefaultCursorName; 134 | } 135 | 136 | } // namespace 137 | 138 | WebviewBridge::WebviewBridge(flutter::BinaryMessenger* messenger, 139 | flutter::TextureRegistrar* texture_registrar, 140 | GraphicsContext* graphics_context, 141 | std::unique_ptr webview) 142 | : webview_(std::move(webview)), texture_registrar_(texture_registrar) { 143 | texture_bridge_ = 144 | std::make_unique(graphics_context, webview_->surface()); 145 | 146 | flutter_texture_ = 147 | std::make_unique(flutter::GpuSurfaceTexture( 148 | kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle, 149 | [bridge = static_cast(texture_bridge_.get())]( 150 | size_t width, 151 | size_t height) -> const FlutterDesktopGpuSurfaceDescriptor* { 152 | return bridge->GetSurfaceDescriptor(width, height); 153 | })); 154 | 155 | texture_id_ = texture_registrar->RegisterTexture(flutter_texture_.get()); 156 | texture_bridge_->SetOnFrameAvailable( 157 | [this]() { texture_registrar_->MarkTextureFrameAvailable(texture_id_); }); 158 | // texture_bridge_->SetOnSurfaceSizeChanged([this](Size size) { 159 | // webview_->SetSurfaceSize(size.width, size.height); 160 | //}); 161 | 162 | const auto method_channel_name = 163 | std::format("io.jns.webview.win/{}", texture_id_); 164 | method_channel_ = 165 | std::make_unique>( 166 | messenger, method_channel_name, 167 | &flutter::StandardMethodCodec::GetInstance()); 168 | method_channel_->SetMethodCallHandler([this](const auto& call, auto result) { 169 | HandleMethodCall(call, std::move(result)); 170 | }); 171 | 172 | const auto event_channel_name = 173 | std::format("io.jns.webview.win/{}/events", texture_id_); 174 | event_channel_ = 175 | std::make_unique>( 176 | messenger, event_channel_name, 177 | &flutter::StandardMethodCodec::GetInstance()); 178 | 179 | auto handler = std::make_unique< 180 | flutter::StreamHandlerFunctions>( 181 | [this](const flutter::EncodableValue* arguments, 182 | std::unique_ptr>&& 183 | events) { 184 | event_sink_ = std::move(events); 185 | RegisterEventHandlers(); 186 | return nullptr; 187 | }, 188 | [this](const flutter::EncodableValue* arguments) { 189 | event_sink_ = nullptr; 190 | return nullptr; 191 | }); 192 | 193 | event_channel_->SetStreamHandler(std::move(handler)); 194 | } 195 | 196 | WebviewBridge::~WebviewBridge() { 197 | method_channel_->SetMethodCallHandler(nullptr); 198 | texture_registrar_->UnregisterTexture(texture_id_); 199 | } 200 | 201 | void WebviewBridge::RegisterEventHandlers() { 202 | webview_->OnUrlChanged([this](const std::string& url) { 203 | const auto event = flutter::EncodableValue(flutter::EncodableMap{ 204 | {flutter::EncodableValue(kEventType), 205 | flutter::EncodableValue("urlChanged")}, 206 | {flutter::EncodableValue(kEventValue), flutter::EncodableValue(url)}, 207 | }); 208 | EmitEvent(event); 209 | }); 210 | 211 | webview_->OnLoadError([this](COREWEBVIEW2_WEB_ERROR_STATUS web_status) { 212 | const auto event = flutter::EncodableValue(flutter::EncodableMap{ 213 | {flutter::EncodableValue(kEventType), 214 | flutter::EncodableValue("onLoadError")}, 215 | {flutter::EncodableValue(kEventValue), 216 | flutter::EncodableValue(static_cast(web_status))}, 217 | }); 218 | EmitEvent(event); 219 | }); 220 | 221 | webview_->OnLoadingStateChanged([this](WebviewLoadingState state) { 222 | const auto event = flutter::EncodableValue(flutter::EncodableMap{ 223 | {flutter::EncodableValue(kEventType), 224 | flutter::EncodableValue("loadingStateChanged")}, 225 | {flutter::EncodableValue(kEventValue), 226 | flutter::EncodableValue(static_cast(state))}, 227 | }); 228 | EmitEvent(event); 229 | }); 230 | 231 | webview_->OnDownloadEvent([this](WebviewDownloadEvent webviewDownloadEvent) { 232 | const auto event = flutter::EncodableValue(flutter::EncodableMap{ 233 | {flutter::EncodableValue(kEventType), 234 | flutter::EncodableValue("downloadEvent")}, 235 | {flutter::EncodableValue(kEventValue), 236 | flutter::EncodableValue(flutter::EncodableMap{ 237 | {flutter::EncodableValue("kind"), 238 | flutter::EncodableValue( 239 | static_cast(webviewDownloadEvent.kind))}, 240 | {flutter::EncodableValue("url"), 241 | flutter::EncodableValue(webviewDownloadEvent.url)}, 242 | {flutter::EncodableValue("resultFilePath"), 243 | flutter::EncodableValue(webviewDownloadEvent.resultFilePath)}, 244 | {flutter::EncodableValue("bytesReceived"), 245 | flutter::EncodableValue(webviewDownloadEvent.bytesReceived)}, 246 | {flutter::EncodableValue("totalBytesToReceive"), 247 | flutter::EncodableValue( 248 | webviewDownloadEvent.totalBytesToReceive)}, 249 | })}}); 250 | EmitEvent(event); 251 | }); 252 | 253 | webview_->OnHistoryChanged([this](WebviewHistoryChanged historyChanged) { 254 | const auto event = flutter::EncodableValue(flutter::EncodableMap{ 255 | {flutter::EncodableValue(kEventType), 256 | flutter::EncodableValue("historyChanged")}, 257 | {flutter::EncodableValue(kEventValue), 258 | flutter::EncodableValue(flutter::EncodableMap{ 259 | {flutter::EncodableValue("canGoBack"), 260 | flutter::EncodableValue( 261 | static_cast(historyChanged.can_go_back))}, 262 | {flutter::EncodableValue("canGoForward"), 263 | flutter::EncodableValue( 264 | static_cast(historyChanged.can_go_forward))}, 265 | })}, 266 | }); 267 | EmitEvent(event); 268 | }); 269 | 270 | webview_->OnDevtoolsProtocolEvent([this](const std::string& json) { 271 | const auto event = flutter::EncodableValue(flutter::EncodableMap{ 272 | {flutter::EncodableValue(kEventType), 273 | flutter::EncodableValue("securityStateChanged")}, 274 | {flutter::EncodableValue(kEventValue), flutter::EncodableValue(json)}}); 275 | EmitEvent(event); 276 | }); 277 | 278 | webview_->OnDocumentTitleChanged([this](const std::string& title) { 279 | const auto event = flutter::EncodableValue(flutter::EncodableMap{ 280 | {flutter::EncodableValue(kEventType), 281 | flutter::EncodableValue("titleChanged")}, 282 | {flutter::EncodableValue(kEventValue), flutter::EncodableValue(title)}, 283 | }); 284 | EmitEvent(event); 285 | }); 286 | 287 | webview_->OnSurfaceSizeChanged([this](size_t width, size_t height) { 288 | texture_bridge_->NotifySurfaceSizeChanged(); 289 | }); 290 | 291 | webview_->OnCursorChanged([this](const HCURSOR cursor) { 292 | const auto& name = GetCursorName(cursor); 293 | const auto event = flutter::EncodableValue( 294 | flutter::EncodableMap{{flutter::EncodableValue(kEventType), 295 | flutter::EncodableValue("cursorChanged")}, 296 | {flutter::EncodableValue(kEventValue), name}}); 297 | EmitEvent(event); 298 | }); 299 | 300 | webview_->OnWebMessageReceived([this](const std::string& message) { 301 | const auto event = flutter::EncodableValue( 302 | flutter::EncodableMap{{flutter::EncodableValue(kEventType), 303 | flutter::EncodableValue("webMessageReceived")}, 304 | {flutter::EncodableValue(kEventValue), message}}); 305 | EmitEvent(event); 306 | }); 307 | 308 | webview_->OnPermissionRequested( 309 | [this](const std::string& url, WebviewPermissionKind kind, 310 | bool is_user_initiated, 311 | Webview::WebviewPermissionRequestedCompleter completer) { 312 | OnPermissionRequested(url, kind, is_user_initiated, completer); 313 | }); 314 | 315 | webview_->OnContainsFullScreenElementChanged( 316 | [this](bool contains_fullscreen_element) { 317 | const auto event = flutter::EncodableValue(flutter::EncodableMap{ 318 | {flutter::EncodableValue(kEventType), 319 | flutter::EncodableValue("containsFullScreenElementChanged")}, 320 | {flutter::EncodableValue(kEventValue), 321 | contains_fullscreen_element}}); 322 | EmitEvent(event); 323 | }); 324 | } 325 | 326 | void WebviewBridge::OnPermissionRequested( 327 | const std::string& url, 328 | WebviewPermissionKind permissionKind, 329 | bool isUserInitiated, 330 | Webview::WebviewPermissionRequestedCompleter completer) { 331 | auto args = std::make_unique(flutter::EncodableMap{ 332 | {"url", url}, 333 | {"isUserInitiated", isUserInitiated}, 334 | {"permissionKind", static_cast(permissionKind)}}); 335 | 336 | method_channel_->InvokeMethod( 337 | "permissionRequested", std::move(args), 338 | std::make_unique>( 339 | [completer](const flutter::EncodableValue* result) { 340 | auto allow = std::get_if(result); 341 | if (allow != nullptr) { 342 | return completer(*allow ? WebviewPermissionState::Allow 343 | : WebviewPermissionState::Deny); 344 | } 345 | completer(WebviewPermissionState::Default); 346 | }, 347 | [completer](const std::string& error_code, 348 | const std::string& error_message, 349 | const flutter::EncodableValue* error_details) { 350 | completer(WebviewPermissionState::Default); 351 | }, 352 | [completer]() { completer(WebviewPermissionState::Default); })); 353 | } 354 | 355 | void WebviewBridge::HandleMethodCall( 356 | const flutter::MethodCall& method_call, 357 | std::unique_ptr> result) { 358 | const auto& method_name = method_call.method_name(); 359 | 360 | // setCursorPos: [double x, double y] 361 | if (method_name.compare(kMethodSetCursorPos) == 0) { 362 | const auto point = GetPointFromArgs(method_call.arguments()); 363 | if (point) { 364 | webview_->SetCursorPos(point->first, point->second); 365 | return result->Success(); 366 | } 367 | return result->Error(kErrorInvalidArgs); 368 | } 369 | 370 | // setPointerUpdate: 371 | // [int pointer, int event, double x, double y, double size, double pressure] 372 | if (method_name.compare(kMethodSetPointerUpdate) == 0) { 373 | const flutter::EncodableList* list = 374 | std::get_if(method_call.arguments()); 375 | if (!list || list->size() != 6) { 376 | return result->Error(kErrorInvalidArgs); 377 | } 378 | 379 | const auto pointer = std::get_if(&(*list)[0]); 380 | const auto event = std::get_if(&(*list)[1]); 381 | const auto x = std::get_if(&(*list)[2]); 382 | const auto y = std::get_if(&(*list)[3]); 383 | const auto size = std::get_if(&(*list)[4]); 384 | const auto pressure = std::get_if(&(*list)[5]); 385 | 386 | if (pointer && event && x && y && size && pressure) { 387 | webview_->SetPointerUpdate(*pointer, 388 | static_cast(*event), 389 | *x, *y, *size, *pressure); 390 | return result->Success(); 391 | } 392 | return result->Error(kErrorInvalidArgs); 393 | } 394 | 395 | // setScrollDelta: [double dx, double dy] 396 | if (method_name.compare(kMethodSetScrollDelta) == 0) { 397 | const auto delta = GetPointFromArgs(method_call.arguments()); 398 | if (delta) { 399 | webview_->SetScrollDelta(delta->first, delta->second); 400 | return result->Success(); 401 | } 402 | return result->Error(kErrorInvalidArgs); 403 | } 404 | 405 | // setPointerButton: {"button": int, "isDown": bool} 406 | if (method_name.compare(kMethodSetPointerButton) == 0) { 407 | const auto& map = std::get(*method_call.arguments()); 408 | 409 | const auto button = map.find(flutter::EncodableValue("button")); 410 | const auto isDown = map.find(flutter::EncodableValue("isDown")); 411 | if (button != map.end() && isDown != map.end()) { 412 | const auto buttonValue = std::get_if(&button->second); 413 | const auto isDownValue = std::get_if(&isDown->second); 414 | if (buttonValue && isDownValue) { 415 | webview_->SetPointerButtonState( 416 | static_cast(*buttonValue), *isDownValue); 417 | return result->Success(); 418 | } 419 | } 420 | return result->Error(kErrorInvalidArgs); 421 | } 422 | 423 | // setSize: [double width, double height, double scale_factor] 424 | if (method_name.compare(kMethodSetSize) == 0) { 425 | auto size = GetPointAndScaleFactorFromArgs(method_call.arguments()); 426 | if (size) { 427 | const auto [width, height, scale_factor] = size.value(); 428 | 429 | webview_->SetSurfaceSize(static_cast(width), 430 | static_cast(height), 431 | static_cast(scale_factor)); 432 | 433 | texture_bridge_->Start(); 434 | return result->Success(); 435 | } 436 | return result->Error(kErrorInvalidArgs); 437 | } 438 | 439 | // loadUrl: string 440 | if (method_name.compare(kMethodLoadUrl) == 0) { 441 | if (const auto url = std::get_if(method_call.arguments())) { 442 | webview_->LoadUrl(*url); 443 | return result->Success(); 444 | } 445 | return result->Error(kErrorInvalidArgs); 446 | } 447 | 448 | // loadStringContent: string 449 | if (method_name.compare(kMethodLoadStringContent) == 0) { 450 | if (const auto content = 451 | std::get_if(method_call.arguments())) { 452 | webview_->LoadStringContent(*content); 453 | return result->Success(); 454 | } 455 | return result->Error(kErrorInvalidArgs); 456 | } 457 | 458 | // reload 459 | if (method_name.compare(kMethodReload) == 0) { 460 | if (webview_->Reload()) { 461 | return result->Success(); 462 | } 463 | return result->Error(kMethodFailed); 464 | } 465 | 466 | // stop 467 | if (method_name.compare(kMethodStop) == 0) { 468 | if (webview_->Stop()) { 469 | return result->Success(); 470 | } 471 | return result->Error(kMethodFailed); 472 | } 473 | 474 | // goBack 475 | if (method_name.compare(kMethodGoBack) == 0) { 476 | if (webview_->GoBack()) { 477 | return result->Success(); 478 | } 479 | return result->Error(kMethodFailed); 480 | } 481 | 482 | // goForward 483 | if (method_name.compare(kMethodGoForward) == 0) { 484 | if (webview_->GoForward()) { 485 | return result->Success(); 486 | } 487 | return result->Error(kMethodFailed); 488 | } 489 | 490 | // suspend 491 | if (method_name.compare(kMethodSuspend) == 0) { 492 | texture_bridge_->Stop(); 493 | webview_->Suspend(); 494 | return result->Success(); 495 | } 496 | 497 | // resume 498 | if (method_name.compare(kMethodResume) == 0) { 499 | webview_->Resume(); 500 | texture_bridge_->Start(); 501 | return result->Success(); 502 | } 503 | 504 | // setVirtualHostNameMapping [string hostName, string path, int accessKind] 505 | if (method_name.compare(kMethodSetVirtualHostNameMapping) == 0) { 506 | const flutter::EncodableList* list = 507 | std::get_if(method_call.arguments()); 508 | if (!list || list->size() != 3) { 509 | return result->Error(kErrorInvalidArgs); 510 | } 511 | 512 | const auto hostName = std::get_if(&(*list)[0]); 513 | const auto path = std::get_if(&(*list)[1]); 514 | const auto accessKind = std::get_if(&(*list)[2]); 515 | 516 | if (hostName && path && accessKind) { 517 | webview_->SetVirtualHostNameMapping( 518 | *hostName, *path, 519 | static_cast(*accessKind)); 520 | return result->Success(); 521 | } 522 | return result->Error(kErrorInvalidArgs); 523 | } 524 | 525 | // clearVirtualHostNameMapping: string 526 | if (method_name.compare(kMethodClearVirtualHostNameMapping) == 0) { 527 | if (const auto hostName = 528 | std::get_if(method_call.arguments())) { 529 | if (webview_->ClearVirtualHostNameMapping(*hostName)) { 530 | return result->Success(); 531 | } 532 | } 533 | return result->Error(kErrorInvalidArgs); 534 | } 535 | 536 | if (method_name.compare(kMethodAddScriptToExecuteOnDocumentCreated) == 0) { 537 | if (const auto script = std::get_if(method_call.arguments())) { 538 | std::shared_ptr> 539 | shared_result = std::move(result); 540 | 541 | webview_->AddScriptToExecuteOnDocumentCreated( 542 | *script, [shared_result](bool success, const std::string& script_id) { 543 | if (success) { 544 | shared_result->Success(script_id); 545 | } else { 546 | shared_result->Error(kScriptFailed, "Executing script failed."); 547 | } 548 | }); 549 | return; 550 | } 551 | return result->Error(kErrorInvalidArgs); 552 | } 553 | 554 | if (method_name.compare(kMethodRemoveScriptToExecuteOnDocumentCreated) == 0) { 555 | if (const auto script_id = 556 | std::get_if(method_call.arguments())) { 557 | std::shared_ptr> 558 | shared_result = std::move(result); 559 | 560 | webview_->RemoveScriptToExecuteOnDocumentCreated(*script_id); 561 | shared_result->Success(); 562 | return; 563 | } 564 | return result->Error(kErrorInvalidArgs); 565 | } 566 | 567 | // executeScript: string 568 | if (method_name.compare(kMethodExecuteScript) == 0) { 569 | if (const auto script = std::get_if(method_call.arguments())) { 570 | std::shared_ptr> 571 | shared_result = std::move(result); 572 | 573 | webview_->ExecuteScript( 574 | *script, 575 | [shared_result](bool success, const std::string& json_result) { 576 | if (success) { 577 | shared_result->Success(json_result); 578 | } else { 579 | shared_result->Error(kScriptFailed, "Executing script failed."); 580 | } 581 | }); 582 | return; 583 | } 584 | return result->Error(kErrorInvalidArgs); 585 | } 586 | 587 | // postWebMessage: string 588 | if (method_name.compare(kMethodPostWebMessage) == 0) { 589 | if (const auto message = 590 | std::get_if(method_call.arguments())) { 591 | if (webview_->PostWebMessage(*message)) { 592 | return result->Success(); 593 | } 594 | return result->Error(kErrorNotSupported, "Posting the message failed."); 595 | } 596 | return result->Error(kErrorInvalidArgs); 597 | } 598 | 599 | // setUserAgent: string 600 | if (method_name.compare(kMethodSetUserAgent) == 0) { 601 | if (const auto user_agent = 602 | std::get_if(method_call.arguments())) { 603 | if (webview_->SetUserAgent(*user_agent)) { 604 | return result->Success(); 605 | } 606 | return result->Error(kErrorNotSupported, 607 | "Setting the user agent failed."); 608 | } 609 | return result->Error(kErrorInvalidArgs); 610 | } 611 | 612 | // setBackgroundColor: int 613 | if (method_name.compare(kMethodSetBackgroundColor) == 0) { 614 | if (const auto color = std::get_if(method_call.arguments())) { 615 | if (webview_->SetBackgroundColor(*color)) { 616 | return result->Success(); 617 | } 618 | return result->Error(kErrorNotSupported, 619 | "Setting the background color failed."); 620 | } 621 | return result->Error(kErrorInvalidArgs); 622 | } 623 | 624 | // setZoomFactor: double 625 | if (method_name.compare(kMethodSetZoomFactor) == 0) { 626 | if (const auto factor = std::get_if(method_call.arguments())) { 627 | if (webview_->SetZoomFactor(*factor)) { 628 | return result->Success(); 629 | } 630 | return result->Error(kErrorNotSupported, 631 | "Setting the zoom factor failed."); 632 | } 633 | return result->Error(kErrorInvalidArgs); 634 | } 635 | 636 | // openDevTools 637 | if (method_name.compare(kMethodOpenDevTools) == 0) { 638 | if (webview_->OpenDevTools()) { 639 | return result->Success(); 640 | } 641 | return result->Error(kMethodFailed); 642 | } 643 | 644 | // clearCookies 645 | if (method_name.compare(kMethodClearCookies) == 0) { 646 | if (webview_->ClearCookies()) { 647 | return result->Success(); 648 | } 649 | return result->Error(kMethodFailed); 650 | } 651 | 652 | // clearCache 653 | if (method_name.compare(kMethodClearCache) == 0) { 654 | if (webview_->ClearCache()) { 655 | return result->Success(); 656 | } 657 | return result->Error(kMethodFailed); 658 | } 659 | 660 | // setCacheDisabled: bool 661 | if (method_name.compare(kMethodSetCacheDisabled) == 0) { 662 | if (const auto disabled = std::get_if(method_call.arguments())) { 663 | if (webview_->SetCacheDisabled(*disabled)) { 664 | return result->Success(); 665 | } 666 | } 667 | return result->Error(kErrorInvalidArgs); 668 | } 669 | 670 | // setPopupWindowPolicy: int 671 | if (method_name.compare(kMethodSetPopupWindowPolicy) == 0) { 672 | if (const auto index = std::get_if(method_call.arguments())) { 673 | switch (*index) { 674 | case 1: 675 | webview_->SetPopupWindowPolicy(WebviewPopupWindowPolicy::Deny); 676 | break; 677 | case 2: 678 | webview_->SetPopupWindowPolicy( 679 | WebviewPopupWindowPolicy::ShowInSameWindow); 680 | break; 681 | default: 682 | webview_->SetPopupWindowPolicy(WebviewPopupWindowPolicy::Allow); 683 | break; 684 | } 685 | return result->Success(); 686 | } 687 | return result->Error(kErrorInvalidArgs); 688 | } 689 | 690 | if (method_name.compare(kMethodSetFpsLimit) == 0) { 691 | if (const auto value = std::get_if(method_call.arguments())) { 692 | texture_bridge_->SetFpsLimit(*value == 0 ? std::nullopt 693 | : std::make_optional(*value)); 694 | return result->Success(); 695 | } 696 | } 697 | 698 | result->NotImplemented(); 699 | } 700 | -------------------------------------------------------------------------------- /windows/webview_bridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "graphics_context.h" 11 | #include "texture_bridge.h" 12 | #include "webview.h" 13 | 14 | class WebviewBridge { 15 | public: 16 | WebviewBridge(flutter::BinaryMessenger* messenger, 17 | flutter::TextureRegistrar* texture_registrar, 18 | GraphicsContext* graphics_context, 19 | std::unique_ptr webview); 20 | ~WebviewBridge(); 21 | 22 | TextureBridge* texture_bridge() const { return texture_bridge_.get(); } 23 | 24 | int64_t texture_id() const { return texture_id_; } 25 | 26 | private: 27 | std::unique_ptr flutter_texture_; 28 | std::unique_ptr texture_bridge_; 29 | std::unique_ptr webview_; 30 | std::unique_ptr> event_sink_; 31 | std::unique_ptr> 32 | event_channel_; 33 | std::unique_ptr> 34 | method_channel_; 35 | 36 | flutter::TextureRegistrar* texture_registrar_; 37 | int64_t texture_id_; 38 | 39 | void HandleMethodCall( 40 | const flutter::MethodCall& method_call, 41 | std::unique_ptr> result); 42 | void RegisterEventHandlers(); 43 | 44 | template 45 | void EmitEvent(const T& value) { 46 | if (event_sink_) { 47 | event_sink_->Success(value); 48 | } 49 | } 50 | 51 | void OnPermissionRequested( 52 | const std::string& url, WebviewPermissionKind permissionKind, 53 | bool is_user_initiated, 54 | Webview::WebviewPermissionRequestedCompleter completer); 55 | }; 56 | -------------------------------------------------------------------------------- /windows/webview_host.cc: -------------------------------------------------------------------------------- 1 | #include "webview_host.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "util/rohelper.h" 9 | 10 | using namespace Microsoft::WRL; 11 | 12 | // static 13 | std::unique_ptr WebviewHost::Create( 14 | WebviewPlatform* platform, std::optional user_data_directory, 15 | std::optional browser_exe_path, 16 | std::optional arguments) { 17 | wil::com_ptr opts; 18 | if (arguments.has_value()) { 19 | opts = Microsoft::WRL::Make(); 20 | std::wstring warguments(arguments.value().begin(), arguments.value().end()); 21 | opts->put_AdditionalBrowserArguments(warguments.c_str()); 22 | } 23 | 24 | std::promise result_promise; 25 | wil::com_ptr env; 26 | auto result = CreateCoreWebView2EnvironmentWithOptions( 27 | browser_exe_path.has_value() ? browser_exe_path->c_str() : nullptr, 28 | user_data_directory.has_value() ? user_data_directory->c_str() : nullptr, opts.get(), 29 | Callback( 30 | [&promise = result_promise, &ptr = env]( 31 | HRESULT r, ICoreWebView2Environment* env) -> HRESULT { 32 | promise.set_value(r); 33 | ptr.swap(env); 34 | return S_OK; 35 | }) 36 | .Get()); 37 | 38 | if (SUCCEEDED(result)) { 39 | result = result_promise.get_future().get(); 40 | if ((SUCCEEDED(result) || result == RPC_E_CHANGED_MODE) && env) { 41 | auto webview_env3 = env.try_query(); 42 | if (webview_env3) { 43 | return std::unique_ptr( 44 | new WebviewHost(platform, std::move(webview_env3))); 45 | } 46 | } 47 | } 48 | 49 | return {}; 50 | } 51 | 52 | WebviewHost::WebviewHost(WebviewPlatform* platform, 53 | wil::com_ptr webview_env) 54 | : webview_env_(webview_env) { 55 | compositor_ = platform->graphics_context()->CreateCompositor(); 56 | } 57 | 58 | void WebviewHost::CreateWebview(HWND hwnd, bool offscreen_only, 59 | bool owns_window, 60 | WebviewCreationCallback callback) { 61 | CreateWebViewCompositionController( 62 | hwnd, [=, self = this]( 63 | wil::com_ptr controller, 64 | std::unique_ptr error) { 65 | if (controller) { 66 | std::unique_ptr webview(new Webview( 67 | std::move(controller), self, hwnd, owns_window, offscreen_only)); 68 | callback(std::move(webview), nullptr); 69 | } else { 70 | callback(nullptr, std::move(error)); 71 | } 72 | }); 73 | } 74 | 75 | void WebviewHost::CreateWebViewPointerInfo(PointerInfoCreationCallback callback) { 76 | 77 | ICoreWebView2PointerInfo *pointer; 78 | auto hr = webview_env_->CreateCoreWebView2PointerInfo(&pointer); 79 | 80 | if (FAILED(hr)) { 81 | callback(nullptr, WebviewCreationError::create(hr, "CreateWebViewPointerInfo failed.")); 82 | } else if (SUCCEEDED(hr)) { 83 | callback(std::move(wil::com_ptr(pointer)), nullptr); 84 | } 85 | } 86 | 87 | void WebviewHost::CreateWebViewCompositionController( 88 | HWND hwnd, CompositionControllerCreationCallback callback) { 89 | auto hr = webview_env_->CreateCoreWebView2CompositionController( 90 | hwnd, 91 | Callback< 92 | ICoreWebView2CreateCoreWebView2CompositionControllerCompletedHandler>( 93 | [callback](HRESULT hr, 94 | ICoreWebView2CompositionController* compositionController) 95 | -> HRESULT { 96 | if (SUCCEEDED(hr)) { 97 | callback( 98 | std::move(wil::com_ptr( 99 | compositionController)), 100 | nullptr); 101 | } else { 102 | callback(nullptr, WebviewCreationError::create( 103 | hr, 104 | "CreateCoreWebView2CompositionController " 105 | "completion handler failed.")); 106 | } 107 | 108 | return S_OK; 109 | }) 110 | .Get()); 111 | 112 | if (FAILED(hr)) { 113 | callback(nullptr, 114 | WebviewCreationError::create( 115 | hr, "CreateCoreWebView2CompositionController failed.")); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /windows/webview_host.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "graphics_context.h" 10 | #include "webview.h" 11 | #include "webview_platform.h" 12 | #include "windows.ui.composition.h" 13 | 14 | struct WebviewCreationError { 15 | HRESULT hr; 16 | std::string message; 17 | 18 | explicit WebviewCreationError(HRESULT hr, std::string message) 19 | : hr(hr), message(message) {} 20 | 21 | static std::unique_ptr create( 22 | HRESULT hr, const std::string message) { 23 | return std::make_unique(hr, message); 24 | } 25 | }; 26 | 27 | class WebviewHost { 28 | public: 29 | typedef std::function, 30 | std::unique_ptr)> 31 | WebviewCreationCallback; 32 | typedef std::function, 33 | std::unique_ptr)> 34 | CompositionControllerCreationCallback; 35 | typedef std::function, 36 | std::unique_ptr)> 37 | PointerInfoCreationCallback; 38 | 39 | static std::unique_ptr Create( 40 | WebviewPlatform* platform, 41 | std::optional user_data_directory = std::nullopt, 42 | std::optional browser_exe_path = std::nullopt, 43 | std::optional arguments = std::nullopt); 44 | 45 | void CreateWebview(HWND hwnd, bool offscreen_only, bool owns_window, 46 | WebviewCreationCallback callback); 47 | 48 | void CreateWebViewPointerInfo(PointerInfoCreationCallback cb); 49 | 50 | winrt::com_ptr compositor() 51 | const { 52 | return compositor_; 53 | } 54 | 55 | private: 56 | winrt::com_ptr compositor_; 57 | wil::com_ptr webview_env_; 58 | 59 | WebviewHost(WebviewPlatform* platform, 60 | wil::com_ptr webview_env); 61 | void CreateWebViewCompositionController( 62 | HWND hwnd, CompositionControllerCreationCallback cb); 63 | }; 64 | -------------------------------------------------------------------------------- /windows/webview_platform.cc: -------------------------------------------------------------------------------- 1 | #include "webview_platform.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | WebviewPlatform::WebviewPlatform() 11 | : rohelper_(std::make_unique(RO_INIT_SINGLETHREADED)) { 12 | if (rohelper_->WinRtAvailable()) { 13 | DispatcherQueueOptions options{sizeof(DispatcherQueueOptions), 14 | DQTYPE_THREAD_CURRENT, DQTAT_COM_STA}; 15 | 16 | if (FAILED(rohelper_->CreateDispatcherQueueController( 17 | options, dispatcher_queue_controller_.put()))) { 18 | std::cerr << "Creating DispatcherQueueController failed." << std::endl; 19 | return; 20 | } 21 | 22 | if (!IsGraphicsCaptureSessionSupported()) { 23 | std::cerr << "Windows::Graphics::Capture::GraphicsCaptureSession is not " 24 | "supported." 25 | << std::endl; 26 | return; 27 | } 28 | 29 | graphics_context_ = std::make_unique(rohelper_.get()); 30 | valid_ = graphics_context_->IsValid(); 31 | } 32 | } 33 | 34 | bool WebviewPlatform::IsGraphicsCaptureSessionSupported() { 35 | HSTRING className; 36 | HSTRING_HEADER classNameHeader; 37 | 38 | if (FAILED(rohelper_->GetStringReference( 39 | RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession, 40 | &className, &classNameHeader))) { 41 | return false; 42 | } 43 | 44 | ABI::Windows::Graphics::Capture::IGraphicsCaptureSessionStatics* 45 | capture_session_statics; 46 | if (FAILED(rohelper_->GetActivationFactory( 47 | className, 48 | __uuidof( 49 | ABI::Windows::Graphics::Capture::IGraphicsCaptureSessionStatics), 50 | (void**)&capture_session_statics))) { 51 | return false; 52 | } 53 | 54 | boolean is_supported = false; 55 | if (FAILED(capture_session_statics->IsSupported(&is_supported))) { 56 | return false; 57 | } 58 | 59 | return !!is_supported; 60 | } 61 | 62 | std::optional WebviewPlatform::GetDefaultDataDirectory() { 63 | PWSTR path_tmp; 64 | if (!SUCCEEDED( 65 | SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path_tmp))) { 66 | return std::nullopt; 67 | } 68 | auto path = std::filesystem::path(path_tmp); 69 | CoTaskMemFree(path_tmp); 70 | 71 | wchar_t filename[MAX_PATH]; 72 | GetModuleFileName(nullptr, filename, MAX_PATH); 73 | path /= "flutter_webview_windows"; 74 | path /= std::filesystem::path(filename).stem(); 75 | 76 | return path.wstring(); 77 | } 78 | -------------------------------------------------------------------------------- /windows/webview_platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "graphics_context.h" 10 | #include "util/rohelper.h" 11 | 12 | class WebviewPlatform { 13 | public: 14 | WebviewPlatform(); 15 | bool IsSupported() { return valid_; } 16 | std::optional GetDefaultDataDirectory(); 17 | bool IsGraphicsCaptureSessionSupported(); 18 | GraphicsContext* graphics_context() const { 19 | return graphics_context_.get(); 20 | }; 21 | 22 | rx::RoHelper* rohelper() const { return rohelper_.get(); } 23 | 24 | private: 25 | std::unique_ptr rohelper_; 26 | winrt::com_ptr 27 | dispatcher_queue_controller_; 28 | std::unique_ptr graphics_context_; 29 | bool valid_ = false; 30 | }; 31 | -------------------------------------------------------------------------------- /windows/webview_windows_plugin.cc: -------------------------------------------------------------------------------- 1 | #include "include/webview_windows/webview_windows_plugin.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "util/string_converter.h" 13 | #include "webview_bridge.h" 14 | #include "webview_host.h" 15 | #include "webview_platform.h" 16 | 17 | #pragma comment(lib, "dxgi.lib") 18 | #pragma comment(lib, "d3d11.lib") 19 | 20 | namespace { 21 | 22 | constexpr auto kMethodInitialize = "initialize"; 23 | constexpr auto kMethodDispose = "dispose"; 24 | constexpr auto kMethodInitializeEnvironment = "initializeEnvironment"; 25 | constexpr auto kMethodGetWebViewVersion = "getWebViewVersion"; 26 | 27 | constexpr auto kErrorCodeInvalidId = "invalid_id"; 28 | constexpr auto kErrorCodeEnvironmentCreationFailed = 29 | "environment_creation_failed"; 30 | constexpr auto kErrorCodeEnvironmentAlreadyInitialized = 31 | "environment_already_initialized"; 32 | constexpr auto kErrorCodeWebviewCreationFailed = "webview_creation_failed"; 33 | constexpr auto kErrorUnsupportedPlatform = "unsupported_platform"; 34 | 35 | template 36 | std::optional GetOptionalValue(const flutter::EncodableMap& map, 37 | const std::string& key) { 38 | const auto it = map.find(flutter::EncodableValue(key)); 39 | if (it != map.end()) { 40 | const auto val = std::get_if(&it->second); 41 | if (val) { 42 | return *val; 43 | } 44 | } 45 | return std::nullopt; 46 | } 47 | 48 | class WebviewWindowsPlugin : public flutter::Plugin { 49 | public: 50 | static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); 51 | 52 | WebviewWindowsPlugin(flutter::TextureRegistrar* textures, 53 | flutter::BinaryMessenger* messenger); 54 | 55 | virtual ~WebviewWindowsPlugin(); 56 | 57 | private: 58 | std::unique_ptr platform_; 59 | std::unique_ptr webview_host_; 60 | std::unordered_map> instances_; 61 | 62 | WNDCLASS window_class_ = {}; 63 | flutter::TextureRegistrar* textures_; 64 | flutter::BinaryMessenger* messenger_; 65 | 66 | bool InitPlatform(); 67 | 68 | void CreateWebviewInstance( 69 | std::unique_ptr>); 70 | // Called when a method is called on this plugin's channel from Dart. 71 | void HandleMethodCall( 72 | const flutter::MethodCall& method_call, 73 | std::unique_ptr> result); 74 | }; 75 | 76 | // static 77 | void WebviewWindowsPlugin::RegisterWithRegistrar( 78 | flutter::PluginRegistrarWindows* registrar) { 79 | auto channel = 80 | std::make_unique>( 81 | registrar->messenger(), "io.jns.webview.win", 82 | &flutter::StandardMethodCodec::GetInstance()); 83 | 84 | auto plugin = std::make_unique( 85 | registrar->texture_registrar(), registrar->messenger()); 86 | 87 | channel->SetMethodCallHandler( 88 | [plugin_pointer = plugin.get()](const auto& call, auto result) { 89 | plugin_pointer->HandleMethodCall(call, std::move(result)); 90 | }); 91 | 92 | registrar->AddPlugin(std::move(plugin)); 93 | } 94 | 95 | WebviewWindowsPlugin::WebviewWindowsPlugin(flutter::TextureRegistrar* textures, 96 | flutter::BinaryMessenger* messenger) 97 | : textures_(textures), messenger_(messenger) { 98 | window_class_.lpszClassName = L"FlutterWebviewMessage"; 99 | window_class_.lpfnWndProc = &DefWindowProc; 100 | RegisterClass(&window_class_); 101 | } 102 | 103 | WebviewWindowsPlugin::~WebviewWindowsPlugin() { 104 | instances_.clear(); 105 | UnregisterClass(window_class_.lpszClassName, nullptr); 106 | } 107 | 108 | void WebviewWindowsPlugin::HandleMethodCall( 109 | const flutter::MethodCall& method_call, 110 | std::unique_ptr> result) { 111 | if (method_call.method_name().compare(kMethodInitializeEnvironment) == 0) { 112 | if (webview_host_) { 113 | return result->Error(kErrorCodeEnvironmentAlreadyInitialized, 114 | "The webview environment is already initialized"); 115 | } 116 | 117 | if (!InitPlatform()) { 118 | return result->Error(kErrorUnsupportedPlatform, 119 | "The platform is not supported"); 120 | } 121 | 122 | const auto& map = std::get(*method_call.arguments()); 123 | 124 | std::optional browser_exe_wpath = std::nullopt; 125 | std::optional browser_exe_path = 126 | GetOptionalValue(map, "browserExePath"); 127 | if (browser_exe_path) { 128 | browser_exe_wpath = util::Utf16FromUtf8(*browser_exe_path); 129 | } 130 | 131 | std::optional user_data_wpath = std::nullopt; 132 | std::optional user_data_path = 133 | GetOptionalValue(map, "userDataPath"); 134 | if (user_data_path) { 135 | user_data_wpath = util::Utf16FromUtf8(*user_data_path); 136 | } else { 137 | user_data_wpath = platform_->GetDefaultDataDirectory(); 138 | } 139 | 140 | std::optional additional_args = 141 | GetOptionalValue(map, "additionalArguments"); 142 | 143 | webview_host_ = std::move(WebviewHost::Create( 144 | platform_.get(), user_data_wpath, browser_exe_wpath, additional_args)); 145 | if (!webview_host_) { 146 | return result->Error(kErrorCodeEnvironmentCreationFailed); 147 | } 148 | 149 | return result->Success(); 150 | } 151 | 152 | if (method_call.method_name().compare(kMethodGetWebViewVersion) == 0) { 153 | LPWSTR version_info = nullptr; 154 | auto hr = 155 | GetAvailableCoreWebView2BrowserVersionString(nullptr, &version_info); 156 | if (SUCCEEDED(hr) && version_info != nullptr) { 157 | return result->Success( 158 | flutter::EncodableValue(util::Utf8FromUtf16(version_info))); 159 | } else { 160 | return result->Success(); 161 | } 162 | } 163 | 164 | if (method_call.method_name().compare(kMethodInitialize) == 0) { 165 | return CreateWebviewInstance(std::move(result)); 166 | } 167 | 168 | if (method_call.method_name().compare(kMethodDispose) == 0) { 169 | if (const auto texture_id = std::get_if(method_call.arguments())) { 170 | const auto it = instances_.find(*texture_id); 171 | if (it != instances_.end()) { 172 | instances_.erase(it); 173 | return result->Success(); 174 | } 175 | } 176 | return result->Error(kErrorCodeInvalidId); 177 | } else { 178 | result->NotImplemented(); 179 | } 180 | } 181 | 182 | void WebviewWindowsPlugin::CreateWebviewInstance( 183 | std::unique_ptr> result) { 184 | if (!InitPlatform()) { 185 | return result->Error(kErrorUnsupportedPlatform, 186 | "The platform is not supported"); 187 | } 188 | 189 | if (!webview_host_) { 190 | webview_host_ = std::move(WebviewHost::Create( 191 | platform_.get(), platform_->GetDefaultDataDirectory())); 192 | if (!webview_host_) { 193 | return result->Error(kErrorCodeEnvironmentCreationFailed); 194 | } 195 | } 196 | 197 | auto hwnd = 198 | CreateWindowEx(0, window_class_.lpszClassName, L"", 0, 0, 0, 0, 0, 199 | HWND_MESSAGE, nullptr, window_class_.hInstance, nullptr); 200 | 201 | std::shared_ptr> 202 | shared_result = std::move(result); 203 | webview_host_->CreateWebview( 204 | hwnd, true, true, 205 | [shared_result, this](std::unique_ptr webview, 206 | std::unique_ptr error) { 207 | if (!webview) { 208 | if (error) { 209 | return shared_result->Error( 210 | kErrorCodeWebviewCreationFailed, 211 | std::format( 212 | "Creating the webview failed: {} (HRESULT: {:#010x})", 213 | error->message, error->hr)); 214 | } 215 | return shared_result->Error(kErrorCodeWebviewCreationFailed, 216 | "Creating the webview failed."); 217 | } 218 | 219 | auto bridge = std::make_unique( 220 | messenger_, textures_, platform_->graphics_context(), 221 | std::move(webview)); 222 | auto texture_id = bridge->texture_id(); 223 | instances_[texture_id] = std::move(bridge); 224 | 225 | auto response = flutter::EncodableValue(flutter::EncodableMap{ 226 | {flutter::EncodableValue("textureId"), 227 | flutter::EncodableValue(texture_id)}, 228 | }); 229 | 230 | shared_result->Success(response); 231 | }); 232 | } 233 | 234 | bool WebviewWindowsPlugin::InitPlatform() { 235 | if (!platform_) { 236 | platform_ = std::make_unique(); 237 | } 238 | return platform_->IsSupported(); 239 | } 240 | 241 | } // namespace 242 | 243 | void WebviewWindowsPluginRegisterWithRegistrar( 244 | FlutterDesktopPluginRegistrarRef registrar) { 245 | WebviewWindowsPlugin::RegisterWithRegistrar( 246 | flutter::PluginRegistrarManager::GetInstance() 247 | ->GetRegistrar(registrar)); 248 | } 249 | --------------------------------------------------------------------------------