This was a very informative and very, very long post.
Everyone: here's Antranig's summary, in case you didn't have time to read to
the bottom. It's important information:
A) NEVER use unprotected try/catch in browser-side JavaScript, use the wrapper
fluid.tryCatch instead
B) When in trouble in IE and/or a test case, add the "notrycatch" parameter to
the URL
C) Watch out for new-style IoC debugging traces in the case of failure, read
from the bottom up
Colin
On 2011-05-12, at 4:32 AM, Antranig Basman wrote:
> Whilst dealing with our logging/JSON issues for FLUID-4197, I ran into a test
> which failed under IE8 (UploaderCompatibilityTests.js) and was sunk into the
> full horror of how nasty IE debugging generally is. Given that IoC debugging
> is also generally nasty, the combination of the two issues was strongly
> unpleasant... despite getting an "object has no property xxxx" error it was
> entirely impossible to figure out, for example, even what line of code the
> problem was thrown from.
>
> TRY-CATCH-FINALLY
> =================
>
> Having some experience of the terrain, I went for the "magic catch block"
> that I know lives within qunit.js to comment it out, since in the past, I had
> anecdotal evidence that removing try... catch blocks can make IE failures and
> test exceptions easier to pinpoint... I discovered that the code had been
> upgraded since I had last visited it, to include the following very
> interesting section...
>
> if ( config.notrycatch ) {
> this.callback.call(this.testEnvironment);
> return;
> }
> try {
> this.callback.call(this.testEnvironment);
> } catch(e) {
>
> It's clear that the qunit framework author had run into similar issues -
> tracing this back to its source, it seems that adding the query parameter
> "notrycatch" to the URL of any qunit test page will automatically disable the
> try/catch block with no code hacking required. This is already well worth
> knowing.
>
> After this I got to thinking... is this kind of insanity REALLY necessary?
> Going on an all-out trawl for information on exception tracing on IE, I
> discovered that it really is. The following guide, "3 painful ways to obtain
> a stack trace"
>
> http://blog.yoursway.com/2009/07/3-painful-ways-to-obtain-stack-trace-in.html
>
> looks like the result of pretty good research, and declares quite firmly
> that, given an Exception/Error object in IE, there is *NO* way to use it to
> yield a stack trace. What is worse - having actually CAUGHT the exception,
> whatever stack information the browser might ever have yielded to you has
> been PERMANENTLY DESTROYED. The guide explains that parts of this may be
> reconstructed by piecing together info from multiple catch sites, but as well
> as this being ridiculous, the crucial info, from the very top of the stack
> can never be recovered.
>
> Doublechecking the "exception stripping code" that I ripped off for
> FluidDebugging.js from "emwendelin"
> https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
> confirms that yes - under IE, you can get your CURRENT stack information by
> use of the "callee" property - but once the stack has returned and the
> exception has been caught, it is destroyed for good.
>
> Even Firefox is pretty awkward about yielding exception info in Firebug -
> which is the reason for our long-standing and rather odd behaviour inside
> fluid.fail() which rather than throwing a user exception which is worthless
> for debugging purposes, instead causes a language-level failure which will
> trip the debugger - but given the IE behaviour, there is only one and very
> unpalatable conclusion to draw....
>
> try-catch blocks SHOULD NOT be used in JavaScript code running in the
> browser!
>
> This seems to reflect a fairly basic misunderstanding by browser authors as
> to the way exceptions are meant to be used (at least, reflected in the ways
> people are used to using them for some reliable purposes in Java, C++, etc.)
> - it seems clear that firstly, they are intended to be "unrecoverable" and to
> signal a logic failure (aka an assertion failure) - and the only reliable way
> to get full debugging info for a failure is to have a completely
> UNINTERRUPTED path from the point of the failure back down to the browser,
> with no try-catch blocks intervening.
>
> However, this situation may not last forever on the browser, and may not even
> reflect the state of environments such as node.js on the server. So we really
> need to be able to have the moral effect of try-catch-finally together with
> the ability to disable it completely depending on environment.
>
> Taking a leaf out of the qunit author's book, I was led to write the
> following function, the sight of which one would normally expect to excite
> abuse, hilarity and contempt:
>
> fluid.tryCatch = function(tryfun, catchfun, finallyfun) {
> finallyfun = finallyfun || fluid.identity;
> if (fluid.notrycatch) {
> var togo = tryfun();
> finallyfun();
> return togo;
> }
> else {
> try {
> return tryfun();
> }
> catch (e) {
> if (catchfun) {
> catchfun(e);
> }
> else {
> throw(e);
> }
> }
> finally {
> finallyfun();
> }
> }
> };
>
>
> This function is now in Fluid.js... and is used by ALL sites in framework
> code which issue try blocks... being the Fluid Event system and 3-4 sites in
> IoC. Following the qunit author the framework responds to exactly the SAME
> URL parameter "notrycatch" that is recognised by qunit, giving us now a "one
> stop shop" for test cases in IE - for example,
>
> file:///E:/Source/gits/infusion-master/src/webapp/tests/component-tests/uploader/html/UploaderCompatibility-test.html?notrycatch
>
> will stick you right into the IE8 debugger at the failure line, no matter how
> deep the failure occurs.
>
> Getting back to the original issue...
>
> IoC DEBUGGING
> =============
>
> When working with Clayton last week, there was yet more brutal evidence of
> how opaque it can be to debug IoC issues, especially at a distance... and
> part of the issue is not unrelated to the previous point. The most suitable
> way in Java, for example, to supply extra failure semantics characterising
> what the framework thinks it is doing at some deeply nested callsite, is to
> piggy-back on try..catch blocks, which approach was used in the
> UniversalRuntimeException system that was used in RSF. Already being
> suspicious of exceptions in JavaScript, I'd held off implementing this, and
> the research above only cements the point - since catch blocks, ESPECIALLY in
> the crucial situations in IE where debug traces would be most helpful, cannot
> be used, we have to use a quite different system... which pushes information
> onto a custom stack BEFORE the point of failure, rather than tagging
> information down cascading catch blocks AFTER the failure. This system is now
> in FluidIoC.js, under t
he name of functions "fluid.pushActivity" and "fluid.wrapActivity". The
crucial centre of the system indeed uses the fluid.tryCatch function listed
above, and looks like this:
>
> root.activityStack = root.activityStack.concat(frames);
> return fluid.tryCatch(func, null, function() {
> root.activityStack.length -= frames.length;
> });
>
> The "activity stack" is now available at all points of the framework code,
> and is queried from fluid.fail (if IoC is loaded) to tack contextual
> information on failure traces. Now rather than being just told that
> "something has failed" there will be a pretty intelligible trace of "while"
> context messages tracing through the intentions of the framework that brought
> it to that point - for example, here is a trace from one of the test cases in
> FluidIoC.js under the new system:
>
> Wed May 11 2011 22:11:08 GMT-0600 (Mountain Daylight Time): ASSERTION FAILED:
> Component { typeName: "fluid.tests.circular.local" id: 12} at path "local" of
> parent { typeName: "fluid.tests.circular.strategy" id: 8} cannot be used for
> lookup since it is still in creation. Please reorganise your dependencies so
> that they no longer contain circular references
> while resolving context value {strategy}.local
> while invoking invoker with name fluid.tests.circular.eventBinder on
> component Object { typeName="fluid.tests.circular.engine", id=13, more...}
> while instantiating dependent component with name "engine" with record
> Object { type="fluid.tests.circular.engine"} as child of Object {
> typeName="fluid.tests.circular.strategy", id=8, more...}
> while expanding component options {engine}.swfUpload with record Object {
> marker={...}, value="{engine}.swfUpload", localRecord={...}} for component
> Object { typeName="fluid.tests.circular.local", id=12, more...}
> while instantiating dependent component with name "local" with record
> Object { type="fluid.tests.circular.local"} as child of Object {
> typeName="fluid.tests.circular.strategy", id=8, more...}
>
> This looks much better in the debugger that as ASCII - since through
> upgrading the "fluid.fail"/"fluid.log" support for the original issue
> FLUID-4197, these objects that appear in the trace are actually
> clickable/expandable JSON or DOM views as natively supported by console.log
> on the "good browsers" we talked about this afternoon. But the crucial point
> is the five "while" clauses - as well as the headline message (which is all
> we got before) we can now see exactly what the framework was doing on the way
> to the failure site.
>
> Reading up from the bottom,
> i) it started to instantiate the subcomponent "local" of the
> "fluid.tests.circular.strategy" object
> ii) it started to expand its options, and discovered a reference
> {engine}.swfUpload, which led it to
> iii) immediately start on a sibling subcomponent (a "ginger instantiation"),
> component "engine"
> iv) on which it found an invoker (invoked on construction) named "eventBinder"
> v) in whose configuration it found a reference to "{strategy}.local", a
> circular reference to the component at i)
>
> UPLOADER TESTS
> ==============
>
> So... this is all relatively jolly. Leading back to the original cause,
> UploaderCompatibilityTests.js on IE8... with the tools mentioned before, it
> was then not too hard to find out that the cause was that it was using the
> "native" progressiveEnhancer for IE8, rather than an appropriately mocked
> testing instance - which given I don't have Flash installed, was falling back
> to the "singleFileUploader" which didn't have the expected event attached (we
> should make sure that fails more gracefully when we have a chance).
>
>
> The main points of news again in short:
>
> A) NEVER use unprotected try/catch in browser-side JavaScript, use the
> wrapper fluid.tryCatch instead
> B) When in trouble in IE and/or a test case, add the "notrycatch" parameter
> to the URL
> C) Watch out for new-style IoC debugging traces in the case of failure, read
> from the bottom up
> D) Functional programming in JavaScript is GOOD, in the same sense that Alma
> Cogan ISN'T, given that all of the above stuff could be implemented with very
> little intrusion on the codebase even where it was previously using
> language-native facilities
---
Colin Clark
Technical Lead, Fluid Project
http://fluidproject.org
_______________________________________________________
fluid-work mailing list - [email protected]
To unsubscribe, change settings or access archives,
see http://fluidproject.org/mailman/listinfo/fluid-work