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 &mdash; 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 &mdash; 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 */ };
+}
+&hellip;
+
+// Smooth over browser differences
+var jQuery = &hellip;
+    bindReady: function() {
+                &hellip;
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+                if ( document.addEventListener ) {
+                        document.addEventListener( &hellip; );
+                // If IE event model is used
+                } else if ( document.attachEvent ) {
+                        document.attachEvent( &hellip; );
+                }
+   &hellip;
+
+// 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;
+}
+&hellip;
+</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">
+&hellip;
+
+var jQuery = &hellip;
+    bindReady: function() {
+                &hellip;
+                document.addEventListener( &hellip; );
+   &hellip;
+
+&hellip;
+</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 &rarr;</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 &rarr;</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">
+&lt;script&gt;(function () { // Sanity Check
+var fingerprint = {  // Highly predictive key/value pairs
+  "typeof addEventListener": "function",
+  &hellip;
+};
+for (var k in fingerprint) {
+  try {
+    if (fingerprint[k] === (new Function(k))()) { continue; }
+  } catch (ex) {}
+  document.location = /* Unoptimized version */;
+}
+})()&lt;/script&gt;
+&lt;script src="version-specific-application-code.js"&gt;&lt;/script&gt;
+&hellip;
+</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>

Reply via email to