├── .gitignore ├── LICENSE ├── README.md ├── build ├── azure-pipelines.yml ├── jobs │ ├── publish-package.yml │ └── test-package.yml ├── scripts │ └── xcode-update.sh └── steps │ ├── announce-version.yml │ ├── build.yml │ ├── prepare-build.yml │ ├── publish-artifacts.yml │ ├── set-assembly-info.yml │ ├── set-runtime.yml │ └── test.yml ├── docs └── assets │ ├── SampleScreenshot.png │ └── SampleVideo.gif ├── global.json └── src ├── Axemasta.SuperWebView.sln ├── Axemasta.SuperWebView ├── AssemblyInfo.shared.cs ├── Axemasta.SuperWebView.csproj ├── Converters │ └── SuperWebViewSourceTypeConverter.shared.cs ├── Helpers │ └── EmbeddedResourceHelper.shared.cs ├── Interfaces │ ├── IDeferralToken.shared.cs │ ├── ISuperWebViewController.shared.cs │ ├── ISuperWebViewDelegate.shared.cs │ └── IWebNavigationEventArgs.shared.cs ├── Internals │ ├── Constants.android.cs │ ├── Constants.ios.cs │ ├── Constants.shared.cs │ ├── InjectJavaScriptDelegate.shared.cs │ └── RendererRegistry.shared.cs ├── Models │ ├── BrowserInvocationEventArgs.shared.cs │ ├── DeferrableEventArgs.shared.cs │ ├── DeferralToken.shared.cs │ ├── EmbeddedJavaScript.shared.cs │ ├── JavaScript.shared.cs │ ├── NavigationCancelledEventArgs.shared.cs │ ├── ProgressEventArgs.shared.cs │ ├── RawJavaScript.shared.cs │ ├── SuperHtmlWebViewSource.shared.cs │ ├── SuperUrlWebViewSource.shared.cs │ ├── SuperWebNavigatedEventArgs.shared.cs │ ├── SuperWebNavigatingEventArgs.shared.cs │ ├── SuperWebViewSource.shared.cs │ └── UrlEventArgs.shared.cs ├── Renderers │ ├── Android │ │ ├── ActivityResultCallbackRegistry.android.cs │ │ ├── Forms.android.cs │ │ ├── FormsSuperWebChromeClient.android.cs │ │ ├── FormsSuperWebViewClient.android.cs │ │ ├── JSBridge.android.cs │ │ ├── PlatformSpecific.shared.cs │ │ └── SuperWebViewRenderer.android.cs │ └── iOS │ │ ├── Forms.ios.cs │ │ ├── Platform.ios.cs │ │ ├── PlatformSpecific.shared.cs │ │ ├── SuperCookieManager.ios.cs │ │ ├── SuperWebViewNavigationDelegate.ios.cs │ │ ├── SuperWebViewUIDelegate.ios.cs │ │ ├── SuperWkWebViewRenderer.ios.cs │ │ ├── UIApplicationExtensions.ios.cs │ │ └── WKWebViewHelper.ios.cs ├── Scripts │ ├── invokenative.android.js │ └── invokenative.ios.js └── SuperWebView.shared.cs ├── Samples ├── Axemasta.SuperWebView.Sample.Android │ ├── Assets │ │ └── AboutAssets.txt │ ├── Axemasta.SuperWebView.Sample.Android.csproj │ ├── MainActivity.cs │ ├── Properties │ │ ├── AndroidManifest.xml │ │ └── AssemblyInfo.cs │ ├── Resources │ │ ├── AboutResources.txt │ │ ├── Resource.designer.cs │ │ ├── mipmap-anydpi-v26 │ │ │ ├── icon.xml │ │ │ └── icon_round.xml │ │ ├── mipmap-hdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-mdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── icon.png │ │ │ └── launcher_foreground.png │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ └── Services │ │ └── UrlProvider.cs ├── Axemasta.SuperWebView.Sample.iOS │ ├── AppDelegate.cs │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon1024.png │ │ │ ├── Icon120.png │ │ │ ├── Icon152.png │ │ │ ├── Icon167.png │ │ │ ├── Icon180.png │ │ │ ├── Icon20.png │ │ │ ├── Icon29.png │ │ │ ├── Icon40.png │ │ │ ├── Icon58.png │ │ │ ├── Icon60.png │ │ │ ├── Icon76.png │ │ │ ├── Icon80.png │ │ │ └── Icon87.png │ ├── Axemasta.SuperWebView.Sample.iOS.csproj │ ├── Entitlements.plist │ ├── Info.plist │ ├── Main.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Resources │ │ ├── Default-568h@2x.png │ │ ├── Default-Portrait.png │ │ ├── Default-Portrait@2x.png │ │ ├── Default.png │ │ ├── Default@2x.png │ │ └── LaunchScreen.storyboard │ └── Services │ │ └── UrlProvider.cs └── Axemasta.SuperWebView.Sample │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── Axemasta.SuperWebView.Sample.csproj │ ├── Pages │ ├── BrowserPage.xaml │ └── BrowserPage.xaml.cs │ ├── Scripts │ ├── jquery-3.5.1.min.js │ └── spy.js │ ├── Services │ └── IUrlProvider.cs │ └── Views │ ├── BlockPage.html │ └── CoolPage.html └── Tests └── Axemasta.SuperWebView.UnitTests ├── Axemasta.SuperWebView.UnitTests.csproj ├── EmbeddedResourceHelperTests.cs └── ProgressEventArgsTests.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | .DS_Store 332 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Axemasta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Super Web View 2 | Fork of Xamarin.Forms.WebView with common custom renderer features as standard 3 | 4 | I created this control to better fulfill my own requirements for a web browsing component on Xamarin Forms. This control is a direct fork of the Xamarin control, I have extensively used code form the Xamarin Forms repository to create this library. 5 | 6 | For complete documentation about this control, see the [Wiki][SuperWebViewWiki]. 7 | 8 | For a fully implemented sample app, see the [sample app](# Sample App). 9 | 10 | ## Build Status 11 | 12 | | | Build Status | 13 | |-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 14 | | Axemasta.SuperWebView | [](https://axemasta.visualstudio.com/GitHub%20Pipelines/_build/latest?definitionId=4&branchName=refs%2Fpull%2F1%2Fmerge) | 15 | 16 | 17 | 18 | ## Better Navigation 19 | 20 | The original `WebView` components does not best support complicated scenarios such as asyncronously cancelling navigation to a website. I originally discovered this limitation when implementing website filtering in an app and raised [this](https://github.com/xamarin/Xamarin.Forms/pull/14137) PR to address the limitation. Unfortunately it doesn't look like it will ever make it into Forms with the blocking of breaking changes in Forms 5, as it moves to LTS. 21 | 22 | Using the new `SuperWebNavigatingArgs` it is possible to defer navigation until you have confirmed whether a url can be accessed, for usage examples see the [Wiki][SuperWebViewWikiNavigation]. 23 | 24 | 25 | 26 | ## More Events 27 | 28 | There are multiple events that I have added to the control including loading progress and javascript callbacks. All of the additional evens are things you might want to add in a custom renderer. Each event is intended to enable common behaviour that is expected of a web browsing app. 29 | 30 | 31 | 32 | ## Better Platform Customisaton 33 | 34 | The native platforms have some cool features which can be quite hard to tap into, this control aims to allow you to specifically override the behaviour of native api's, and easily reintegrate back into the forms control. 35 | 36 | 37 | 38 | ## NuGet Package 39 | 40 | This library is available on NuGet.org, you should install it into all of your Forms projects (shared, ios, android). 41 | 42 | | Package | NuGet | 43 | | ------------------------------------------ | ------------------------------------------------------------ | 44 | | [Axemasta.SuperWebView][SuperWebViewNuGet] | [![SuperWebViewNuGetShield]][SuperWebViewNuGet]| 45 | 46 | ## Sample App 47 | 48 | This project comes with a fully implemented sample app to demonstrate all of the features of this control. I will be updating the wiki with all of the current possibilities but for now please run the sample app to get a flavour of whats possible. Nothing in the sample app requires custom renderers and is available out of the box! 49 | 50 |  51 | 52 | [SuperWebViewNuGet]: https://www.nuget.org/packages/Axemasta.SuperWebView/ 53 | [SuperWebViewNuGetShield]: https://img.shields.io/nuget/v/Axemasta.SuperWebView.svg 54 | [SuperWebViewWiki]:https://github.com/Axemasta/SuperWebView/wiki 55 | [SuperWebViewWikiNavigation]:https://github.com/Axemasta/SuperWebView/wiki/Navigation -------------------------------------------------------------------------------- /build/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - main 5 | - azure-pipeline 6 | - pre-release 7 | paths: 8 | include: 9 | - src/* 10 | - build/* 11 | exclude: 12 | - README.md 13 | 14 | variables: 15 | buildConfiguration: 'Release' 16 | version.Major: 0 17 | version.Minor: 4 18 | version.Revision: $[counter(format('{0}.{1}_{2}', variables['version.Major'], variables['version.Minor'], eq(variables['Build.Reason'], 'PullRequest')), 0)] 19 | version.Build: $[counter(variables['build'], 1)] 20 | versionNumber: $(version.Major).$(version.Minor).$(version.Revision) 21 | assemblyVersion: $(version.Major).$(version.Minor).$(version.Revision).$(version.Build) 22 | vmImage: macOS-10.15 23 | isPR: $[eq(variables['Build.Reason'], 'PullRequest')] 24 | isPreRelease: $[ne(variables['Build.SourceBranchName'], 'main')] 25 | xcodeVersion: 12.4 26 | xcodeRoot: '/Applications/Xcode_$(xcodeVersion).app' #https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md#xcode 27 | buildPlatform: Any CPU 28 | 29 | stages: 30 | - stage: buildTest 31 | displayName: Build & Test 32 | jobs: 33 | - template: jobs/test-package.yml 34 | parameters: 35 | vmImage: $(vmImage) 36 | 37 | - stage: buildpublish 38 | displayName: Build & Publish 39 | dependsOn: buildTest 40 | condition: and(succeeded(), eq(variables.isPR, false)) 41 | jobs: 42 | - template: jobs/publish-package.yml 43 | parameters: 44 | vmImage: $(vmImage) -------------------------------------------------------------------------------- /build/jobs/publish-package.yml: -------------------------------------------------------------------------------- 1 | # Build Library, Publish Artifacts Ready For Distribution 2 | 3 | parameters: 4 | solution: '**/*.sln' 5 | vmImage: macOS-latest 6 | buildConfiguration: 'Release' 7 | 8 | jobs: 9 | - job: buildArtifacts 10 | displayName: Build Package Artifacts 11 | pool: 12 | vmImage: ${{ parameters.vmImage }} 13 | demands: 14 | - MSBuild 15 | workspace: 16 | clean: all 17 | steps: 18 | 19 | # - script: echo '##vso[task.setvariable variable=versionName]$(versionName)-pre' 20 | # displayName: 'Set Pre Release Version' 21 | # condition: eq(variables.isPreRelease, true) 22 | 23 | - template: ../steps/announce-version.yml 24 | 25 | - template: ../steps/set-runtime.yml 26 | 27 | - template: ../steps/set-assembly-info.yml 28 | 29 | - template: ../steps/build.yml 30 | parameters: 31 | solution: '**/Axemasta.SuperWebView.csproj' 32 | 33 | - template: ../steps/publish-artifacts.yml 34 | parameters: 35 | feedId: $(Axemasta.GitHubPackages.FeedId) -------------------------------------------------------------------------------- /build/jobs/test-package.yml: -------------------------------------------------------------------------------- 1 | # Build, Test & Publish The Test Results 2 | 3 | parameters: 4 | solution: '**/*.sln' 5 | vmImage: windows-latest 6 | buildConfiguration: 'Release' 7 | 8 | jobs: 9 | - job: runTests 10 | displayName: Perform Tests 11 | pool: 12 | vmImage: ${{ parameters.vmImage }} 13 | demands: 14 | - MSBuild 15 | workspace: 16 | clean: all 17 | 18 | steps: 19 | - template: ../steps/announce-version.yml 20 | 21 | - template: ../steps/prepare-build.yml 22 | parameters: 23 | solution: ${{ parameters.solution }} 24 | 25 | - template: ../steps/test.yml 26 | parameters: 27 | projects: '**/*Tests.csproj' 28 | buildConfiguration: ${{ parameters.buildConfiguration }} 29 | publishResults: true -------------------------------------------------------------------------------- /build/scripts/xcode-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # https://github.com/PrismLibrary/Prism/blob/master/build/scripts/xcode-update.sh 3 | # echo "Setting Xcode Override to: $xcodeVersion" 4 | xcodeRoot=/Applications/Xcode_$xcodeVersion.app 5 | 6 | echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'$xcodeRoot 7 | sudo xcode-select --switch $xcodeRoot/Contents/Developer 8 | 9 | # /usr/bin/xcodebuild -version 10 | # xcversion installed 11 | 12 | # Apply temporary workaround for https://github.com/actions/virtual-environments/issues/1932 13 | rm -f ${HOME}/Library/Preferences/Xamarin/Settings.plist -------------------------------------------------------------------------------- /build/steps/announce-version.yml: -------------------------------------------------------------------------------- 1 | # Announces the pipeline build variables 2 | # Used mainly for testing 3 | 4 | steps: 5 | - script: | 6 | echo 'Printing variables' 7 | echo 'Assembly Version: $(assemblyVersion)' 8 | echo 'MajorMinor Version: $(version.Major).$(version.Minor)' 9 | echo 'Revision Version: $(version.Revision)' 10 | echo 'Version Number: $(versionNumber)' 11 | displayName: Print Variables -------------------------------------------------------------------------------- /build/steps/build.yml: -------------------------------------------------------------------------------- 1 | # Builds the solution 2 | 3 | parameters: 4 | solution: '**/*.sln' 5 | buildConfiguration: 'Release' 6 | msbuildArguments: '/p:JavaSdkDirectory="$(JAVA_HOME)"' 7 | 8 | steps: 9 | 10 | - template: prepare-build.yml 11 | parameters: 12 | solution: ${{ parameters.solution }} 13 | 14 | - task: MSBuild@1 15 | displayName: Build ${{ parameters.solutionName }} 16 | inputs: 17 | solution: ${{ parameters.solution }} 18 | platform: '$(buildPlatform)' 19 | configuration: '$(buildConfiguration)' 20 | msbuildArguments: ${{ parameters.msbuildArguments }} 21 | maximumCpuCount: true -------------------------------------------------------------------------------- /build/steps/prepare-build.yml: -------------------------------------------------------------------------------- 1 | # Prepares the pipeline for a build 2 | 3 | parameters: 4 | solution: '**/*.sln' 5 | 6 | steps: 7 | - task: UseDotNet@2 8 | displayName: Use global.json .NET Version 9 | inputs: 10 | packageType: 'sdk' 11 | useGlobalJson: true 12 | 13 | - task: NuGetToolInstaller@1 14 | displayName: Use latest NuGet 15 | inputs: 16 | checkLatest: true 17 | 18 | - task: NuGetCommand@2 19 | displayName: 'Restore NuGet Packages' 20 | inputs: 21 | command: 'restore' 22 | restoreSolution: ${{ parameters.solution }} 23 | feedsToUse: 'select' 24 | noCache: true 25 | verbosityRestore: normal -------------------------------------------------------------------------------- /build/steps/publish-artifacts.yml: -------------------------------------------------------------------------------- 1 | # Publishes NuGet Packages To Axemasta's Private Feed 2 | 3 | parameters: 4 | feedId: '00000000-0000-0000-0000-000000000000' 5 | 6 | steps: 7 | - task: NuGetCommand@2 8 | inputs: 9 | command: 'push' 10 | packagesToPush: '$(System.DefaultWorkingDirectory)/**/*.nupkg;!$(System.DefaultWorkingDirectory)/**/*.symbols.nupkg' 11 | nuGetFeedType: 'internal' 12 | publishVstsFeed: ${{ parameters.feedId }} -------------------------------------------------------------------------------- /build/steps/set-assembly-info.yml: -------------------------------------------------------------------------------- 1 | # Sets the assembly info for the Axemasta.SuperWebView package 2 | 3 | steps: 4 | - task: Assembly-Info-NetCore@2 5 | displayName: 'Set Assembly Info' 6 | inputs: 7 | Path: '$(Build.SourcesDirectory)' 8 | FileNames: '**/Axemasta.SuperWebView.csproj' 9 | InsertAttributes: true 10 | FileEncoding: 'auto' 11 | WriteBOM: false 12 | GeneratePackageOnBuild: true 13 | Authors: 'Axemasta' 14 | Product: 'Axemasta.SuperWebView' 15 | Description: 'Fork of Xamarin.Forms.WebView with common custom renderer features as standard' 16 | PackageProjectUrl: 'https://github.com/Axemasta/SuperWebView' 17 | RepositoryUrl: 'https://github.com/Axemasta/SuperWebView' 18 | PackageLicenseUrl: 'https://github.com/Axemasta/SuperWebView/blob/main/LICENSE' 19 | PackageReleaseNotes: 'TODO' 20 | Culture: 'en-GB' 21 | VersionNumber: '$(assemblyVersion)' 22 | FileVersionNumber: '$(assemblyVersion)' 23 | InformationalVersion: '$(versionNumber)' 24 | PackageVersion: '$(versionNumber)' 25 | LogLevel: 'verbose' 26 | FailOnWarning: false 27 | DisableTelemetry: false -------------------------------------------------------------------------------- /build/steps/set-runtime.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | jdkTestFolder: $(XA.Jdk11.Folder) 3 | 4 | steps: 5 | 6 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/shell-script?view=azure-devops#example 7 | - script: chmod +x build/scripts/xcode-update.sh 8 | displayName: chmod xcode-update.sh 9 | 10 | - script: build/scripts/xcode-update.sh 11 | displayName: Force Xcode Update 12 | env: 13 | xcodeVersion: $(xcodeVersion) 14 | condition: ne(variables['xcodeOverride'], '') 15 | 16 | - task: UseDotNet@2 17 | displayName: 'Use global.json SDK' 18 | inputs: 19 | packageType: 'sdk' 20 | useGlobalJson: true 21 | 22 | - task: JavaToolInstaller@0 23 | inputs: 24 | versionSpec: '11' 25 | jdkArchitectureOption: 'x64' 26 | jdkSourceOption: 'PreInstalled' 27 | 28 | # https://github.com/xamarin/xamarin-android/issues/5999 29 | 30 | - script: echo '##vso[task.setvariable variable=JI_JAVA_HOME]$(JAVA_HOME_11_X64)' 31 | displayName: set JI_JAVA_HOME 32 | 33 | - script: | 34 | export JI_JAVA_HOME="$JAVA_HOME_11_X64" 35 | displayName: Set Java Home Path 36 | 37 | - script: | 38 | dotnet tool install --global boots --ignore-failed-sources 39 | boots --stable Mono 40 | boots --stable XamarinAndroid 41 | boots --stable XamariniOS 42 | displayName: Ensure Latest Xamarin & Mono SDKs -------------------------------------------------------------------------------- /build/steps/test.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | projects: '**/*Tests.csproj' 3 | buildConfiguration: 'Release' 4 | publishResults: true 5 | 6 | steps: 7 | - task: DotNetCoreCLI@2 8 | displayName: 'dotnet test' 9 | inputs: 10 | command: 'test' 11 | projects: ${{ parameters.projects }} 12 | configuration: ${{ parameters.buildConfiguration }} 13 | testRunTitle: 'Test Run $(Build.BuildId)' 14 | arguments: '/p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=$(Build.SourcesDirectory)/TestResults/Coverage/' 15 | publishTestResults: ${{ parameters.publishResults }} 16 | 17 | - task: PublishCodeCoverageResults@1 18 | displayName: 'Publish Code Coverage Report' 19 | condition: eq(${{ parameters.publishResults }}, true) 20 | inputs: 21 | codeCoverageTool: 'cobertura' 22 | summaryFileLocation: '$(Build.SourcesDirectory)/**/coverage.cobertura.xml' -------------------------------------------------------------------------------- /docs/assets/SampleScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/docs/assets/SampleScreenshot.png -------------------------------------------------------------------------------- /docs/assets/SampleVideo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/docs/assets/SampleVideo.gif -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "5.0.203", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | }, 7 | "msbuild-sdks": { 8 | "MSBuild.Sdk.Extras": "3.0.23", 9 | "Microsoft.Build.CentralPackageVersions": "2.0.79" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.809.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Axemasta.SuperWebView", "Axemasta.SuperWebView\Axemasta.SuperWebView.csproj", "{73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{7947717C-904C-48E0-A2E8-E94AF2FDE795}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Axemasta.SuperWebView.Sample.Android", "Samples\Axemasta.SuperWebView.Sample.Android\Axemasta.SuperWebView.Sample.Android.csproj", "{17A614EE-50D2-49D3-9379-D2A8532007B2}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Axemasta.SuperWebView.Sample.iOS", "Samples\Axemasta.SuperWebView.Sample.iOS\Axemasta.SuperWebView.Sample.iOS.csproj", "{3B828290-5C46-47BC-A78E-DA65B571676A}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Axemasta.SuperWebView.Sample", "Samples\Axemasta.SuperWebView.Sample\Axemasta.SuperWebView.Sample.csproj", "{F81087F0-B9E0-4F6C-B53C-561B053FD478}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3300E84E-6B1A-4654-BC7F-F3C8B16A111C}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Axemasta.SuperWebView.UnitTests", "Tests\Axemasta.SuperWebView.UnitTests\Axemasta.SuperWebView.UnitTests.csproj", "{55BC9A07-3E28-42CC-8649-409AECD61927}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 25 | Release|iPhoneSimulator = Release|iPhoneSimulator 26 | Debug|iPhone = Debug|iPhone 27 | Release|iPhone = Release|iPhone 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 35 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 36 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 37 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 38 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Debug|iPhone.ActiveCfg = Debug|Any CPU 39 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Debug|iPhone.Build.0 = Debug|Any CPU 40 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Release|iPhone.ActiveCfg = Release|Any CPU 41 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D}.Release|iPhone.Build.0 = Release|Any CPU 42 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 47 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 48 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 49 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 50 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Debug|iPhone.ActiveCfg = Debug|Any CPU 51 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Debug|iPhone.Build.0 = Debug|Any CPU 52 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Release|iPhone.ActiveCfg = Release|Any CPU 53 | {17A614EE-50D2-49D3-9379-D2A8532007B2}.Release|iPhone.Build.0 = Release|Any CPU 54 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 55 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 56 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator 57 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Release|Any CPU.Build.0 = Release|iPhoneSimulator 58 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 59 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 60 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 61 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 62 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Debug|iPhone.ActiveCfg = Debug|iPhone 63 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Debug|iPhone.Build.0 = Debug|iPhone 64 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Release|iPhone.ActiveCfg = Release|iPhone 65 | {3B828290-5C46-47BC-A78E-DA65B571676A}.Release|iPhone.Build.0 = Release|iPhone 66 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 71 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 72 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 73 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 74 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Debug|iPhone.ActiveCfg = Debug|Any CPU 75 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Debug|iPhone.Build.0 = Debug|Any CPU 76 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Release|iPhone.ActiveCfg = Release|Any CPU 77 | {F81087F0-B9E0-4F6C-B53C-561B053FD478}.Release|iPhone.Build.0 = Release|Any CPU 78 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 83 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 84 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 85 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 86 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Debug|iPhone.ActiveCfg = Debug|Any CPU 87 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Debug|iPhone.Build.0 = Debug|Any CPU 88 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Release|iPhone.ActiveCfg = Release|Any CPU 89 | {55BC9A07-3E28-42CC-8649-409AECD61927}.Release|iPhone.Build.0 = Release|Any CPU 90 | EndGlobalSection 91 | GlobalSection(SolutionProperties) = preSolution 92 | HideSolutionNode = FALSE 93 | EndGlobalSection 94 | GlobalSection(ExtensibilityGlobals) = postSolution 95 | SolutionGuid = {6A0C2E3A-2D2B-4C69-85AB-843F1365CCBA} 96 | EndGlobalSection 97 | GlobalSection(NestedProjects) = preSolution 98 | {17A614EE-50D2-49D3-9379-D2A8532007B2} = {7947717C-904C-48E0-A2E8-E94AF2FDE795} 99 | {3B828290-5C46-47BC-A78E-DA65B571676A} = {7947717C-904C-48E0-A2E8-E94AF2FDE795} 100 | {F81087F0-B9E0-4F6C-B53C-561B053FD478} = {7947717C-904C-48E0-A2E8-E94AF2FDE795} 101 | {55BC9A07-3E28-42CC-8649-409AECD61927} = {3300E84E-6B1A-4654-BC7F-F3C8B16A111C} 102 | EndGlobalSection 103 | EndGlobal 104 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/AssemblyInfo.shared.cs: -------------------------------------------------------------------------------- 1 | using Axemasta.SuperWebView; 2 | using Axemasta.SuperWebView.Internals; 3 | using Xamarin.Forms; 4 | 5 | #if __IOS__ 6 | using Xamarin.Forms.Platform.iOS; 7 | //[assembly: ExportRenderer(typeof(SuperWebView), typeof(Axemasta.SuperWebView.iOS.SuperWebViewLegacyRenderer))] 8 | [assembly: ExportRenderer(typeof(SuperWebView), typeof(Axemasta.SuperWebView.iOS.SuperWkWebViewRenderer))] 9 | #endif 10 | 11 | #if __ANDROID__ 12 | using Xamarin.Forms.Platform.Android; 13 | [assembly: ExportRenderer(typeof(SuperWebView), typeof(Axemasta.SuperWebView.Droid.SuperWebViewRenderer))] 14 | #endif -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Axemasta.SuperWebView.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;Xamarin.iOS10;MonoAndroid10.0; 5 | Debug;Release 6 | false 7 | Drop In WebView Replacement For Xamarin.Forms 8 | Alternate WebView implementation with many common features implemented as standard 9 | webview;xamarin;xamarin.forms;axemasta 10 | 11 | 12 | 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 1701;1702;CS1998 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Converters/SuperWebViewSourceTypeConverter.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | 4 | namespace Axemasta.SuperWebView 5 | { 6 | [Xamarin.Forms.Xaml.TypeConversion(typeof(SuperUrlWebViewSource))] 7 | public class SuperWebViewSourceTypeConverter : TypeConverter 8 | { 9 | public override object ConvertFromInvariantString(string value) 10 | { 11 | if (value == null) 12 | throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(SuperUrlWebViewSource))); 13 | 14 | return new SuperUrlWebViewSource { Url = value }; 15 | } 16 | 17 | public override string ConvertToInvariantString(object value) 18 | { 19 | if (!(value is SuperUrlWebViewSource uwvs)) 20 | throw new NotSupportedException(); 21 | 22 | return uwvs.Url; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Helpers/EmbeddedResourceHelper.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace Axemasta.SuperWebView 7 | { 8 | /// 9 | /// Embedded Resource Helper 10 | /// 11 | public static class EmbeddedResourceHelper 12 | { 13 | /// 14 | /// Load Resource From Assembly 15 | /// 16 | /// 17 | /// 18 | /// 19 | public static string Load(string resourceName, string assemblyName) 20 | { 21 | if (string.IsNullOrEmpty(resourceName)) throw new ArgumentNullException(nameof(resourceName)); 22 | if (string.IsNullOrEmpty(assemblyName)) throw new ArgumentNullException(nameof(assemblyName)); 23 | 24 | Assembly assembly = Assembly.Load(assemblyName); 25 | 26 | if (assembly is null) 27 | throw new InvalidOperationException($"Could not load assembly: {assemblyName}"); 28 | 29 | string[] assemblyResources = assembly.GetManifestResourceNames(); 30 | 31 | string resource = assemblyResources.FirstOrDefault(r => r == resourceName); 32 | 33 | if (resource is null) 34 | throw new InvalidOperationException($"No assembly resource found in assembly: {assembly.FullName} with name: {resourceName}"); 35 | 36 | using (var stream = assembly.GetManifestResourceStream(resource)) 37 | { 38 | if (stream is null) 39 | return null; 40 | 41 | using (var streamReader = new StreamReader(stream)) 42 | { 43 | return streamReader.ReadToEnd(); 44 | } 45 | } 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Interfaces/IDeferralToken.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Axemasta.SuperWebView 3 | { 4 | public interface IDeferralToken 5 | { 6 | void Complete(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Interfaces/ISuperWebViewController.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | using Xamarin.Forms.Internals; 4 | 5 | namespace Axemasta.SuperWebView 6 | { 7 | public interface ISuperWebViewController : IViewController 8 | { 9 | bool CanGoBack { get; set; } 10 | bool CanGoForward { get; set; } 11 | event EventHandler EvalRequested; 12 | event EvaluateJavaScriptDelegate EvaluateJavaScriptRequested; 13 | event EventHandler GoBackRequested; 14 | event EventHandler GoForwardRequested; 15 | event EventHandler ReloadRequested; 16 | void SendNavigated(SuperWebNavigatedEventArgs args); 17 | void SendNavigating(SuperWebNavigatingEventArgs args); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Interfaces/ISuperWebViewDelegate.shared.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView 2 | { 3 | public interface ISuperWebViewDelegate 4 | { 5 | /// 6 | /// Load Html, For internal use by SuperWebView platform renderers 7 | /// 8 | /// Html content 9 | /// Base url of the current platform 10 | /// Title of the html page, this will update Url property 11 | void LoadHtml(string html, string baseUrl, string title); 12 | 13 | /// 14 | /// Load Html, For internal use by SuperWebView platform renderers 15 | /// 16 | /// Html content 17 | /// Base url of the current platform 18 | void LoadHtml(string html, string baseUrl); 19 | 20 | /// 21 | /// Load Url, For internal use by SuperWebView platform renderers 22 | /// 23 | /// Url to load 24 | void LoadUrl(string url); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Interfaces/IWebNavigationEventArgs.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | 4 | namespace Axemasta.SuperWebView 5 | { 6 | public interface ISuperWebNavigationEventArgs 7 | { 8 | WebNavigationEvent NavigationEvent { get; } 9 | 10 | SuperWebViewSource Source { get; } 11 | 12 | string Url { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Internals/Constants.android.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView.Internals 2 | { 3 | /// 4 | /// Android Constants 5 | /// 6 | internal class AndroidConstants 7 | { 8 | internal const string InvokeNativeScriptPath = "Axemasta.SuperWebView.Scripts.invokenative.android.js"; 9 | 10 | internal const string JSBridge = "jsBridge"; 11 | 12 | internal const string AssetBaseUrl = "file:///android_asset/"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Internals/Constants.ios.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView.Internals 2 | { 3 | /// 4 | /// iOS Constants 5 | /// 6 | internal class iOSConstants 7 | { 8 | internal const string InvokeNativeScriptPath = "Axemasta.SuperWebView.Scripts.invokenative.ios.js"; 9 | 10 | internal const string EstimatedProgress = "estimatedProgress"; 11 | 12 | internal const string Url = "URL"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Internals/Constants.shared.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView.Internals 2 | { 3 | /// 4 | /// Constants 5 | /// 6 | internal static class Constants 7 | { 8 | /// 9 | /// Invoke Action 10 | /// 11 | internal const string InvokeAction = "invokeAction"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Internals/InjectJavaScriptDelegate.shared.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Axemasta.SuperWebView.Internals 4 | { 5 | [EditorBrowsable(EditorBrowsableState.Never)] 6 | public delegate void InjectJavaScriptDelegate(string script); 7 | } 8 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Internals/RendererRegistry.shared.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView.Internals 2 | { 3 | internal class _SuperWebViewRenderer 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/BrowserInvocationEventArgs.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Axemasta.SuperWebView 3 | { 4 | public class BrowserInvocationEventArgs : EventArgs 5 | { 6 | public string Message { get; } 7 | 8 | public BrowserInvocationEventArgs(string message) 9 | { 10 | this.Message = message; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/DeferrableEventArgs.shared.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Axemasta.SuperWebView 7 | { 8 | public class DeferrableEventArgs : EventArgs 9 | { 10 | private int _deferralCount; 11 | private Func _deferralFinishedTask; 12 | private TaskCompletionSource _deferredTaskCompletionSource; 13 | private bool _deferralCompleted = false; 14 | 15 | public DeferrableEventArgs(bool canCancel) 16 | { 17 | #if NETSTANDARD2_0 18 | _deferralFinishedTask = () => Task.CompletedTask; 19 | #else 20 | _deferralFinishedTask = () => Task.Delay(0); 21 | #endif 22 | 23 | CanCancel = canCancel; 24 | } 25 | 26 | public bool CanCancel { get; } 27 | 28 | public bool Cancel() 29 | { 30 | if (!CanCancel) 31 | return false; 32 | 33 | Cancelled = true; 34 | return true; 35 | } 36 | 37 | public bool Cancelled { get; private set; } 38 | 39 | public IDeferralToken GetDeferral() 40 | { 41 | if (_deferralCompleted) 42 | throw new InvalidOperationException("Deferral has already been completed"); 43 | 44 | if (!CanCancel) 45 | return new EmptyDeferralToken(); 46 | 47 | DeferralRequested = true; 48 | var currentCount = Interlocked.Increment(ref _deferralCount); 49 | if (currentCount == 1) 50 | { 51 | _deferredTaskCompletionSource = new TaskCompletionSource(); 52 | } 53 | 54 | return new DeferralToken(DecrementDeferral); 55 | } 56 | 57 | private async void DecrementDeferral() 58 | { 59 | if (Interlocked.Decrement(ref _deferralCount) == 0) 60 | { 61 | _deferralCompleted = true; 62 | 63 | try 64 | { 65 | var task = _deferralFinishedTask(); 66 | _deferralFinishedTask = null; 67 | await task; 68 | _deferredTaskCompletionSource.SetResult(!Cancelled); 69 | } 70 | catch (TaskCanceledException) 71 | { 72 | _deferredTaskCompletionSource.SetCanceled(); 73 | } 74 | catch (Exception exc) 75 | { 76 | _deferredTaskCompletionSource.SetException(exc); 77 | } 78 | 79 | _deferredTaskCompletionSource = null; 80 | } 81 | } 82 | 83 | internal Task DeferredTask => _deferredTaskCompletionSource?.Task; 84 | internal bool Animate { get; set; } 85 | 86 | public bool DeferralRequested { get; set; } 87 | 88 | internal int DeferralCount => _deferralCount; 89 | 90 | internal bool NavigationDelayedOrCancelled => 91 | Cancelled || DeferralCount > 0; 92 | 93 | public void RegisterDeferralCompletedCallBack(Func deferralFinishedTask) 94 | { 95 | _deferralFinishedTask = deferralFinishedTask; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/DeferralToken.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace Axemasta.SuperWebView 5 | { 6 | public class DeferralToken : IDeferralToken 7 | { 8 | Action _completed; 9 | 10 | internal DeferralToken(Action completed) 11 | { 12 | _completed = completed; 13 | } 14 | 15 | public void Complete() 16 | { 17 | var taskToComplete = Interlocked.Exchange(ref _completed, null); 18 | 19 | if (taskToComplete != null) 20 | taskToComplete?.Invoke(); 21 | } 22 | 23 | internal bool IsCompleted => _completed == null; 24 | } 25 | 26 | /// 27 | /// Blank Deferral Token For When Cancellation Is Not Enabled 28 | /// 29 | public class EmptyDeferralToken : IDeferralToken 30 | { 31 | /* If CanCancel == false & the user has not checked for this, GetDeferralToken() would otherwise 32 | * return null causing the Complete(); method to throw a NullReferenceException. This class prevents 33 | * this condition from occuring 34 | */ 35 | 36 | public void Complete() 37 | { 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/EmbeddedJavaScript.shared.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView 2 | { 3 | public class EmbeddedJavaScript : JavaScript 4 | { 5 | public string Path { get; } 6 | 7 | public string AssemblyName { get; } 8 | 9 | public EmbeddedJavaScript(string name, string path, string assemblyName) 10 | { 11 | this.Name = name; 12 | this.Path = path; 13 | this.AssemblyName = assemblyName; 14 | } 15 | 16 | protected override string Load() 17 | { 18 | return EmbeddedResourceHelper.Load(Path, AssemblyName); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/JavaScript.shared.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView 2 | { 3 | /// 4 | /// Java Script 5 | /// 6 | public abstract class JavaScript 7 | { 8 | public string Name { get; set; } 9 | 10 | protected abstract string Load(); 11 | 12 | public bool TryLoadScript(out string content) 13 | { 14 | content = null; 15 | 16 | try 17 | { 18 | content = Load(); 19 | 20 | return !string.IsNullOrEmpty(content); 21 | } 22 | catch 23 | { 24 | return false; 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/NavigationCancelledEventArgs.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Axemasta.SuperWebView 4 | { 5 | public class NavigationCancelledEventArgs : EventArgs 6 | { 7 | public string Url { get; } 8 | 9 | public NavigationCancelledEventArgs(string url) 10 | { 11 | Url = url; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/ProgressEventArgs.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Axemasta.SuperWebView 3 | { 4 | /// 5 | /// Progress Event Args 6 | /// 7 | public class ProgressEventArgs : EventArgs 8 | { 9 | /// 10 | /// Progress Complete Normalized Between 0 & 1 11 | /// 12 | public double NormalisedProgress { get; } 13 | 14 | /// 15 | /// Percentage Complete 16 | /// 17 | public double PercentageComplete { get; } 18 | 19 | public ProgressEventArgs(double progress, int maximum) 20 | { 21 | NormalisedProgress = Math.Round(progress / maximum, 4); 22 | 23 | PercentageComplete = Math.Round(NormalisedProgress * 100, 2); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/RawJavaScript.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Axemasta.SuperWebView 3 | { 4 | /// 5 | /// Raw Java Script 6 | /// 7 | public class RawJavaScript : JavaScript 8 | { 9 | public string Content { get; set; } 10 | 11 | public RawJavaScript(string name, string content) 12 | { 13 | if (string.IsNullOrEmpty(name)) 14 | throw new ArgumentNullException(nameof(name), "Script must have a name"); 15 | 16 | this.Name = name; 17 | this.Content = content; 18 | } 19 | 20 | protected override string Load() 21 | { 22 | // Sanitise javascript here 23 | 24 | return Content; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/SuperHtmlWebViewSource.shared.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Xamarin.Forms; 3 | 4 | namespace Axemasta.SuperWebView 5 | { 6 | public class SuperHtmlWebViewSource : SuperWebViewSource 7 | { 8 | public static readonly BindableProperty HtmlProperty = BindableProperty.Create(nameof(Html), typeof(string), typeof(SuperHtmlWebViewSource), default(string), 9 | propertyChanged: (bindable, oldvalue, newvalue) => ((SuperHtmlWebViewSource)bindable).OnSourceChanged()); 10 | 11 | public static readonly BindableProperty BaseUrlProperty = BindableProperty.Create(nameof(BaseUrl), typeof(string), typeof(SuperHtmlWebViewSource), default(string), 12 | propertyChanged: (bindable, oldvalue, newvalue) => ((SuperHtmlWebViewSource)bindable).OnSourceChanged()); 13 | 14 | public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(SuperHtmlWebViewSource), default(string), 15 | propertyChanged: (bindable, oldvalue, newvalue) => ((SuperHtmlWebViewSource)bindable).OnSourceChanged()); 16 | 17 | public string BaseUrl 18 | { 19 | get { return (string)GetValue(BaseUrlProperty); } 20 | set { SetValue(BaseUrlProperty, value); } 21 | } 22 | 23 | public string Html 24 | { 25 | get { return (string)GetValue(HtmlProperty); } 26 | set { SetValue(HtmlProperty, value); } 27 | } 28 | 29 | public string Title 30 | { 31 | get { return (string)GetValue(TitleProperty); } 32 | set { SetValue(TitleProperty, value); } 33 | } 34 | 35 | [EditorBrowsable(EditorBrowsableState.Never)] 36 | public override void Load(ISuperWebViewDelegate renderer) 37 | { 38 | if (string.IsNullOrEmpty(Title)) 39 | { 40 | renderer.LoadHtml(Html, BaseUrl); 41 | } 42 | else 43 | { 44 | renderer.LoadHtml(Html, BaseUrl, Title); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/SuperUrlWebViewSource.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Xamarin.Forms; 4 | 5 | namespace Axemasta.SuperWebView 6 | { 7 | public class SuperUrlWebViewSource : SuperWebViewSource 8 | { 9 | public static readonly BindableProperty UrlProperty = BindableProperty.Create(nameof(Url), typeof(string), typeof(SuperUrlWebViewSource), default(string), 10 | propertyChanged: (bindable, oldvalue, newvalue) => ((SuperUrlWebViewSource)bindable).OnSourceChanged()); 11 | 12 | public string Url 13 | { 14 | get { return (string)GetValue(UrlProperty); } 15 | set { SetValue(UrlProperty, value); } 16 | } 17 | 18 | [EditorBrowsable(EditorBrowsableState.Never)] 19 | public override void Load(ISuperWebViewDelegate renderer) 20 | { 21 | renderer.LoadUrl(Url); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/SuperWebNavigatedEventArgs.shared.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms; 2 | 3 | namespace Axemasta.SuperWebView 4 | { 5 | public class SuperWebNavigatedEventArgs : ISuperWebNavigationEventArgs 6 | { 7 | public WebNavigationResult Result { get; } 8 | 9 | public WebNavigationEvent NavigationEvent { get; } 10 | 11 | public SuperWebViewSource Source { get; } 12 | 13 | public string Url { get; } 14 | 15 | public SuperWebNavigatedEventArgs(WebNavigationEvent navigationEvent, SuperWebViewSource source, string url, WebNavigationResult result) 16 | { 17 | NavigationEvent = navigationEvent; 18 | Source = source; 19 | Url = url; 20 | Result = result; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/SuperWebNavigatingEventArgs.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | 4 | namespace Axemasta.SuperWebView 5 | { 6 | public class SuperWebNavigatingEventArgs : DeferrableEventArgs, ISuperWebNavigationEventArgs 7 | { 8 | public WebNavigationEvent NavigationEvent { get; set; } 9 | 10 | public SuperWebViewSource Source { get; set; } 11 | 12 | public string Url { get; set; } 13 | 14 | public SuperWebNavigatingEventArgs(WebNavigationEvent navigationEvent, SuperWebViewSource source, string url, bool canCancel) 15 | : base(canCancel) 16 | { 17 | NavigationEvent = navigationEvent; 18 | Source = source; 19 | Url = url; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/SuperWebViewSource.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Xamarin.Forms; 4 | 5 | namespace Axemasta.SuperWebView 6 | { 7 | public abstract class SuperWebViewSource : BindableObject 8 | { 9 | public static implicit operator SuperWebViewSource(Uri url) 10 | { 11 | return new SuperUrlWebViewSource { Url = url?.AbsoluteUri }; 12 | } 13 | 14 | public static implicit operator SuperWebViewSource(string url) 15 | { 16 | return new SuperUrlWebViewSource { Url = url }; 17 | } 18 | 19 | protected void OnSourceChanged() 20 | { 21 | EventHandler eh = SourceChanged; 22 | if (eh != null) 23 | eh(this, EventArgs.Empty); 24 | } 25 | 26 | [EditorBrowsable(EditorBrowsableState.Never)] 27 | public abstract void Load(ISuperWebViewDelegate renderer); 28 | 29 | internal event EventHandler SourceChanged; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Models/UrlEventArgs.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Axemasta.SuperWebView 3 | { 4 | public class UrlEventArgs : EventArgs 5 | { 6 | public string NewUrl { get; } 7 | 8 | public UrlEventArgs(string newUrl) 9 | { 10 | this.NewUrl = newUrl; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/Android/ActivityResultCallbackRegistry.android.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using Android.App; 4 | using Android.Content; 5 | using Xamarin.Forms; 6 | using Xamarin.Forms.Platform.Android; 7 | 8 | namespace Axemasta.SuperWebView.Droid 9 | { 10 | public static class ActivityResultCallbackRegistry 11 | { 12 | static readonly ConcurrentDictionary> s_activityResultCallbacks = 13 | new ConcurrentDictionary>(); 14 | 15 | static int s_nextActivityResultCallbackKey; 16 | 17 | public static void InvokeCallback(int requestCode, Result resultCode, Intent data) 18 | { 19 | Action callback; 20 | 21 | if (s_activityResultCallbacks.TryGetValue(requestCode, out callback)) 22 | { 23 | callback(resultCode, data); 24 | } 25 | } 26 | 27 | internal static int RegisterActivityResultCallback(Action callback) 28 | { 29 | int requestCode = s_nextActivityResultCallbackKey; 30 | 31 | while (!s_activityResultCallbacks.TryAdd(requestCode, callback)) 32 | { 33 | s_nextActivityResultCallbackKey += 1; 34 | requestCode = s_nextActivityResultCallbackKey; 35 | } 36 | 37 | s_nextActivityResultCallbackKey += 1; 38 | 39 | return requestCode; 40 | } 41 | 42 | internal static void UnregisterActivityResultCallback(int requestCode) 43 | { 44 | Action callback; 45 | s_activityResultCallbacks.TryRemove(requestCode, out callback); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/Android/Forms.android.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Android.OS; 3 | 4 | namespace Axemasta.SuperWebView.Droid 5 | { 6 | public static class Forms 7 | { 8 | static BuildVersionCodes? s_sdkInt; 9 | 10 | internal static BuildVersionCodes SdkInt 11 | { 12 | get 13 | { 14 | if (!s_sdkInt.HasValue) 15 | s_sdkInt = Build.VERSION.SdkInt; 16 | return (BuildVersionCodes)s_sdkInt; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/Android/FormsSuperWebChromeClient.android.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Android.App; 4 | using Android.Content; 5 | using Android.Webkit; 6 | using Xamarin.Forms.Internals; 7 | using Object = Java.Lang.Object; 8 | 9 | namespace Axemasta.SuperWebView.Droid 10 | { 11 | public class FormsSuperWebChromeClient : WebChromeClient 12 | { 13 | public event EventHandler ProgressChanged; 14 | 15 | Activity _activity; 16 | List _requestCodes; 17 | 18 | public override bool OnShowFileChooser(global::Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams) 19 | { 20 | base.OnShowFileChooser(webView, filePathCallback, fileChooserParams); 21 | return ChooseFile(filePathCallback, fileChooserParams.CreateIntent(), fileChooserParams.Title); 22 | } 23 | 24 | public void UnregisterCallbacks() 25 | { 26 | if (_requestCodes == null || _requestCodes.Count == 0 || _activity == null) 27 | return; 28 | 29 | foreach (int requestCode in _requestCodes) 30 | { 31 | ActivityResultCallbackRegistry.UnregisterActivityResultCallback(requestCode); 32 | } 33 | 34 | _requestCodes = null; 35 | } 36 | 37 | protected bool ChooseFile(IValueCallback filePathCallback, Intent intent, string title) 38 | { 39 | if (_activity == null) 40 | return false; 41 | 42 | Action callback = (resultCode, intentData) => 43 | { 44 | if (filePathCallback == null) 45 | return; 46 | 47 | Object result = ParseResult(resultCode, intentData); 48 | filePathCallback.OnReceiveValue(result); 49 | }; 50 | 51 | _requestCodes = _requestCodes ?? new List(); 52 | 53 | int newRequestCode = ActivityResultCallbackRegistry.RegisterActivityResultCallback(callback); 54 | 55 | _requestCodes.Add(newRequestCode); 56 | 57 | _activity.StartActivityForResult(Intent.CreateChooser(intent, title), newRequestCode); 58 | 59 | return true; 60 | } 61 | 62 | protected override void Dispose(bool disposing) 63 | { 64 | if (disposing) 65 | UnregisterCallbacks(); 66 | base.Dispose(disposing); 67 | } 68 | 69 | protected virtual Object ParseResult(Result resultCode, Intent data) 70 | { 71 | return FileChooserParams.ParseResult((int)resultCode, data); 72 | } 73 | 74 | internal void SetContext(Context thisActivity) 75 | { 76 | _activity = thisActivity as Activity; 77 | if (_activity == null) 78 | Log.Warning(nameof(SuperWebViewRenderer), $"Failed to set the activity of the WebChromeClient, can't show pickers on the Webview"); 79 | } 80 | 81 | public override void OnProgressChanged(WebView view, int newProgress) 82 | { 83 | ProgressChanged?.Invoke(this, new ProgressEventArgs(newProgress, 100)); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/Android/FormsSuperWebViewClient.android.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using Android.Graphics; 7 | using Android.Runtime; 8 | using Android.Webkit; 9 | using Xamarin.Forms; 10 | using Xamarin.Forms.Platform.Android; 11 | using WView = Android.Webkit.WebView; 12 | 13 | namespace Axemasta.SuperWebView.Droid 14 | { 15 | public class FormsSuperWebViewClient : WebViewClient 16 | { 17 | private readonly List _scripts; 18 | WebNavigationResult _navigationResult = WebNavigationResult.Success; 19 | SuperWebViewRenderer _renderer; 20 | string _lastUrlNavigatedCancel; 21 | 22 | public FormsSuperWebViewClient(SuperWebViewRenderer renderer) 23 | { 24 | _renderer = renderer ?? throw new ArgumentNullException(nameof(renderer)); 25 | _scripts = new List(); 26 | } 27 | 28 | protected FormsSuperWebViewClient(IntPtr javaReference, JniHandleOwnership transfer) 29 | : base(javaReference, transfer) 30 | { 31 | _scripts = new List(); 32 | } 33 | 34 | async Task SendNavigatingCanceledAsync(string url) 35 | { 36 | if (_renderer == null) 37 | return true; 38 | 39 | return await _renderer.SendNavigatingCanceledAsync(url); 40 | } 41 | 42 | [Obsolete("ShouldOverrideUrlLoading(view,url) is obsolete as of version 4.0.0. This method was deprecated in API level 24.")] 43 | [EditorBrowsable(EditorBrowsableState.Never)] 44 | // api 19-23 45 | public override bool ShouldOverrideUrlLoading(WView view, string url) 46 | { 47 | OverrideUrlLoading(url, () => SendNavigatingCanceledAsync(url)); 48 | 49 | return true; 50 | } 51 | 52 | // api 24+ 53 | public override bool ShouldOverrideUrlLoading(WView view, IWebResourceRequest request) 54 | { 55 | var url = request?.Url?.ToString(); 56 | 57 | OverrideUrlLoading(url, () => SendNavigatingCanceledAsync(url)); 58 | 59 | return true; 60 | } 61 | 62 | public override async void OnPageStarted(WView view, string url, Bitmap favicon) 63 | { 64 | if (_renderer?.Element == null || string.IsNullOrWhiteSpace(url) || url == WebViewRenderer.AssetBaseUrl) 65 | return; 66 | 67 | _renderer.SyncNativeCookiesToElement(url); 68 | var cancel = false; 69 | 70 | if (!url.Equals(_renderer.UrlCanceled, StringComparison.OrdinalIgnoreCase)) 71 | { 72 | cancel = await SendNavigatingCanceledAsync(url); 73 | } 74 | 75 | _renderer.UrlCanceled = null; 76 | 77 | if (cancel) 78 | { 79 | _navigationResult = WebNavigationResult.Cancel; 80 | view.StopLoading(); 81 | } 82 | else 83 | { 84 | _navigationResult = WebNavigationResult.Success; 85 | base.OnPageStarted(view, url, favicon); 86 | } 87 | } 88 | 89 | public override void OnPageFinished(WView view, string url) 90 | { 91 | if (_renderer?.Element == null || url == WebViewRenderer.AssetBaseUrl) 92 | return; 93 | 94 | var source = new SuperUrlWebViewSource { Url = url }; 95 | _renderer.IgnoreSourceChanges = true; 96 | _renderer.ElementController.SetValueFromRenderer(SuperWebView.SourceProperty, source); 97 | _renderer.IgnoreSourceChanges = false; 98 | 99 | bool navigate = _navigationResult == WebNavigationResult.Failure ? !url.Equals(_lastUrlNavigatedCancel, StringComparison.OrdinalIgnoreCase) : true; 100 | _lastUrlNavigatedCancel = _navigationResult == WebNavigationResult.Cancel ? url : null; 101 | 102 | if (navigate) 103 | { 104 | var args = new SuperWebNavigatedEventArgs(_renderer.GetCurrentWebNavigationEvent(), source, url, _navigationResult); 105 | _renderer.SyncNativeCookiesToElement(url); 106 | _renderer.ElementController.SendNavigated(args); 107 | } 108 | 109 | if (_scripts != null && _scripts.Count > 0) 110 | { 111 | foreach (string script in _scripts) 112 | { 113 | view.EvaluateJavascript(script, null); 114 | } 115 | } 116 | 117 | _renderer.UpdateCanGoBackForward(); 118 | 119 | base.OnPageFinished(view, url); 120 | } 121 | 122 | [Obsolete("OnReceivedError is obsolete as of version 2.3.0. This method was deprecated in API level 23.")] 123 | [EditorBrowsable(EditorBrowsableState.Never)] 124 | public override void OnReceivedError(WView view, ClientError errorCode, string description, string failingUrl) 125 | { 126 | if (failingUrl == _renderer?.Control.Url) 127 | { 128 | _navigationResult = WebNavigationResult.Failure; 129 | if (errorCode == ClientError.Timeout) 130 | _navigationResult = WebNavigationResult.Timeout; 131 | } 132 | #pragma warning disable 618 133 | base.OnReceivedError(view, errorCode, description, failingUrl); 134 | #pragma warning restore 618 135 | } 136 | 137 | public override void OnReceivedError(WView view, IWebResourceRequest request, WebResourceError error) 138 | { 139 | if (request.Url.ToString() == _renderer?.Control.Url) 140 | { 141 | _navigationResult = WebNavigationResult.Failure; 142 | if (error.ErrorCode == ClientError.Timeout) 143 | _navigationResult = WebNavigationResult.Timeout; 144 | } 145 | base.OnReceivedError(view, request, error); 146 | } 147 | 148 | protected override void Dispose(bool disposing) 149 | { 150 | base.Dispose(disposing); 151 | if (disposing) 152 | _renderer = null; 153 | } 154 | 155 | public async void OverrideUrlLoading(string url, Func> urlEvaluator) 156 | { 157 | if (urlEvaluator == null) 158 | { 159 | _renderer.LoadUrl(url); 160 | return; 161 | } 162 | 163 | var canload = !await urlEvaluator.Invoke(); 164 | 165 | if (canload) 166 | { 167 | _renderer.LoadUrl(url); 168 | } 169 | } 170 | 171 | public void InjectScript(string script) 172 | { 173 | _scripts.Add(script); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/Android/JSBridge.android.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Android.Webkit; 3 | using Axemasta.SuperWebView.Internals; 4 | using Java.Interop; 5 | using Xamarin.Forms.Internals; 6 | 7 | namespace Axemasta.SuperWebView.Droid 8 | { 9 | public class JSBridge : Java.Lang.Object 10 | { 11 | private readonly WeakReference _renderer; 12 | 13 | public JSBridge(SuperWebViewRenderer renderer) 14 | { 15 | _renderer = new WeakReference(renderer); 16 | } 17 | 18 | [JavascriptInterface] 19 | [Export(Constants.InvokeAction)] 20 | public void InvokeAction(string data) 21 | { 22 | if (_renderer is null) 23 | return; 24 | 25 | if (!_renderer.TryGetTarget(out SuperWebViewRenderer renderer)) 26 | { 27 | Log.Warning(nameof(JSBridge), "Could not get target for renderer"); 28 | return; 29 | } 30 | 31 | if (renderer.SuperWebView is null) 32 | { 33 | Log.Warning(nameof(JSBridge), "Renderer Element was null"); 34 | return; 35 | } 36 | 37 | renderer.SuperWebView.SendBrowserInvocation(new BrowserInvocationEventArgs(data)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/Android/PlatformSpecific.shared.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms; 2 | 3 | namespace Axemasta.SuperWebView.PlatformConfiguration.AndroidSpecific 4 | { 5 | using FormsElement = Axemasta.SuperWebView.SuperWebView; 6 | using AndroidPlatform = Xamarin.Forms.PlatformConfiguration.Android; 7 | 8 | public enum MixedContentHandling 9 | { 10 | AlwaysAllow = 0, 11 | NeverAllow = 1, 12 | CompatibilityMode = 2 13 | } 14 | 15 | public static class AndroidConfiguration 16 | { 17 | #region Mixed Content Mode 18 | 19 | public static readonly BindableProperty MixedContentModeProperty = BindableProperty.Create("MixedContentMode", typeof(MixedContentHandling), typeof(SuperWebView), MixedContentHandling.NeverAllow); 20 | 21 | public static MixedContentHandling GetMixedContentMode(BindableObject element) 22 | { 23 | return (MixedContentHandling)element.GetValue(MixedContentModeProperty); 24 | } 25 | 26 | public static void SetMixedContentMode(BindableObject element, MixedContentHandling value) 27 | { 28 | element.SetValue(MixedContentModeProperty, value); 29 | } 30 | 31 | public static MixedContentHandling MixedContentMode(this IPlatformElementConfiguration config) 32 | { 33 | return GetMixedContentMode(config.Element); 34 | } 35 | 36 | public static IPlatformElementConfiguration SetMixedContentMode(this IPlatformElementConfiguration config, MixedContentHandling value) 37 | { 38 | SetMixedContentMode(config.Element, value); 39 | return config; 40 | } 41 | 42 | #endregion Mixed Content Mode 43 | 44 | #region Enable Zoom Controls 45 | 46 | public static readonly BindableProperty EnableZoomControlsProperty = BindableProperty.Create("EnableZoomControls", typeof(bool), typeof(FormsElement), false); 47 | 48 | public static bool GetEnableZoomControls(FormsElement element) 49 | { 50 | return (bool)element.GetValue(EnableZoomControlsProperty); 51 | } 52 | 53 | public static void SetEnableZoomControls(FormsElement element, bool value) 54 | { 55 | element.SetValue(EnableZoomControlsProperty, value); 56 | } 57 | 58 | public static void EnableZoomControls(this IPlatformElementConfiguration config, bool value) 59 | { 60 | SetEnableZoomControls(config.Element, value); 61 | } 62 | public static bool ZoomControlsEnabled(this IPlatformElementConfiguration config) 63 | { 64 | return GetEnableZoomControls(config.Element); 65 | } 66 | 67 | public static IPlatformElementConfiguration SetEnableZoomControls(this IPlatformElementConfiguration config, bool value) 68 | { 69 | SetEnableZoomControls(config.Element, value); 70 | return config; 71 | } 72 | 73 | #endregion Enable Zoom Controls 74 | 75 | #region Display Zoom Controls 76 | 77 | public static readonly BindableProperty DisplayZoomControlsProperty = BindableProperty.Create("DisplayZoomControls", typeof(bool), typeof(FormsElement), true); 78 | 79 | public static bool GetDisplayZoomControls(FormsElement element) 80 | { 81 | return (bool)element.GetValue(DisplayZoomControlsProperty); 82 | } 83 | 84 | public static void SetDisplayZoomControls(FormsElement element, bool value) 85 | { 86 | element.SetValue(DisplayZoomControlsProperty, value); 87 | } 88 | 89 | public static void DisplayZoomControls(this IPlatformElementConfiguration config, bool value) 90 | { 91 | SetDisplayZoomControls(config.Element, value); 92 | } 93 | 94 | public static bool ZoomControlsDisplayed(this IPlatformElementConfiguration config) 95 | { 96 | return GetDisplayZoomControls(config.Element); 97 | } 98 | 99 | public static IPlatformElementConfiguration SetDisplayZoomControls(this IPlatformElementConfiguration config, bool value) 100 | { 101 | SetDisplayZoomControls(config.Element, value); 102 | return config; 103 | } 104 | 105 | #endregion Display Zoom Controls 106 | 107 | #region Hardening Enabled 108 | 109 | public static readonly BindableProperty HardeningEnabledProperty = BindableProperty.Create("HardeningEnabled", typeof(bool), typeof(FormsElement), false); 110 | 111 | public static IPlatformElementConfiguration SetHardeningEnabled(this IPlatformElementConfiguration config, bool value) 112 | { 113 | SetHardeningEnabled(config.Element, value); 114 | return config; 115 | } 116 | 117 | public static void SetHardeningEnabled(BindableObject element, bool value) 118 | { 119 | element.SetValue(HardeningEnabledProperty, value); 120 | } 121 | 122 | public static bool GetHardeningEnabled(BindableObject element) 123 | { 124 | return (bool)element.GetValue(HardeningEnabledProperty); 125 | } 126 | 127 | #endregion Hardening Enabled 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/Android/SuperWebViewRenderer.android.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using Android.Content; 7 | using Android.Webkit; 8 | using Xamarin.Forms; 9 | using Xamarin.Forms.Internals; 10 | using Xamarin.Forms.Platform.Android; 11 | using Xamarin.Forms.PlatformConfiguration.AndroidSpecific; 12 | using AWebView = Android.Webkit.WebView; 13 | using AMixedContentHandling = Android.Webkit.MixedContentHandling; 14 | using Axemasta.SuperWebView.Internals; 15 | using Axemasta.SuperWebView.PlatformConfiguration.AndroidSpecific; 16 | 17 | namespace Axemasta.SuperWebView.Droid 18 | { 19 | public class SuperWebViewRenderer : ViewRenderer, ISuperWebViewDelegate 20 | { 21 | WebNavigationEvent _eventState; 22 | FormsSuperWebViewClient _webViewClient; 23 | FormsSuperWebChromeClient _webChromeClient; 24 | bool _isDisposed = false; 25 | protected internal ISuperWebViewController ElementController => Element; 26 | protected internal bool IgnoreSourceChanges { get; set; } 27 | protected internal string UrlCanceled { get; set; } 28 | 29 | protected internal SuperWebView SuperWebView => Element as SuperWebView; 30 | 31 | public SuperWebViewRenderer(Context context) 32 | : base(context) 33 | { 34 | AutoPackage = false; 35 | } 36 | 37 | public void LoadHtml(string html, string baseUrl) 38 | { 39 | _eventState = WebNavigationEvent.NewPage; 40 | Control.LoadDataWithBaseURL(baseUrl ?? AndroidConstants.AssetBaseUrl, html, "text/html", "UTF-8", null); 41 | } 42 | 43 | public void LoadHtml(string html, string baseUrl, string title) 44 | { 45 | _eventState = WebNavigationEvent.NewPage; 46 | Control.LoadDataWithBaseURL(baseUrl ?? AndroidConstants.AssetBaseUrl, html, "text/html", "UTF-8", null); 47 | 48 | SuperWebView.SendUrlChanged(new UrlEventArgs(title)); 49 | } 50 | 51 | public void LoadUrl(string url) 52 | { 53 | LoadUrl(url, true); 54 | 55 | SuperWebView.SendUrlChanged(new UrlEventArgs(url)); 56 | } 57 | 58 | async void LoadUrl(string url, bool fireNavigatingCanceled) 59 | { 60 | if (!fireNavigatingCanceled || !await SendNavigatingCanceledAsync(url)) 61 | { 62 | _eventState = WebNavigationEvent.NewPage; 63 | Control.LoadUrl(url); 64 | } 65 | } 66 | 67 | protected internal async Task SendNavigatingCanceledAsync(string url) 68 | { 69 | if (Element == null || string.IsNullOrWhiteSpace(url)) 70 | return true; 71 | 72 | if (url == AndroidConstants.AssetBaseUrl) 73 | return false; 74 | 75 | var args = new SuperWebNavigatingEventArgs(_eventState, new SuperUrlWebViewSource { Url = url }, url, true); 76 | SyncNativeCookies(url); 77 | ElementController.SendNavigating(args); 78 | UpdateCanGoBackForward(); 79 | 80 | var cancel = false; 81 | 82 | if (args.DeferralRequested) 83 | { 84 | try 85 | { 86 | cancel = !await Task.Run(() => args.DeferredTask) 87 | .ConfigureAwait(true); 88 | } 89 | catch (TaskCanceledException) 90 | { 91 | // If this throws it will brick the execution 92 | } 93 | catch (Exception ex) 94 | { 95 | Log.Warning("diagnostics", "an exception occurred sending navigating cancelled"); 96 | Log.Warning("diagnostics", ex.ToString()); 97 | } 98 | } 99 | 100 | if (cancel) 101 | SuperWebView.SendNavigationCancelled(new NavigationCancelledEventArgs(url)); 102 | 103 | return cancel; 104 | } 105 | 106 | protected override void Dispose(bool disposing) 107 | { 108 | if (_isDisposed) 109 | return; 110 | 111 | _isDisposed = true; 112 | 113 | if (disposing) 114 | { 115 | if (Element != null) 116 | { 117 | Control?.StopLoading(); 118 | 119 | ElementController.EvalRequested -= OnEvalRequested; 120 | ElementController.GoBackRequested -= OnGoBackRequested; 121 | ElementController.GoForwardRequested -= OnGoForwardRequested; 122 | ElementController.ReloadRequested -= OnReloadRequested; 123 | ElementController.EvaluateJavaScriptRequested -= OnEvaluateJavaScriptRequested; 124 | 125 | _webViewClient?.Dispose(); 126 | _webChromeClient?.Dispose(); 127 | } 128 | } 129 | 130 | base.Dispose(disposing); 131 | } 132 | 133 | protected virtual FormsSuperWebViewClient GetWebViewClient() 134 | { 135 | return new FormsSuperWebViewClient(this); 136 | } 137 | 138 | protected virtual FormsSuperWebChromeClient GetFormsWebChromeClient() 139 | { 140 | return new FormsSuperWebChromeClient(); 141 | } 142 | 143 | protected override Size MinimumSize() 144 | { 145 | return new Size(Context.ToPixels(40), Context.ToPixels(40)); 146 | } 147 | 148 | protected override AWebView CreateNativeControl() 149 | { 150 | var webView = new AWebView(Context); 151 | webView.Settings.SetSupportMultipleWindows(true); 152 | 153 | return webView; 154 | } 155 | 156 | public void SetHardeningMode() 157 | { 158 | if (Control is null || Element is null) 159 | return; 160 | 161 | var hardening = AndroidConfiguration.GetHardeningEnabled(Element); 162 | 163 | // If hardening enabled, prevent these from occuring 164 | this.Control.Settings.AllowFileAccess = !hardening; 165 | this.Control.Settings.AllowFileAccessFromFileURLs = !hardening; 166 | this.Control.Settings.AllowContentAccess = !hardening; 167 | } 168 | 169 | internal WebNavigationEvent GetCurrentWebNavigationEvent() 170 | { 171 | return _eventState; 172 | } 173 | 174 | private void OnProgressChanged(object sender, ProgressEventArgs e) 175 | { 176 | if (SuperWebView == null) 177 | return; 178 | 179 | SuperWebView.SendProgressChanged(e); 180 | } 181 | 182 | protected override void OnElementChanged(ElementChangedEventArgs e) 183 | { 184 | base.OnElementChanged(e); 185 | 186 | if (Control == null) 187 | { 188 | var webView = CreateNativeControl(); 189 | #pragma warning disable 618 // This can probably be replaced with LinearLayout(LayoutParams.MatchParent, LayoutParams.MatchParent); just need to test that theory 190 | webView.LayoutParameters = new global::Android.Widget.AbsoluteLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent, 0, 0); 191 | #pragma warning restore 618 192 | 193 | _webViewClient = GetWebViewClient(); 194 | webView.SetWebViewClient(_webViewClient); 195 | 196 | _webChromeClient = GetFormsWebChromeClient(); 197 | _webChromeClient.SetContext(Context); 198 | webView.SetWebChromeClient(_webChromeClient); 199 | _webChromeClient.ProgressChanged += OnProgressChanged; 200 | 201 | //if (Context.IsDesignerContext()) 202 | //{ 203 | // SetNativeControl(webView); 204 | // return; 205 | //} 206 | 207 | webView.Settings.JavaScriptEnabled = true; 208 | webView.Settings.DomStorageEnabled = true; 209 | SetNativeControl(webView); 210 | 211 | SetHardeningMode(); 212 | 213 | //Control.SetWebViewClient(new JavascriptWebViewClient(this, _scripts)); 214 | webView.AddJavascriptInterface(new JSBridge(this), AndroidConstants.JSBridge); 215 | 216 | InjectJSBridge(); 217 | 218 | SuperWebView.InjectJavaScriptRequested += OnInjectScriptRequested; 219 | SuperWebView.SendRendererInitialised(); 220 | } 221 | 222 | if (e.OldElement != null) 223 | { 224 | var oldElementController = e.OldElement as ISuperWebViewController; 225 | oldElementController.EvalRequested -= OnEvalRequested; 226 | oldElementController.EvaluateJavaScriptRequested -= OnEvaluateJavaScriptRequested; 227 | oldElementController.GoBackRequested -= OnGoBackRequested; 228 | oldElementController.GoForwardRequested -= OnGoForwardRequested; 229 | oldElementController.ReloadRequested -= OnReloadRequested; 230 | } 231 | 232 | if (e.NewElement != null) 233 | { 234 | var newElementController = e.NewElement as ISuperWebViewController; 235 | newElementController.EvalRequested += OnEvalRequested; 236 | newElementController.EvaluateJavaScriptRequested += OnEvaluateJavaScriptRequested; 237 | newElementController.GoBackRequested += OnGoBackRequested; 238 | newElementController.GoForwardRequested += OnGoForwardRequested; 239 | newElementController.ReloadRequested += OnReloadRequested; 240 | 241 | UpdateMixedContentMode(); 242 | UpdateEnableZoomControls(); 243 | UpdateDisplayZoomControls(); 244 | } 245 | 246 | Load(); 247 | } 248 | 249 | private void InjectJSBridge() 250 | { 251 | try 252 | { 253 | var assemblyName = GetType().Assembly.FullName; 254 | 255 | var invokeNative = EmbeddedResourceHelper.Load(AndroidConstants.InvokeNativeScriptPath, assemblyName); 256 | 257 | _webViewClient.InjectScript(invokeNative); 258 | } 259 | catch (Exception ex) 260 | { 261 | Log.Warning(nameof(SuperWebViewRenderer), $"Could not load {AndroidConstants.InvokeNativeScriptPath}"); 262 | Log.Warning(nameof(SuperWebViewRenderer), ex.ToString()); 263 | } 264 | } 265 | 266 | private void OnInjectScriptRequested(string script) 267 | { 268 | _webViewClient.InjectScript(script); 269 | } 270 | 271 | protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) 272 | { 273 | base.OnElementPropertyChanged(sender, e); 274 | 275 | switch (e.PropertyName) 276 | { 277 | case "Source": 278 | Load(); 279 | break; 280 | case "MixedContentMode": 281 | UpdateMixedContentMode(); 282 | break; 283 | case "EnableZoomControls": 284 | UpdateEnableZoomControls(); 285 | break; 286 | case "DisplayZoomControls": 287 | UpdateDisplayZoomControls(); 288 | break; 289 | } 290 | } 291 | 292 | HashSet _loadedCookies = new HashSet(); 293 | 294 | Uri CreateUriForCookies(string url) 295 | { 296 | if (url == null) 297 | return null; 298 | 299 | Uri uri; 300 | 301 | if (url.Length > 2000) 302 | url = url.Substring(0, 2000); 303 | 304 | if (Uri.TryCreate(url, UriKind.Absolute, out uri)) 305 | { 306 | if (String.IsNullOrWhiteSpace(uri.Host)) 307 | return null; 308 | 309 | return uri; 310 | } 311 | 312 | return null; 313 | } 314 | 315 | CookieCollection GetCookiesFromNativeStore(string url) 316 | { 317 | CookieContainer existingCookies = new CookieContainer(); 318 | var cookieManager = CookieManager.Instance; 319 | var currentCookies = cookieManager.GetCookie(url); 320 | var uri = CreateUriForCookies(url); 321 | 322 | if (currentCookies != null) 323 | { 324 | foreach (var cookie in currentCookies.Split(';')) 325 | existingCookies.SetCookies(uri, cookie); 326 | } 327 | 328 | return existingCookies.GetCookies(uri); 329 | } 330 | 331 | void InitialCookiePreloadIfNecessary(string url) 332 | { 333 | var myCookieJar = Element.Cookies; 334 | if (myCookieJar == null) 335 | return; 336 | 337 | var uri = CreateUriForCookies(url); 338 | if (uri == null) 339 | return; 340 | 341 | if (!_loadedCookies.Add(uri.Host)) 342 | return; 343 | 344 | var cookies = myCookieJar.GetCookies(uri); 345 | 346 | if (cookies != null) 347 | { 348 | var existingCookies = GetCookiesFromNativeStore(url); 349 | foreach (Cookie cookie in existingCookies) 350 | { 351 | if (cookies[cookie.Name] == null) 352 | myCookieJar.Add(cookie); 353 | } 354 | } 355 | } 356 | 357 | internal void SyncNativeCookiesToElement(string url) 358 | { 359 | var myCookieJar = Element.Cookies; 360 | if (myCookieJar == null) 361 | return; 362 | 363 | var uri = CreateUriForCookies(url); 364 | if (uri == null) 365 | return; 366 | 367 | var cookies = myCookieJar.GetCookies(uri); 368 | var retrieveCurrentWebCookies = GetCookiesFromNativeStore(url); 369 | 370 | foreach (Cookie cookie in cookies) 371 | { 372 | var nativeCookie = retrieveCurrentWebCookies[cookie.Name]; 373 | if (nativeCookie == null) 374 | cookie.Expired = true; 375 | else 376 | cookie.Value = nativeCookie.Value; 377 | } 378 | 379 | SyncNativeCookies(url); 380 | } 381 | 382 | void SyncNativeCookies(string url) 383 | { 384 | var uri = CreateUriForCookies(url); 385 | if (uri == null) 386 | return; 387 | 388 | var myCookieJar = Element.Cookies; 389 | if (myCookieJar == null) 390 | return; 391 | 392 | InitialCookiePreloadIfNecessary(url); 393 | var cookies = myCookieJar.GetCookies(uri); 394 | if (cookies == null) 395 | return; 396 | 397 | var retrieveCurrentWebCookies = GetCookiesFromNativeStore(url); 398 | 399 | var cookieManager = CookieManager.Instance; 400 | cookieManager.SetAcceptCookie(true); 401 | for (var i = 0; i < cookies.Count; i++) 402 | { 403 | var cookie = cookies[i]; 404 | var cookieString = cookie.ToString(); 405 | cookieManager.SetCookie(cookie.Domain, cookieString); 406 | } 407 | 408 | foreach (Cookie cookie in retrieveCurrentWebCookies) 409 | { 410 | if (cookies[cookie.Name] != null) 411 | continue; 412 | 413 | var cookieString = $"{cookie.Name}=; max-age=0;expires=Sun, 31 Dec 2017 00:00:00 UTC"; 414 | cookieManager.SetCookie(cookie.Domain, cookieString); 415 | } 416 | } 417 | 418 | void Load() 419 | { 420 | if (IgnoreSourceChanges) 421 | return; 422 | 423 | Element.Source?.Load(this); 424 | 425 | UpdateCanGoBackForward(); 426 | } 427 | 428 | void OnEvalRequested(object sender, EvalRequested eventArg) 429 | { 430 | LoadUrl("javascript:" + eventArg.Script, false); 431 | } 432 | 433 | Task OnEvaluateJavaScriptRequested(string script) 434 | { 435 | var jsr = new JavascriptResult(); 436 | 437 | Control.EvaluateJavascript(script, jsr); 438 | 439 | return jsr.JsResult; 440 | } 441 | 442 | void OnGoBackRequested(object sender, EventArgs eventArgs) 443 | { 444 | if (Control.CanGoBack()) 445 | { 446 | _eventState = WebNavigationEvent.Back; 447 | Control.GoBack(); 448 | } 449 | 450 | UpdateCanGoBackForward(); 451 | } 452 | 453 | void OnGoForwardRequested(object sender, EventArgs eventArgs) 454 | { 455 | if (Control.CanGoForward()) 456 | { 457 | _eventState = WebNavigationEvent.Forward; 458 | Control.GoForward(); 459 | } 460 | 461 | UpdateCanGoBackForward(); 462 | } 463 | 464 | void OnReloadRequested(object sender, EventArgs eventArgs) 465 | { 466 | SyncNativeCookies(Control.Url?.ToString()); 467 | _eventState = WebNavigationEvent.Refresh; 468 | Control.Reload(); 469 | } 470 | 471 | protected internal void UpdateCanGoBackForward() 472 | { 473 | if (Element == null || Control == null) 474 | return; 475 | 476 | ElementController.CanGoBack = Control.CanGoBack(); 477 | ElementController.CanGoForward = Control.CanGoForward(); 478 | 479 | SuperWebView.SendCanGoBackwardsChanged(EventArgs.Empty); 480 | SuperWebView.SendCanGoForwardsChanged(EventArgs.Empty); 481 | } 482 | 483 | void UpdateMixedContentMode() 484 | { 485 | if (Control != null && ((int)Forms.SdkInt >= 21)) 486 | { 487 | Control.Settings.MixedContentMode = (AMixedContentHandling)Element.OnThisPlatform().MixedContentMode(); 488 | } 489 | } 490 | 491 | void UpdateEnableZoomControls() 492 | { 493 | var value = Element.OnThisPlatform().ZoomControlsEnabled(); 494 | Control.Settings.SetSupportZoom(value); 495 | Control.Settings.BuiltInZoomControls = value; 496 | } 497 | 498 | void UpdateDisplayZoomControls() 499 | { 500 | Control.Settings.DisplayZoomControls = Element.OnThisPlatform().ZoomControlsDisplayed(); 501 | } 502 | 503 | class JavascriptResult : Java.Lang.Object, IValueCallback 504 | { 505 | TaskCompletionSource source; 506 | public Task JsResult { get { return source.Task; } } 507 | 508 | public JavascriptResult() 509 | { 510 | source = new TaskCompletionSource(); 511 | } 512 | 513 | public void OnReceiveValue(Java.Lang.Object result) 514 | { 515 | string json = ((Java.Lang.String)result).ToString(); 516 | source.SetResult(json); 517 | } 518 | } 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/iOS/Forms.ios.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | namespace Axemasta.SuperWebView.iOS 4 | { 5 | public static class Forms 6 | { 7 | static bool? s_isiOS11OrNewer; 8 | static bool? s_isiOS12OrNewer; 9 | static bool? s_isiOS13OrNewer; 10 | 11 | internal static bool IsiOS11OrNewer 12 | { 13 | get 14 | { 15 | if (!s_isiOS11OrNewer.HasValue) 16 | s_isiOS11OrNewer = UIDevice.CurrentDevice.CheckSystemVersion(11, 0); 17 | return s_isiOS11OrNewer.Value; 18 | } 19 | } 20 | 21 | internal static bool IsiOS12OrNewer 22 | { 23 | get 24 | { 25 | if (!s_isiOS12OrNewer.HasValue) 26 | s_isiOS12OrNewer = UIDevice.CurrentDevice.CheckSystemVersion(12, 0); 27 | return s_isiOS12OrNewer.Value; 28 | } 29 | } 30 | 31 | internal static bool IsiOS13OrNewer 32 | { 33 | get 34 | { 35 | if (!s_isiOS13OrNewer.HasValue) 36 | s_isiOS13OrNewer = UIDevice.CurrentDevice.CheckSystemVersion(13, 0); 37 | return s_isiOS13OrNewer.Value; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/iOS/Platform.ios.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | using Xamarin.Forms.Internals; 4 | using Xamarin.Forms.Platform.iOS; 5 | 6 | namespace Axemasta.SuperWebView.iOS 7 | { 8 | public class Platform 9 | { 10 | internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer", typeof(IVisualElementRenderer), typeof(Platform), default(IVisualElementRenderer), 11 | propertyChanged: (bindable, oldvalue, newvalue) => 12 | { 13 | #if DEBUG 14 | if (oldvalue != null && newvalue != null) 15 | { 16 | Log.Warning("Renderer", $"{bindable} already has a renderer attached to it: {oldvalue}. Please figure out why and then fix it."); 17 | } 18 | #endif 19 | var view = bindable as VisualElement; 20 | if (view != null) 21 | view.IsPlatformEnabled = newvalue != null; 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/iOS/PlatformSpecific.shared.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView.PlatformConfiguration.iOSSpecific 2 | { 3 | using Xamarin.Forms; 4 | using FormsElement = Axemasta.SuperWebView.SuperWebView; 5 | using iOS = Xamarin.Forms.PlatformConfiguration.iOS; 6 | 7 | public static class iOSConfiguration 8 | { 9 | #region Allows Link Preview 10 | 11 | public static readonly BindableProperty AllowsLinkPreviewProperty = BindableProperty.Create("AllowLinkPreview", typeof(bool), typeof(iOSConfiguration), false); 12 | 13 | public static IPlatformElementConfiguration SetAllowsLinkPreview(this IPlatformElementConfiguration config, bool value) 14 | { 15 | SetAllowsLinkPreview(config.Element, value); 16 | return config; 17 | } 18 | 19 | public static void SetAllowsLinkPreview(BindableObject element, bool value) 20 | { 21 | element.SetValue(AllowsLinkPreviewProperty, value); 22 | } 23 | 24 | public static bool GetAllowsLinkPreview(BindableObject element) 25 | { 26 | return (bool)element.GetValue(AllowsLinkPreviewProperty); 27 | } 28 | 29 | #endregion Allows Link Preview 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/iOS/SuperCookieManager.ios.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Axemasta.SuperWebView.iOS 3 | { 4 | public class SuperCookieManager 5 | { 6 | public SuperCookieManager() 7 | { 8 | //TODO: This class will perform the cookie related tasks for the WKWebView 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/iOS/SuperWebViewNavigationDelegate.ios.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CoreFoundation; 4 | using Foundation; 5 | using WebKit; 6 | using Xamarin.Forms; 7 | using Xamarin.Forms.Internals; 8 | 9 | namespace Axemasta.SuperWebView.iOS 10 | { 11 | public class SuperWebViewNavigationDelegate : WKNavigationDelegate 12 | { 13 | private NSUrl _lastWebsite; 14 | 15 | private bool _pageIsLocal; 16 | 17 | readonly SuperWkWebViewRenderer _renderer; 18 | WebNavigationEvent _lastEvent; 19 | 20 | public SuperWebViewNavigationDelegate(SuperWkWebViewRenderer renderer) 21 | { 22 | if (renderer is null) 23 | throw new ArgumentNullException(nameof(renderer)); 24 | 25 | _renderer = renderer; 26 | 27 | _pageIsLocal = false; 28 | } 29 | 30 | SuperWebView WebView => _renderer.WebView; 31 | 32 | string GetCurrentUrl() 33 | { 34 | return _renderer?.Url?.AbsoluteUrl?.ToString(); 35 | } 36 | 37 | public override void DidFailNavigation(WKWebView webView, WKNavigation navigation, NSError error) 38 | { 39 | var url = GetCurrentUrl(); 40 | WebView.SendNavigated( 41 | new SuperWebNavigatedEventArgs(_lastEvent, new SuperUrlWebViewSource { Url = url }, url, WebNavigationResult.Failure) 42 | ); 43 | 44 | _renderer.UpdateCanGoBackForward(); 45 | } 46 | 47 | public override void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error) 48 | { 49 | var url = GetCurrentUrl(); 50 | WebView.SendNavigated( 51 | new SuperWebNavigatedEventArgs(_lastEvent, new SuperUrlWebViewSource { Url = url }, url, WebNavigationResult.Failure) 52 | ); 53 | 54 | _renderer.UpdateCanGoBackForward(); 55 | } 56 | 57 | public bool IsPageLocal() 58 | { 59 | return _pageIsLocal; 60 | } 61 | 62 | public NSUrl GetLastWebsite() 63 | { 64 | return _lastWebsite; 65 | } 66 | 67 | public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation) 68 | { 69 | if (webView.IsLoading) 70 | return; 71 | 72 | var url = GetCurrentUrl(); 73 | if (url == $"file://{NSBundle.MainBundle.BundlePath}/") 74 | { 75 | ProcessNavigatedToLocal(url); 76 | return; 77 | } 78 | 79 | _lastWebsite = _renderer?.Url; 80 | 81 | _renderer._ignoreSourceChanges = true; 82 | WebView.SetValueFromRenderer(SuperWebView.SourceProperty, new SuperUrlWebViewSource { Url = url }); 83 | _renderer._ignoreSourceChanges = false; 84 | 85 | ProcessNavigatedToWebsite(url); 86 | } 87 | 88 | public void ProcessNavigatedToLocal(string url) 89 | { 90 | _pageIsLocal = true; 91 | var args = new SuperWebNavigatedEventArgs(_lastEvent, WebView.Source, url, WebNavigationResult.Success); 92 | WebView.SendNavigated(args); 93 | _renderer.UpdateCanGoBackForward(); 94 | } 95 | 96 | async void ProcessNavigatedToWebsite(string url) 97 | { 98 | _pageIsLocal = false; 99 | try 100 | { 101 | if (_renderer?.WebView?.Cookies != null) 102 | await _renderer.SyncNativeCookiesToElement(url); 103 | } 104 | catch (Exception exc) 105 | { 106 | Log.Warning(nameof(SuperWkWebViewRenderer), $"Failed to Sync Cookies {exc}"); 107 | } 108 | 109 | var args = new SuperWebNavigatedEventArgs(_lastEvent, WebView.Source, url, WebNavigationResult.Success); 110 | WebView.SendNavigated(args); 111 | _renderer.UpdateCanGoBackForward(); 112 | } 113 | 114 | public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation) 115 | { 116 | } 117 | 118 | // https://stackoverflow.com/questions/37509990/migrating-from-uiwebview-to-wkwebview 119 | public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler) 120 | { 121 | var navEvent = WebNavigationEvent.NewPage; 122 | var navigationType = navigationAction.NavigationType; 123 | switch (navigationType) 124 | { 125 | case WKNavigationType.LinkActivated: 126 | navEvent = WebNavigationEvent.NewPage; 127 | 128 | if (navigationAction.TargetFrame == null) 129 | webView?.LoadRequest(navigationAction.Request); 130 | 131 | break; 132 | case WKNavigationType.FormSubmitted: 133 | navEvent = WebNavigationEvent.NewPage; 134 | break; 135 | case WKNavigationType.BackForward: 136 | navEvent = _renderer._lastBackForwardEvent; 137 | break; 138 | case WKNavigationType.Reload: 139 | navEvent = WebNavigationEvent.Refresh; 140 | break; 141 | case WKNavigationType.FormResubmitted: 142 | navEvent = WebNavigationEvent.NewPage; 143 | break; 144 | case WKNavigationType.Other: 145 | navEvent = WebNavigationEvent.NewPage; 146 | break; 147 | } 148 | 149 | _lastEvent = navEvent; 150 | var request = navigationAction.Request; 151 | var lastUrl = request.Url.ToString(); 152 | 153 | /* Without this the deferral token will cause a completely undiagnosable termination **/ 154 | 155 | if (lastUrl.StartsWith("file://")) 156 | { 157 | var backArgs = new SuperWebNavigatingEventArgs(navEvent, new SuperUrlWebViewSource { Url = lastUrl }, lastUrl, false); 158 | 159 | WebView.SendNavigating(backArgs); 160 | _renderer.UpdateCanGoBackForward(); 161 | 162 | decisionHandler(WKNavigationActionPolicy.Allow); 163 | return; 164 | } 165 | 166 | if (SiteAlreadyVisited(_renderer.BackForwardList, lastUrl)) 167 | { 168 | var backArgs = new SuperWebNavigatingEventArgs(navEvent, new SuperUrlWebViewSource { Url = lastUrl }, lastUrl, false); 169 | 170 | WebView.SendNavigating(backArgs); 171 | _renderer.UpdateCanGoBackForward(); 172 | 173 | decisionHandler(WKNavigationActionPolicy.Allow); 174 | return; 175 | } 176 | 177 | var args = new SuperWebNavigatingEventArgs(navEvent, new SuperUrlWebViewSource { Url = lastUrl }, lastUrl, true); 178 | 179 | /* 180 | * Register the deferral before sending the args incase the code using the deferral token 181 | * is not executed async. In that scenario the token completion will be called before the 182 | * callback is registered and the decision handler won't get called 183 | */ 184 | args.RegisterDeferralCompletedCallBack(() => NavigatingDeterminedCallback(args, decisionHandler)); 185 | 186 | WebView.SendNavigating(args); 187 | _renderer.UpdateCanGoBackForward(); 188 | 189 | // user is not trying to cancel navigation, allow navigation 190 | if (!args.DeferralRequested) 191 | { 192 | decisionHandler(WKNavigationActionPolicy.Allow); 193 | return; 194 | } 195 | } 196 | 197 | bool UrlMatches(NSUrl url1, NSUrl url2) 198 | { 199 | if (url1 == null || url2 == null) 200 | return false; 201 | 202 | if (url1 == url2) 203 | return true; 204 | 205 | if (url1.AbsoluteString.Contains(url2.AbsoluteString)) 206 | return true; 207 | 208 | if (url2.AbsoluteString.Contains(url1.AbsoluteString)) 209 | return true; 210 | 211 | return false; 212 | } 213 | 214 | bool SiteAlreadyVisited(WKBackForwardList wkBackForwardList, string url) 215 | { 216 | if (wkBackForwardList is null) return false; 217 | if (wkBackForwardList.BackItem is null && wkBackForwardList.CurrentItem is null) return false; 218 | 219 | try 220 | { 221 | var nsUrl = new NSUrl(url); 222 | 223 | if (UrlMatches(wkBackForwardList.CurrentItem.Url, nsUrl)) 224 | return true; 225 | 226 | var backItems = wkBackForwardList.BackList; 227 | 228 | foreach (var backItem in backItems) 229 | { 230 | if (UrlMatches(backItem.Url, nsUrl)) 231 | return true; 232 | } 233 | } 234 | catch (Exception ex) 235 | { 236 | Log.Warning(nameof(SuperWebViewNavigationDelegate), "An exception occurred determining whether site had already been visited"); 237 | Log.Warning(nameof(SuperWebViewNavigationDelegate), ex.ToString()); 238 | } 239 | 240 | return false; 241 | } 242 | 243 | async Task NavigatingDeterminedCallback(SuperWebNavigatingEventArgs args, Action decisionHandler) 244 | { 245 | /* 246 | * Decision handler MUST be called otherwise WKWebView throws the following exception: 247 | * Objective-C exception thrown. Name: NSInternalInconsistencyException Reason: 248 | * Completion handler passed to -[Xamarin_Forms_Platform_iOS_WkWebViewRenderer_CustomWebViewNavigationDelegate webView:decidePolicyForNavigationAction:decisionHandler:] was not called 249 | */ 250 | 251 | /* 252 | * The previous implementation was causing a crash when the UI invoked a url change by performing 253 | * an action like pressing a Button and updating the SuperWebView source. 254 | * 255 | * The crash had very limited diagnostics but here is a diagnostic from the simulator: 256 | * 10:06:35.541054+0100 com.apple.CoreSimulator.SimDevice.00000000-0000-0000-0000-000000000000 257 | * (UIKitApplication:com.axemasta.SuperWebViewSample[f025][rb-legacy][86754]) 258 | * Service exited due to SIGTRAP 259 | * 260 | * I found the following xamarin-macios posts: 261 | * https://github.com/xamarin/xamarin-macios/issues/4130 262 | * https://github.com/xamarin/xamarin-macios/pull/4312/files 263 | * 264 | * It looks like when blocking a thread, it must be released back on the main thread otherwise the WKWebView 265 | * will crash. I have updated the implementation to directly access the iOS threads because Device.BeginInvoke... 266 | * was still causing the issue to occur 267 | */ 268 | 269 | Action action = () => DetermineNavigating(args, decisionHandler); 270 | 271 | if (NSThread.IsMain) 272 | { 273 | action.Invoke(); 274 | return; 275 | } 276 | 277 | DispatchQueue.MainQueue.DispatchSync(action); 278 | } 279 | 280 | void DetermineNavigating(SuperWebNavigatingEventArgs args, Action decisionHandler) 281 | { 282 | 283 | var action = args.Cancelled ? WKNavigationActionPolicy.Cancel : WKNavigationActionPolicy.Allow; 284 | 285 | if (action == WKNavigationActionPolicy.Cancel) 286 | WebView.SendNavigationCancelled(new NavigationCancelledEventArgs(args.Url)); 287 | 288 | decisionHandler(action); 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/iOS/SuperWebViewUIDelegate.ios.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Foundation; 3 | using UIKit; 4 | using WebKit; 5 | 6 | namespace Axemasta.SuperWebView.iOS 7 | { 8 | public class SuperWebViewUIDelegate : WKUIDelegate 9 | { 10 | static string LocalOK = NSBundle.FromIdentifier("com.apple.UIKit").GetLocalizedString("OK"); 11 | static string LocalCancel = NSBundle.FromIdentifier("com.apple.UIKit").GetLocalizedString("Cancel"); 12 | 13 | public override void RunJavaScriptAlertPanel(WKWebView webView, string message, WKFrameInfo frame, Action completionHandler) 14 | { 15 | PresentAlertController( 16 | webView, 17 | message, 18 | okAction: _ => completionHandler() 19 | ); 20 | } 21 | 22 | public override void RunJavaScriptConfirmPanel(WKWebView webView, string message, WKFrameInfo frame, Action completionHandler) 23 | { 24 | PresentAlertController( 25 | webView, 26 | message, 27 | okAction: _ => completionHandler(true), 28 | cancelAction: _ => completionHandler(false) 29 | ); 30 | } 31 | 32 | public override void RunJavaScriptTextInputPanel( 33 | WKWebView webView, string prompt, string defaultText, WKFrameInfo frame, Action completionHandler) 34 | { 35 | PresentAlertController( 36 | webView, 37 | prompt, 38 | defaultText: defaultText, 39 | okAction: x => completionHandler(x.TextFields[0].Text), 40 | cancelAction: _ => completionHandler(null) 41 | ); 42 | } 43 | 44 | static string GetJsAlertTitle(WKWebView webView) 45 | { 46 | // Emulate the behavior of UIWebView dialogs. 47 | // The scheme and host are used unless local html content is what the webview is displaying, 48 | // in which case the bundle file name is used. 49 | 50 | if (webView.Url != null && webView.Url.AbsoluteString != $"file://{NSBundle.MainBundle.BundlePath}/") 51 | return $"{webView.Url.Scheme}://{webView.Url.Host}"; 52 | 53 | return new NSString(NSBundle.MainBundle.BundlePath).LastPathComponent; 54 | } 55 | 56 | static UIAlertAction AddOkAction(UIAlertController controller, Action handler) 57 | { 58 | var action = UIAlertAction.Create(LocalOK, UIAlertActionStyle.Default, (_) => handler()); 59 | controller.AddAction(action); 60 | controller.PreferredAction = action; 61 | return action; 62 | } 63 | 64 | static UIAlertAction AddCancelAction(UIAlertController controller, Action handler) 65 | { 66 | var action = UIAlertAction.Create(LocalCancel, UIAlertActionStyle.Cancel, (_) => handler()); 67 | controller.AddAction(action); 68 | return action; 69 | } 70 | 71 | static void PresentAlertController( 72 | WKWebView webView, 73 | string message, 74 | string defaultText = null, 75 | Action okAction = null, 76 | Action cancelAction = null) 77 | { 78 | var controller = UIAlertController.Create(GetJsAlertTitle(webView), message, UIAlertControllerStyle.Alert); 79 | 80 | if (defaultText != null) 81 | controller.AddTextField((textField) => textField.Text = defaultText); 82 | 83 | if (okAction != null) 84 | AddOkAction(controller, () => okAction(controller)); 85 | 86 | if (cancelAction != null) 87 | AddCancelAction(controller, () => cancelAction(controller)); 88 | 89 | GetTopViewController(UIApplication.SharedApplication.GetKeyWindow().RootViewController) 90 | .PresentViewController(controller, true, null); 91 | } 92 | 93 | static UIViewController GetTopViewController(UIViewController viewController) 94 | { 95 | if (viewController is UINavigationController navigationController) 96 | return GetTopViewController(navigationController.VisibleViewController); 97 | 98 | if (viewController is UITabBarController tabBarController) 99 | return GetTopViewController(tabBarController.SelectedViewController); 100 | 101 | if (viewController.PresentedViewController != null) 102 | return GetTopViewController(viewController.PresentedViewController); 103 | 104 | return viewController; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/iOS/UIApplicationExtensions.ios.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | namespace Axemasta.SuperWebView.iOS 4 | { 5 | internal static class UIApplicationExtensions 6 | { 7 | public static UIWindow GetKeyWindow(this UIApplication application) 8 | { 9 | #if __MOBILE__ 10 | var windows = application.Windows; 11 | 12 | for (int i = 0; i < windows.Length; i++) 13 | { 14 | var window = windows[i]; 15 | if (window.IsKeyWindow) 16 | return window; 17 | } 18 | 19 | return null; 20 | #else 21 | return application.KeyWindow; 22 | #endif 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Renderers/iOS/WKWebViewHelper.ios.cs: -------------------------------------------------------------------------------- 1 | using WebKit; 2 | 3 | namespace Axemasta.SuperWebView.iOS 4 | { 5 | /// 6 | /// WKWebView Helper 7 | /// 8 | public static class WKWebViewHelper 9 | { 10 | internal static WKProcessPool _sharedPool; 11 | 12 | /// 13 | /// Create Pooled Configuration 14 | /// 15 | /// 16 | public static WKWebViewConfiguration CreatedPooledConfiguration() 17 | { 18 | /* 19 | * https://developer.apple.com/forums/thread/99674 20 | * WKWebView and making sure cookies synchronize is really quirky 21 | * The main workaround I've found for ensuring that cookies synchronize 22 | * is to share the Process Pool between all WkWebView instances. 23 | * It also has to be shared at the point you call init 24 | */ 25 | 26 | var config = new WKWebViewConfiguration(); 27 | 28 | if (_sharedPool == null) 29 | { 30 | _sharedPool = config.ProcessPool; 31 | } 32 | else 33 | { 34 | config.ProcessPool = _sharedPool; 35 | } 36 | 37 | return config; 38 | } 39 | 40 | public static void DisposeSharedPool() 41 | { 42 | _sharedPool.Dispose(); 43 | _sharedPool = null; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Scripts/invokenative.android.js: -------------------------------------------------------------------------------- 1 | function invokeNative(payload) { 2 | jsBridge.invokeAction(payload); 3 | } -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/Scripts/invokenative.ios.js: -------------------------------------------------------------------------------- 1 | function invokeNative(payload) { 2 | window.webkit.messageHandlers.invokeAction.postMessage(payload); 3 | } -------------------------------------------------------------------------------- /src/Axemasta.SuperWebView/SuperWebView.shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Net; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | using Axemasta.SuperWebView.Internals; 8 | using Xamarin.Forms; 9 | using Xamarin.Forms.Internals; 10 | 11 | namespace Axemasta.SuperWebView 12 | { 13 | /// 14 | /// Super Web View 15 | /// 16 | [RenderWith(typeof(_SuperWebViewRenderer))] 17 | public class SuperWebView : View, ISuperWebViewController, IElementConfiguration 18 | { 19 | public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(SuperWebViewSource), typeof(SuperWebView), default(SuperWebViewSource), 20 | propertyChanging: (bindable, oldvalue, newvalue) => 21 | { 22 | var source = oldvalue as SuperWebViewSource; 23 | if (source != null) 24 | source.SourceChanged -= ((SuperWebView)bindable).OnSourceChanged; 25 | }, propertyChanged: (bindable, oldvalue, newvalue) => 26 | { 27 | var source = newvalue as SuperWebViewSource; 28 | var webview = (SuperWebView)bindable; 29 | if (source != null) 30 | { 31 | source.SourceChanged += webview.OnSourceChanged; 32 | SetInheritedBindingContext(source, webview.BindingContext); 33 | } 34 | }); 35 | 36 | static readonly BindablePropertyKey CanGoBackPropertyKey = BindableProperty.CreateReadOnly(nameof(CanGoBack), typeof(bool), typeof(SuperWebView), false); 37 | 38 | public static readonly BindableProperty CanGoBackProperty = CanGoBackPropertyKey.BindableProperty; 39 | 40 | static readonly BindablePropertyKey CanGoForwardPropertyKey = BindableProperty.CreateReadOnly(nameof(CanGoForward), typeof(bool), typeof(SuperWebView), false); 41 | 42 | public static readonly BindableProperty CanGoForwardProperty = CanGoForwardPropertyKey.BindableProperty; 43 | 44 | public static readonly BindableProperty CookiesProperty = BindableProperty.Create(nameof(Cookies), typeof(CookieContainer), typeof(SuperWebView), null); 45 | 46 | static readonly BindablePropertyKey ProgressPropertyKey = BindableProperty.CreateReadOnly(nameof(Progress), typeof(double), typeof(SuperWebView), (double)0); 47 | 48 | public static readonly BindableProperty UriProperty = BindableProperty.Create(nameof(Uri), typeof(string), typeof(SuperWebView), default(string), BindingMode.OneWayToSource); 49 | 50 | public static readonly BindableProperty ProgressProperty = ProgressPropertyKey.BindableProperty; 51 | 52 | readonly Lazy> _platformConfigurationRegistry; 53 | 54 | public SuperWebView() 55 | { 56 | _platformConfigurationRegistry = new Lazy>(() => new PlatformConfigurationRegistry(this)); 57 | 58 | this.RendererInitialised += OnRendererInitialised; 59 | } 60 | 61 | public IPlatformElementConfiguration On() where T : IConfigPlatform 62 | { 63 | return _platformConfigurationRegistry.Value.On(); 64 | } 65 | 66 | [EditorBrowsable(EditorBrowsableState.Never)] 67 | bool ISuperWebViewController.CanGoBack 68 | { 69 | get { return CanGoBack; } 70 | set { SetValue(CanGoBackPropertyKey, value); } 71 | } 72 | 73 | public bool CanGoBack 74 | { 75 | get { return (bool)GetValue(CanGoBackProperty); } 76 | } 77 | 78 | [EditorBrowsable(EditorBrowsableState.Never)] 79 | bool ISuperWebViewController.CanGoForward 80 | { 81 | get { return CanGoForward; } 82 | set { SetValue(CanGoForwardPropertyKey, value); } 83 | } 84 | 85 | public bool CanGoForward 86 | { 87 | get { return (bool)GetValue(CanGoForwardProperty); } 88 | } 89 | 90 | public CookieContainer Cookies 91 | { 92 | get { return (CookieContainer)GetValue(CookiesProperty); } 93 | set { SetValue(CookiesProperty, value); } 94 | } 95 | 96 | [Xamarin.Forms.TypeConverter(typeof(SuperWebViewSourceTypeConverter))] 97 | public SuperWebViewSource Source 98 | { 99 | get { return (SuperWebViewSource)GetValue(SourceProperty); } 100 | set { SetValue(SourceProperty, value); } 101 | } 102 | 103 | public double Progress 104 | { 105 | get => (double)GetValue(ProgressProperty); 106 | set => SetValue(ProgressProperty, value); 107 | } 108 | 109 | public string Uri 110 | { 111 | get => (string)GetValue(UriProperty); 112 | set => SetValue(UriProperty, value); 113 | } 114 | 115 | public void Eval(string script) 116 | { 117 | EventHandler handler = EvalRequested; 118 | handler?.Invoke(this, new EvalRequested(script)); 119 | } 120 | 121 | public async Task EvaluateJavaScriptAsync(string script) 122 | { 123 | EvaluateJavaScriptDelegate handler = EvaluateJavaScriptRequested; 124 | 125 | if (script == null) 126 | return null; 127 | 128 | //make all the platforms mimic Android's implementation, which is by far the most complete. 129 | if (Xamarin.Forms.Device.RuntimePlatform != "Android") 130 | { 131 | script = EscapeJsString(script); 132 | script = "try{JSON.stringify(eval('" + script + "'))}catch(e){'null'};"; 133 | } 134 | 135 | var result = await handler?.Invoke(script); 136 | 137 | //if the js function errored or returned null/undefined treat it as null 138 | if (result == "null") 139 | result = null; 140 | 141 | //JSON.stringify wraps the result in literal quotes, we just want the actual returned result 142 | //note that if the js function returns the string "null" we will get here and not above 143 | else if (result != null) 144 | result = result.Trim('"'); 145 | 146 | return result; 147 | } 148 | 149 | public void GoBack() 150 | => GoBackRequested?.Invoke(this, EventArgs.Empty); 151 | 152 | public void GoForward() 153 | => GoForwardRequested?.Invoke(this, EventArgs.Empty); 154 | 155 | public void Reload() 156 | => ReloadRequested?.Invoke(this, EventArgs.Empty); 157 | 158 | public event EventHandler Navigated; 159 | 160 | public event EventHandler Navigating; 161 | 162 | public event EventHandler NavigationCancelled; 163 | 164 | public event EventHandler CanGoBackChanged; 165 | 166 | public event EventHandler CanGoForwardChanged; 167 | 168 | /// 169 | /// Called When WebView Javascript Calls Back To The App 170 | /// 171 | public event EventHandler BrowserInvocation; 172 | 173 | public void SendBrowserInvocation(BrowserInvocationEventArgs args) 174 | { 175 | BrowserInvocation?.Invoke(this, args); 176 | } 177 | 178 | protected override void OnBindingContextChanged() 179 | { 180 | base.OnBindingContextChanged(); 181 | 182 | SuperWebViewSource source = Source; 183 | 184 | if (source != null) 185 | { 186 | SetInheritedBindingContext(source, BindingContext); 187 | } 188 | } 189 | 190 | protected override void OnPropertyChanged(string propertyName) 191 | { 192 | if (propertyName == nameof(BindingContext)) 193 | { 194 | SuperWebViewSource source = Source; 195 | if (source != null) 196 | SetInheritedBindingContext(source, BindingContext); 197 | } 198 | 199 | base.OnPropertyChanged(propertyName); 200 | } 201 | 202 | protected void OnSourceChanged(object sender, EventArgs e) 203 | { 204 | OnPropertyChanged(SourceProperty.PropertyName); 205 | } 206 | 207 | event EventHandler ISuperWebViewController.EvalRequested 208 | { 209 | add { EvalRequested += value; } 210 | remove { EvalRequested -= value; } 211 | } 212 | 213 | [EditorBrowsable(EditorBrowsableState.Never)] 214 | public event EventHandler EvalRequested; 215 | 216 | [EditorBrowsable(EditorBrowsableState.Never)] 217 | public event EvaluateJavaScriptDelegate EvaluateJavaScriptRequested; 218 | 219 | [EditorBrowsable(EditorBrowsableState.Never)] 220 | public event InjectJavaScriptDelegate InjectJavaScriptRequested; 221 | 222 | [EditorBrowsable(EditorBrowsableState.Never)] 223 | public event EventHandler GoBackRequested; 224 | 225 | [EditorBrowsable(EditorBrowsableState.Never)] 226 | public event EventHandler GoForwardRequested; 227 | 228 | public event EventHandler ProgressChanged; 229 | 230 | public event EventHandler UrlPropertyChanged; 231 | 232 | [EditorBrowsable(EditorBrowsableState.Never)] 233 | public void SendNavigated(SuperWebNavigatedEventArgs args) 234 | { 235 | Navigated?.Invoke(this, args); 236 | } 237 | 238 | [EditorBrowsable(EditorBrowsableState.Never)] 239 | public void SendNavigating(SuperWebNavigatingEventArgs args) 240 | { 241 | Navigating?.Invoke(this, args); 242 | } 243 | 244 | [EditorBrowsable(EditorBrowsableState.Never)] 245 | public event EventHandler ReloadRequested; 246 | 247 | [EditorBrowsable(EditorBrowsableState.Never)] 248 | public void SendCanGoBackwardsChanged(EventArgs args) 249 | { 250 | CanGoBackChanged?.Invoke(this, args); 251 | } 252 | 253 | [EditorBrowsable(EditorBrowsableState.Never)] 254 | public void SendCanGoForwardsChanged(EventArgs args) 255 | { 256 | CanGoForwardChanged?.Invoke(this, args); 257 | } 258 | 259 | /// 260 | /// Send Progress Update 261 | /// 262 | /// 263 | /// 264 | [EditorBrowsable(EditorBrowsableState.Never)] 265 | public void SendProgressChanged(ProgressEventArgs args) 266 | { 267 | ProgressChanged?.Invoke(this, args); 268 | } 269 | 270 | /// 271 | /// Send Navigation Cancelled 272 | /// 273 | /// 274 | [EditorBrowsable(EditorBrowsableState.Never)] 275 | public void SendNavigationCancelled(NavigationCancelledEventArgs args) 276 | { 277 | NavigationCancelled?.Invoke(this, args); 278 | } 279 | 280 | /// 281 | /// Send Url Changed 282 | /// 283 | /// 284 | [EditorBrowsable(EditorBrowsableState.Never)] 285 | public void SendUrlChanged(UrlEventArgs args) 286 | { 287 | UrlPropertyChanged?.Invoke(this, args); 288 | } 289 | 290 | [EditorBrowsable(EditorBrowsableState.Never)] 291 | public event EventHandler RendererInitialised; 292 | 293 | /// 294 | /// Inject Javascript Into The DOM 295 | /// 296 | /// 297 | public void InjectJavascript(List scripts) 298 | { 299 | if (scripts is null || scripts.Count < 1) 300 | return; 301 | 302 | if (!_rendererInitialised) 303 | { 304 | // Need to wait for SetElement to finish setting up before the handler is available for use 305 | RendererInitialised += (s, e) => OnInjectJavascript(scripts); 306 | } 307 | } 308 | 309 | private bool _rendererInitialised; 310 | 311 | private void OnRendererInitialised(object sender, EventArgs e) 312 | { 313 | this.RendererInitialised -= OnRendererInitialised; 314 | _rendererInitialised = true; 315 | } 316 | 317 | private void OnInjectJavascript(List scripts) 318 | { 319 | RendererInitialised -= (s, e) => OnInjectJavascript(scripts); 320 | 321 | InjectJavaScriptDelegate handler = InjectJavaScriptRequested; 322 | 323 | foreach (var script in scripts) 324 | { 325 | // load script 326 | var loaded = script.TryLoadScript(out string scriptContent); 327 | 328 | if (!loaded) 329 | { 330 | // Warn 331 | Log.Warning(nameof(SuperWebView), $"Unable to load script content for: {script.Name}"); 332 | continue; 333 | } 334 | 335 | // Send load to renderer 336 | handler?.Invoke(scriptContent); 337 | } 338 | } 339 | 340 | [EditorBrowsable(EditorBrowsableState.Never)] 341 | public void SendRendererInitialised() 342 | { 343 | this.RendererInitialised?.Invoke(this, EventArgs.Empty); 344 | } 345 | 346 | static string EscapeJsString(string js) 347 | { 348 | if (js == null) 349 | return null; 350 | 351 | if (!js.Contains("'")) 352 | return js; 353 | 354 | //get every quote in the string along with all the backslashes preceding it 355 | var singleQuotes = Regex.Matches(js, @"(\\*?)'"); 356 | 357 | var uniqueMatches = new List(); 358 | 359 | for (var i = 0; i < singleQuotes.Count; i++) 360 | { 361 | var matchedString = singleQuotes[i].Value; 362 | if (!uniqueMatches.Contains(matchedString)) 363 | { 364 | uniqueMatches.Add(matchedString); 365 | } 366 | } 367 | 368 | uniqueMatches.Sort((x, y) => y.Length.CompareTo(x.Length)); 369 | 370 | //escape all quotes from the script as well as add additional escaping to all quotes that were already escaped 371 | for (var i = 0; i < uniqueMatches.Count; i++) 372 | { 373 | var match = uniqueMatches[i]; 374 | var numberOfBackslashes = match.Length - 1; 375 | var slashesToAdd = (numberOfBackslashes * 2) + 1; 376 | var replacementStr = "'".PadLeft(slashesToAdd + 1, '\\'); 377 | js = Regex.Replace(js, @"(?<=[^\\])" + Regex.Escape(match), replacementStr); 378 | } 379 | 380 | return js; 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Axemasta.SuperWebView.Sample.Android.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {17A614EE-50D2-49D3-9379-D2A8532007B2} 7 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | {c9e5eea5-ca05-42a1-839b-61506e0a37df} 9 | Library 10 | Axemasta.SuperWebView.Sample.Droid 11 | Axemasta.SuperWebView.Sample.Android 12 | True 13 | True 14 | Resources\Resource.designer.cs 15 | Resource 16 | Properties\AndroidManifest.xml 17 | Resources 18 | Assets 19 | v11.0 20 | true 21 | true 22 | Xamarin.Android.Net.AndroidClientHandler 23 | 24 | 25 | 26 | 27 | true 28 | portable 29 | false 30 | bin\Debug 31 | DEBUG; 32 | prompt 33 | 4 34 | None 35 | 36 | 37 | true 38 | portable 39 | true 40 | bin\Release 41 | prompt 42 | 4 43 | true 44 | false 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 2.3.0.759 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | {F81087F0-B9E0-4F6C-B53C-561B053FD478} 97 | Axemasta.SuperWebView.Sample 98 | 99 | 100 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D} 101 | Axemasta.SuperWebView 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Android.App; 4 | using Android.Content.PM; 5 | using Android.Runtime; 6 | using Android.OS; 7 | 8 | namespace Axemasta.SuperWebView.Sample.Droid 9 | { 10 | [Activity(Label = "Axemasta.SuperWebView.Sample", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)] 11 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity 12 | { 13 | protected override void OnCreate(Bundle savedInstanceState) 14 | { 15 | base.OnCreate(savedInstanceState); 16 | 17 | Xamarin.Essentials.Platform.Init(this, savedInstanceState); 18 | global::Xamarin.Forms.Forms.Init(this, savedInstanceState); 19 | LoadApplication(new App()); 20 | } 21 | 22 | public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults) 23 | { 24 | Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); 25 | 26 | base.OnRequestPermissionsResult(requestCode, permissions, grantResults); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using Android.App; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Axemasta.SuperWebView.Sample.Android")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("Axemasta.SuperWebView.Sample.Android")] 14 | [assembly: AssemblyCopyright("Copyright © 2014")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: ComVisible(false)] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | [assembly: AssemblyVersion("1.0.0.0")] 26 | [assembly: AssemblyFileVersion("1.0.0.0")] 27 | 28 | // Add some common permissions, these can be removed if not needed 29 | [assembly: UsesPermission(Android.Manifest.Permission.Internet)] 30 | [assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)] 31 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.xml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable-hdpi/ 12 | icon.png 13 | 14 | drawable-ldpi/ 15 | icon.png 16 | 17 | drawable-mdpi/ 18 | icon.png 19 | 20 | layout/ 21 | main.xml 22 | 23 | values/ 24 | strings.xml 25 | 26 | In order to get the build system to recognize Android resources, set the build action to 27 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 28 | instead operate on resource IDs. When you compile an Android application that uses resources, 29 | the build system will package the resources for distribution and generate a class called 30 | "Resource" that contains the tokens for each one of the resources included. For example, 31 | for the above Resources layout, this is what the Resource class would expose: 32 | 33 | public class Resource { 34 | public class drawable { 35 | public const int icon = 0x123; 36 | } 37 | 38 | public class layout { 39 | public const int main = 0x456; 40 | } 41 | 42 | public class strings { 43 | public const int first_string = 0xabc; 44 | public const int second_string = 0xbcd; 45 | } 46 | } 47 | 48 | You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main 49 | to reference the layout/main.xml file, or Resource.strings.first_string to reference the first 50 | string in the dictionary file values/strings.xml. 51 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-anydpi-v26/icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-anydpi-v26/icon_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-hdpi/icon.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-hdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-hdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-mdpi/icon.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-mdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-mdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xhdpi/icon.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xxhdpi/icon.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xxhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xxhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xxxhdpi/icon.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xxxhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/mipmap-xxxhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #3F51B5 5 | #303F9F 6 | #FF4081 7 | 8 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.Android/Services/UrlProvider.cs: -------------------------------------------------------------------------------- 1 | using Axemasta.SuperWebView.Sample.Services; 2 | using Xamarin.Forms; 3 | 4 | [assembly: Dependency(typeof(Axemasta.SuperWebView.Sample.Droid.Services.UrlProvider))] 5 | 6 | namespace Axemasta.SuperWebView.Sample.Droid.Services 7 | { 8 | public class UrlProvider : IUrlProvider 9 | { 10 | public string GetBaseUrl() 11 | { 12 | return "file:///android_asset/"; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace Axemasta.SuperWebView.Sample.iOS 9 | { 10 | // The UIApplicationDelegate for the application. This class is responsible for launching the 11 | // User Interface of the application, as well as listening (and optionally responding) to 12 | // application events from iOS. 13 | [Register("AppDelegate")] 14 | public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate 15 | { 16 | // 17 | // This method is invoked when the application has loaded and is ready to run. In this 18 | // method you should instantiate the window, load the UI into it and then make the window 19 | // visible. 20 | // 21 | // You have 17 seconds to return from this method, or iOS will terminate your application. 22 | // 23 | public override bool FinishedLaunching(UIApplication app, NSDictionary options) 24 | { 25 | LightSwitch.LightSwitchAgent.Init(); 26 | global::Xamarin.Forms.Forms.Init(); 27 | LoadApplication(new App()); 28 | 29 | return base.FinishedLaunching(app, options); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "scale": "2x", 5 | "size": "20x20", 6 | "idiom": "iphone", 7 | "filename": "Icon40.png" 8 | }, 9 | { 10 | "scale": "3x", 11 | "size": "20x20", 12 | "idiom": "iphone", 13 | "filename": "Icon60.png" 14 | }, 15 | { 16 | "scale": "2x", 17 | "size": "29x29", 18 | "idiom": "iphone", 19 | "filename": "Icon58.png" 20 | }, 21 | { 22 | "scale": "3x", 23 | "size": "29x29", 24 | "idiom": "iphone", 25 | "filename": "Icon87.png" 26 | }, 27 | { 28 | "scale": "2x", 29 | "size": "40x40", 30 | "idiom": "iphone", 31 | "filename": "Icon80.png" 32 | }, 33 | { 34 | "scale": "3x", 35 | "size": "40x40", 36 | "idiom": "iphone", 37 | "filename": "Icon120.png" 38 | }, 39 | { 40 | "scale": "2x", 41 | "size": "60x60", 42 | "idiom": "iphone", 43 | "filename": "Icon120.png" 44 | }, 45 | { 46 | "scale": "3x", 47 | "size": "60x60", 48 | "idiom": "iphone", 49 | "filename": "Icon180.png" 50 | }, 51 | { 52 | "scale": "1x", 53 | "size": "20x20", 54 | "idiom": "ipad", 55 | "filename": "Icon20.png" 56 | }, 57 | { 58 | "scale": "2x", 59 | "size": "20x20", 60 | "idiom": "ipad", 61 | "filename": "Icon40.png" 62 | }, 63 | { 64 | "scale": "1x", 65 | "size": "29x29", 66 | "idiom": "ipad", 67 | "filename": "Icon29.png" 68 | }, 69 | { 70 | "scale": "2x", 71 | "size": "29x29", 72 | "idiom": "ipad", 73 | "filename": "Icon58.png" 74 | }, 75 | { 76 | "scale": "1x", 77 | "size": "40x40", 78 | "idiom": "ipad", 79 | "filename": "Icon40.png" 80 | }, 81 | { 82 | "scale": "2x", 83 | "size": "40x40", 84 | "idiom": "ipad", 85 | "filename": "Icon80.png" 86 | }, 87 | { 88 | "scale": "1x", 89 | "size": "76x76", 90 | "idiom": "ipad", 91 | "filename": "Icon76.png" 92 | }, 93 | { 94 | "scale": "2x", 95 | "size": "76x76", 96 | "idiom": "ipad", 97 | "filename": "Icon152.png" 98 | }, 99 | { 100 | "scale": "2x", 101 | "size": "83.5x83.5", 102 | "idiom": "ipad", 103 | "filename": "Icon167.png" 104 | }, 105 | { 106 | "scale": "1x", 107 | "size": "1024x1024", 108 | "idiom": "ios-marketing", 109 | "filename": "Icon1024.png" 110 | } 111 | ], 112 | "properties": {}, 113 | "info": { 114 | "version": 1, 115 | "author": "xcode" 116 | } 117 | } -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon20.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon29.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon76.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Axemasta.SuperWebView.Sample.iOS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | 8.0.30703 7 | 2.0 8 | {3B828290-5C46-47BC-A78E-DA65B571676A} 9 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | {6143fdea-f3c2-4a09-aafa-6e230626515e} 11 | Exe 12 | Axemasta.SuperWebView.Sample.iOS 13 | Resources 14 | Axemasta.SuperWebView.Sample.iOS 15 | true 16 | NSUrlSessionHandler 17 | automatic 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\iPhoneSimulator\Debug 24 | DEBUG 25 | prompt 26 | 4 27 | x86_64 28 | None 29 | true 30 | iPhone Developer 31 | 32 | 33 | none 34 | true 35 | bin\iPhoneSimulator\Release 36 | prompt 37 | 4 38 | None 39 | x86_64 40 | 41 | 42 | true 43 | full 44 | false 45 | bin\iPhone\Debug 46 | DEBUG 47 | prompt 48 | 4 49 | ARM64 50 | iPhone Developer 51 | true 52 | Entitlements.plist 53 | None 54 | -all 55 | 56 | 57 | none 58 | true 59 | bin\iPhone\Release 60 | prompt 61 | 4 62 | ARM64 63 | iPhone Developer 64 | Entitlements.plist 65 | SdkOnly 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | false 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | 86 | 87 | false 88 | 89 | 90 | false 91 | 92 | 93 | false 94 | 95 | 96 | false 97 | 98 | 99 | false 100 | 101 | 102 | false 103 | 104 | 105 | false 106 | 107 | 108 | false 109 | 110 | 111 | false 112 | 113 | 114 | false 115 | 116 | 117 | false 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 2.3.0.759 133 | 134 | 135 | 1.0.0 136 | 137 | 138 | 139 | 140 | {F81087F0-B9E0-4F6C-B53C-561B053FD478} 141 | Axemasta.SuperWebView.Sample 142 | 143 | 144 | {73BBC8E3-42C8-40D3-BF3A-6FAE8F0C540D} 145 | Axemasta.SuperWebView 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UISupportedInterfaceOrientations 11 | 12 | UIInterfaceOrientationPortrait 13 | UIInterfaceOrientationLandscapeLeft 14 | UIInterfaceOrientationLandscapeRight 15 | 16 | UISupportedInterfaceOrientations~ipad 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationPortraitUpsideDown 20 | UIInterfaceOrientationLandscapeLeft 21 | UIInterfaceOrientationLandscapeRight 22 | 23 | MinimumOSVersion 24 | 8.0 25 | CFBundleDisplayName 26 | Axemasta.SuperWebView.Sample 27 | CFBundleIdentifier 28 | com.Axemasta.SuperWebView-Sample 29 | CFBundleVersion 30 | 1.0 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | CFBundleName 34 | Axemasta.SuperWebView.Sample 35 | XSAppIconAssets 36 | Assets.xcassets/AppIcon.appiconset 37 | NSUserTrackingUserDescription 38 | Allow this app to track usage? 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Main.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | namespace Axemasta.SuperWebView.Sample.iOS 4 | { 5 | public class Application 6 | { 7 | // This is the main entry point of the application. 8 | static void Main(string[] args) 9 | { 10 | // if you want to use a different Application Delegate class from "AppDelegate" 11 | // you can specify it here. 12 | UIApplication.Main(args, null, typeof(AppDelegate)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Axemasta.SuperWebView.Sample.iOS")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Axemasta.SuperWebView.Sample.iOS")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("72bdc44f-c588-44f3-b6df-9aace7daafdd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default-568h@2x.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default-Portrait.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default-Portrait@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default-Portrait@2x.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Axemasta/SuperWebView/9f50b155e225c4a07c5431e1c02839a6a461c37d/src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/Default@2x.png -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Resources/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample.iOS/Services/UrlProvider.cs: -------------------------------------------------------------------------------- 1 | using Axemasta.SuperWebView.Sample.Services; 2 | using Foundation; 3 | using Xamarin.Forms; 4 | 5 | [assembly: Dependency(typeof(Axemasta.SuperWebView.Sample.iOS.Services.UrlProvider))] 6 | 7 | namespace Axemasta.SuperWebView.Sample.iOS.Services 8 | { 9 | public class UrlProvider : IUrlProvider 10 | { 11 | public string GetBaseUrl() 12 | { 13 | return NSBundle.MainBundle.BundlePath; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/App.xaml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | #F2F2F7 9 | #FFFFFF 10 | #EDEDEE 11 | #333d37 12 | 13 | #Black 14 | #1C1C1E 15 | #3a3a3c 16 | #FFFFFF 17 | 18 | 21 | 22 | 25 | 26 | 27 | #DEDEE0 28 | 1 29 | 30 | 31 | 32 | #323234 33 | 1 34 | 35 | 36 | 39 | 40 | 43 | 44 | 48 | 49 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using AP.MobileToolkit.Fonts; 5 | using Axemasta.SuperWebView.Sample.Pages; 6 | using Xamarin.Forms; 7 | using Xamarin.Forms.Internals; 8 | using Xamarin.Forms.Xaml; 9 | 10 | namespace Axemasta.SuperWebView.Sample 11 | { 12 | public partial class App : Application 13 | { 14 | public App() 15 | { 16 | InitializeComponent(); 17 | 18 | Log.Listeners.Add(new DelegateLogListener(OnLogListener)); 19 | TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; 20 | AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; 21 | 22 | FontRegistry.RegisterFonts(FontAwesomeBrands.Font, 23 | FontAwesomeRegular.Font, 24 | FontAwesomeSolid.Font); 25 | 26 | MainPage = new BrowserPage(); 27 | } 28 | 29 | private void OnLogListener(string arg1, string arg2) 30 | { 31 | Console.WriteLine($"App - OnLogListener - {arg1}: {arg2}"); 32 | } 33 | 34 | private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) 35 | { 36 | foreach (var exception in e.Exception.InnerExceptions) 37 | { 38 | Console.WriteLine($"App - OnUnobservedTaskException - An exception occured: {exception.ToString()}"); 39 | } 40 | } 41 | 42 | private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) 43 | { 44 | var exception = e.ExceptionObject as Exception; 45 | 46 | if (exception == null) 47 | { 48 | Console.WriteLine("App - OnUnhandledException - Could not cast UnhandledExceptionEventArgs ExceptionObject to exception..."); 49 | return; 50 | } 51 | 52 | Console.WriteLine($"App - OnUnhandledException - An exception occured: {exception}"); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms.Xaml; 2 | 3 | [assembly: XamlCompilation(XamlCompilationOptions.Compile)] -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/Axemasta.SuperWebView.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | portable 10 | true 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 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/Pages/BrowserPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/Pages/BrowserPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Threading.Tasks; 5 | using Axemasta.SuperWebView.PlatformConfiguration.iOSSpecific; 6 | using Axemasta.SuperWebView.PlatformConfiguration.AndroidSpecific; 7 | using Axemasta.SuperWebView.Sample.Services; 8 | using Xamarin.Forms; 9 | using Xamarin.Forms.PlatformConfiguration; 10 | using Xamarin.Forms.PlatformConfiguration.iOSSpecific; 11 | 12 | namespace Axemasta.SuperWebView.Sample.Pages 13 | { 14 | public partial class BrowserPage : ContentPage 15 | { 16 | Lazy _coolPage; 17 | Lazy _blockPage; 18 | Lazy _localBaseUrl; 19 | 20 | string LoadCoolPage() 21 | { 22 | Debug.WriteLine("LoadBlockPage - Executing lazy load"); 23 | 24 | var assemblyName = typeof(BrowserPage).Assembly.FullName; 25 | 26 | var page = EmbeddedResourceHelper.Load("Axemasta.SuperWebView.Sample.Views.CoolPage.html", assemblyName); 27 | 28 | return page; 29 | } 30 | 31 | string LoadBlockPage() 32 | { 33 | Debug.WriteLine("LoadBlockPage - Executing lazy load"); 34 | 35 | var assemblyName = typeof(BrowserPage).Assembly.FullName; 36 | 37 | var page = EmbeddedResourceHelper.Load("Axemasta.SuperWebView.Sample.Views.BlockPage.html", assemblyName); 38 | 39 | return page; 40 | } 41 | 42 | string LoadBaseUrl() 43 | { 44 | Debug.WriteLine("LoadBaseUrl - Executing lazy load"); 45 | 46 | var urlProvider = DependencyService.Get(); 47 | 48 | if (urlProvider is null) 49 | return string.Empty; 50 | 51 | return urlProvider.GetBaseUrl(); 52 | } 53 | 54 | public BrowserPage() 55 | { 56 | InitializeComponent(); 57 | 58 | On() 59 | .SetUseSafeArea(true); 60 | 61 | superWebView.On() 62 | .SetAllowsLinkPreview(false); 63 | 64 | superWebView.On() 65 | .SetHardeningEnabled(false); 66 | 67 | _coolPage = new Lazy(LoadCoolPage, true); 68 | _blockPage = new Lazy(LoadBlockPage, true); 69 | _localBaseUrl = new Lazy(LoadBaseUrl, true); 70 | 71 | backButton.Clicked += OnBackRequested; 72 | forwardButton.Clicked += OnForwardRequested; 73 | reloadButton.Clicked += OnReloadRequested; 74 | localPage.Clicked += OnLocalPage; 75 | 76 | superWebView.Navigating += OnNavigating; 77 | superWebView.Navigated += OnNavigated; 78 | superWebView.NavigationCancelled += OnNavigationCancelled; 79 | superWebView.ProgressChanged += OnProgress; 80 | superWebView.BrowserInvocation += OnBrowserInvocation; 81 | superWebView.CanGoBackChanged += OnCanGoBackChanged; 82 | superWebView.CanGoForwardChanged += OnCanGoForwardChanged; 83 | superWebView.UrlPropertyChanged += OnUrlChanged; 84 | 85 | var assemblyName = this.GetType().Assembly.FullName; 86 | 87 | var scripts = new List() 88 | { 89 | new EmbeddedJavaScript("JQuery", "Axemasta.SuperWebView.Sample.Scripts.jquery-3.5.1.min.js", assemblyName), 90 | new EmbeddedJavaScript("Spy", "Axemasta.SuperWebView.Sample.Scripts.spy.js", assemblyName) 91 | }; 92 | 93 | superWebView.InjectJavascript(scripts); 94 | } 95 | 96 | private void OnUrlChanged(object sender, UrlEventArgs e) 97 | { 98 | addressLabel.Text = e.NewUrl; 99 | } 100 | 101 | private void OnLocalPage(object sender, EventArgs e) 102 | { 103 | Debug.WriteLine("Load local page"); 104 | 105 | var html = _coolPage.Value; 106 | var baseUrl = _localBaseUrl.Value; 107 | 108 | var htmlWebSource = new SuperHtmlWebViewSource() 109 | { 110 | Html = html, 111 | BaseUrl = baseUrl, 112 | Title = "My Cool Local Page" 113 | }; 114 | 115 | superWebView.Source = htmlWebSource; 116 | } 117 | 118 | private void OnCanGoForwardChanged(object sender, EventArgs e) 119 | { 120 | backButton.IsEnabled = superWebView.CanGoBack; 121 | } 122 | 123 | private void OnCanGoBackChanged(object sender, EventArgs e) 124 | { 125 | forwardButton.IsEnabled = superWebView.CanGoForward; 126 | } 127 | 128 | private void OnReloadRequested(object sender, EventArgs e) 129 | { 130 | superWebView.Reload(); 131 | } 132 | 133 | private void OnForwardRequested(object sender, EventArgs e) 134 | { 135 | superWebView.GoForward(); 136 | } 137 | 138 | private void OnBackRequested(object sender, EventArgs e) 139 | { 140 | superWebView.GoBack(); 141 | } 142 | 143 | private void OnBrowserInvocation(object sender, BrowserInvocationEventArgs e) 144 | { 145 | Debug.WriteLine($"OnBrowserInvocation - Invoked with data: {e.Message}"); 146 | } 147 | 148 | private void OnNavigationCancelled(object sender, NavigationCancelledEventArgs e) 149 | { 150 | Debug.WriteLine($"OnNavigationCancelled - Navigation to site cancelled: {e.Url}"); 151 | } 152 | 153 | private void OnProgress(object sender, ProgressEventArgs e) 154 | { 155 | Debug.WriteLine($"OnProgress: {e.PercentageComplete}%"); 156 | Debug.WriteLine($"OnProgress - Raw: {e.NormalisedProgress}"); 157 | 158 | var progress = e.PercentageComplete / 100; // XF ProgressBar accepts 0-1 159 | 160 | progressBar.ProgressTo(progress, 250, Easing.SinIn); 161 | } 162 | 163 | private async void OnNavigating(object sender, SuperWebNavigatingEventArgs e) 164 | { 165 | Debug.WriteLine($"OnNavigating Fired - {e.Url}"); 166 | 167 | if (e.CanCancel) 168 | { 169 | var token = e.GetDeferral(); 170 | 171 | bool canBrowse = await CanBrowse(e.Url); 172 | 173 | if (!canBrowse) 174 | { 175 | e.Cancel(); 176 | } 177 | 178 | token.Complete(); 179 | 180 | if (e.Cancelled) 181 | { 182 | var html = _blockPage.Value; 183 | var baseUrl = _localBaseUrl.Value; 184 | 185 | html = html.Replace("${blockedSite}", e.Url); 186 | 187 | var htmlWebSource = new SuperHtmlWebViewSource() 188 | { 189 | Html = html, 190 | BaseUrl = baseUrl, 191 | Title = "Site Blocked" 192 | }; 193 | 194 | superWebView.Source = htmlWebSource; 195 | } 196 | } 197 | } 198 | 199 | private void OnNavigated(object sender, SuperWebNavigatedEventArgs e) 200 | { 201 | Debug.WriteLine($"OnNavigated Fired - {e.Url}"); 202 | } 203 | 204 | private async Task CanBrowse(string url) 205 | { 206 | await Task.Delay(1000); 207 | 208 | try 209 | { 210 | var uri = new Uri(url); 211 | 212 | var isBbc = uri.Host.Contains("bbc.co.uk"); 213 | 214 | return !isBbc; 215 | } 216 | catch 217 | { 218 | return false; 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/Scripts/spy.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | 3 | SpyOnUser(); 4 | 5 | function SpyOnUser() { 6 | 7 | document.onkeypress = function (e) { 8 | e = e || window.event; 9 | 10 | invokeNative(e.key); 11 | } 12 | } 13 | }); -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/Services/IUrlProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Axemasta.SuperWebView.Sample.Services 2 | { 3 | public interface IUrlProvider 4 | { 5 | string GetBaseUrl(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/Views/BlockPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Cool Page 6 | 7 | 8 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | Site Blocked 152 | 153 | 154 | 155 | Access to the following site has been blocked: ${blockedSite} 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/Samples/Axemasta.SuperWebView.Sample/Views/CoolPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Cool Page 6 | 7 | 8 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | Local Page 152 | 153 | 154 | 155 | This is a local html page being displayed! 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/Tests/Axemasta.SuperWebView.UnitTests/Axemasta.SuperWebView.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Tests/Axemasta.SuperWebView.UnitTests/EmbeddedResourceHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace Axemasta.SuperWebView.UnitTests 5 | { 6 | [TestFixture] 7 | public class EmbeddedResourceHelperTests 8 | { 9 | [Test] 10 | public void Load_NoResourceName_Should_ThrowArgumentException() 11 | { 12 | var expectedException = new ArgumentNullException("resourceName"); 13 | 14 | var ex = Assert.Throws(() => EmbeddedResourceHelper.Load(null, null)); 15 | 16 | Assert.AreEqual(expectedException.Message, ex.Message); 17 | } 18 | 19 | [Test] 20 | public void Load_NoAssemblyName_Should_ThrowArgumentException() 21 | { 22 | var expectedException = new ArgumentNullException("assemblyName"); 23 | 24 | var ex = Assert.Throws(() => EmbeddedResourceHelper.Load("myResource", null)); 25 | 26 | Assert.AreEqual(expectedException.Message, ex.Message); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Tests/Axemasta.SuperWebView.UnitTests/ProgressEventArgsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace Axemasta.SuperWebView.UnitTests 5 | { 6 | [TestFixture] 7 | public class ProgressEventArgsTests 8 | { 9 | [Test] 10 | // iOS Tests 11 | [TestCase(0.1, 1, 0.1, 10)] 12 | [TestCase(1, 1, 1, 100)] 13 | [TestCase(0, 1, 0, 0)] 14 | [TestCase(0.1454833984375, 1, 0.1455, 14.55)] 15 | [TestCase(0.752, 1, 0.752, 75.2)] 16 | [TestCase(0.897883218134872, 1, 0.8979, 89.79)] 17 | 18 | // Android Tests 19 | [TestCase(10, 100, 0.1, 10)] 20 | [TestCase(0, 100, 0, 0)] 21 | [TestCase(23, 100, 0.23, 23)] 22 | [TestCase(26, 100, 0.26, 26)] 23 | [TestCase(35, 100, 0.35, 35)] 24 | [TestCase(48, 100, 0.48, 48)] 25 | [TestCase(50, 100, 0.5, 50)] 26 | [TestCase(70, 100, 0.7, 70)] 27 | [TestCase(80, 100, 0.8, 80)] 28 | [TestCase(100, 100, 1, 100)] 29 | public void Constructor_Should_SetFieldsCorrectly( 30 | double progressValue, int progressLimit, 31 | double expectedNormalized, double expectedPercentage) 32 | { 33 | var progressArgs = new ProgressEventArgs(progressValue, progressLimit); 34 | 35 | Assert.IsTrue(progressArgs.NormalisedProgress <= 1, "Normalized progress was greater than 1"); 36 | Assert.IsTrue(progressArgs.PercentageComplete <= 100, "Percentage progress was greater than 100"); 37 | 38 | Assert.AreEqual(expectedNormalized, progressArgs.NormalisedProgress); 39 | Assert.AreEqual(expectedPercentage, progressArgs.PercentageComplete); 40 | } 41 | } 42 | } 43 | --------------------------------------------------------------------------------
Access to the following site has been blocked: ${blockedSite}
This is a local html page being displayed!