Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
Enable WebView to render local HTML files on WinRT platforms (#277)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
hartez authored and Jason Smith committed Aug 3, 2016
1 parent b60fa6a commit 8c1107e
Show file tree
Hide file tree
Showing 16 changed files with 240 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Xamarin.Forms.ControlGallery.WP8/local.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
2 changes: 2 additions & 0 deletions Xamarin.Forms.ControlGallery.Windows/default.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
html,body{margin:0;padding:10}
body,p,h1{font-family:Chalkduster;font-style: italic;}
10 changes: 10 additions & 0 deletions Xamarin.Forms.ControlGallery.Windows/local.html
Original file line number Diff line number Diff line change
@@ -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>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
2 changes: 2 additions & 0 deletions Xamarin.Forms.ControlGallery.WindowsPhone/default.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
html,body{margin:0;padding:10}
body,p,h1{font-family:Chalkduster;font-style: italic;}
10 changes: 10 additions & 0 deletions Xamarin.Forms.ControlGallery.WindowsPhone/local.html
Original file line number Diff line number Diff line change
@@ -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>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 2 additions & 0 deletions Xamarin.Forms.ControlGallery.WindowsUniversal/default.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
html,body{margin:0;padding:10}
body,p,h1{font-family:Chalkduster;font-style: italic;}
10 changes: 10 additions & 0 deletions Xamarin.Forms.ControlGallery.WindowsUniversal/local.html
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
59 changes: 52 additions & 7 deletions Xamarin.Forms.Platform.WinRT/WebViewRenderer.cs
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -16,19 +22,59 @@ public class WebViewRenderer : ViewRenderer<WebView, Windows.UI.Xaml.Controls.We
{
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)
Expand Down Expand Up @@ -165,7 +211,6 @@ void SendNavigated(UrlWebViewSource source, WebNavigationEvent evnt, WebNavigati
_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;
Expand Down

0 comments on commit 8c1107e

Please sign in to comment.