Out of Hanwell

June 27, 2006

Using window.onload over HTTPS

Filed under: Internet Explorer — Matthias Miller @ 2:36 pm

Rob mentioned that the window.onload solution generates a secure/nonsecure warning when used over a secure connection. This is because the script’s src references the “javascript:” scheme instead of “https:”.

I pointed the script’s src to “https:///”, a URL that obviously does not exist, and ran the script against several automated and manual tests. Oddly, onreadystatechange no longer gets fired for the “loading” state, but all tests did pass as expected. (These were the same tests that I used to verify the original proposal.)

I would have posted a comment on Dean’s blog to interact about this, but it looks like his site is temporarily unavailable. I’ve done my best to test this and it works for me, but let me know what you all find.

Update: This does not work in IE7b3 because of a regression in URL resolution. It attempts to resolve “https:///” relative to the current domain, thus loading “https://domain/” instead. After some testing, I found that it does not attempt to resolve invalid IPv6 URLs, such as “https://%5B%5D/”. Also, the URL scheme is optional, so the script can point to “//[]”.

Update: Internet Explorer 6 does try to resolve “//[]”. Alistair Potts suggested using “//0” instead–which appears to be the equivalent of “//0.0.0.0”, based on behavior in IE7–and later pointed out that Internet Explorer does not even attempt to resolve “//:”. Firefox, however, does attempt to resolve “//:”, causing an inconvenient status until it times out.

Advertisements

June 12, 2006

Big Problems

Filed under: Development — Matthias Miller @ 6:10 am

Have you ever felt like this? It reminds me of the window.onload problem.

Big problems are terrifying. There’s an almost physical pain in facing them. It’s like having a vacuum cleaner hooked up to your imagination. All your initial ideas get sucked out immediately, and you don’t have any more, and yet the vacuum cleaner is still sucking.

(From How to Do What You Love by Paul Graham)

June 8, 2006

The window.onload Problem Revisited

Filed under: JavaScript — Matthias Miller @ 6:01 pm

Today I was researching ways to provide Mozilla’s DOMContentLoaded functionality in Internet Explorer. Dean Edwards has already demonstrated two ways that this can be done, using the script “defer” trick and behaviors. Both methods require that an external file be included–either “ie_onload.js” or “loaded.htc”.

I need this feature for a JavaScript library, so I specifically want it to be available without forcing the user to include these external files. I also want it to be self-contained so that a developer can drop a single script into a page or site to get the DOMContentedLoaded-like functionality.

Approach A: JavaScript URL

After a lot of hunting, it finally occurred to me that the script’s “src” attribute can reference JavaScript code. The simplest approach is to replace ie_onload.js with a JavaScript URL. Instead of this…

<!--[if IE]><script defer src="ie_onload.js"></script><![endif]-->

…you use this:

<!--[if IE]><script defer src="javascript:'init()'"></script><![endif]-->

Dean Edwards commented on his blog that the “defer” technique only works with external scripts. At first glance, this seems to contradict my findings. But I think he only meant that the “defer” technique requires that the script use the “src” attribute (vs. an inline script) and not that the code must be contained in an external file.

It is important to note that the init function is included in quotes. Internet Explorer will evaluate the JavaScript expression in the src attribute and use the result as the contents of the <script> tag. Because Internet Explorer evaluates the src attribute immediately, the init function must be called from the script itself.

The biggest problem I discovered is that this approach is broken in Internet Explorer 7 Beta 2. However, there is a bug filed for it, so if you’d like to use this, go vote for a fix!

Approach B: Using the script’s onreadystatechange

I also pursued an alternative approach. While I was perusing MSDN’s documentation on the SCRIPT element, I found that it also supports the onreadystatechange event. Because Internet Explorer waits to load these scripts until the DOM has loaded, the page can watch for changes to the script’s readyState. When the script starts loading, the page can trigger DOMContentLoaded initialization.

Even though IE7b2 does not execute the JavaScript in the src attribute, it does trigger onreadystatechange correctly (fortunately!). Unlike the previous example, this code does not need any conditional comments since onreadystatechange will only be called in IE.

Scripts created with createElement do not respect the defer attribute, apparently because they are loaded (although not executed) before the script is attached to the document. Scripts created with innerHTML must be preceded by a node of a certain type, and they appear to suffer the same problems as createElement.

Given these findings, the init.js script would look something like this:

document.write('<script id="__init_script" defer="true" src="//[]"></script>');

function registerInit(callback) {
/* for Mozilla */

if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", callback, false);
}

/* for Internet Explorer */

if (document.getElementById) {
var deferScript = document.getElementById('__init_script');
if (deferScript) {
deferScript.onreadystatechange = function() {
if (this.readyState == 'complete') {
callback();
}
};

/* check whether script has already completed */
deferScript.onreadystatechange();

/* clear reference to prevent leaks in IE */
deferScript = null;
}
}

/* for other browsers */
window.onload = callback;
}

The code in the HTML file would look like this:

function init() {
// quit if this function has already been called
if (arguments.callee.done) return;

// flag this function so we don't do the same thing twice
arguments.callee.done = true;

// do initialization here
};

registerInit(init);

Disclaimer

Although this script seems to work for me, I place no guarantee on it. I know that Dean Edwards has tried more things than I can imagine, so I’ll be slow to call this the final solution. I’m very interested in criticism.

I should also note that this is not the actual code that I will be using in the library. It needs to support multiple event handlers, and it should call the event handlers only once. I have not included these details for simplicity.

Update: I fixed both approaches, which were broken. Although the onreadystatechange approach is not as robust as it could be (it would be nice not to depend on document.write), it does seem to be a step in the right direction.

Update: Applying fixes for HTTPS.

June 5, 2006

JSLint considered harmful?

Filed under: Uncategorized — Matthias Miller @ 8:54 am

Dean Edwards pointed to a comment by Michael Geary regarding JSLint. Although I cannot speak on behalf of JSLint, I can speak on behalf of JavaScript Lint. I’d like to first respond to the specific situations, then to the larger question about whether lints–JavaScript Lint or JSLint–are helpful or harmful.

  • Is eval evil? There’s one situation that JSLint correctly warns against. Sometimes I see code like this to retrieve property values: eval("myobj." + prop), when instead it could simply be myobj[prop]. The problem, of course, is that I use JSON in my code, and I need to use eval.

    JavaScript Lint does not warn against the use of eval.

  • ==/!= vs. ===/!==

    JSLint also prohibits “== null” and “!= null”, but there is a perfectly good use of these notations – the common case where you really don’t care to distinguish between null and undefined, but do wish to distinguish those from other “false” values.

    To be honest, I implemented this warning in JavaScript Lint because JSLint had it. I tend to use strict warnings whenever possible, so I’ll either use if (x === 0) or if (!x), but rarely if (x != null). The argument against the warning is very a good one.

    This warning can be disabled in JavaScript Lint.

  • Required curly braces on all block statements. When I e-mailed Douglas Crockford, proposing a patch that would make this optional, he responded that this is a very common error. For example:

    if (c)
    a;
    b;
    

    I disagree. I work on a 7000-line JavaScript codebase (still growing), and I’ve never encountered that problem. I’ve also worked with a developer who had no prior experience in the Java/C/C++ language family, and I recall no incident of this bug in that developer’s code.

    This is one of few warnings that are disabled by default in JavaScript Lint.

  • Repeated “var” declarations.

    JSLint’s prohibition against repeated “var” declarations can lead to fragile code:

    function foo() {
    for( var i = 0; i < a.length; i++ ) {
    }
    // and later in the function:
    for( i = 0; i < b.length; i++ ) {
    }
    }

    If you later refactor that code, it would be easy to forget to put the “var” back in.

    This warning can be disabled in JavaScript Lint.

    Both lints, if they are enabled to detect undeclared variables, should warn you if you refactor code and forget to put it back in. The “declaration warning” could reasonably be suppressed if you immediately assign a value at the second declaration. The concern, I think, is that you forget that this variable may have been previously assigned.

  • JSLint disallows i++ and i– for no good reason. Rather than warning against all uses of the ++/–, JavaScript Lint only warns when it’s used as part of another statement, such as x = --i or if (i--). It’s not that prefix/postfix increments/decrements are inherently wrong. It’s just that they’re hard to get right when they’re used within an expression.

    This warning can be disabled in JavaScript Lint.

Are lints useful or harmful?

To be useful, the lint must adapt to your code, rather than your code adapting to the lint. I believe that’s the most significant difference between JavaScript Lint and JSLint. Speaking of the curly-brace-blocks setting, Douglas Crockford told me, “Laxity defeats the whole point of a lint program.” To some degree, he’s right. But lint programs must also be very pragmatic.

JavaScript Lint is loaded with configuration options. In fact, every warning mentioned here (besides the eval warning, which JavaScript Lint doesn’t have) can be disabled in JavaScript Lint. Yes, this makes JavaScript Lint more difficult to configure, but these settings are also what allow JavaScript Lint to be run on large codebases.

I don’t use JavaScript Lint as the final sign-off for correct code. I simply use it to find the most common typos, syntax errors, and dumb goofs before I test my page in the browser. I validate my code in three ways:

  • JavaScript Lint, for initial checking. (I also run JSLint during a nightly build process, but I prefer JavaScript Lint when I’m coding because it’s so much faster.)
  • Unit tests, for core reuseable components.
  • Manual tests for integration testing, cross-browser quirks, and such.

Lints can be very useful if they’re used right. But if they’re not, Michael Geary is quite right in calling them harmful.

Create a free website or blog at WordPress.com.