WebAssembly in Action

Author of the book "WebAssembly in Action"
Save 40% with the code: ggallantbl
The book's original source code can be downloaded from the Manning website and GitHub. The GitHub repository includes an updated-code branch that has been adjusted to work with the latest version of Emscripten (currently version 3.1.44).
Showing posts with label Metro. Show all posts
Showing posts with label Metro. Show all posts

Wednesday, September 12, 2012

Windows 8: Windows Store apps and the WebView control


I recently started looking into creating a simple Windows 8, Windows Store app, that would contain a browser window and a setting somewhere to allow the URI to be modified.

The app itself is quite simple in concept so I figured it would be a breeze to implement for Windows 8.

To display a web page I needed to use a WebView control and I simply set the Source property to the desired Uri.


The Settings Flyout

After successfully building my solution, and seeing the web page displayed, I decided it was time to move on to building the option to allow a user to modify the URI.

To try and keep things consistent with other Windows Store apps, I decided that it would be best to try and integrate the settings window into the Settings Charm of Windows.

I did some research and integrating into the Settings Charm seemed pretty straightforward in that you simply add an event handler for the CommandsRequested event of the Settings Charm and when the event is called, create your SettingsCommand callback.


Showing a Settings flyout seemed straightforward but I couldn't seem to get it to display.

While searching the internet, looking for articles or forum posts that might shed some light on my issue, I ran across the Callisto open-source toolkit, created by Tim Heuer, that offers boilerplate code for Windows Store apps.

You can find Tim's Callisto blog here: http://timheuer.com/blog/archive/2012/05/31/introducing-callisto-a-xaml-toolkit-for-metro-apps.aspx

The GitHub repository for the Callisto toolkit can be found here: https://github.com/timheuer/callisto

To install the Callisto package into your project via NuGet (https://nuget.org/packages/Callisto), you can use the following command:

PM> Install-Package Callisto


Unfortunately, even with the Callisto toolkit, my settings flyout still wasn't being displayed.

When I was trying to create the settings flyout myself, before I discovered Callisto, I needed to set the height of the flyout so I started to wonder if perhaps the WebView control's VerticalAlignment setting of Stretch was the issue since I wasn't setting an explicit height value.

I set the height of the WebView control to 700 pixels and, when I ran my app, I discovered what the issue really was.

It turns out that the settings flyout was being displayed all along but the WebView control was hiding it because I can now see it being displayed behind the WebView control.


WebViewBrush

After a bit more research, I came to discover that you need a workaround with the WebView control when displaying a flyout (unfortunately, this reminds me of IE 6 and how Select objects would show through a floating div).

The following is the suggested workaround for displaying a flyout over a WebView control:
  • Add a Rectangle object to your view's XMAL with the same dimensions as your WebView control
  • Just before you show the flyout, create a WebViewBrush with the current contents of the WebView control (basically a screen shot)
  • Apply the brush to the Rectangle control
  • Hide the WebView control
  • Display the flyout
  • When the flyout closes, redisplay the WebView control
  • Clear the brush from the Rectangle control

I modified my view to use the workaround and my settings flyout now displays which I'm very happy about.

The main issue that I see now is that there is a noticeable flicker when switching to the WebViewBrush.

My initial thought, since the Redraw method of the brush is asynchronous, was that the WebView control was being hidden a fraction of a second before the brush had time to finish loading resulting in the background of the view being briefly visible causing the flicker.


I tried everything I could think of to get rid of the flicker including making the WebViewBrush a member variable and constructing it during the view's constructor.

I even tried placing the Redraw call before the settings flyout code creation to try and give the brush a few more milliseconds to load but the flicker remained.


async Task.Delay

Something I was curious about was if causing the app to sleep, in between the Redraw call of the brush and the hiding of the WebView, would help.

As it turns out there is no Thread.Sleep method available in a Windows Store app but I did find a workaround using an async Task.Delay(5000) call (I used 5000 milliseconds just so that it was obvious that the sleep call worked)

Much to my pleasant surprise, the flicker disappeared!


When I looked closer at the code, a thought crossed my mind...

I had both the WebViewBrush’s Redraw method as well as the Rectangle's Fill property being set before the delay.

What if the flicker wasn't because of the Redraw method of the brush after all?

What if the issue is with the Fill property of the Rectangle object?

I moved the rectangle's Fill call to after the delay and the flicker returned which indicates to me that the issue is not the brush but rather the Rectangle’s Fill property.


As I started thinking about what the issue might be, a thought occurred to me...

What if this is behaving similar to how the UI thread behaves in a web browser?

In a web browser there is only the one UI thread for a window so when you want to update the UI while processing, your UI request gets added to the end of the list of things that the window plans to do once the currently executing code completes.

Typically, if your code might take a few seconds to complete, you would want to display a processing indicator of some sort while your code executes (so that the user doesn't think that the page froze).

If you simply tell the UI processing control to display and then start processing, the UI control won't display until your code completes since the UI request is queued up for execution next by the browser window and your code is what it's currently working on.

To get around this UI update issue, in a web browser, you tell the UI control to display which adds that item to the end of the window's queue and then you set a timeout to call the function that will do the actual processing which puts the processing function on the queue just after the UI update.

When the original function exits, the UI is updated and then the processing function is called.

Based on the behavior I'm seeing, I believe the Windows Store app is using a similar technique to the UI thread of a web browser window


If I set the Rectangle's Fill property with the WebViewBrush and then add an async delay of 1 millisecond before proceeding to display the settings flyout, there is no flicker!

I still saw the flicker once in a while when closing the Settings flyout via the back button and then redisplaying the flyout really quick. Increasing the delay to about 100 milliseconds seems to improve that scenario.


Example Code

The following is an example of the XAML needed:
<Grid>
<WebView x:Name="wvBrowser" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<Rectangle x:Name="rectWebViewBrush" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></Rectangle>
</Grid>

The following is an example of the source code needed to show the Settings flyout when dealing with a WebView control:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested;
}

protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
SettingsPane.GetForCurrentView().CommandsRequested -= MainPage_CommandsRequested;

base.OnNavigatingFrom(e);
}


// Called by the Settings charm to find out what Settings links
// to display and the code to call when clicked.
void MainPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
{
// Set up the Link for the Settings charm
SettingsCommand cmdSettingsOptions = new SettingsCommand("cmdSettingsOptionsLabel", "Options", (x) =>
{
// Get a brush from the WebView's content (basically,
// a screen shot)
WebViewBrush wvbBrush = new WebViewBrush();
wvbBrush.SourceName = "wvBrowser";
wvbBrush.Redraw();

// Fill the Rectangle object with the brush
rectWebViewBrush.Fill = wvbBrush;

// Show the settings flyout
ShowSettingsFlyout();
});

// Add our Setting link to our applications list of settings
args.Request.ApplicationCommands.Add(cmdSettingsOptions);
}


// Creates and displays the Settings flyout:
async void ShowSettingsFlyout()
{
// Give the Rectangle a chance to refresh so that we
// don't have flicker
await Task.Delay(100);

// Create the Settings flyout and display it (this is using
// the Callisto open-source toolkit). Additional attributes
// can be set like the background color and header brush
// to better represent your app's look and feel
SettingsFlyout settings = new SettingsFlyout();
settings.FlyoutWidth = Callisto.Controls.SettingsFlyout.SettingsFlyoutWidth.Narrow;
settings.HeaderText = "Options";

// Intercept the setting's closed event so that we can
// switch back to the WebView control
settings.Closed += settings_Closed;

// Our UserControl derived class that holds the controls
// for our settings
settings.Content = new SettingsOptionsContent();

// Switch to the WebViewBrush and then show the
// settings flyout
SwitchToWebViewScreenShot(true);
settings.IsOpen = true;
}


void SwitchToWebViewScreenShot(bool bSwitchToScreenShot)
{
// If we're to show the screen shot then...
if (bSwitchToScreenShot)
{
// Hide the WebView control (MainPage_CommandsRequested
// has already set the rectangle's fill with a screen shot of the
// contents of the WebView control)
wvBrowser.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
else // We're to show the WebView again...
{
// Show the WebView control and remove the WebViewBrush
// from the Rectangle
wvBrowser.Visibility = Windows.UI.Xaml.Visibility.Visible;
rectWebViewBrush.Fill = new SolidColorBrush(Colors.Transparent);
}
}


// Called when the Settings flyout closes
void settings_Closed(object sender, object e)
{
// Switch back to the WebView control rather than the screen shot
SwitchToWebViewScreenShot(false);
}



In Summary

WebView controls don't allow a flyout to appear over it and require the use of a WebViewBrush to basically display a screen shot while the flyout is displayed.

The filling of a Rectangle object with a WebViewBrush, and immediately hiding the WebView control, introduces a flicker that can be circumvented by interrupting the UI's processing flow by adding an async delay before hiding the WebView control.

Windows Store apps don't support Thread.Sleep but you can accomplish a similar effect by using 'async Task.Delay'


When I was researching the WebViewBrush flicker issue, several forum posts indicated that there was a bug filed for it so there is potential that the flicker will not be an issue forever.

In the mean time, however, I hope this helps.


Download the Source Code

A download of the project (C# and built using Visual Studio Express 2012 for Windows 8) can be found in the following location:
https://github.com/downloads/dovicoapi/DOVICOTimerForWindowsStore/DOVICOTimerForWindowsStore.zip

Wednesday, March 2, 2011

Viewing JavaScript Errors in Internet Explorer 9 and 10

With Internet Explorer 10 (IE 10), it is not currently possible to view JavaScript errors when using the metro version of the browser.

The contents of this article still apply to IE 10 but only when using the desktop version.

To switch from the metro version of IE 10 to the desktop version, click on the Settings button in the browser and choose the 'View on the desktop' menu item.

JavaScript error reporting is a bit different in Internet Explorer 9 and 10 compared to previous Internet Explorer browsers since, by default, the Status bar is not visible and depending on your settings this might be the only place that would indicate if there was an error on the page.

You can turn on the Status bar either through the menu system by pressing Alt on your keyboard to show the menu bar and then navigating to the View, Toolbars, Status bar menu item.

Another approach to showing the Status bar is to right-click on title/tab area and choose the Status bar option from the context menu.


JavaScript Errors on the Status Bar

Getting errors to show via the status bar seems to be hit and miss because sometimes it works for me and sometimes it doesn't. It is my guess that Microsoft intends to remove the status bar error log functionality altogether and that it is simply an oversight (bug) that lets us view the error log in the first place.

In my testing if you start the browser normally the Status bar behavior I'm about to describe will not work.

If you launch IE from a pinned site, however, this Status bar behavior usually works.


To pin a site, drag the icon that is on the address bar to the Windows taskbar.

(click to view the image full size)


In your Advanced settings, if you have the 'Display a notification about every script error' option turned off then even if you have the status bar visible, you will not know there was an error.

The only hint of a JavaScript error would be if the page didn't respond properly (something didn't load for example).

The following is a screen shot of a website launched from a pinned site that has thrown an error (the status bar doesn't indicate that there was an error):

(click to view the image full size)

If you double-click anywhere on the status bar, if there was a JavaScript error on the page, the error log will pop-up.

(click to view the image full size)

If you have the 'Display a notification about every script error' option turned on then you will get a bit more feedback when a JavaScript error happens on the page.

Now, when a JavaScript error happens, the error log pops up showing us the error and, when we dismiss it, the status bar tells us there was an error on the page.

(click to view the image full size)


Whether or not the status bar was visible at the time of the JavaScript error doesn't matter. If you show the status bar and then double-click on it, you will see the error log.


Viewing JavaScript Errors from the Developer Tools

Since the status bar is no longer reliable for viewing if there was a JavaScript error on the page there is another approach available for viewing JavaScript errors.

Internet Explorer has a set of tools built in called Developer Tools and they can be displayed by pressing F12. The following is a screen shot of the Developer Tools window:

(click to view the image full size)

In the Developer Tools is a Console tab that shows you a log of all JavaScript errors on the page since the Developer Tools were opened.

The trick with the Console tab of the Developer Tools is that the error log only starts logging errors once the Developer Tools window is opened.

(click to view the image full size)

You will need to repeat the action that triggered the error in the first place to see the error in the Console tab.

The following is a screen shot of the Console tab with a JavaScript error logged:

(click to view the image full size)



In Conclusion

If you think there was a JavaScript error on the page, and the web page was launched from a pinned site, then double-clicking the Status bar usually displays the JavaScript error log if there were errors.

The Console tab of the Developer Tools (F12) will also give you a list of all JavaScript errors that happened on the page regardless of if the web page was launched from a pinned site or not.

The only issue with the Console tab's error logging is that it only starts logging errors once the Developer Tools window has been opened forcing one to repeat the steps to try and trigger the error again.