SSRS Report Viewer控制浏览器兼容性

Otp*_*tpi 12 reporting-services

MS Report Viewer Control与Firefox和Safari等浏览器的兼容性如何?

如果您知道SSRS的任何第三方报告查看者,也请发布.

Ste*_*ger 27

编辑 - 2016/2017更新

SSRS 2016+可以说是跨浏览器兼容的.
表格边框在IE10中没有正确显示,但无论如何IE10正在消失.
在其他浏览器中,它没问题.

您仍需要注册add_pageLoaded才能翻译参数提示.

Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function () { fixReportingServices('rpt-container'); });
Run Code Online (Sandbox Code Playgroud)

此外,像在版本<2016年,你需要添加PageCountMode="Actual"RS:ReportViewerHost控制.

<RS:ReportViewerHost ID="ReportViewerControl" PageCountMode="Actual" runat="server" />
Run Code Online (Sandbox Code Playgroud)

如果你想拥有有意义的分页号码.

此外,当您使用datepickers在查询字符串中设置语言时也存在问题(SSRS <2016也是如此).context.Request.UserLanguages[i]如果希望datepicker使用非浏览器语言,则需要在HTTP模块中的context.BeginRequest中设置查询字符串中指定的语言.

此外,如果要安全地在Internet中的iframe中使用SSRS,则需要在每个HTTP响应中添加X-Frame-Options和Content-Security-Policy HTTP-headers.有关详细信息,请参阅我的github-repository.

</EndEdit>
Run Code Online (Sandbox Code Playgroud)

预-2016:

我可以告诉你,我有9年的该死的经验(SSRS 2005,SSRS 2008 R1&R2,2012和SSRS 2014).

让我向你保证,因为SSRS HTML报告依赖于IE5-Quirksmode,所以他们没有机会在Internet Explorer以外的任何浏览器中正确呈现(IE <10我可能倾向于添加).

如果你有对报告服务器的管理员访问权限,并且我强调IF,你可以在ReportViewer页面上添加jQuery例程和CSS来纠正最基本的问题(比如你看不到超过1厘米的报告),否则,HTML渲染仍然看起来非常可怕(边距,表格边框,图片填充,列大小,文本字段大小,标题和/或页面对齐等,简而言之 - 几乎任何事情)

当然,ActiveX打印机控件只能在InternetExplorer(在Windows上)上工作,因为只支持ActiveX.

让我也向您保证,绝对没有办法让ReportManager在IE以外的任何其他浏览器中工作.

至于Internet Explorer支持,应该提到的是,从IE版本10开始,IE默认为webkit-quirksmode(出于兼容性原因)而不是IE5 quirksmode,因此呈现的HTML看起来与Chrome/Firefox/Safari中的一样糟糕对于IE 10+,除非您在ReportViewer.aspx页面中添加了与meta xua兼容的IE5.

后者可能不适用于Intranet,因为IE在Intranet站点上时会自动回退到IE 7兼容模式(但仅限于任何Windows版本<8).

在Windows 8中,localhost链接和计算机名链接(无法在stackoverflow帖子中添加http://)未分配给本地Intranet区域(可能是快速入侵,因此IE不会回退到IE7兼容模式),并且因此,Windows身份验证将失败.

还应该提到的是,不可能为ReportManager添加与meta xua兼容的IE5,因为没有可以编辑的页面(已编译的不可更新的ASP.NET网站项目......).
如果没有使用dev-tools在quirksmode中切换reportmanager,那么在IE 10+中就不可能使用ReportManager.

然后,另外要提到的是,从IE9开始,IE从父页面继承了iframe元素的doctype,并且也没有办法改变它(不将父页面更改为所需的子页面doc -模式).

因此,如果你足够聪明地使用iframe作为报告,因为在弹出窗口阻止程序时代任何理智的人都不想要弹出窗口,报告页面将继承父页面的doctype.如果这不是IE5-QuirksMode,它将使报告与Safari/Chrome/Firefox/Opera上的任何IE> 8一样可怕,而不管ReportViewer.aspx中任何可能的与meta xua兼容的ie5标记.

至于替代方案,免费且具有类似的功能集,只有Eclipse BIRT(幸运的是,HTML呈现并不相似).

应该提到的是,BIRT虽然是根据Eclipse公共许可证授权的,但它使用的是SUN Java,因此您使用的是Oracle技术,该技术受其各自的许可条款约束.
此外,您使用的是非Microsoft技术,Excel-Sheets BIRT渲染实际上是XML文件,在Office 2010+中打开时会产生(谁会猜到)相当惊人的警告.

然后,收取相当大的费用,有http://www.stimulsoft.com报告,Telerik报道.
从它的技术外观来看,我建议使用stimulsoft报告,但是:免责声明,我没有使用其中任何一种,因为它们不是免费的.

至于替代的SSRS渲染引擎(不是观众btw),只有www.fyireporting.com/ ?,这里有一个分支http://www.codeproject.com/Articles/138271/An-Open-Source-RDL-发动机.

但是fyiReporting是不完整的并且正在进行中(如果没有放弃),所以我不会使用它.


最重要的是,如果你的产品只是IE和windows,windows版本<8(并且没有 - 不是<=以防你没有注意),在内部网中,从弹出窗口而不是iframe调用,然后去与SSRS.
对于其他任何事情,请使用stimulsoft报告(问题是,您必须重做所有报告,因为没有自动迁移 - 甚至不是非工作迁移).

PS:
And by adding "a little javascript", i mean the below thing(2008 R1 Edition, won't work on 2008R2+, may work on 2005, and note that you need to embed jQuery and jQuery Migrate in an inline-script tag, if the client PC's don't have access to http(s)://ajax.aspnetcdn.com [note: putting the scripts into the Pages folder and adding a relative or absolute link won't work...]):

<%@ Register TagPrefix="RS" Namespace="Microsoft.ReportingServices.WebServer" Assembly="ReportingServicesWebServer" %>
<%@ Page Language="C#" AutoEventWireup="true" Inherits="Microsoft.ReportingServices.WebServer.ReportViewerPage" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>

<%= System.Web.HttpContext.Current.Request.Browser.Browser == "IE" && System.Globalization.CultureInfo.InvariantCulture.CompareInfo.IndexOf(System.Convert.ToString(System.Web.HttpContext.Current.Request.QueryString), "stylesheet", System.Globalization.CompareOptions.IgnoreCase) == -1 ? (System.Web.HttpContext.Current.Request.Browser.Browser != "IE" ? "": "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=5\">") : "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">" %>

    <head id="headID" runat="server">
    <title>Report Viewer</title>




    <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jquery.migrate/jquery-migrate-1.1.0.min.js"></script>



    <script type="text/javascript">

        var bInFrameOrIframe = false;
        var iLanguageIndex = 0;
        var language = "DE_LOL";
        var UpdateLock = false;



        if (window.self === window.top) {
            // alert("true");
            // not in a frame 
            // Use IE5 quirksmode 
            //document.writeln('<meta http-equiv="X-UA-Compatible" content="IE=5" />');
            bInFrameOrIframe = false
        }
        else {
            // in a frame, FMS
            bInFrameOrIframe = true;
        }


        function TranslateParameterPrompts() {

            //mTo = false;
            $("table[id^='ParametersGridReportViewerControl'] span").each(function(index) {
                var strText = $(this).text();
                if (strText != null && strText.indexOf('/') != -1) {
                    strText = strText.split('/');
                    if (iLanguageIndex < strText.length)
                        strText = strText[iLanguageIndex];
                    else {
                        if (strText.length > 0)
                            strText = strText[0];
                    }

                    $(this).text(strText);
                }

            });

            //setTimeout(function(){mTo = true}, 5000);
        }


        function setTableSize() {
            //$("[id$='ReportViewerControl']")
            $(
                $(
                    $("#ReportFrameReportViewerControl")[0].contentWindow.document
                )
                .find("[id$='report']")[0].contentWindow.document.body
            ).find('*')
            .each(function() {
                //console.log("Processing an element");
            //var cls = $(this).attr("class");

            try {

                    // Don't add a border to sort-arrow
                    if ($(this).is('img')) {
                        return;
                    }


                    var anywidth = $(this).css('width');
                    var anywidth = parseFloat(anywidth);
                    //console.log("anywidth: " + anywidth);


                    //var lol = $(this).css('borderLeftWidth');
                    var blw = $(this).css('border-left-width');
                    var brw = $(this).css('border-right-width');
                    var btw = $(this).css('border-top-width');
                    var bbw = $(this).css('border-bottom-width');

                    var borls = $(this).css('border-left-style') == "solid";
                    var borrs = $(this).css('border-right-style') == "solid";
                    var borts = $(this).css('border-top-style') == "solid";
                    var borbs = $(this).css('border-bottom-style') == "solid";



                    var blw = parseFloat(blw);
                    var brw = parseFloat(brw);
                    var btw = parseFloat(btw);
                    var bbw = parseFloat(bbw);

                    //parseInt($(this).css("borderRightWidth"))
                    //console.log(parseInt($(this).css("borderLeftWidth")));

                    UpdateLock = true;


                    // Set width to 1px where 0px
                    if (anywidth == 0)
                        $(this).css('width', '1px');


                    if (borls && blw == 0.0 || (blw > 0.0 && blw < 1.0)) {
                        //console.log("setting border width");
                        $(this).css('border-left-width', '1px');
                    }

                    if (borrs && brw == 0.0 || (brw > 0.0 && brw < 1.0)) {
                        $(this).css('border-right-width', '1px');
                    }

                    if (borts && btw == 0.0 || (btw > 0.0 && btw < 1.0)) {
                        $(this).css('border-top-width', '1px');
                    }

                    if (borbs && bbw == 0.0 || (bbw > 0.0 && bbw < 1.0)) {
                        $(this).css('border-bottom-width', '1px');
                    }

                    UpdateLock = false;
                }
                catch (ex) {
                    UpdateLock = false;
                    //console.log(ex);
                }

            });             // End $('*').each

            // console.log("loop");




            var $img = $("img[onload^='this.fitproportional=true']");
            if ($img == null) {
                // console.log("img is null");
                return;
            }
            var $div = $img.parent();
            if ($div == null) {
                // console.log("div is null");
                return;
            }

            UpdateLock = true;
            {
                $img.removeAttr("height");
                $img.css('max-width', '100%')
                $img.css('max-height', '100%')

                $div.css('text-align', 'right');

                var divMinWidth = parseFloat($div.css('min-width'));
                var divWidth = parseFloat($div.css('width'));

                var divMinHeight = parseFloat($div.css('min-height'));
                var divHeight = parseFloat($div.css('height'));

                // console.log("width: " + divWidth);
                // console.log("height: " + divHeight);
                // console.log("min-width: " + divMinWidth);
                // console.log("min-height: " + divMinHeight);

                if (divMinWidth != 0)
                    $div.css('width', $div.css('min-width'));

                if (divMinHeight != 0)
                    $div.css('height', $div.css('min-height'));

            }
            UpdateLock = false;

        }


        $(document).ready(function() {

            switch (language) {
                case "fr":
                    iLanguageIndex = 1;
                    break;
                case "it":
                    iLanguageIndex = 2;
                    break;
                case "en":
                    iLanguageIndex = 3;
                    break;
                default: // "DE" 
                    iLanguageIndex = 0;
            }

            TranslateParameterPrompts();
            // setInterval(function() { TranslateParameterPrompts() }, 100);


            if ($.browser.msie && !bInFrameOrIframe)
                return;

            // if ($.browser.webkit)
            //setTableSize();

            $("[id$='ReportFrameReportViewerControl']").load(function() {
                //setNewHeight();
                //alert("Loading");

                setTableSize();

                $(
                    $("[id$='ReportFrameReportViewerControl']")[0].contentWindow.document
                )
                .find("[id$='report']").load(function() {
                    //alert("load report");
                    setTableSize();
                }
                ); // End load #report

            }); // End Function load #ReportFrameReportViewerControl

        });    // End Function document.ready

    </script>

</head>
<body style="margin: 0px; overflow: auto">
    <form style="width:100%;height:100%" runat="server" ID="ReportViewerForm">
        <RS:ReportViewerHost ID="ReportViewerControl" runat="server" />
    </form>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

Note: UpdateLock stems from 2012 backport and is not really used here, and language stems from backport too, setInterval on TranslateParameterPrompts is necessary in 2012 and function for 2012 has only the little addition of "label" in the jQuery selector...

    function TranslateParameterPrompts() {

        //mTo = false;
        $("table[id^='ParametersGridReportViewerControl'] label span").each(function(index) {
            var strText = $(this).text();
            if (strText != null && strText.indexOf('/') != -1) {
                strText = strText.split('/');
                if (iLanguageIndex < strText.length)
                    strText = strText[iLanguageIndex];
                else 
                { 
                    if(strText.length > 0)
                        strText = strText[0];
                }

                $(this).text(strText);
            }

        });

        //setTimeout(function(){mTo = true}, 5000);
    }
Run Code Online (Sandbox Code Playgroud)

Infering the language from the browser language goes like this, btw:

<script type="text/javascript">
    language = <%= System.Web.HttpContext.Current.Request.UserLanguages != null ? "\"" + System.Convert.ToString(System.Web.HttpContext.Current.Request.UserLanguages[0]) + "\"" : "null" %>;

    if(language == null)
        language = window.navigator.userLanguage || window.navigator.language;

    if(language != null)
        language = language.substr(0,2).toLowerCase();

</script>
Run Code Online (Sandbox Code Playgroud)

And in 2012 you need to Dispose of the session cookies, otherwise you get "HTTP 400: Header too long" after opening about 120 reports:

<script type="text/C#" runat="server">
protected string ClearSessionKeepAliveCookiesToPreventHttp400HeaderTooLong()
{
    if(Request == null || Request.Cookies == null)
        return "";

    if(Request.Cookies.Count < 60)
        return "";

    // System.Web.HttpContext.Current.Response.Write("<h1>"+Request.Cookies.Count.ToString()+"</h1>");
    for(int i = 0; i < System.Web.HttpContext.Current.Request.Cookies.Count; ++i)
    {
        if(StringComparer.OrdinalIgnoreCase.Equals(Request.Cookies[i].Name, System.Web.Security.FormsAuthentication.FormsCookieName))
            continue;

        if(!Request.Cookies[i].Name.EndsWith("_SKA", System.StringComparison.OrdinalIgnoreCase))
            continue;

        if(i > 60)
            break;

        //System.Web.HttpContext.Current.Response.Write("<h1>"+Request.Cookies[i].Name+"</h1>");

        System.Web.HttpCookie c = new System.Web.HttpCookie( Request.Cookies[i].Name );
        //c.Expires = System.DateTime.Now.AddDays( -1 );
        c.Expires = new System.DateTime(1970, 1 ,1);
        c.Path = Request.ApplicationPath + "/Pages";
        c.Secure = false;
        c.HttpOnly = true;

        // http://stackoverflow.com/questions/5517273/httpcookiecollection-add-vs-httpcookiecollection-set-does-the-request-cookies
        //Response.Cookies[Request.Cookies[i].Name] = c;
        //Response.Cookies.Add(c);
        Response.Cookies.Set(c);
    }

    return "";
}


</script>
Run Code Online (Sandbox Code Playgroud)

And on 2012, you need to listen to content update, because it uses Microsoft Ajax controls

function cbOnContentUpdate() {
    //console.log("content update");
    alterTableBorderWidth();
    // TranslateParameterPrompts();
} // End Callback cbOnContentUpdate


var hLastTimeout = null;

function queueUpdate() {

    if (UpdateLock)
        return;


    if (hLastTimeout != null)
        clearTimeout(hLastTimeout);

    hLastTimeout = window.setTimeout(function() { cbOnContentUpdate(); }, 50);

    //window.setTimeout(function() { cbOnContentUpdate(); }, 3000);
} // End Function queueUpdate


$(document).ready(function() {

    switch (language) {
        case "fr":
            iLanguageIndex = 1;
            break;
        case "it":
            iLanguageIndex = 2;
            break;
        case "en":
            iLanguageIndex = 3;
            break;
        default: // "DE" 
            iLanguageIndex = 0;
    }

    setInterval(function() { TranslateParameterPrompts() }, 100);

    // opt-out for non-framed IE, because that crook supports IE5-Quirks (framed takes parent-doctype in IE >= 9)
    if ($.browser.msie && !areWeInFrame())
        return;


    // if ($.browser.webkit)

    // console.log('Setting event listener!');

    // http://stackoverflow.com/questions/4979738/fire-jquery-event-on-div-change
    //$("[id$='ReportViewerControl']").bind('DOMNodeInserted DOMNodeRemoved', function(event) {
    //$("[id$='ReportArea']")
    $("body")
     .bind('DOMNodeInserted DOMNodeRemoved DOMSubtreeModified', function(event) {

         if (event.type == 'DOMNodeInserted') {
             //console.log('Content added! Current content:' + '\n\n' + this.innerHTML);
             //console.log('Content added!');
             queueUpdate();
         }
         else {
             //console.log('Content removed! Current content:' + '\n\n' + this.innerHTML);
             //console.log('Content removed!');
             queueUpdate();
         }
     }); // End Bind IRM

});   // End Function $(document).ready
Run Code Online (Sandbox Code Playgroud)

And note that for 2012, setTableSize is alterTableBorderWidth, and the selector goes like this:

 function alterTableBorderWidth() 
        {
            //$('*')
            $("[id$='ReportViewerControl']").find('*')
            .each(function() {
Run Code Online (Sandbox Code Playgroud)

Btw, if you want to remove the PRINT icon, because it's only a source of sorrow, that goes like this (for German, English, French and Italian [the languages spoken in Switzerland + English]).

This CSS is for 2008 R1, don't know and care if it's the same in 2012.

input[type="image"][title="Drucken"], input[type="image"][title="Print"], input[type="image"][title="Imprimer"], input[type="image"][title="Stampa"]
{
    display: none !important;
}
Run Code Online (Sandbox Code Playgroud)

And if that still doesn't deter you, then go right ahead and use forms authentication in SSRS 2012 (hint 1: there is no ms-provied sample as for 2005-2008R2, you need to write it yourself) ;)

Hint2: if you followed hint1, forms authentication redirect doesn't work when you have a colon in the URL [bug?], you can steal the code from mono and write your own redirect.

Hint3: if you followed hint2 and got forms authentication working, then you probably want to change the upload tool you're going to write, because you won't like to do manually "for each report browse-directory - check allow override checkbox - upload report - set datasource) for 120 reports or so, then you will be inheriting a class from ReportingService2005 and override GetWebRequest and GetWebResponse

''' <summary>
''' Overriding the method defined in the base class.
''' </summary>
''' <param name="uri"></param>
''' <returns></returns>
Protected Overrides Function GetWebRequest(uri As Uri) As System.Net.WebRequest
    Dim request As System.Net.HttpWebRequest
    request = DirectCast(System.Net.HttpWebRequest.Create(uri), System.Net.HttpWebRequest)
    request.Credentials = MyBase.Credentials
    request.CookieContainer = New System.Net.CookieContainer()

    If m_authCookie IsNot Nothing Then
        request.CookieContainer.Add(m_authCookie)
    End If

    Return request
End Function ' GetWebRequest 



''' <summary>
''' Overriding the method defined in the base class.
''' </summary>
''' <param name="request"></param>
''' <returns></returns>
Protected Overrides Function GetWebResponse(request As System.Net.WebRequest) As System.Net.WebResponse
    Dim response As System.Net.WebResponse = MyBase.GetWebResponse(request)

    ' http://social.msdn.microsoft.com/Forums/sqlserver/en-US/f68c3f2f-c498-4566-8ba4-ffd5070b8f7f/problem-with-ssrs-forms-authentication
    Dim cookieName As String = response.Headers("RSAuthenticationHeader")
    If cookieName IsNot Nothing Then
        m_authCookieName = cookieName
        Dim webResponse As System.Net.HttpWebResponse = DirectCast(response, System.Net.HttpWebResponse)
        Dim authCookie As System.Net.Cookie = webResponse.Cookies(cookieName)

        ' Save it for future reference and use.
        m_authCookie = authCookie
    End If

    Return response
End Function ' GetWebResponse 
Run Code Online (Sandbox Code Playgroud)

Hint 4: The above methods won't work if you haven't installed service pack 1 + 2 on SSRS 2008 R2...

Hint 5: The TranslateParameters method is there because you cannot (by design) translate the parameter prompts to multiple languages by SSRS, so you write:

Raum / Local / Locale / Room 
Run Code Online (Sandbox Code Playgroud)

which uses schema:

Text_DE / Text_FR / Text_IT / Text_EN
Run Code Online (Sandbox Code Playgroud)

And then do split by '/' on the text (which is a little error prone if your prompt text contains '/' somewhere), and then select the right text by index of the language, by choosing MIN(splitarray.length, index) btw, just in case somewhere you only have Text_DE/Text_FR or whatever, first checking if the text contains a '/' at all, of course.

Hint 6: Need I go on ? (i could still expand this post to tenfold size, but I think I should go back to work now :) )

If you want to see just how f*up it looks, this is how vanilla ssrs renders in chrome

Vanilla SSRS chrome

This is what it looks like in IE11, with IE5 compatibility set via xua

Geb

And this is what it should be, and what you can get it to render with much tinkering with javascript and CSS SSRS in Chrome with CSS and JS applied

Addendum:
Oh, and it gets even better

If you want to run reportviewer in a language specified by your application (which is NOT identical to the browser language), you need to set the culture of reportviewer to the culture specified in your query string...

<script type="text/C#" runat="server">
    protected override void InitializeCulture()
    {
        string sprache = System.Web.HttpContext.Current.Request.QueryString["in_sprache"];

        // System.Web.HttpContext.Current.Response.Write(sprache);

        switch(System.Globalization.CultureInfo.InvariantCulture.TextInfo.ToLower(sprache))
        {
            case "fr":
                sprache = "fr-CH";
                break;
            case "it":
                sprache = "it-CH";
                break;
            case "en":
                sprache = "en-US";
                break;
            default:
                sprache = "de-CH";
                break;
        }

        // System.Web.HttpContext.Current.Response.Write(sprache);

        System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(sprache);
        System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(sprache);
        base.InitializeCulture();
    }



</script>
Run Code Online (Sandbox Code Playgroud)

If you want to set a static culture, you can do so in the page declaration

<%@ Page UICulture="de" Culture="de-CH" %>
Run Code Online (Sandbox Code Playgroud)

And if you try to remove the the print icon and the atom-feed, doing it with styles, no matter how, it only works in Chrome...

    input[type="image"][title="Drucken"], input[type="image"][title="Print"],    input[type="image"][title="Imprimer"], input[type="image"][title="Stampa"]
    {
        display: none !important;
    }


    input[type="image"][title="In Datenfeed exportieren"], input[type="image"][title="Exporter vers un flux de données"], input[type="image"][title="Esporta in feed di dati"], input[type="image"][title="Export to Data Feed"]
    {
        display: none !important;
    }
    */
    input[type="image"][src$="Microsoft.Reporting.WebForms.Icons.Print.gif"] {
        display: none !important;
    }

    input[type="image"][src$="Microsoft.Reporting.WebForms.Icons.AtomDataFeed.gif"] {
        display: none !important;
    }
Run Code Online (Sandbox Code Playgroud)

But you can actually remove at least the print icon in internet-explorer by adding ShowPrintButton="false" to the reportviewer control:

<RS:ReportViewerHost ID="ReportViewerControl" ShowPrintButton="false" runat="server" />
Run Code Online (Sandbox Code Playgroud)

Another idea: in SSRS 2012 & 2014 (and possibly 2008R2), you can also add your SSRS-fixing javaScript method into add_pageLoaded of ScriptManager. Like this:

Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function () { fixReportingServices('rpt-container'); });
Run Code Online (Sandbox Code Playgroud)

Then you don't have to listen to page change events, which speeds things up, because loaded is only called once at the end of each update.

WARNING

Because in the Internet, IE doesn't default to compatibility view,

<meta http-equiv="X-UA-Compatible" content="IE=5">
Run Code Online (Sandbox Code Playgroud)

won't help you with certain issues either (rotate270 text/vertical text).

Even in IE, it only works properly when you are in the intranet, because only then it uses "compatibility view". While the meta-tag sets the brower into IE5-Quirksmode, it doesn't enable "compatibility view", so it isn't the true quirksmode...


The*_*TXI 0

我没有注意到它在 Firefox 或 Safari 中显示有任何实际问题。有时我认为它可能在 Firefox 中渲染得更好,但我没有任何实际的基准来支持这一点。