WTF! MicrosoftAjax.js vs ‘use strict’ vs Firefox vs IE

Hi folks

I fixed a very weird bug today.

Initially the bug was raised that some actions causes JavaScript error in Firefox:

TypeError: access to strict mode caller function is censored

This was not happening in IE or Chrome.

I started to debug and found that the problem is within MicrosoftAjax framework. I am working on a legacy ASP.NET WebForms project which uses and relies on it heavily.

For the blogging purposes I looked for a debug/unminified version of the problematic script and found it here: http://ajax.aspnetcdn.com/ajax/4.5.2/1/MicrosoftAjaxWebForms.Debug.js

I would like to refer to some lines so I uploaded the same file to gist.

The problematic line was https://gist.github.com/mnaoumov/55be04ca588ff22f851f#file-microsoftajaxwebforms-debug-js-L738

while (caller.arguments.callee.caller && --recursionLimit) {

where at some stage caller.arguments.callee variable equals to the function that is defined in some script with

"use strict";

directive

and therefore it prohibited to call its caller

And the reason why this issue occurred in Firefox only is the line https://gist.github.com/mnaoumov/55be04ca588ff22f851f#file-microsoftajaxwebforms-debug-js-L733

var event = window.event;

window.event is undefined in Firefox. This event variable is not really needed. The only place it is used https://gist.github.com/mnaoumov/55be04ca588ff22f851f#file-microsoftajaxwebforms-debug-js-L785 would be called under pretty rare condition according to the code flow.

activeElement = event ? (event.target || event.srcElement) : null;

So in order to fix the problem I think it is pretty safe to fake **window.event** for this function.

So I’ve written a hack

/**
 * This is required to fix a bug in MicrosoftAjaxWebForms.js
 * in Firefox where if window.event is not initialized, it loops stack
 * via arguments.callee.caller chain and breaks because of the
 * "use strict" mode
 */
function hackEventWithinDoPostBack() {
    var originalDoPostBack = window.__doPostBack;

    window.__doPostBack = function hackedDoPostBack() {
        if (!window.event)
            window.event = {};
        return originalDoPostBack.apply(this, arguments);
    };
}

And this worked fine in Firefox. I thought it would work fine in IE and Chrome as well because I am checking for window.event before overriding but it was not!

IE11 started to fail in other places with the following error

Assignment to read-only properties is not allowed in strict mode

That was caused by the

window.event = {};

line written above. I found that window.event still can be undefined but you are not allowed to set it explicitly.

That appeared to be a bit tricky to overcome and eventually I came up with a pretty sophisticated solution

/**
 * This is required to fix a bug in MicrosoftAjaxWebForms.js
 * in Firefox where if window.event is not initialized, it loops stack
 * via arguments.callee.caller chain and breaks because of the
 * "use strict" mode
 *
 * Hacking window.event property is required because it is
 * not settable in Internet Explorer
 */
function hackEventWithinDoPostBack() {
    var originalEventDescriptor = Object.getOwnPropertyDescriptor(Window.prototype, "event");
    var hackEventVariable = false;
    var eventPropertyHolder;
    Object.defineProperty(window, "event", {
        configurable: true,
        get: function get() {
            var result = originalEventDescriptor ? originalEventDescriptor.get.apply(this, arguments) : eventPropertyHolder;
            if (result || !hackEventVariable)
                return result;
            return {};
        },
        set: function set(value) {
            if (originalEventDescriptor)
                originalEventDescriptor.set.apply(this, arguments);
            else
                eventPropertyHolder = value;
        }
    });

    var originalDoPostBack = window.__doPostBack;

    window.__doPostBack = function hackedDoPostBack() {
        hackEventVariable = true;
        originalDoPostBack.apply(this, arguments);
        hackEventVariable = false;
    };
}

This works in IE, Chrome and Firefox.

Such a workaround, isn’t it?!

Stay tuned

Join the Conversation

9 Comments

  1. Its nice , can you please update for safari . the error in safari
    TypeError: Requested property descriptor of a value that is not an object.

  2. sreenath, not sure if you still need to know this or not, but I placed this in my MasterPage’s Page_Load event:

    protected void Page_Load(object sender, EventArgs e)
    {
    string script2 = “hackEventWithinDoPostBack();”;
    ScriptManager.RegisterStartupScript(this, this.GetType(), “script2”, script2, true);
    }

    And placed the javascript code in the MasterPage’s markup with my other scripts. This caused the process to be called on every page load. I’m not sure if that’s the most correct way of doing it, but it worked on my first attempt. Seemed to work on a lot of complex things I’m doing where other tricks didn’t work. On dropdown boxes I could use a postback timeout, but transferring things in listboxes was another story. This hack did the job. I’m so glad this fix was posted. I run into Microsoft bugs a lot. I’m surprised this one hasn’t been addressed.

  3. Have you tried this solution on a Mac? I tried to use this solution in my master page and it does not work in Safari or Firefox on my Mac.

    The error I am getting in Safari is “Function.caller used to retrieve strict caller”

  4. Hey just want you to know that your code helped me! Cheers from East Asia! đŸ™‚

Leave a comment