IEのクローズ処理をハンドリングする

普通はそんなの難しいからあきらめてもらうのだが、どうしてもやりたいというので考えることにした。
まずは、Googleで調べてみるとそれなりにHITする。みんな苦労しているんだな。

onbeforeunload

最初にたどり着いたのは、onbeforeunload イベントを利用する方法。onbeforeunload は ページがアンロードされる前に発生するものなので、リンクを押したりしたりするだけでも発生する。なので、クローズ以外でも発生することが問題になり、これを対策する必要がある。がんばってみよう。

よく利用されている対策は、onbeforeunloadイベントが発生した際の event.clientYやevent.clientXを調べて0より小さい場合はCloseだと判断する方法である。しかし、この方法だと更新ボタンを押してもCloseだと判断してしまう問題がある。

これを対策する方法としては、先ほどのevent.clientXをもっと厳密に調べるアイデアがあるようで、それが以下に紹介されていた。IE6で確認したが、更新ボタンをうまく無視できた。すごい力技!!


End User Sessions When the Browser Closes With Remote Scripting


ただ、それでも「File->close」や「Alt-F4」には対応できないようだ。また、こんなことはしないだろうが、xボタンの付近でF5押しても誤った認識をしてしまう。

この方法はあきらめたほうがよいかな。

監視用Window

次に思いつくアイデアとしては、別のWindowから監視する方法だが、監視用のWindowをはじめからは上げられない場合は駄目かな。と思っていたが、先ほどのonbeforeunload のタイミングで監視用のWindowを上げる方法を利用した方法があった。


Detect browser closing through clicks on the X button


簡略化したスクリプトで試してみた。

my.html
<script language=javascript>
function launch_spyWin() 
{
   spyWin = open('spy.html','spyWin',
       'width=100,height=100,left=2000,top=0,status=0');
    spyWin.blur();
}
onunload = launch_spyWin;
</script>
spy.html
<script language='javascript'>
 function check_opener() { 
	if (opener && opener.closed){ 
		alert("IEが閉じられます");
		parent.opener='';
		parent.close();
	} else{
		parent.opener='';
		parent.close(); 
	}
	
}
onload = function() { 
	self.blur();
	setTimeout('check_opener()',0);
}
</script>

上手くいくようだ。GOOD、GOOD!!
codeprojectでの評価は3ちょっとと高くないけど素晴らしい。

ActiveX

とりあえず目標達成したのだが、もっと直接的な方法で本当にIEのCloseを取ることができないかな。
JavaScriptでは無理でもActiveXを利用したらできるかな。
調べてみると、どうやらブラウザ(IE)コンポーネントのOnQuitをハンドルすればいいようだ。


ということで、ActiveXからブラウザ(IE)コンポーネントを取得すれば方法を調査。


ActiveX コントロールから トップレベル IWebBrowser2 インターフェイスを取得する方法
#自動翻訳の割にはまともなタイトルだ


VC++か、.NETでのサンプルはないかなと調べてみるとこんなのがあった。


Re: WinForms control in Internet Explorer の#10のMSFTのリプライ


サンプルをもとに実際に作ってみる。確認だけなので、よく理解しないしていない部分はあるがとりあえず作ってみたら、動いた。ヤッタ!!

using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security;
using System.Windows.Forms;
using SHDocVw; //c:\WINDOWS\system32\shdocvw.dll


[Guid("76DC5F81-459D-4fb7-A9B5-8D183CD0EB0F")]
public class WatchQuit : System.Windows.Forms.Control
{
    private Guid SID_STopLevelBrowser = 
        new Guid(0x4C96BE40, 0x915C, 0x11CF, 0x99, 0xD3, 0x00, 0xAA, 0x00, 0x4A, 0xE8, 0x37);
    private Guid SID_SWebBrowserApp = typeof(SHDocVw.IWebBrowserApp).GUID;

    public WatchQuit()
    {
        this.HandleCreated += new EventHandler(WatchQuit_Created);
    }

    void WatchQuit_Created(object sender, EventArgs e)
    {
        try
        {
            Guid guidIServiceProvider = typeof(IServiceProvider).GUID;
            Guid guidIWebBrowser2 = typeof(SHDocVw.IWebBrowser2).GUID;
            object objIServiceProvider2;
            object objIWebBrowser2;

            Type typeIOleObject = this.GetType().GetInterface("IOleObject", true);

            //call the method on that interface
            object oleClientSite = typeIOleObject.InvokeMember("GetClientSite",
                BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public,
                 null, this, null);

            IServiceProvider serviceProvider = oleClientSite as IServiceProvider;
            serviceProvider.QueryService(ref SID_STopLevelBrowser, ref guidIServiceProvider, 
                out objIServiceProvider2);

            serviceProvider = objIServiceProvider2 as IServiceProvider;
            serviceProvider.QueryService(ref SID_SWebBrowserApp, ref guidIWebBrowser2, 
                out objIWebBrowser2);

            IWebBrowser2 webBrowser = objIWebBrowser2 as IWebBrowser2;
            InternetExplorer ie = (InternetExplorer)webBrowser;
            ie.OnQuit += new DWebBrowserEvents2_OnQuitEventHandler(ie_OnQuit);
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.ToString());
        }
    }

    void ie_OnQuit()
    {
        MessageBox.Show("IEを終了します");
    }
}

[ComImport, Guid("6d5140c1-7436-11ce-8034-00aa006009fa"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IServiceProvider
{
    void QueryService(ref Guid guidService, ref Guid riid,
    [MarshalAs(UnmanagedType.Interface)] out object ppvObject);
}

あとは以下のようなObjectタグでActiveXをHTMLに張り付ければOK。

<object id="WatchQuit" classid="CLSID:76DC5F81-459D-4fb7-A9B5-8D183CD0EB0F"></object>

結論

IEのクローズ処理は取れないことはない。でもやっぱりトリッキーなので、まずはあきらめてもらうほうがいいのだろうな。

追記

残念ながらIE7では試していない。