Revision: 4129
Author: [email protected]
Date: Tue Jun 15 16:13:28 2010
Log: blog post for JSKB
http://code.google.com/p/google-caja/source/detail?r=4129
Added:
/trunk/doc/html/jskb.html
=======================================
--- /dev/null
+++ /trunk/doc/html/jskb.html Tue Jun 15 16:13:28 2010
@@ -0,0 +1,429 @@
+<html>
+<head>
+<title>JSKB — Code that Shrinks</title>
+<style>
+.graph {
+ position: relative;
+ width: 400px;
+ height: 247px;
+ border: 1px solid black;
+}
+
+.bar {
+ position: absolute;
+ float: left;
+ bottom: 2px;
+ border: 2px outset gray;
+}
+
+.legend {
+ position: absolute;
+ top: 0px;
+ right: -16ex;
+ width: 15ex;
+ border: 1px dotted #888;
+ margin: 4px;
+ background-color: white
+}
+
+.color-0, .chunk-0 { background-color: green }
+.color-1, .chunk-1 { background-color: blue }
+.color-2, .chunk-2 { background-color: red }
+.color-3, .chunk-3 { background-color: purple }
+.color-4, .chunk-4 { background-color: cyan }
+.color-5, .chunk-5 { background-color: orange }
+
+.chunk-0, .chunk-1, .chunk-2, .chunk-3, .chunk-4, .chunk-5 {
+ width: 100%; position: absolute;
+}
+
+.color-0, .color-1, .color-2, .color-3, .color-4, .color-5 {
+ width: 10px; height: 10px; display: inline-block; margin: 2px
+}
+
+.item { width: 13ex; display: block }
+
+.title {
+ display: block;
+ width; 100%;
+ text-align: center;
+ font-weight: bold
+}
+
+.xaxis {
+ bottom: -3ex;
+ width: 100%;
+ position: absolute;
+ text-align: center;
+ padding: 2px;
+ font-size: smaller;
+}
+
+.caption {
+ position: absolute;
+ top: -2.5ex;
+ text-align: center;
+ width: 100%;
+ font-size: smaller;
+ font-weight: bold;
+}
+
+address { float: right }
+</style>
+<script>
+function $(id) { return document.getElementById(id); }
+
+function reduce(arr, f, init) {
+ var x = init;
+ for (var i = 0, n = arr.length; i < n; ++i) {
+ x = f(x, arr[i]);
+ }
+ return x;
+}
+
+function bar(values, total, text, out) {
+ function isBar(el) {
+ return !!el.className && /(^| )bar( |$)/.test(el.className);
+ }
+ var idx = 0;
+ for (var c = out.firstChild; c; c = c.nextSibling) {
+ if (isBar(c)) { ++idx; }
+ }
+ function pct(n, t, d) { return (100 * n / t + (d || 0)).toFixed(2)
+ '%'; }
+ var bar = document.createElement('DIV');
+ bar.className = 'bar';
+ var bottom = 0;
+ var valueSum = reduce(values, function (a, b) { return a + b; }, 0);
+ for (var i = 0, n = values.length; i < n; ++i) {
+ var value = values[i];
+ if (!value) { continue; }
+ var chunk = document.createElement('DIV');
+ chunk.className = 'chunk-' + i;
+ chunk.style.height = pct(value, valueSum);
+ chunk.style.bottom = pct(bottom, valueSum);
+ bar.appendChild(chunk);
+ bottom += value;
+ }
+ bar.style.height = pct(bottom, total);
+ out.appendChild(bar);
+
+ if (!valueSum) { bar.style.visibility = 'hidden'; }
+
+ if (text) {
+ var caption = document.createElement('DIV');
+ caption.className = 'caption';
+ caption.appendChild(document.createTextNode(text));
+ bar.appendChild(caption);
+ }
+
+ var nBars = idx + 1;
+ var nSeen = 0;
+ for (var c = out.firstChild; c; c = c.nextSibling) {
+ if (!isBar(c)) { continue; }
+ c.style.left = pct(nSeen++, nBars, .5);
+ c.style.width = pct(1, nBars, -2);
+ }
+}
+
+function legend(titleText, names, out) {
+ var legend = document.createElement('DIV');
+ legend.className = 'legend';
+ var title = document.createElement('DIV');
+ title.className = 'title';
+ title.appendChild(document.createTextNode(titleText));
+ legend.appendChild(title);
+ for (var i = 0, n = names.length; i < n; ++i) {
+ var item = document.createElement('DIV');
+ item.className = 'item';
+ var square = document.createElement('DIV');
+ square.className = 'color-' + i;
+ item.appendChild(square);
+ item.appendChild(document.createTextNode(names[i]));
+ legend.appendChild(item);
+ }
+ legend.style.height = ((names.length * 2) + 3) + 'ex';
+ out.appendChild(legend);
+}
+</script>
+<link type="text/css" rel="stylesheet"
+
href="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css"
+/>
+<script type="text/javascript"
+ src="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js"
+></script>
+</head>
+<body onload=prettyPrint()>
+<address><a href="mailto:[email protected]">Lindsey Simon</a>,
+<a href="mailto:[email protected]">Mike Samuel</a>
+on June 2010</address>
+<h1>JSKB — Code that Shrinks</h1>
+<p>JavaScript development has changed a lot recently.
+High quality libraries let developers do more with less code.
+But they often mean shipping a lot more code.
+
+<div class=graph id=reuse
+ title="Libraries let developers write less code but larger apps"></div>
+<script>(function () {
+ var g = $('reuse');
+ bar([0, 20], 55, 'No Library', g);
+ bar([40, 10], 55, 'Library', g);
+ legend('Code Written', ['Library', 'Developer'], g)
+})()</script>
+
+<p>Lots of compilers
+(<a href="http://www.crockford.com/javascript/jsmin.html"
+ title="Crock's JSMin">JSMin</a>,
+ <a href="http://shrinksafe.dojotoolkit.org/"
+ title="Shrink Safe">Dojo</a>,
+ <a href="http://developer.yahoo.com/yui/compressor/"
+ title="YUI Compressor">YUI</a>,
+ <a href="http://code.google.com/closure/compiler/"
+ title="Closure Compiler">Closure</a>,
+ <a href="http://caja.appspot.com/tools/" title="Caja Web Tools">Caja</a>)
+address that problem by removing unused library code, stripping
+comments, and doing other space optimizations, often getting a 30-60%
+reduction before gzip.</p>
+
+<div class=graph id=minified
+ title="Code minifiers can reduce code size"></div>
+<script>(function () {
+ var g = $('minified');
+ bar([100], 110, 'Normal', g);
+ bar([40], 110, 'Minified', g);
+})()</script>
+
+<p>But libraries need to support a wide range of browsers, and they do
+this by <b>backporting new features</b>, providing <b>abstraction
layers</b>,
+and including code to address obscure <b>browser version specific bugs</b>.
+As browsers implement new features come out, libraries still need to
+ship entirely redundant code that emulates those features. And
+libraries tend to accumulate code to fix bugs that becomes less
+and less relevant as those older browsers' market-share wanes.
+
+<pre class="prettyprint lang-js">
+if (!window.JSON) { // Back port JSON if not implemented natively
+ window.JSON = { /* Snip 400 lines of code */ };
+}
+…
+
+// Smooth over browser differences
+var jQuery = …
+ bindReady: function() {
+ …
+
+ // Mozilla, Opera and webkit nightlies currently support
this event
+ if ( document.addEventListener ) {
+ document.addEventListener( … );
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ document.attachEvent( … );
+ }
+ …
+
+// Work-around browser-specific bugs.
+// On some interpreters, arguments is an array but does not concat
properly.
+var workaroundBuggyConcat = (function () {
+ return arguments instanceof Array
+ && ([].concat(arguments))[0][0] !== 1;
+ })(1, 2);
+if (workaroundBuggyConcat && arr.callee) {
+ var newArr = [];
+ for (var i = arr.length; --i >= 0;) { newArr[i] = arr[i]; }
+ arr = newArr;
+}
+…
+</pre>
+
+<p>Only a bit of this code is needed on newer browsers.
+For example, Firefox3.5 provides a native <code>JSON</code> implementation,
+so this code could be boiled down to:
+<pre class="prettyprint lang-js">
+…
+
+var jQuery = …
+ bindReady: function() {
+ …
+ document.addEventListener( … );
+ …
+
+…
+</pre>
+
+<div class=graph id=shipped-by-kind
+ title="Size increase due to legacy browser support can outstrip increase
due to app features"><div class=xaxis>Time →</div></div>
+<script>(function () {
+ var g = $('shipped-by-kind');
+ bar([60, 15, 5, 5], 120, null, g);
+ bar([65, 17, 7, 7], 120, null, g);
+ bar([65, 19, 9, 9], 120, null, g);
+ bar([70, 21, 11, 11], 120, null, g);
+
+ legend('Code shipped', ['Core', 'Backport', 'Abstract', 'Bug fixes'], g);
+})()</script>
+
+<p>So if we can build smarts into compilers that take into account the
+browser version, as newer browsers become more prevalent, we could see
+shipped code shrinking over time instead. Developers will be able to
+more aggressively take advantage of new library features and new
+platform features without incurring a code-size cost due to legacy
+platforms.
+
+<div class=graph id=with-jskb
+ title="Core app size increases over time as features rollout but browser
specific parts decrease"><div class=xaxis>Time →</div></div>
+<script>(function () {
+ var g = $('with-jskb');
+ bar([60, 15, 2, 3], 120, null, g);
+ bar([65, 10, 3, 3], 120, null, g);
+ bar([65, 7, 4, 2], 120, null, g);
+ bar([70, 5, 5, 2], 120, null, g);
+
+ legend('Code shipped', ['Core', 'Backport', 'Abstract', 'Bug fixes'], g);
+})()</script>
+
+<h2>Making Compilers Version Sensitive</h2>
+<p>The <a href="http://www.browserscope.org/"Browserscope</a> and
+<a href="http://code.google.com/p/google-caja">Caja</a> projects have
+teamed up to provide <a
href="http://www.browserscope.org/jskb/about">JSKB</a>,
+a knowledge base accessible as
+a <a href="http://www.browserscope.org/jskb/json?ua=Firefox+3.5">web
+service</a> that returns JSON containing information about the
+JavaScript environment in which a browser runs.
+
+<p>An optimizer is a program that takes into account knowledge about
+the environment in which a system runs to produce better code. This
+web-service provides compilers with just such information about the
+environment. The Caja Web Tools include just such an <a
href="http://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ancillary/opt/ParseTreeKB.java">optimizer</a>
(<a
href="http://code.google.com/p/google-caja/source/browse/trunk/tests/com/google/caja/ancillary/opt/ParseTreeKBTest.java#428">tests</a>).
+
+<p>A project can compile multiple versions of its app for different
+browsers, and as newer browsers rollout new features, fix bugs and
+gain market-share; you serve more of the smaller version and less of
+the larger.
+
+<p>Obviously, user agent string matching is a black-art, but
+this scheme allows a lot of flexibility in how coarsely one
+groups browsers, and allows easy verification of user-agent
+assumptions. Please see verification and fingerprinting below.
+
+<h2>Feature Testing on Steroids</h2>
+<p>The result of the web-service is a JSON structure like
+<pre class=prettyprint>
+{
+ "!!this.window !== 'undefined' && this === window": true,
+ "typeof addEventListener": "function",
+ "typeof attachEvent": "undefined"
+}
+</pre>
+mapping JavaScript expressions to results. In this case, it looks
+like we're dealing with a browser because <code>this ===
+window</code>, and it looks like it's probably not IE because it
+supports <code>addEventListener</code> but not <code>attachEvent</code>.
+
+<p>With this knowledge, we can optimize:
+<pre class="prettyprint lang-js">if (window.addEventListener) {
+ el.addEventListener("onclick", handler);
+} else if (window.attachEvent) {
+ el.attachEvent("onclick", handler);
+} else {
+ throw new Error("Cannot register events on " + el);
+}</pre>
+to
+<pre class="prettyprint lang-js">el.addEventListener(type, handler);</pre>
+making the code shorter, and eliminating a check every time the code runs.
+
+<h3>Where to draw the line around User-Agents</h3>
+<p>This format for representing knowledge about an environment
+is simple, but it's also composable.
+If we know the following about browser X:
+<pre class=prettyprint>
+{
+ "!!this.window !== 'undefined' && this === window": true,
+ "typeof addEventListener": "function",
+ "typeof attachEvent": "undefined"
+ "typeof document.body.outerHTML": "undefined",
+}
+</pre>
+and the following about browser Y:
+<pre class=prettyprint>
+{
+ "!!this.window !== 'undefined' && this === window": true,
+ "typeof addEventListener": "function",
+ "typeof attachEvent": "object",
+ "addEventListener.length": 3
+}
+</pre>
+then we can figure out what we know about both groups by just
+taking only the (key, value) pairs that are in both:
+<pre class=prettyprint>
+{
+ "!!this.window !== 'undefined' && this === window": true,
+ "typeof addEventListener": "function",
+}
+</pre>
+which lets us group browsers as coarsely or as finely as we like.
+
+<p>One may want to distinguish "new" browsers from "old",
+"A-grade" from "other", or group by code-brance:
+Firefox3+, IE8+, Webkitty, Other.
+
+<h3>Verifiable</h3>
+<p>This format also allows us to verify our assumptions. If you test
+your JavaScript app in various browsers with
+<a href="http://seleniumhq.org/">selenium</a>, your tests can check
+all your environmental assumptions.
+
+<h4>Fingerprinting</h4>
+<p>Some browsers, e.g. Konqueror, change their user-agent. It's
+possible that this user-agent misreporting could break apps on those
+browsers. But an app can check its assumptions before loading code
+and switch over to an unoptimized version on problematic browsers.
+
+<pre class="prettyprint lang-html">
+<script>(function () { // Sanity Check
+var fingerprint = { // Highly predictive key/value pairs
+ "typeof addEventListener": "function",
+ …
+};
+for (var k in fingerprint) {
+ try {
+ if (fingerprint[k] === (new Function(k))()) { continue; }
+ } catch (ex) {}
+ document.location = /* Unoptimized version */;
+}
+})()</script>
+<script src="version-specific-application-code.js"></script>
+…
+</pre>
+
+
+<h2>Agile Rollouts</h2>
+<p>Imagine you've just read about a zero-day vulnerability affecting a
+specific older browser version. You whip out a quick patch, but you
+can't deploy to production because the code change could affect
+main-stream browser versions.
+
+<p>If you generate different source versions for different browsers,
+you can add the feature test you use to enable your patch to your
+knowledge base. If the compiled code doesn't change for mainstream
+versions you can be sure that rolling out your patch for a minor
+browser will not affect the experience of the majority of your users.
+
+<p>Partitioning browsers can let you be agile in responding to
+threats that require faster action than a full test and release
+cycle, by decoupling changes that affect one browser group from
+those affecting others.
+
+<h2>Security Concerns</h2>
+<p>Browserscope is a great way to collect information about browsers
+useful to the web as a whole, but it requires browsers to self-report.
+As such, it is subject to spamming. This means that projects that
+rely on security checks being present wherever they are relevant
+should be suspicious of browser groups with small numbers of tests.
+Such projects should use tools like selenium to generate and/or verify
+their own knowledge bases.
+
+<p>The problem of verifying that a user-agent is accurately reporting
+its internal state is a continuing area of research in computer science
+and is not practical with current web technology.
+
+</body>
+</html>