惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
雷峰网
雷峰网
The Register - Security
The Register - Security
The Cloudflare Blog
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
I
InfoQ
博客园 - 三生石上(FineUI控件)
H
Help Net Security
博客园 - 司徒正美
Vercel News
Vercel News
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
B
Blog
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
L
LangChain Blog
人人都是产品经理
人人都是产品经理
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recorded Future
Recorded Future
小众软件
小众软件
Martin Fowler
Martin Fowler
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Apple Machine Learning Research
Apple Machine Learning Research
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
Y
Y Combinator Blog
V
Visual Studio Blog
F
Fortinet All Blogs
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
V
V2EX
Blog — PlanetScale
Blog — PlanetScale

Henri Sivonen’s pages

Parin vuoden tutkimattomuus crates.io: Rust Package Registry Asiakirjatonta toimintaa It’s not wrong that "🤦🏼‍♂️".length == 7 Koulutartuntojen tilastointimenettely Perusteasiakirjoja hallussapitämättä ikärajoitettu Asiantuntijat ja nukkuva vallan vahtikoira Koronapassilausunto Suppealla tietopohjalla ohimeneväksi väitetty Text Encoding Menu in 2021 The Text Encoding Submenu Is Gone An HTML5 Conformance Checker Not Part of the Technology Stack Browser Technology Stack Bogo-XML Declaration Returns to Gecko A Look at Encoding Detection and Encoding Menu Telemetry from Firefox 86 Why Supporting Unlabeled UTF-8 in HTML on the Web Would Be Problematic Rust Target Names Aren’t Passed to LLVM Toimintamalli Activating Browser Modes with Doctype Johtopäätöksiä mallin rakenteesta Tehtävänmäärittelyä kirjoittamatta ja kuolemia laskematta laumasuojamallinnettu Character Encoding Menu in 2014 Erillissuosituksen tarpeettomuudesta yleissuosituksen poikkeukseksi? STM:n maskiaikajana Rust 2021 Oma-aloitteisesti mallinnettu Kokopinovaatimuksin kilpailutettu chardetng: A More Compact Character Encoding Detector for the Legacy Web Varauksia paisutellen tiedotettu Perusteasiakirjoitta tiedotettu Always Use UTF-8 & Always Label Your HTML Saying So IME Smoke Testing The Validator.nu HTML Parser About the Hiragino Fonts with CSS It’s Time to Stop Adding New Features for Non-Unicode Execution Encodings in C++ Rust 2020 The Last of the Parsing Quirks About about:blank Rust 2019 a Web-Compatible Character Encoding Library in Rust How I Wrote a Modern C++ Library in Rust Using cargo-fuzz to Transfer Code Review of Simple Safe Code to Complex Code that Uses unsafe A Rust Crate that Also Quacks Like a Modern C++ Library #Rust2018 No Namespaces in JSON, Please A Lecture about HTML5 Julkisesti luotettu varmenne ikidomainille TLS:ää (SSL:ää) varten -webkit-HTML5 Lists in Attribute Values The Sad Story of PNG Gamma “Correction” If You Want Software Freedom on Phones, You Should Work on Firefox OS, Custom Hardware and Web App Self-Hostablility HTML5 Parser Improvements ARIA in HTML5 Integration: Document Conformance (Draft, Take Two) Schema.org and Pre-Existing Communities Lowering memory requirements by replacing Schematron HTML5 Parsing in Gecko: A Build Introducing SAX Tree NVDL Support in Validator.nu HOWTO Avoid Being Called a Bozo When Producing XML An Unofficial Q&A about the Discontinuation of the XHTML2 WG Thoughts on HTML5 Becoming a W3C Recommendation Four Finnish Banks Training Users to Give Banking Credentials to Another Site Unimpressed by Leopard Sergeant Semantics The Content Sink Inheritance Diagram – 2006-06-30 What is EME? About Points and Pixels as Units The Performance Cost of the HTML Tree Builder Social Media Impression Management The spacer Element Is Gone Openmind 2006 Performance Mistake XHTML and Mobile Devices WebM-Enabled Browser Usage Share Exceeds H.264-Enabled Browser Usage Share on Desktop (in StatCounter Numbers) HTML5 Parser-Based View Source Syntax Highlighting Vendor Prefixes Are Hurting the Web Accept-Charset Is No More Dualroids Writing Structural Stylable Document in Mozilla Editor ISO-8859-15 on haitallinen Hourglass The Scientific Method According to Hixie Maemo Source Code Karpelan lukkovertaus ontuu Digitaalisesta arkistoinnista ARIA in HTML5 Integration: Document Conformance (Draft) XHTML—What’s the Point? (Draft, incomplete) Mac OS X Browser Comparison HOWTO Spot a Wannabe Web Standards Advocate An Idea About Intermediate Language Trees and Web UI Generation Thoughts on Using SSL/TLS Certificates as the Solution to Phishing Bureaucracy Meets the Web Europe Day HOWTO Establish a 100% Literacy Rate What to Do with All These Photos? Charmod Norm Checking Validator Web Service Interface Ideas DTDs Don’t Work on the Web EFFI’s Day in Court
HTML5 Script Execution Changes in Firefox 4
Henri Sivonen · 2011-12-22 · via Henri Sivonen’s pages

In Firefox 4, script execution changed to be more HTML5-compliant than before. This means that in some cases sites that sniff for Firefox or Gecko may break.

If you use LABjs

You should update to LABjs 1.0.4 (or later).

If you use the “order” plug-in for RequireJS

You should update to RequireJS 0.15.0 (or later).

If you use the multi-file version OpenLayers

You should switch to the single-file version of OpenLayers. (The upcoming 2.11 release is expected to fix the multi-file version.)

If your site/app works cross-browser without browser sniffing…

You don’t need to read further.

(However, if you triage bugs on bugzilla.mozilla.org, you might still want to read on.)

Note: This post is about when and how the script loader decides to evaluate scripts. This isn’t about what happens in the JavaScript engine once the script evaluation starts. Nothing discussed here has anything to do with JavaScript engine changes in Firefox 4.

Terminology

To be able to discuss this topic precisely, let’s define some terminology.

External script

An HTML script element that has the src attribute or an SVG script element that has the href attribute in the XLink namespace.

Inline script

An HTML or SVG script element that is not an external script.

Parser-inserted script

An HTML or SVG script element that has been instantiated by HTML parser parsing a script tag from source code coming from the network stream or document.write(), by the XML parser parsing a script tag from source code coming from the network stream or by the XSLT processor when performing a transformation triggered by <?xml-stylesheet?> or by XSLTProcessor.transformToDocument().

A parser-inserted script can lose its parser-insertedness and become a script-inserted script if the script doesn’t start when the parser tries to run the script. This happens if the script is not in a document at that time or if the script is an inline script that doesn’t have non-whitespace text content.

Script-inserted script

An HTML or SVG script element that is not parser-inserted as defined above (including scripts created using Range.createContextualFragment() and XSLTProcessor.transformToFragment()).

Async script

An external script whose .async DOM property getter returns true.

Defer script

A parser-inserted external script that is not an async script and whose .defer DOM property getter returns true.

Running a script

The process of attempting to start a script. This may involve fetching the script text over the network before executing the script. (Blame the HTML5 spec for giving distinct meanings to “run” and “execute”.) Parser-inserted scripts are run when the parser processes the </script> end tag. Script-inserted scripts are run when the script element node is inserted into a document.

Executing a script

The act of passing the script text to the JavaScript engine and asking the JavaScript engine to evaluate the text as JavaScript.

The Old Firefox Behavior

The old Firefox behavior was to execute non-async, non-defer scripts in the order in which they were run. (See above for the definitions of “execute” and “run”.) This may seem logical, but it caused some problems, because it meant that:

  1. Script-inserted inline scripts didn’t execute synchronously if there were external scripts being fetched. This caused undesirable effects when inserting an inline script in order to evaluate some JavaScript in the global scope and there were pending external script fetches. (E.g. jQuery uses this method of evaluating JavaScript text in the global scope.) In IE and WebKit, this trick for evaluating some text as JavaScript in the global scope would always evaluate the script text synchronously.

  2. Script-inserted scripts could block the parser upon the next parser-inserted script. This meant that sites that tried to use script-inserted external scripts instead of parser-inserted external scripts as a performance trick to overlap script fetching and parsing wouldn’t get the performance benefit they got in IE and WebKit if there was a subsequent parser-inserted script in their document.

  3. Script-inserted external non-async, non-defer scripts executed in the order they were inserted into the document.

The last point is actually desirable in some cases.

The Opera Behavior

Opera behaves like Firefox 3.6 except script-inserted external scripts block the parser even if there are no subsequent parser-inserted scripts.

The Old IE/WebKit Behavior

In IE and in Safari releases (but no longer in WebKit nightlies), the behavior for script-inserted scripts is as follows:

  1. Script-inserted inline scripts are executed synchronously upon insertion.

  2. Script-inserted external scripts are executed as soon as the script file has been fetched. This means that script-inserted external scripts can execute in any order depending on network conditions.

  3. If a script inserts an external script with a bogus MIME type in the type attribute, the external file is fetched and the onload event is fired for it, but the file isn’t executed as a script.

The HTML5 Behavior

HTML5 standardizes the intersection of the guarantees provided by the legacy versions of the top four browser engines. That is, the HTML5 behavior is like the old IE/WebKit behavior except scripts with bogus types aren’t fetched.

Specifically, the HTML5 behavior is as follows when the interaction with pending style sheet loads is omitted from discussion for simplicity:

  • Defer scripts (which can only be external and parser-inserted per terminology above) execute when the parser has processed the entire input stream. The defer scripts execute in the order in which they appeared in the input stream.

  • Parser-inserted async scripts and script-inserted external scripts don’t block anything and aren’t blocked by anything. They execute as soon as the script file has been fetched.

  • Script-inserted inline scripts execute syncronously when inserted. As a result, they don’t block on anything and can’t be blocked by anything.

  • Script-inserted external scripts execute as soon as they become available from the network by default.

  • Script-inserted external scripts whose .async DOM property returns false execute in the insertion order relative to other such scripts. (The default is true. Hence, the default behavior mentioned in the previous bullet point.)

  • Each parser-inserted non-async, non-defer scripts blocks the parser until they have been executed. Since the parser doesn’t have a chance to insert the next script until the previous one is done, it follows that parser-inserted non-async, non-defer scripts execute in the order their end tags appear in the input stream.

This is fine, because it is the simplest and the most performant solution that is compatible with existing sites that are compatible with existing browsers without browser sniffing. After all, cross-browser-compatible sites (that don’t sniff) can’t rely on characteristics of script loading that aren’t provided by all browsers.

Firefox 4 implements the HTML5 behavior. The main reason for making the change now in Firefox was that the interaction of the old Firefox behavior and HTML5-compliant document.write() was problematic.

The Problem

So the HTML5 behavior works for existing sites that use Same Markup (to use Microsoft’s term) for all browsers. The problem is that some sites don’t. That is, sites that sniff for Firefox/Gecko (and Opera) can break.

So far, I’ve seen three classes of sniffing:

  1. Sniffing for Firefox/Gecko and Opera and running HTML5-incompatible code in Firefox and Opera and cross-browser / HTML5-compatible code otherwise.

  2. Sniffing for IE and WebKit and running and cross-browser / HTML5-compatible code in IE and WebKit and running HTML5-incomptible code otherwise.

  3. Sniffing for Firefox/Gecko and Opera and running HTML5-incompatible code in Firefox and Opera and differently HTML5-incompatible code that works in IE and old WebKit otherwise. (Seen in the LABjs library (in versions earlier than 1.0.4) and in the “order” plug-in for the RequireJS library in (in versions earlier that 0.15.0). LABjs 1.0.4 and RequireJS 0.15.0 has been updated to support Firefox nightlies.)

Note that the third kind of sniffing breaks in WebKit nightlies, too.

The Solutions

If you want to add an external script from a script during the parse and you want the script to block subsequent scripts appearing in the network stream…

Use document.write("\u003Cscript src='foo.js'>\u003C/script>");. This solution is cross-browser-compatible without sniffing. If you write multiple scripts at once, Firefox 4 downloads the scripts in parallel, so concern about parallel downloads is no longer a reason to avoid document.write in Firefox.

Alternatively, you could use plain <script> tags in the HTML source transferred over HTTP. This way Firefox (and other browsers) that implement speculative script loading will start fetching the scripts even earlier and will load the scripts in parallel.

If you want to run a script-inserted inline script after a script-inserted external script…

Set an onload event handler on the external script and insert the inline script from the event handler. This solution is cross-browser-compatible without sniffing.

If you want to load multiple interdependent script-inserted external scripts in parallel after the HTML parser has finished (and onload has fired) and want them to execute in a certain order…

In Firefox 4, in compliance with the latest HTML5 drafts, the .async DOM property returns true for script-inserted scripts by default. However, script-inserted external scripts whose .async DOM property returns false execute in the insertion order relative to other such scripts.

Thus, you can feature-detect if document.createElement("script").async evaluates to true and assume that if it does, setting .async=false on multiple script-inserted external scripts makes those scripts execute in the insertion order.

To make your code work robustly and as performantly as possible in both Firefox 4 and Firefox 3.6, I encourage you not only to set .async=false for script-inserted external scripts that you want to execute in the insertion order but also to explicitly set .async=true on script-inserted external scripts that don’t have ordering dependencies.

Unfortunately, this solution won’t work cross-browser until other browser implement this behavior, so for the time being, if document.createElement("script").async does not evaluate to true, you need to fall back onto whatever sniffing-based code you had (which probably fails with WebKit trunk) or onto fetching scripts one by one.

Note: Be sure to have the latest beta or nightly for testing your code.