WebBrowserコントロールでスクリプトエラーの警告を抑制する

WinFormで簡易ブラウザ機能を持つアプリやPDF、Excelビューアを作ろうとした場合、一番簡単なのはSystem.Windows.Forms.WebBrowserコントロールを使う方法ですが、普通にWebBrowserコントロールを張り付けただけでは、

  • 特定のサイトにアクセスしたときにスクリプトエラーの警告が表示される

という問題が発生します(livedoorニュースの各記事にアクセスすると顕著)。
根本的には「そのサイトのJavascriptの書き方が間違っている」(未定義変数とか)なのですが、一般的なブラウザではエラーが表示されないので、

  • お前の作ったアプリが悪い!バグだ!

という濡れ衣を着せられることも。

仕方がないのでスクリプトエラーの警告を抑制するための方法を模索するわけですが、これについては、Microsoftが当初言ってた(というか今でも言ってる)「対策」と称するものがことごとくハズレなので、さらにタチが悪い。

  1. IEのインターネットオプション>詳細設定>スクリプトデバッグを使用しない(その他)をチェックする→ハズレ
  2. WebBrowser.OnDocumentCompletedイベントをフックしDocument.Window.Errorハンドラに何もしないメソッドを登録する→ハズレ
  3. WebBrowser.ScriptErrorsSuppressedプロパティをtrueにする→ハズレ:エラーは出なくなるが、副作用としてBasic認証のWEBサイトにアクセスできなくなる
  4. IOleCommandTargetインターフェースを実装し、スクリプトエラー時にIOleCommandTarget->ExecメソッドでS_OKを返す→これが正解

ということで、最後の方法が紹介されている.Net WebBrowser and ScriptErrorsSuppressedをベースに、プロパティの変更&ユーザーコントロール化したものを以下に掲載します。
WebBrowserコントロールをこのExtendedWebBrowserユーザーコントロールに張り替えるだけでOKです。

  • ExtendedWebBrowser.ScriptErrorsSuppressedプロパティ:正しくスクリプトエラーのみを抑制する
  • ExtendedWebBrowser.Silentプロパティ:もとのScriptErrorsSuppressedプロパティ=IWebBrowser2.Silentに対応

[ExtendedWebBrowser.cs]

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

///
/// Extended WebBrowser Control to suppress script-errors correctly
/// Refer to http://osexpert.blog.com/2011/09/14/net-webbrowser-and-scripterrorssuppressed/
/// 
namespace WebBrowserApp
{
    ///
    /// Based on http://support.microsoft.com/kb/261003
    ///
    public partial class ExtendedWebBrowser : WebBrowser
    {
        protected class ExtendedWebBrowserSite : WebBrowserSite, IOleCommandTarget
        {
            const int OLECMDID_SHOWSCRIPTERROR = 40;

            static Guid CGID_DocHostCommandHandler = new Guid("F38BC242-B950-11D1-8918-00C04FC2C836");

            const int S_OK = 0;
            const int OLECMDERR_E_NOTSUPPORTED = -2147221248;

            private ExtendedWebBrowser parent = null;

            public ExtendedWebBrowserSite(ExtendedWebBrowser wb) : base(wb)
            {
                parent = wb;
            }

            #region IOleCommandTarget Members
            public int QueryStatus(Guid pguidCmdGroup, int cCmds, IntPtr prgCmds, IntPtr pCmdText)
            {
                return OLECMDERR_E_NOTSUPPORTED;
            }

            public int Exec(Guid pguidCmdGroup, int nCmdID, int nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
            {
                if (parent._scriptErrorsSuppressed && pguidCmdGroup == CGID_DocHostCommandHandler)
                {
                    if (nCmdID == OLECMDID_SHOWSCRIPTERROR)
                    {
                        // do not need to alter pvaOut as the docs says, enough to return S_OK here
                        return S_OK;
                    }
                }

                return OLECMDERR_E_NOTSUPPORTED;
            }
            #endregion
        }

        #region ExtenededWebBrowser Properties
        // override base.ScriptErrorsSuppressed property for bug-fix
        public bool _scriptErrorsSuppressed = false;
        public new bool ScriptErrorsSuppressed
        {
            get { return _scriptErrorsSuppressed; }
            set { _scriptErrorsSuppressed = value; }
        }

        // remapped base.ScriptErrorsSuppressed to this.Silent property(correspond to IWebBrowser2.Silent)
        public bool Silent
        {
            get { return base.ScriptErrorsSuppressed; }
            set { base.ScriptErrorsSuppressed = value; }
        }
        #endregion

        protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
        {
            return new ExtendedWebBrowserSite(this);
        }

        public ExtendedWebBrowser()
        {
            InitializeComponent();
        }

        #region Unfortunately, this famous workaround does not any effect on the script error...
        //protected override void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e)
        //{
        //    if (_scriptErrorsSuppressed)
        //    {
        //        base.Document.Window.Error += new HtmlElementErrorEventHandler(WindowError);
        //    }

        //    base.OnDocumentCompleted(e);
        //}

        //private void WindowError(object sender, HtmlElementErrorEventArgs e)
        //{
        //    // ignore this error to suppress the error dialog
        //    e.Handled = true;
        //}
        #endregion
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), ComVisible(true),
    Guid("B722BCCB-4E68-101B-A2BC-00AA00404770")]
    public interface IOleCommandTarget
    {
        [return: MarshalAs(UnmanagedType.I4)]
        [PreserveSig]
        int QueryStatus([In, MarshalAs(UnmanagedType.LPStruct)] Guid pguidCmdGroup,
          int cCmds,
          IntPtr prgCmds,
          IntPtr pCmdText);

        [return: MarshalAs(UnmanagedType.I4)]
        [PreserveSig]
        int Exec([In, MarshalAs(UnmanagedType.LPStruct)] Guid pguidCmdGroup,
          int nCmdID,
          int nCmdexecopt,
          IntPtr pvaIn,
          IntPtr pvaOut);
    }
}
|cs|<
[ExtendedWebBrowser.Designer.cs]
>||
namespace WebBrowserApp
{
    partial class ExtendedWebBrowser
    {
        /// <summary> 
        /// 必要なデザイナー変数です。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary> 
        /// 使用中のリソースをすべてクリーンアップします。
        /// </summary>
        /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region コンポーネント デザイナーで生成されたコード

        /// <summary> 
        /// デザイナー サポートに必要なメソッドです。このメソッドの内容を 
        /// コード エディターで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }

        #endregion
    }
}