Thursday, February 09, 2006

Why I'm giving up Ayende's NHibernate.Generics (for now)

Ayende's NHibernate.Generics is truly an incredible advance in supporting generic collections with NHibernate, and solving the two-way relationship problem at the same time.  I switched all my projects to use it because it made the code so much prettier than my own MyNHibernateContrib project. 

However, after running into several unforeseen issues, I've been forced to revert all my code back to MyNHibernateContrib for my largest projects.  The code may look a little uglier, but it works quite well.  Here are the issues I've had in trying Ayende's library.  He already knows about some of these and warns about them on this blog, but the extent to which they cause problems were greater than I anticipated based on his web page.
  1. As yet unpersisted entities added to a lazy and not yet loaded collection do not show up in the collection at all.  And cascade save from the collection to the entity never happens!  So my tens of thousands of lines of code written to take advantage of cascade save has bugs ridden through it suddenly, as entities are not saving all over the place.
  2. When I try to solve the the previous problem by disabling his extra lazy-loading feature for collections, I get lazy-loading exceptions thrown from NHibernate that I just haven't been able to solve.
  3. List collection persistence seems shaky (indexes within the list randomly changed).
  4. Map collection (Dictionary) persistence is non-existent.
  5. NHibernate collection cache doesn't match added contents, when an entity is added to an unloaded collection with something like apple.Tree = someTree;  someTree.Apples, when loaded in some later ISession, does not include the apple, even when reloaded.  The NH cache must be refreshed for it to ever show up.
I hope Ayende and/or the NHibernate team can fix these issues so I can get back to pretty code.  I applaud Ayende's work, and I am still using his library on my smaller projects. Keep up the good work, Ayende!  And if anyone (including myself) has some time to help with these bugs or implementation holes, by all means, please contribute to his project.

How to crash IE with just 4 levels of recursion in CSS and JavaScript

I can crash your Internet Explorer 6.0 (with all the latest patches) anytime you visit my page, with no warning at all.  Fun stuff.  I'm explaining how to do it here, not so that you can go crashing other people's pages, but so that I have a URL reference to give Microsoft for a bug report, and so that others who run into this bug accidentally (as I did) may have a page to find that explains exactly what is going wrong and how they can workaround it.  This isn't a security hole, as far as I can tell, so I don't think it's unsafe for me to post this.
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
    <head>
        <link rel="stylesheet" type="text/css" href="1deep.css" />
        <script type="text/javascript" src="css_bug.js">script>
    head>
    <body>
        The CSS and JavaScript will soon crash this page!<br/>
        If you see a scripting warning in IE, let script run.  This 
        warning does NOT come up when this web page is accessed over 
        the Internet (just over your hard drive, ironically!)<br/>
        <input type="button" onclick="alert('CSS depth: ' + discoverCssDepth())" 
            value="Click me to calculate CSS depth (and crash)"/><br/>
        Sometimes IE won't crash immediately after clicking the button.
        You may have to click around, or even close IE, for the crash to occur.
    body>
html>
1deep.css
@import url(2deep.css);
2deep.css
@import url(3deep.css);
3deep.css
@import url(4deep.css);
4deep.css

css_bug.js
function discoverCssDepth(sheet) {
    var maxDepth = 0;
    if( !sheet ) { // if this is the outermost call
        var depth = 0;
        for( var i = 0; i < document.styleSheets.length; i++ ) {
            depth = discoverCssDepth( document.styleSheets[i] );
            if (depth > maxDepth) maxDepth = depth;
        }
        return maxDepth;
    } else {
//        alert('looking at ' + sheet.href);
        maxDepth = 0;
        var depth = 0;
        // Enumerate over subsheets, if any
        if( sheet.imports ) {
//            alert('Has ' + sheet.imports.length + ' sub css files.');
            for( var i = 0; i < sheet.imports.length; i++ ) {
                depth = discoverCssDepth( sheet.imports[i] );
                if (depth > maxDepth) maxDepth = depth;
            }
        } else if (sheet.cssRules) { // Mozilla style
            for( var i = 0; i < sheet.cssRules.length; i++ ) {
                if (sheet.cssRules[i].styleSheet) {
                    depth = discoverCssDepth( sheet.cssRules[i].styleSheet );
                    if (depth > maxDepth) maxDepth = depth;
                }
            }
        }
        return 1+maxDepth;
    }
}
Note that if you try this on your local hard drive, you'll see IE give an Active Scripting warning that you must approve before the code will run and the browser will crash. But over the Internet, the warning does not show up, and the code just executes!

Tuesday, February 07, 2006

Adobe SVG Viewer in .NET WinForms

I was working on a C# WinForms project where I had to embed the Adobe SVG Viewer in my form.  The only distribution available comes in the form of an ActiveX control.  The control becomes useful when you set its SRC property to some .svg file to render.  Unfortunately this must happen at design-time or else a ActiveXStateException is thrown:
System.AccessViolationException was unhandled
  Message="Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
  Source="Interop.SVGACTIVEXLib"
  StackTrace:
       at SVGACTIVEXLib.ISVGControl.set_SRC(String pVal)
       at AxSVGACTIVEXLib.AxSVGCtl.set_SRC(String value)
The control also offers setSrc and getSrc methods, but these fail with the same exception.  More inspection led to the discovery that the control's SRC attribute can only be set after being freshly dropped onto the design surface.  What to do? (answer follows...)

In my case, I wanted to generate SVG files at run-time using temporary file storage space, which meant the filename would not be predictable at design time.  A lot of Googling turned up an Adobe support article revealed that only an ActiveX host that implements IHtmlDocument2 can set the SRC attribute for security reasons, starting at their SVG 3.03 version. 

Well, seeing as I didn't want to rewrite a web browser, I decided to re-use the .NET component for IE.  I would wrap the SVG Viewer ActiveX control into a web browser control, inside a User Control that would hide the ugly workaround.  It turns out that very little code was required.  The result is that my SVG Viewer can effectively display whatever SVG I want at run-time, with no apparent side effects.

I include the code below, licensed with X11:
SVGViewer.cs
/// 
/// A user control that wraps up Adobe's SVG Viewer in an
/// IE web browser to allow for changing the source file
/// at run-time.
///

///
/// This is necessary due to:
/// http://support.adobe.com/devsup/devsup.nsf/docs/54114.htm
/// Also references:
/// http://www.csharphelp.com/archives/archive146.html
/// http://ryanfarley.com/blog/archive/2004/12/23/1330.aspx
/// http://www.adobe.com/svg/viewer/install/svgtest.html
///
public partial class SvgViewer : UserControl
{
public SvgViewer()
{
InitializeComponent();
}

const string svgHtml = "<html><head><style>body {{ margin: 0; padding: 0; }} </style><embed src='{0}' width='100%' height='100%' type='text/html; charset=UTF-8' />";

private string source;
[Bindable(true)]
[Category("Appearance")]
[Description("The SVG file to display.")]
public string Source
{
get { return source; }
set
{
source = value;
UpdateHtml();
}
}

protected virtual void UpdateHtml()
{
if (webBrowser1.Document.Body != null)
webBrowser1.Document.OpenNew(true);
webBrowser1.Visible = !string.IsNullOrEmpty(source);
if (string.IsNullOrEmpty(source)) return;
webBrowser1.Document.Write(string.Format(svgHtml, source));
}
}
SVGViewer.Designer.cs
namespace Phylogenetics
{
partial class SvgViewer
{
///
/// Required designer variable.
///

private System.ComponentModel.IContainer components = null;

///
/// Clean up any resources being used.
///

/// <param name="disposing" />true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Component Designer generated code

///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///

private void InitializeComponent()
{
this.webBrowser1 = new System.Windows.Forms.WebBrowser();
this.SuspendLayout();
//
// webBrowser1
//
this.webBrowser1.AllowNavigation = false;
this.webBrowser1.AllowWebBrowserDrop = false;
this.webBrowser1.Dock = System.Windows.Forms.DockStyle.Fill;
this.webBrowser1.IsWebBrowserContextMenuEnabled = false;
this.webBrowser1.Location = new System.Drawing.Point(0, 0);
this.webBrowser1.MinimumSize = new System.Drawing.Size(20, 20);
this.webBrowser1.Name = "webBrowser1";
this.webBrowser1.ScriptErrorsSuppressed = true;
this.webBrowser1.ScrollBarsEnabled = false;
this.webBrowser1.Size = new System.Drawing.Size(150, 150);
this.webBrowser1.TabIndex = 0;
this.webBrowser1.Url = new System.Uri("about:blank", System.UriKind.Absolute);
this.webBrowser1.WebBrowserShortcutsEnabled = false;
//
// SvgViewer
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.webBrowser1);
this.Name = "SvgViewer";
this.ResumeLayout(false);

}

#endregion

private System.Windows.Forms.WebBrowser webBrowser1;

}
}