Is this a Safari bug?

On my final editing pass of our upcoming book, The JavaScript Anthology, I’ve spotted what appears to be a previously unrecorded bug in Safari:

function preload(url) {
  var img = new Image();
  img.onload = function() {
    alert(this); // What is this?
  };
  img.src = url;
}
preload('chewbacca.jpg');

In well-behaved browsers like Firefox, Internet Explorer 6 and Opera 8.5, the above script loads the image and then displays some variation of “[object HTMLImageElement]“, which indicates that this refers to the image object for which the load event has just fired.

In Safari 1.3 and 2.0, however, the alert displays “[object window]“, because this refers instead to the window object within which the script is running.

Naughty Safari! Google didn’t seem to know about this bug when I asked it. Anyone seen this behaviour reported before?

Until this bug gets fixed, you should use a closure to refer to an image object from within its onload the event handler. That is, refer to a variable in the handler function’s enclosing scope.

function preload(url) {
  var img = new Image();
  img.onload = function() {
    alert(img);
  };
  img.src = url;
}
preload('chewbacca.jpg');

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • http://www.dustindiaz.com polvero

    Well, I know that in almost any browser, when you alert(this) on anchor elements eg: the ‘a’ tag, you receive the href source. Something I’ve never understood when technically we should be seeing something like HTMLAnchorElement or something like that. If I wanted the ‘href’ I would reference it by saying this.href.

    Wierd.

  • Etnu

    It’s a fairly well-known gotcha that simply assigning onclick, onload, etc. in javascript is dangerous.

    Stick with addEventListener and attachEvent. Only directly bind event handlers when you absolutely must support old browsers.

  • Anonymous

    That bug goes away if you use var img = document.createElement('img');. This relates to a known Safari bug: http://bugzilla.opendarwin.org/show_bug.cgi?id=3869

    I would tend to disagree with Etnu on the dangers of onclick etc – I’ve found them to be far more predictable in cross-browser situations than the more modern addEventListener and attachEvent. The benefit of the latter is that they allow multiple handlers to be attached to a single event, but if you don’t need that ability onclick tends to work as well (if not better) with far less browser tricks required.

  • http://www.norfolkbroads.com/ Mr The Duke

    Well, I know that in almost any browser, when you alert(this) on anchor elements eg: the ‘a’ tag, you receive the href source. Something I’ve never understood when technically we should be seeing something like HTMLAnchorElement or something like that.

    You do see an anchor element Polvero, though it’s not obvious. alert() shows the string representation of an object as returned by toString(). Most HTML elements don’t have toString. Anchors return their href.

    Proof that you’ve really got a link object:
    <a href="ftp://test" onclick="alert(this.protocol)">Click me</a>

  • http://www.dustindiaz.com polvero

    MrDuke, I guess I was just expecting something more like HTMLAnchorElement. I was a bit confused by seeing the href attribute being returned.

  • Anonymous

    Using onclick is a bad idea if you plan to distribute your code because your onclick event may overwrite another script’s onclick. However if you’re operating in a relatively closed environment and know that there won’t be interferance, onclick provides a nice and elegant way of assigning events.

  • http://www.calcResult.co.uk omnicity

    Maybe the example got mangled or over-simplified, but there is nothing in the code that defines ‘this’, so therefore it _should_ refer to the top-level ‘window’ object.

    If that is different from other browsers then I suspect that they are the ones that have got it wrong, not Safari.

  • http://www.sitepoint.com/ Kevin Yank

    Omnicity,

    Maybe the example got mangled or over-simplified, but there is nothing in the code that defines ‘this’, so therefore it _should_ refer to the top-level ‘window’ object.

    If that is different from other browsers then I suspect that they are the ones that have got it wrong, not Safari.

    I’m afraid you need to do some more homework, here. this is a special keyword in JavaScript that points to the object through which the currently executing method was invoked. Even Safari does this correctly in most cases.

    this does not need to be defined; in fact, because it is a JavaScript keyword, attempting to define it would be an error.

  • http://www.calcResult.co.uk omnicity

    If I’m wrong, then I’m certainly not the only one: first result in Google:
    http://www.quirksmode.org/js/this.html

    In JavaScript this always refers to the “owner” of the function we’re executing, or rather, to the object that a function is a method of. When we define our faithful function doSomething() in a page, its owner is the page, or rather, the window object (or global object) of JavaScript.

    My terminology was probably not perfect though; I agree that this does not need to be defined like a normal var but it does require context, which is often an object constructor, or else an onClick(this) construct. However your example is neither of these, but implies that it one of them was intended.

  • http://www.sitepoint.com/ Kevin Yank

    Omnicity,

    The page you cite is absolutely right, and it’s saying the exact same thing I’m saying. Read the section under the heading “Copying”.

    Their example:

    function doSomething()
    {
       this.style.color = '#cc0000';
    }
    element.onclick = doSomething;

    When doSomething is called as a method of element, this will refer to element in the function body.

    My example:

    img.onload = function() { 
      alert(this); // What is this? 
    };

    This is analogous in structure to the code above, except for the fact that the function is created anonymously–without a name–and is assigned directly as a method of img (thereby providing the context of which you speak). When the function is called as a method of img, this will refer to img in the function body.

    The page you cite actually has an example of this form under Examples – copying:

    element.onclick = function () {this.style.color = '#cc0000';}
  • http://www.calcResult.co.uk omnicity

    No, the exact example is shown under examples – referring.

    I think you are being confused by the fact that you are effectively using a compound statement. If you re-write the code to use a non-anonymous function you will see why ‘Window’ is the correct context.

  • http://www.sitepoint.com/ Kevin Yank

    Omnicity,

    No, the exact example is shown under examples—referring.

    Here’s the example you refer to in full, along with the declaration of doSomething():

    element.onclick = function () {doSomething()}
    
    function doSomething()
    {
    	this.style.color = '#cc0000';
    }

    In this case, this does refer to window, because doSomething is called not as a method of some object, but as an ordinary function.

    Here’s how my example above would look if it were written the same way:

    img.onload = function() { doAlert(); }; 
    
    function doAlert() {
      alert(this); // this refers to window
    }

    In this case too, this would refer to the window object, because doAlert is being called as a function (doAlert();), not as a method of a particular object (object.doAlert();).

    But my example above isn’t coded this way. It’s actually coded using the same structure as this example from Examples – copying:

    element.onclick = function () {this.style.color = '#cc0000';}

    In this case, because the function is being called as an event handler of element (and thus as a method of element), this refers to element.

    Likewise, in my code:

    img.onload = function() { 
      alert(this); // What is this? 
    };

    Because the function is called as a method of img, this refers to img.

    I think you are being confused by the fact that you are effectively using a compound statement. If you re-write the code to use a non-anonymous function you will see why ‘Window’ is the correct context.

    Since you think it’s the anonymous function that’s confusing me, here’s how I’d rewrite the code to use a non-anonymous function, and still have this refer to img:

    img.onload = doAlert;
    
    function doAlert() {
      alert(this);
    }

    Again, because doAlert is getting called as an event handler (and thus a method) of img, this refers to img inside the function.

    This is again confirmed on the page you cited (see “The difference”).

  • memet

    Kevin (and Omnicity):

    I think the bottom line that both of you have not mentionned is that the language is interpreted as statements. As such:

    var myFunc = doSomething; //1

    and

    var myFunc = doSomething(); //2

    Are two completely different statements. The first one assigns a function ‘pointer’, or a function call ‘context’ (including possibly a closure) to myFunc, whereas the second one calls doSomething’2 and assigns myFunc the result of that function call.
    This might seem trivial, but it’s essential: you can name a function without calling it. And by assigning by name you make a copy of the value of the variable (in this case a function).
    Anonymous functions are also a way of naming a function without giving it a name. They do not execute the function, just describe it.

    As you will notice in the examples, copying is via:

    element.onclick = doSomething
    element.addEventListener('click',doSomething,false)
    element.onclick = function () {this.style.color = '#cc0000';}
    <element onclick="this.style.color = '#cc0000';">

    and refering via:

    element.onclick = function () {doSomething()}
    element.attachEvent('onclick',doSomething)
    <element onclick="doSomething()">

    The first samples are all naming a function, whereas the last ones are all calling it. Except for the pesky attachEvent method which seems to not really abide by a standard way of doing things (namely copying the function ‘pointer’ instead of instancing it).

    Kevin, I personally don’t using the term “method of” for these discussions. It is more like you are overriding a virtual function pointer on an object. The method is always there, it’s called “onclick”, you’re just changing the value it holds.

    The ruling logic is not about being a method of, but rather the difference of naming a function versus calling it. That is, these things are not limited to object methods.

    In essence, it is the onclick method that has a ‘this’ context. Not the actual doSomething function. But when you set the value of onclick to doSomething, you have given it the same context.

    Anyways, my two cents.

  • http://www.sitepoint.com/ Kevin Yank

    memet,

    I see what you’re trying to say, and it’s basically the same thing I’m trying to say — it’s just a different way of looking at things.

    You’re saying: if a function is assigned as the value of an object property (“named”), then it receives the object as the value of this. But if it is just called outright (“called”), it has no object as its context and therefore this gets window as its value.

    I’m saying: when a function is called as an object method (i.e. the function has been assigned as the value of any object property), it executes with that object as its context, so this is the object. But if a function is called as a simple function, then it doesn’t receive any special object as its context, so this is the window.

    We’re saying the same thing, just using different terminology and a slightly different point of view.

  • http://www.calcResult.co.uk omnicity

    To get back on the original topic, I have tried to go to the horses mouth, to see what should happen in this situation.

    If you look at the page that is labelled as p.71 of:
    The ECMA-262 Spec
    you will see three variations on ‘Function Definition’.
    The second one, (the first ‘FunctionExpression’), I believe is what we are discussing here. Unfortunatly, in that document there is at least a minor typo: since step two states: Return Result(2).
    Which is recursive and illogical, and appears to have knocked out the formatting for the next line.

    Does anyone have a correct copy of this section? There is no explanation of why this block differs from the other two, so I don’t want to second-guess the authors intentions.

    Personally, I believe that any compound statement in any language should act the same way as the individual statements evaluated individually. If they don’t then it is a bug in the the language. Kevin: I know that you were able to synthesise a similar construct with individual statements that worked the way that you wanted, but it was not the same as merely splitting the compound statement in two.

  • http://www.sitepoint.com/ Kevin Yank

    Omnicity,

    If you look at the page that is labelled as p.71 of: The ECMA-262 Spec you will see three variations on ‘Function Definition’.

    All along I’ve been saying that the value of this depends on how the function is called, not how it is declared. Quoting from the spec

    From p.39:

    10.1.7 This
    There is a this value associated with every active execution context. The this value depends on the caller and the type of code being executed and is determined when control enters the execution context. The this value associated with an execution context is immutable.

    Translation: this depends on how and where the currently-executing code was called, and what type of code is currently executing (global code, eval code, or function code). In our discussion, the type of code executing is function code.

    So how is this determined for function code?

    From p.40:

    The caller provides the this value. If the this value provided by the caller is not an object (including the case where it is null), then the this value is the global object.

    The “global object” in a browser environment is window.

    Translation: when the caller doesn’t provide a value for this (i.e. the function was called as a standalone function (e.g. doAlert();), this is window. But when the caller does provide a value (e.g. value.doAlert();), this will be that value.

    Not quite convinced? This is stated more formally later in the spec.

    From p.44:

    11.2.3 Function Calls
    The production CallExpression : MemberExpression Arguments is evaluated as follows:
    [...]
    6. If Type(Result(1)) is Reference, Result(6) is GetBase(Result(1)). Otherwise, Result(6) is null.
    7. If Result(6) is an activation object, Result(7) is null. Otherwise, Result(7) is the same as Result(6).
    8. Call the [[Call]] method on Result(3), providing Result(7) as the this value and providing the list Result(2) as the argument values.

    There are a lot of formal declarations to pull apart to understand this, but basically it comes down to Result(7) being caller in caller.someFunction() when MemberExpression is of the form caller.someFunction, and being null when MemberExpression is of the form someFunction.

    Back to you, omnicity:

    Kevin: I know that you were able to synthesise a similar construct with individual statements that worked the way that you wanted, but it was not the same as merely splitting the compound statement in two.

    In this case, it is exactly the same. Starting with this:

    img.onload = function() { 
      alert(this); // What is this? 
    };

    …if the only change I make is to declare the function with a name rather than using an anonymous function, then this is what I get:

    img.onload = doAlert;
    
    function doAlert() {
      alert(this); // What is this?
    };

    The only side-effect of this change is to declare doAlert as a function in the global (window context) namespace. But the fact that I assign a copy of it to onload (thereby putting that copy in img context) means that there will be no change to the way the function executes in response to the event.

    To get your form, I need to make an additional change, which is to wrap the reference to the newly-declared function in another anonymous function reference:

    img.onload = function() { doAlert(); };
    
    function doAlert() {
      alert(this); // What is this?
    }

    With this change, doAlert is no longer in img context when it runs in response to the event. It’s the anonymous function that has img context, and it calls doAlert in the global namespace, thus causing this to be window.

    …of course I don’t expect you to be convinced of this, as you seem to have a different understanding of the meaning of the syntax involved than I do anyway. Allow me to take another approach to convincing you.

    Consider my code again:

    img.onload = function() { 
      alert(this); // What is this? 
    };

    Also consider my alternative formulation:

    img.onload = doAlert;
    
    function doAlert() { 
      alert(this); // What is this? 
    };

    If we set this script up so that img is a reference to an HTML img element, and then assign img.src a new value (as my original code above does), then executing either of these code formulations on a range of current browsers (Internet Explorer 6, Firefox 1, and Opera 8 among others) will reveal that this is a reference to the img object on every browser except Safari.

    Are you suggesting that Safari is the only browser getting it right? If so, read on…

    Now consider this code:

    mylink.onclick = function() { 
      alert(this); // What is this? 
    };

    Also consider the alternative formulation:

    mylink.onclick = doAlert;
    
    function doAlert() { 
      alert(this); // What is this? 
    };

    This time, make mylink a reference to an HTML hyperlink (an a element with an href attribute). When you click the link in the range of current browsers (including Internet Explorer 6, Firefox 1, Opera 8, and Safari!), either of these formulations will reveal that this is the hyperlink (the value of its href will be displayed, as that is what the toString method of a hyperlink is supposed to do).

    If Safari were right in the previous case, why is it wrong in this case? Are you saying Safari is the only browser to get it right, and it only gets it right when it comes to the onload event handler of an Image object?

    You can test even more generally by running this code on various browsers:

    var myObject = {};
    myObject.myMethod = function() {
      alert(this);
    }
    
    myObject.myMethod(); // outputs [object Object]

    As you can see, this is not window, but the object that I stored in myObject. This result is consistent across all current browsers, including Safari.

    And my alternative formulation still holds:

    var myObject = {};
    myObject.myMethod = doAlert;
    
    function doAlert() {
      alert(this);
    }
    
    myObject.myMethod(); // outputs [object Object]
  • http://www.calcResult.co.uk omnicity

    In our discussion, the type of code executing is function code.

    That is your assertion, not mine. You are drawing a conclusion based on an initial assumption that you are correct.
    It appeared to me that this was Global code, which invalidates the rest ot that portion of your argument.

    Are you suggesting that Safari is the only browser getting it right? If so, read on…

    That’s exactly what I was suggesting, as you are well aware.
    I presume that you have seen a program similar to our Who wants to be a Millionaire? It is well known that in the game, you must only use Ask the Audience on the easier questions – ‘collective ignorance’ trumps ‘collect intelligence’ once things get tricky.
    Alternatively, do you think that either George Bush or Tony Blair are really the best people to run their respective countries?
    Three against one is hardly an over-whelming majority anyway!

    …of course I don’t expect you to be convinced of this,

    And I don’t expect you to convince me, I expect you to convince yourself before you raise a bug report.

    I’m sure you have a valid reason for presenting your example code, but one of the reasons that I have had to think hard about this is that I have never written code in this style, and don’t expect to: __proto__ etc. is there for a reason; I hope you have made that clear in your writings.

    Regards, Mike

  • hax

    “You are drawing a conclusion based on an initial assumption that you are correct.” — omnicity, this sentence should apply to yourself.

  • Density

    Wow – that was way too much to wade through because somebody didn’t know that “global” functions are methods of the window object. Troll?

    Anyway, did anyone figure out a workaround for this? I’ve noticed that Safari also returns an onload event for broken images :( and I can’t reference reference it’s properties because of this bug.

  • Density

    Oh. WHoops. Closure, right

  • Lucian

    I agree that this is a safari bug, as in all other browsers behaves correctly.

  • Jay

    Looks like Safari might doing this:

    var theEvent = img.onload;
    theEvent();

    When it should be doing this:

    var theEvent = img.onload;
    theEvent.call(img);