The blog of dlaa.me

Posts from September 2007

Bringing more HTML to Silverlight [HtmlTextBlock improvements]

I blogged about my HtmlTextBlock implementation for Silverlight a few days ago. In that post I described HtmlTextBlock as a "plug-compatible" replacement for TextBlock that knows how to take simple HTML (technically XHTML) and display it in a manner that fairly closely approximates how a web browser does. The responses I've gotten suggest HtmlTextBlock is somewhat popular, so I've spent a bit of time improving upon the original implementation. The HtmlTextBlock demonstration and source code linked to by my earlier post have been updated, so feel free to play along as you read the notes:

HtmlTextBlock Demonstration Page

Notes:

  • I loved the simplicity of using an XmlReader to parse the input to HtmlTextBlock, but I worried that the prevalence of non-XHTML would limit the usefulness of HtmlTextBlock. For example, the following invalid XHTML would fail to parse correctly: foo<b>bar</i>baz. During an informal discussion, Ted Glaza suggested using the browser's Document Object Modelto do the parsing of invalid XHTML - great idea! :)
    • So now when its Text property is set, HtmlTextBlock first treats the input as valid XHTML and tries to parse it with XmlReader. This is the most efficient code path and should be successful for any valid XHTML input.
    • However, if the input can't be parsed in that manner, HtmlTextBlock uses the objects in Silverlight's System.Windows.Browser namespace to dynamically insert the provided text into the host browser's DOM, run some JavaScript code to transform the input into valid XHTML, then extract and parse the transformed text with XmlReader as before.
    • Two techniques are tried:
      • The first method works in browsers that return XHTML for an element's innerHTML property even if the contents of the element are not valid XHTML (ex: Firefox) and simply inserts and retrieves XHTML from the innerHTML property.
      • The second method works in browsers that return the contents of an element as-is (ex: Internet Explorer (though it works fine in Firefox, too)) by walking the node's .firstChild/.nextSibling/.nodeName tree and manually building up the corresponding XHTML as the nodes of the tree are visited. The results of this method appear ideal in most scenarios, though it's possible to come up with edge cases where its output is slightly different from that of the browser itself (Internet Explorer): foo<b><i>bar</b>baz.
    • Strangely, BOTH browsers prefer <br> to <br/>, going as far as transforming the latter into the former despite the fact that doing so creates invalid XHTML!
    • If all parsing attempts fail, the supplied text is used as-is with no formatting applied.
    Unfortunately, there's a catch... Depending on the content of the text, adding it to the DOM could insert untrusted HTML into the host browser's page. A malicious user with control of the text could use the following approach to run their own JavaScript code in the context of the user's browser (Firefox only): </div><script>alert('Script code running!');</script>. As such, the new DOM parsing behavior is disabled by default and can be enabled by setting the UseDomAsParser property of HtmlTextBlock for scenarios when the input text is known to be safe.
  • A kind reader informed me that an HtmlTextBlock created in code (vs. in XAML as my sample demonstrated) did not seem to do text wrapping properly. The problem was caused by the difference in when the Control.Loaded event fires in the two scenarios. The fix is a simple change to HandleLoaded that skips sizing the contained TextBlock if the HtmlTextBlock hasn't been sized itself. Additionally, the sample code now demonstrates how to create a HtmlTextBlock in code - just #define CREATE_IN_CODE to see how.
  • Because HtmlTextBlock used bool variables to track the bold/italic/underline states, nesting a style caused that style to be prematurely removed. Specifically, the following scenario would not display properly: normal <b>bold <b>also bold</b> still bold</b> normal. HtmlTextBlock now handles nesting properly by using int variables.
  • I mentioned last time that making HtmlTextBlock adhere to all the individual browser quirks would be a daunting task. For fun, here's a simple input to demonstrate the point: <p>hello world</p>. Firefox (and HtmlTextBlock) leaves some space above the text because of the <p> element; Internet Explorer does not. :)

In its introductory post, I said that HtmlTextBlock is obviously nothing like a complete HTML rendering engine - and that statement remains true today. However, by taking advantage of the host browser's DOM to transform invalid XHTML input, HtmlTextBlock is much more flexible than it used to be. That - and a few fixes - makes it an even more compelling option for rich text display in Silverlight!

You voted lots, we fixed lots [AJAX Control Toolkit release!]

Last night we published the 10920 release of the AJAX Control Toolkit. This release continued our trend of focusing on the most popular bugs and work items identified by the user community in the support forum and online issue tracker. A number of popular issues got fixed in this release, addressing nearly 1000 user votes!

The release notes from the sample web site detail the improvements:

General fixes:

  • Controls with Embedded styles (Calendar, Tabs and Slider): Toolkit controls no longer need explicit style references when loaded asynchronously. For example, if a Calendar control is placed inside an UpdatePanel and made visible on an UpdatePanel postback, the embedded styles are now loaded properly.
  • PopupBehavior positioning (AutoComplete, Calendar, DropDown, HoverMenu, ListSearch, PopupControl and ValidatorCallout): PopupBehavior now respects the position of its parent element even when the browser window is very narrow or the parent element is close the window edge.
  • Focusing extended controls (Accordion, CollapsiblePanel, DropShadow, Tabs): Pages that use Toolkit controls which re-parent DOM elements can use a workaround to focus a specific element on page load. The new method Utility.SetFocusOnLoad ensures that the desired control receives focus.

Control specific fixes:

  • Calendar: Property to specify the position of Calendar, a default date feature that allows the calendar to start out with a selected date, and a consistent show, hide and focus story that makes the Calendar user experience more intuitive.
  • ModalPopup: Ability to disable repositioning of the ModalPopup in response to window resize and scroll.
  • ConfirmButton: ModalPopup functionality now supported in addition to the regular windows alert dialog.
  • MaskedEdit: Extended Textbox no longer uses Invariant culture if no CultureName is specified and falls back to the Page Culture.
  • AutoComplete: Allow users to associate additional data with the AutoComplete suggestions.
  • Slider: Slider can be easily customized using its various CSS properties.

As with the previous release, we have published "source" and "no-source" versions for .NET 2.0/Visual Studio 2005 as well as for .NET 3.5/Visual Studio 2008 (still in Beta). Unique to the 3.5/2008 versions are the following:

Features:

  • JavaScript IntelliSense support: We have added reference tags to all Toolkit JavaScript files that enables you to take advantage of new features in Visual Studio 2008 Beta 2. With the multi-targeting support in this Visual Studio Beta, IntelliSense will be available for the ASP.NET AJAX 1.0 flavor of the Toolkit as well. This article discusses the reference tag feature in detail.
  • Extender designer support: Enhanced designer support for Toolkit controls using the new "Add Extender" user interface.

One thing we'd hoped to include with this release didn't quite make it in: our new automated testing framework. This framework is based on a different approach than our current framework - one that makes it easy to add additional test cases and leverage existing ones across new scenarios. The new testing framework has already dramatically improved our test coverage, helped identify new issues, and made fixing existing issues less risky!

But we've been iterating on the new framework for the past couple of weeks and faced the usual decision when it came time to finalize this release: slip or ship. We slipped our release date a little in the hopes that we'd be able to include the new framework with this release, but eventually decided not to delay all the great new Toolkit code any longer. We wanted our users to take advantage of the new bits ASAP - so stay tuned for more on the new testing framework in a future release!

As always, it's easy to sample any of the controls (no install required). You can also browse the project web site, download the latest Toolkit, and start creating your own controls and/or contributing to the project!

If you have any feedback, please share it with us on the support forum!

Bringing a bit of HTML to Silverlight [HtmlTextBlock makes rich text display easy!]

Lately I've seen a few people wanting to display rich text in a Silverlight application, but having no way to do so easily. Most recently, I saw Tim Heuer bump into this when displaying RSS content in a neat demo of his. The basic problem is that Silverlight's primary text display object, TextBlock, does not natively have a way to display HTML - and that's the format most rich text is in these days. It seemed that if only there were a TextBlock that took HTML as input and built up a collection of Run and LineBreak objects corresponding to that HTML, things would be easier...

So I wrote HtmlTextBlock, a "plug-compatible" replacement for TextBlock that knows how to take simple HTML (technically XHTML, see the notes below) and display it in a manner that fairly closely approximates how a web browser does. A picture is worth a thousand words, so here's what HtmlTextBlock looks like in action:

HtmlTextBlock Demonstration Page

You can click here (or on the image above) to experiment with HtmlTextBlock in your own browser in an interactive demo page. As usual, I've made the complete source code available, so click here to download the source code and play around with it yourself! (To build the project, you'll want to use Visual Studio 2008 Beta 2 and the latest Silverlight Tools.)

Notes:

  • HtmlTextBlock supports the following HTML elements: <A>, <B>, <BR>, <EM>, <I>, <P>, <STRONG>, and <U>. Attributes are not supported with the exception of the <A> element's HREF attribute (see below).
  • I initially planned to have HtmlTextBlock derive from TextBlock and simply override its Text property. However, TextBlock is sealed, so that wasn't going to work. The next obvious approach was to have HtmlTextBlock be a Control with a TextBlock inside it, so that's what I've done here. My goal of "plug-compatibility" (i.e., the ability to easily replace TextBlock with HtmlTextBlock) meant that I needed to manually implement the various TextBlock properties on HtmlTextBlock and pass them through to the underlying TextBlock. So HtmlTextBlock doesn't actually derive from TextBlock, but it behaves as though it did.
  • Having HtmlTextBlock use TextBlock internally and represent the HTML markup with Run and LineBreak elements is nice because it means that just about everything people already know about TextBlock automatically applies to HtmlTextBlock And I get correct word wrapping behavior for free. :) However, the glitch is that the Run element does not support the MouseLeftButton* family of events, making the handling of <A> link elements difficult. Because HtmlTextBlock can't really tell when the user clicks on a link, it displays the URL of each link so the user can type it in themselves (and typing is necessary because TextBlock doesn't support select+copy either). I can think of a few reasons why Run might not support mouse events, but in this case it would be convenient if it did. :)
  • HtmlTextBlock uses an XmlReader to parse its input when creating the Run and LineBreak elements for display. This makes the parsing implementation simple and robust, but breaks down when the input is not XHTML (such as invalid HTML or valid HTML where empty elements don't have a trailing '/' (ex: "<br>" instead of "<br />")). In the event of a parsing error, HtmlTextBlock's default behavior is to display the input text as-is, just like TextBlock always does. Unfortunately, non-XHTML is pretty common, so certain scenarios may benefit from a custom HTML parser implementation that is more flexible in this regard.
  • HTML rendering is covered by a detailed specification, but some of the rules are not what one might expect. Specifically, the rules for handling spacing and whitespace (' ', '\n', '\t', etc.) can be tricky to get right. I've made some attempt to ensure that HtmlTextBlock follows the rules where possible and convenient, but it was not my goal to achieve 100% compliance in this area. Playing around in the demo application, HtmlTextBlock should pretty closely match the browser's HTML rendering of valid input, but if you know the rules, it's not too difficult to trigger whitespace rendering differences.
  • Silverlight's default font, "Portable User Interface"/"Lucida Sans Unicode"/"Lucida Grande" doesn't seem to render bold or italic text any differently than normal text. This always surprises people the first time they try to figure out why their text isn't bold/italic. I don't know why this is myself, but I'd guess it relates to keeping the download size of Silverlight to a minimum.
  • The demo page makes use of JavaScript -> C# function calls. Hooking this up in code is surprisingly easy, though I did run into a small glitch. If the sample text is deleted entirely (leaving a blank text box) in IE, the JS -> C# call originates from the JS side as it should, but never makes it through to the C# side. Looking at the call to SetText in the debugger, textAreaSampleText.value is "", so this should work in IE just like it does in Firefox - but it didn't for me. The simple workaround was to pass textAreaSampleText.value + "" instead and then things worked in IE, too.

HtmlTextBlock is obviously nothing like a complete HTML rendering engine [that's what web browsers are for! :) ]. However, if you want to add simple support for rich text to your Silverlight application without a lot of work, HtmlTextBlock may be just the thing for you!