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

Reply via email to