Enable WebView to render local HTML files on WinRT platforms (#277)

* Enable WebView to render local HTML files on WinRT platforms

* Add test to demonstrate that the solution works even if
<head> isn't in the HTML string
This commit is contained in:
E.Z. Hart 2016-08-03 15:22:14 -06:00 коммит произвёл Jason Smith
Родитель b60fa6acf8
Коммит b186254d1f
16 изменённых файлов: 240 добавлений и 8 удалений

Просмотреть файл

@ -4,7 +4,7 @@
</head>
<body>
<h1>Xamarin.Forms</h1>
<p>This is a local iOS Html page</p>
<p>This is a local HTML page</p>
</body>
</html>

Двоичные данные
Xamarin.Forms.ControlGallery.Windows/WebImages/XamarinLogo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.8 KiB

Просмотреть файл

@ -189,7 +189,10 @@
<Content Include="Assets\SplashScreen.scale-100.png" />
<Content Include="Assets\StoreLogo.scale-100.png" />
<Content Include="coffee.png" />
<Content Include="default.css" />
<Content Include="local.html" />
<Content Include="toolbar_close.png" />
<Content Include="WebImages\XamarinLogo.png" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">

Просмотреть файл

@ -0,0 +1,2 @@
html,body{margin:0;padding:10}
body,p,h1{font-family:Chalkduster;font-style: italic;}

Просмотреть файл

@ -0,0 +1,10 @@
<html>
<head>
<link rel="stylesheet" href="default.css">
</head>
<body>
<h1>Xamarin.Forms</h1>
<p>This is a local HTML page</p>
</body>
</html>

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.8 KiB

Просмотреть файл

@ -166,7 +166,10 @@
<Content Include="Assets\StoreLogo.scale-240.png" />
<Content Include="Assets\WideLogo.scale-240.png" />
<Content Include="coffee.png" />
<Content Include="default.css" />
<Content Include="local.html" />
<Content Include="toolbar_close.png" />
<Content Include="WebImages\XamarinLogo.png" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">

Просмотреть файл

@ -0,0 +1,2 @@
html,body{margin:0;padding:10}
body,p,h1{font-family:Chalkduster;font-style: italic;}

Просмотреть файл

@ -0,0 +1,10 @@
<html>
<head>
<link rel="stylesheet" href="default.css">
</head>
<body>
<h1>Xamarin.Forms</h1>
<p>This is a local HTML page</p>
</body>
</html>

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.8 KiB

Просмотреть файл

@ -117,8 +117,11 @@
</ItemGroup>
<ItemGroup>
<Content Include="coffee.png" />
<Content Include="default.css" />
<Content Include="invalidimage.jpg" />
<Content Include="local.html" />
<Content Include="toolbar_close.png" />
<Content Include="WebImages\XamarinLogo.png" />
<None Include="project.json" />
<Content Include="..\Xamarin.Forms.ControlGallery.WP8\bank.png">
<Link>bank.png</Link>

Просмотреть файл

@ -0,0 +1,2 @@
html,body{margin:0;padding:10}
body,p,h1{font-family:Chalkduster;font-style: italic;}

Просмотреть файл

@ -0,0 +1,10 @@
<html>
<head>
<link rel="stylesheet" href="default.css">
</head>
<body>
<h1>Xamarin.Forms</h1>
<p>This is a local HTML page</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,141 @@
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Controls
{
[Preserve (AllMembers = true)]
[Issue (IssueTracker.Bugzilla, 32033, "WebView on Windows does not display local HTML files", PlatformAffected.WinRT)]
public class Bugzilla32033 : TestNavigationPage
{
protected override void Init ()
{
PushAsync(Menu());
}
ContentPage Menu()
{
var page = new ContentPage();
var layout = new StackLayout();
var buttonLocal = new Button() {Text = "Local HTML file"};
buttonLocal.Clicked += (sender, args) => Navigation.PushAsync(LocalUrl());
var buttonHtmlString = new Button() {Text = "HTML string with links/refs to local files"};
buttonHtmlString.Clicked += (sender, args) => Navigation.PushAsync(HtmlString());
var buttonHtmlStringNoHead = new Button() {Text = "HTML string with links/refs to local files (no <head>)"};
buttonHtmlStringNoHead.Clicked += (sender, args) => Navigation.PushAsync(HtmlStringNoHead());
layout.Children.Add(buttonLocal);
layout.Children.Add(buttonHtmlString);
layout.Children.Add(buttonHtmlStringNoHead);
page.Content = layout;
return page;
}
static ContentPage LocalUrl()
{
var page = new ContentPage();
var instructions = new Label
{
Text = @"The WebView below should contain the heading 'Xamarin Forms' and text reading 'This is a local HTML page'. All text should be italicized."
};
var webView = new WebView
{
WidthRequest = 300,
HeightRequest = 500,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill,
Source = new UrlWebViewSource() { Url = "local.html" }
};
var layout = new StackLayout { Children = { instructions, webView } };
page.Content = layout;
return page;
}
static ContentPage HtmlString()
{
var page = new ContentPage();
var instructions = new Label
{
Text =
@"The WebView below should contain the heading 'Xamarin Forms', display the Xamarin logo, and have a link labeled 'next page'.
Clicking that link should navigate to a page with the heading 'Xamarin Forms' and text reading 'This is a local HTML page'. All text on both pages should be italicized."
};
var webView = new WebView
{
WidthRequest = 300,
HeightRequest = 500,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill,
Source = new HtmlWebViewSource
{
Html = @"<html>
<head>
<link rel=""stylesheet"" href=""default.css"">
</head>
<body>
<h1>Xamarin.Forms</h1>
<p>The CSS and image are loaded from local files!</p>
<img src='WebImages/XamarinLogo.png'/>
<p><a href=""local.html"">next page</a></p>
</body>
</html>"
}
};
var layout = new StackLayout {HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, Children = { instructions, webView } };
page.Content = layout;
return page;
}
// This test verifies that the <base> injection solution works even if the HTML string doesn't explicitly include a <head> section
static ContentPage HtmlStringNoHead()
{
var page = new ContentPage();
var instructions = new Label
{
Text =
@"The WebView below should contain the heading 'Xamarin Forms', display the Xamarin logo, and have a link labeled 'next page'.
Clicking that link should navigate to a page with the heading 'Xamarin Forms' and text reading 'This is a local HTML page'. "
};
var webView = new WebView
{
WidthRequest = 300,
HeightRequest = 500,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill,
Source = new HtmlWebViewSource
{
Html = @"<html>
<body>
<h1>Xamarin.Forms</h1>
<p>The CSS and image are loaded from local files!</p>
<img src='WebImages/XamarinLogo.png'/>
<p><a href=""local.html"">next page</a></p>
</body>
</html>"
}
};
var layout = new StackLayout {HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, Children = { instructions, webView } };
page.Content = layout;
return page;
}
}
}

Просмотреть файл

@ -46,6 +46,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla31333.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla31366.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla31964.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla32033.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla32034.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla32776.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla32842.xaml.cs">

Просмотреть файл

@ -1,8 +1,14 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.WebUI;
using Windows.UI.Xaml.Controls;
using Windows.Web.Http;
using Xamarin.Forms.Internals;
using static System.String;
#if WINDOWS_UWP
@ -16,19 +22,59 @@ namespace Xamarin.Forms.Platform.WinRT
{
WebNavigationEvent _eventState;
bool _updating;
const string LocalScheme = "ms-appx-web:///";
// Script to insert a <base> tag into an HTML document
const string BaseInsertionScript = @"
var head = document.getElementsByTagName('head')[0];
var bases = head.getElementsByTagName('base');
if(bases.length == 0){
head.innerHTML = 'baseTag' + head.innerHTML;
}";
public void LoadHtml(string html, string baseUrl)
{
/*
* FIXME: If baseUrl is a file URL, set the Base property to its path.
* Otherwise, it doesn't seem as if WebBrowser can handle it.
*/
Control.NavigateToString(html);
if (IsNullOrEmpty(baseUrl))
{
baseUrl = LocalScheme;
}
// Generate a base tag for the document
var baseTag = $"<base href=\"{baseUrl}\"></base>";
string htmlWithBaseTag;
// Set up an internal WebView we can use to load and parse the original HTML string
var internalWebView = new Windows.UI.Xaml.Controls.WebView();
// When the 'navigation' to the original HTML string is done, we can modify it to include our <base> tag
internalWebView.NavigationCompleted += async (sender, args) =>
{
// Generate a version of the <base> script with the correct <base> tag
var script = BaseInsertionScript.Replace("baseTag", baseTag);
// Run it and retrieve the updated HTML from our WebView
await sender.InvokeScriptAsync("eval", new[] { script });
htmlWithBaseTag = await sender.InvokeScriptAsync("eval", new[] { "document.documentElement.outerHTML;" });
// Set the HTML for the 'real' WebView to the updated HTML
Control.NavigateToString(!IsNullOrEmpty(htmlWithBaseTag) ? htmlWithBaseTag : html);
};
// Kick off the initial navigation
internalWebView.NavigateToString(html);
}
public void LoadUrl(string url)
{
Control.Source = new Uri(url);
Uri uri = new Uri(url, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri)
{
uri = new Uri(LocalScheme + url, UriKind.RelativeOrAbsolute);
}
Control.Source = uri;
}
protected override void Dispose(bool disposing)
@ -165,7 +211,6 @@ namespace Xamarin.Forms.Platform.WinRT
_eventState = WebNavigationEvent.NewPage;
}
// Nasty hack because we cant bind this because OneWayToSource isn't a thing in WP8, yay
void UpdateCanGoBackForward()
{
Element.CanGoBack = Control.CanGoBack;