Lately I’ve been swimming around in an old ASP.NET site built in around 2009. The mission was to tweak things just enough that the old monster can actually run on a browser newer than IE8, and keeps kicking along for at least another few years.

So we’re running about, recompiling old .NET DLLs, cleaning things up behind the scenes, but the thing that suddenly became the biggest ticket item, that all the product owners started jumping on, were the old alert prompts that infested the application.

Instead of just having an error div, that gets revealed when needed, the pattern in use was to have the C# code-behind inject an alert() javascript snippet into the response. The problem though was that since this code was written before browsers started fighting spam-like behaviour, it was a complete shock to the owners when the modern browsers started showing these checkboxes on the prompts:

No, we can’t override that. It’s a feature of the browser.”

So, forget about incompatibilites between .NET 1.1 and 4.5.2. The biggest problem of the project became the fact that users could choose to suppress error messages.

¯\_(ツ)_/¯

Part of the mission was to minimise the changes needed to existing code, so going through and finding and changing the many, many instances of this horrorible trick was heavily resisted.

So, I left the existing code alone, and hijacked the builtin alert() & confirm() functions. Instead of popping up the usual window, with its “ignore future messages” option, throw up a bootrap modal [1].

Synchronous vs Asynchronous

There was one main problem with this approach. The built in alert and confirm prompts block execution until the user responds to the prompt.

Not much in the javascript world is allowed to block like this. Normally, you bind to an event, you get a chance to react or ignore, and the browser carries on pumping more events. You aren’t normally able to receive an event and sit on it. Take too long, and you’ll see something like this:

This wasn’t a problem for most the alert() calls. Usually, those were fire and forget. The flow didn’t depend on waiting for the user to acknowledge the alert prompt.

But for confirm() calls, it was a problem. About to leave the page? “Are you sure?” Kind of need to wait for the answer to those ones.

Happily there weren’t too many of these scattered around. It usually involved shuffling the code around slightly, changing from:

function someFunction() {
    stuff_1;
    stuff_2;

    if (confirm("Really?")) {
        do_really();
    } else {
        not_really();
    }

    other_stuff();
}

to something more like:

function someFunction() {
    stuff_1;
    stuff_2;

    confirmPrompt(
        "Really?",
        do_really,  // 'success' callback
        not_really, // 'cancel' callback
        other_stuff // 'always' callback - run after whichever ran before, success or cancel.
    );

}

Bootstrap behaves properly… but the old ASP.NET code makes it choke

The bootstrap codebase is nice.

All it’s internal functions are marked as use "strict";, and it’s all good and proper.

However, when we rewired the confirm() uses to fire the succes/cancel callbacks on modal button clicks (ie. modal hide event), found something odd. eg:

$('#myModal').on('hidden.bs.modal', function (e) {
    // This callback will be called by bootstrap's nice,
    // modern, **strict** code.
    handle_stuff_in_our_callbacks(e);
});

Which is only a problem if our code is doing stuff that breaks in strict mode, right? Nope.

The old versions of ASP.NET (maybe the new ones too? Haven’t checked…) add javascript to the rendered page, to handle the bazillion page/form updates it seems to want to do for you. At the heart of all the client -> ASP runtime chatter is a function called __doPostBack().

Somewhere in the __doPostBack() depths, it attepts to walk the javascript callstack, for some insane reason (probably a sane reason, but it caused me pain, so I’ll call it want I want), using the no-longer-valid in strict mode: arguments.callee.

So, we’ve ended up with this kind of scenario:

Having a button like this:

<button type="button" id="kludge">
    Test strictness kludge
</button>

We have a click event flowing through this sort of setup:

function oldLegacyRoutine() {
    var d = arguments.callee ? arguments.callee.caller : null;
    if (d) {
        // Must be non-strict mode...
        alert("Can access caller: " + (d !== null));
    } else {
        // Chrome & modern IE let you access, but only return null
        // in strict mode.
        // Firefox will throw an exception and
        // not reach here at all.
        alert("Can't access, but didn't explode.");
    }
}

function strictModernCaller(e) {
    "use strict";

    oldLegacyRoutine();
}

$(document).ready(function() {
    $("#kludge").on("click", function(event) {
        try {
            strictModernCaller(event);
        } catch(err) {
            alert("Failure: " + err.toString());
        }
    });
});

So because the bootstrap hide-modal event handler was being run in a strict context, and our old code ended up kicking off ASP.NET post back strict-mode-illegal shananigans, the old ASP.NET javascript completely broke our nice, shiny bootstrap modals… :(

Just get it working

We weren’t going to get a new version of ASP.NET to use, so I cheated. Had to take a shower afterwards, but at least it worked.

Our problem here was that the old code was being run within a strict execution context. It worked “fine” in non-strict mode.

The only strict things were the modern functions.

I altered our callback to be effectively this:

function strictModernCaller(e) {
    "use strict";

    setTimeout(oldLegacyRoutine, 1);
}

Timer callbacks are run in from the context of window. The browser default context isn’t strict, allowing the old framework javascript to run as it was meant in prehistoric times.

[1]If you have to do similar, and hijack alerts & confirms with bootstrap modals, don’t roll your own if you can help it. Use Bootbox instead. Easier.

Comments

comments powered by Disqus