I was trying to debug a web application today, which is running in an Internet Explorer MSHTML window embedded in a thick client application. Weirdly, the app would fail, silently, without any script errors, on the sixth refresh — pressing F5 six times in a row would crash it each and every time. Ctrl+F5 or F5, either would do it.
I was going slightly mad trying to trace this, with no obvious solutions. Finally I loaded the process up in Windbg, not expecting much, and found that three (handled) C++ exceptions were thrown for each of the first 6 loads (initial load, + 5 refreshes):
(958.17ac): C++ EH exception - code e06d7363 (first chance) (958.17ac): C++ EH exception - code e06d7363 (first chance) (958.17ac): C++ EH exception - code e06d7363 (first chance)
However, on the sixth refresh, this exception was happening four times:
(958.17ac): C++ EH exception - code e06d7363 (first chance) (958.17ac): C++ EH exception - code e06d7363 (first chance) (958.17ac): C++ EH exception - code e06d7363 (first chance) (958.17ac): C++ EH exception - code e06d7363 (first chance)
This gave me something to look at! At the very least there was an observable difference between the refreshes within the debugger. A little more spelunking revealed that the very last exception thrown was the relevant one, and it returned the following trace (snipped):
(958.17ac): C++ EH exception - code e06d7363 (first chance) ChildEBP RetAddr 001875e8 75d3359c KERNELBASE!RaiseException+0x58 00187620 72ba1df2 msvcrt!_CxxThrowException+0x48 00187664 72d659cb jscript9!Js::JavascriptExceptionOperators::ThrowExceptionObjectInternal+0xb4 0018767c 72ba1cda jscript9!Js::JavascriptExceptionOperators::ThrowExceptionObject+0x15 001876a8 72bb5175 jscript9!Js::JavascriptExceptionOperators::Throw+0x6e 001876f4 6e9acd03 jscript9!CJavascriptOperations::ThrowException+0x9c 0018771c 6f3029f5 MSHTML!CFastDOM::ThrowDOMError+0x70 00187750 72b68e9d MSHTML!CFastDOM::CWebSocket::DefaultEntryPoint+0xe2 001877b8 72cff6a2 jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x165 00187810 72bb849c jscript9!Js::JavascriptFunction::CallAsConstructor+0xc7 00187830 72bb8537 jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0x39 0018784c 72bb858c jscript9!Js::InterpreterStackFrame::ProfiledNewScObject_Helper+0x53 0018786c 72bb855e jscript9!Js::InterpreterStackFrame::OP_NewScObject_Impl<Js::OpLayoutCallI_OneByte,1>+0x24 00187ba8 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x3c44 00187dbc 132f1ae1 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 WARNING: Frame IP not in any known module. Following frames may be wrong. 00187dc8 72c372dd 0x132f1ae1 00187ed0 138be456 jscript9!Js::JavascriptFunction::EntryApply+0x265 00187f38 72b6669e 0x138be456 00188288 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 001883bc 132f0681 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 001883c8 72b6669e 0x132f0681 00188718 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018887c 132f1b41 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 00188888 72b6669e 0x132f1b41 00188bd8 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 00188d1c 132f1c21 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 00188d28 72bb7642 0x132f1c21 00189080 72bd501f jscript9!Js::InterpreterStackFrame::Process+0x2175 001890b8 72bd507e jscript9!Js::InterpreterStackFrame::OP_TryCatch+0x49 001893f8 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x4e84 00189594 132f0081 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 00189624 72d0134c 0x132f0081 00189650 72bf3de5 jscript9!Js::JavascriptOperators::GetProperty_Internal<0>+0x48 00189678 72b677a7 jscript9!Js::InterpreterStackFrame::OP_ProfiledLoopBodyStart<0,1>+0xa2 001899b8 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x24f7 00189b34 132f0089 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 00189b40 72b6669e 0x132f0089 00189e98 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018a004 132f0099 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018a010 72b6669e 0x132f0099 0018a368 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018a4bc 132f1c69 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018a4c8 72b6669e 0x132f1c69 0018a818 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018a95c 132f1c71 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018a968 72b6669e 0x132f1c71 0018acb8 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018ade4 132f1fb1 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018adf0 72c372dd 0x132f1fb1 0018ae90 72b60685 jscript9!Js::JavascriptFunction::EntryApply+0x265 0018aee0 72bd4fcc jscript9!Js::JavascriptFunction::CallFunction<1>+0x88 0018b220 72bd501f jscript9!Js::InterpreterStackFrame::Process+0x442e 0018b258 72bd507e jscript9!Js::InterpreterStackFrame::OP_TryCatch+0x49 0018b598 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x4e84 0018b71c 132f1ed9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018b728 72b6669e 0x132f1ed9 0018ba78 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018bbac 132f1ca1 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018bbb8 72bb7642 0x132f1ca1 0018bf00 72bd501f jscript9!Js::InterpreterStackFrame::Process+0x2175 0018bf38 72bd507e jscript9!Js::InterpreterStackFrame::OP_TryCatch+0x49 0018c278 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x4e84 0018c3ac 132f1e21 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018c3b8 72bb7642 0x132f1e21 0018c700 72bd501f jscript9!Js::InterpreterStackFrame::Process+0x2175 0018c738 72bd507e jscript9!Js::InterpreterStackFrame::OP_TryCatch+0x49 0018ca78 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x4e84 0018cbb4 132f1e21 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018cbc0 72b6669e 0x132f1e21 0018cf08 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018d03c 132f1e51 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018d048 72b6669e 0x132f1e51 0018d398 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018d4bc 132f0341 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018d4c8 72bb7642 0x132f0341 0018d810 72bd501f jscript9!Js::InterpreterStackFrame::Process+0x2175 0018d848 72bd507e jscript9!Js::InterpreterStackFrame::OP_TryCatch+0x49 0018db88 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x4e84 0018dd44 132f1f99 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018dd50 72b60685 0x132f1f99 0018dd98 72bd4fcc jscript9!Js::JavascriptFunction::CallFunction<1>+0x88 0018e0d8 72bd501f jscript9!Js::InterpreterStackFrame::Process+0x442e 0018e110 72bd507e jscript9!Js::InterpreterStackFrame::OP_TryCatch+0x49 0018e450 72c5bfce jscript9!Js::InterpreterStackFrame::Process+0x4e84 0018e488 72c5bf0f jscript9!Js::InterpreterStackFrame::OP_TryFinally+0xb1 0018e7c8 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x68eb 0018e8f4 132f0351 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018e900 72b6669e 0x132f0351 0018ec48 72b66548 jscript9!Js::InterpreterStackFrame::Process+0xbd7 0018ed94 132f1cf9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018eda0 72b66ce9 0x132f1cf9 0018f0f8 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x1cd7 0018f264 132f1d01 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018f270 72b66ce9 0x132f1d01 0018f5c8 72b66548 jscript9!Js::InterpreterStackFrame::Process+0x1cd7 0018f70c 132f1d49 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 0018f718 72b60685 0x132f1d49 0018f760 72b6100e jscript9!Js::JavascriptFunction::CallFunction<1>+0x88 0018f7cc 72b60f60 jscript9!Js::JavascriptFunction::CallRootFunction+0x93 0018f814 72b60ee7 jscript9!ScriptSite::CallRootFunction+0x42 0018f83c 72b6993c jscript9!ScriptSite::Execute+0x6c 0018f898 72b69878 jscript9!ScriptEngineBase::ExecuteInternal<0>+0xbb 0018f8b0 6eaab78f jscript9!ScriptEngineBase::Execute+0x1c 0018f964 6eaab67c MSHTML!CListenerDispatch::InvokeVar+0x102 0018f990 6eaab21e MSHTML!CListenerDispatch::Invoke+0x61 0018fa28 6eaab385 MSHTML!CEventMgr::_InvokeListeners+0x1a2 0018fb98 6e8a98bb MSHTML!CEventMgr::Dispatch+0x35a 0018fbc0 6e92d0c0 MSHTML!CEventMgr::DispatchEvent+0x8c 0018fd18 6e92da30 MSHTML!CXMLHttpRequest::Fire_onreadystatechange+0x8b 0018fd2c 6e9343ea MSHTML!CXMLHttpRequest::DeferredFire_onreadystatechange+0x52 0018fd5c 6e8a9472 MSHTML!CXMLHttpRequest::DeferredFire_progressEvent+0x10 0018fdac 773f62fa MSHTML!GlobalWndProc+0x1cd 0018fdd8 773f6d3a USER32!InternalCallWinProc+0x23 0018fe50 773f77c4 USER32!UserCallWinProcCheckWow+0x109 0018feb0 773f788a USER32!DispatchMessageWorker+0x3bc
So now I knew that somewhere deep in my Javascript code there was something happening that was bad. Okay… I guess that helps, maybe?
But then, after some more googling, I discovered the following blog post, published a mere 9 days ago: Finally… JavaScript source line info in a dump. Magic! (In fact, I’m just writing this blog post for my future self, so I remember where to find this info in the future.)
After following the specific incantations outlined within that post, I was able to get the exact line of Javascript that was causing my weird error. All of a sudden, things made sense.
0:000> k ChildEBP RetAddr 00186520 76bd22b4 KERNELBASE!RaiseException+0x48 00186558 5900775d msvcrt!_CxxThrowException+0x59 00186588 590076a3 jscript9!Js::JavascriptExceptionOperators::ThrowExceptionObjectInternal+0xb7 001865b8 5901503d jscript9!Js::JavascriptExceptionOperators::Throw+0x77 00186604 6365e79b jscript9!CJavascriptOperations::ThrowException+0x9c 0018662c 63e609aa mshtml!CFastDOM::ThrowDOMError+0x70 00186660 58f1f9a3 mshtml!CFastDOM::CWebSocket::DefaultEntryPoint+0xe2 001866c8 58f29994 jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x165 00186724 58f2988c jscript9!Js::JavascriptFunction::CallAsConstructor+0xc7 00186744 58f29a4a jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0x39 00186760 58f29a7a jscript9!Js::InterpreterStackFrame::ProfiledNewScObject_Helper+0x53 00186780 58f29aa9 jscript9!Js::InterpreterStackFrame::OP_NewScObject_Impl<Js::OpLayoutCallI_OneByte,1>+0x24 00186b58 58f26510 jscript9!Js::InterpreterStackFrame::Process+0x402b 00186d6c 14ea1ae1 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 WARNING: Frame IP not in any known module. Following frames may be wrong. 00186d78 58faa407 js!Anonymous function [http://marcdev2010:4131/app/main/main-controller.js @ 251,7] 00186e70 15511455 jscript9!Js::JavascriptFunction::EntryApply+0x267 00186ed8 58f268e6 js!d [http://marcdev2010:4131/lib/angular/angular.min.js @ 34,479] 001872c8 58f26510 jscript9!Js::InterpreterStackFrame::Process+0x899 001873f4 14ea0681 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 00187400 58f268e6 js!instantiate [http://marcdev2010:4131/lib/angular/angular.min.js @ 35,101] 001877e8 58f26510 jscript9!Js::InterpreterStackFrame::Process+0x899 00187944 14ea1b41 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 00187950 58f268e6 js!Anonymous function [http://marcdev2010:4131/lib/angular/angular.min.js @ 67,280] 00187d38 58f26510 jscript9!Js::InterpreterStackFrame::Process+0x899 00187e74 14ea1c21 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8 ...
When I looked at the line in question, line 251 of main-controller.js, I found:
// TODO: We need to handle the web socket connection going down -- it really should be wrappered
// try {
$scope.watcher = new WebSocket("ws://"+location.host+"/");
// } catch(e) {
// $scope.watcher = null;
// }
$scope.$on('$destroy', function() {
if($scope.watcher) {
$scope.watcher.close();
$scope.watcher = null;
}
});
It turns out that if I press F5, or call browser.navigate from the container app, the $destroy event was never called, and what’s worse, MSHTML wasn’t shutting down the web socket, either. And by default, Internet Explorer has a maximum of six concurrent WebSocket connections. So six page loads and we’re done. The connection error is not triggering a script error in the MSHTML host, sadly, which is why it was so hard to track down.
Yeah, if I had already written the error handling for the WebSocket connection in the JavaScript code, this issue wouldn’t have been so hard to trace. But at least I’ve learned a useful new debugging technique!
Speculation and further investigation:
- I’m still working on why the web socket is staying open between page loads when embedded. It’s not straightforward, and while onbeforeunload is a workaround that, well, works, I hate workarounds when I don’t have a clear answer as to why they are needed.
- It is possible that there is a circular reference leak or similar, but I did kinda think MS had sorted most of those with recent releases of IE.
- This problem is only reproducible within the MSHTML embedded browser, and not within Internet Explorer itself. Changing $scope.watcher to window.watcher made no difference.
- TWebBrowser (Delphi) and TEmbeddedWB (Delphi) both exhibited the same symptoms.
- A far simpler document with a web socket call did not cause the problem.
Updated a few minutes later:
Yes, I have found the cause. It’s a classic Internet Explorer leak: a function within a closure that references the DOM. Simplified, it looks something like:
window.addEventListener( "load", function(event) { var aa = document.getElementById('aa'); var watcher = new WebSocket("ws://"+location.host+"/"); watcher.onmessage = function() { aa.innerHTML = 'message received'; } aa.innerHTML = 'WebSocket started'; }, false);
This will persist the connection between page loads, and quickly eat up your available WebSocket connections. But it still only happens in the embedded web browser. Why that is, I am still exploring.
Updated 30 Nov 2015:
The reason this happens only in the embedded web browser is that by default, the embedded web browser runs in an emulation mode for IE7. Even with the X-UA-Compatible
meta tag, you still need to add your executable to the FEATURE_BROWSER_EMULATION registry key.