<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>John McCutchan on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>John McCutchan</name>
  </author>
  <link href="https://web.dev/authors/johnmccutchan/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hYFvb82T2jrdglYrC0o9.png?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>Developer Relations at Google</subtitle>
  
  
  <entry>
    <title>Use forensics and detective work to solve JavaScript performance mysteries</title>
    <link href="https://web.dev/performance-mystery/"/>
    <updated>2013-06-13T00:00:00Z</updated>
    <id>https://web.dev/performance-mystery/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In recent years, web applications have been sped up considerably. Many applications now run fast enough that I&#39;ve heard some developers wonder aloud &amp;quot;is the web fast enough?&amp;quot;. For some applications it may be, but, for the developers working on high performance applications, we know it is not fast enough. Despite the amazing advances in JavaScript virtual machine technology, a &lt;a href=&quot;https://docs.google.com/a/google.com/document/d/1k8d4SsYJoCfmw6Te8Ijf3WEyotNHp6YMy1PEgn_o5Yg/edit&quot; rel=&quot;noopener&quot;&gt;recent study&lt;/a&gt; showed that Google applications spend between 50% and 70% of their time inside &lt;a href=&quot;https://code.google.com/p/v8/&quot; rel=&quot;noopener&quot;&gt;V8&lt;/a&gt;. Your application has a finite amount of time, shaving cycles off of one system means another system can do more. Remember, applications running at 60fps only have 16ms per frame or else - &lt;a href=&quot;http://jankfree.org/&quot; rel=&quot;noopener&quot;&gt;jank&lt;/a&gt;. Read on, to learn about optimizing JavaScript and profile JavaScript applications, in a from the trenches story of the performance detectives on the V8 team tracking down an obscure performance problem in &lt;a href=&quot;http://www.findyourwaytooz.com/&quot; rel=&quot;noopener&quot;&gt;Find Your Way to Oz&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;google-io-2013-session&quot;&gt;Google I/O 2013 Session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#google-io-2013-session&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I presented this material at Google I/O 2013. Check out the video below:&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;VhpdsjBUS3g&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;h2 id=&quot;why-does-performance-matter&quot;&gt;Why does performance matter? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#why-does-performance-matter&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CPU cycles are a zero sum game. Making one part of your system use fewer allows you to use more in another or run smoother overall. Running faster and doing more are often competing goals. Users demand new features while also expecting your application to run smoother. JavaScript virtual machines keep getting faster but that is not a reason for ignoring performance problems that you can fix today, as the many developers, dealing with performance problems in their web applications already know. In real-time, high frame rate, applications the pressure to be jank free is paramount. &lt;a href=&quot;http://www.insomniacgames.com/&quot; rel=&quot;noopener&quot;&gt;Insomniac Games&lt;/a&gt; produced a &lt;a href=&quot;http://www.eurogamer.net/articles/insomniac-60fps-no-more&quot; rel=&quot;noopener&quot;&gt;study&lt;/a&gt; which showed that a solid, sustained frame rate is important to the success of a game: &amp;quot;A solid frame-rate is still a sign of professional, well-made product.&amp;quot; Web developers take note.&lt;/p&gt;
&lt;h2 id=&quot;solving-performance-problems&quot;&gt;Solving Performance Problems &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#solving-performance-problems&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Solving a performance problem is like solving a crime. You need to carefully examine the evidence, check suspected causes, and experiment with different solutions. All along the way you must document your measurements so that you can be sure you&#39;ve actually fixed the problem. There is very little difference between this method and how criminal detectives crack a case. Detectives examine evidence, interrogate suspects, and run experiments hoping to find the smoking gun.&lt;/p&gt;
&lt;h2 id=&quot;v8-csi-oz&quot;&gt;V8 CSI: Oz &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#v8-csi-oz&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The amazing wizards building &lt;a href=&quot;http://www.findyourwaytooz.com/&quot; rel=&quot;noopener&quot;&gt;Find Your Way to Oz&lt;/a&gt; approached the V8 team with a performance problem they couldn&#39;t solve on their own. Occasionally Oz would freeze, causing jank. The Oz developers had done some initial investigation using the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/performance-reference&quot; rel=&quot;noopener&quot;&gt;Timeline Panel&lt;/a&gt; in &lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome DevTools&lt;/a&gt;. Looking at memory usage they encountered the dreaded &lt;a href=&quot;http://en.wikipedia.org/wiki/Sawtooth_wave&quot; rel=&quot;noopener&quot;&gt;saw tooth&lt;/a&gt; graph. Once per second the garbage collector was collecting 10MB of garbage and the garbage collection pauses corresponded with the jank. Similar to the following screenshot from the Timeline in Chrome Devtools:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Devtools timeline&quot; decoding=&quot;async&quot; height=&quot;81&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 467px) 467px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/zYrtkDoYJZOYGuSg7WVb.png?auto=format&amp;w=934 934w&quot; width=&quot;467&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The V8 detectives, Jakob and Yang took up the case. What took place was a long back and forth between Jakob and Yang from the V8 team and the Oz team. I&#39;ve distilled this conversation down to the important events that helped track down this problem.&lt;/p&gt;
&lt;h2 id=&quot;evidence&quot;&gt;Evidence &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#evidence&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first step is to collect and study the initial evidence.&lt;/p&gt;
&lt;h3 id=&quot;what-type-of-application-are-we-looking-at&quot;&gt;What type of application are we looking at? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#what-type-of-application-are-we-looking-at&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Oz demo is an interactive 3D application.  Because of this, it is very sensitive to pauses caused by garbage collections. Remember, an interactive application running at &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/speed/rendering/&quot; rel=&quot;noopener&quot;&gt;60fps has 16ms to do all JavaScript work and must leave some of that time for Chrome to process the graphics calls and draw the screen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Oz performs a lot of arithmetic computation on double values and makes frequent calls to WebAudio and WebGL.&lt;/p&gt;
&lt;h3 id=&quot;what-kind-of-performance-problem-are-we-seeing&quot;&gt;What kind of performance problem are we seeing? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#what-kind-of-performance-problem-are-we-seeing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We are seeing pauses aka frame drops aka jank. These pauses correlate with garbage collection runs.&lt;/p&gt;
&lt;h3 id=&quot;are-the-developers-following-best-practices&quot;&gt;Are the developers following best practices? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#are-the-developers-following-best-practices&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Yes, the Oz developers are well versed in JavaScript VM performance and optimization techniques. It&#39;s worth noting that the Oz developers were using &lt;a href=&quot;http://coffeescript.org/&quot; rel=&quot;noopener&quot;&gt;CoffeeScript&lt;/a&gt; as their source language and producing JavaScript code via the CoffeeScript compiler. This made some of the investigation trickier because of the disconnect between the code being written by the Oz developers and the code being consumed by V8. Chrome DevTools now supports &lt;a href=&quot;http://net.tutsplus.com/tutorials/tools-and-tips/source-maps-101/&quot; rel=&quot;noopener&quot;&gt;source maps&lt;/a&gt; which would have made this easier.&lt;/p&gt;
&lt;h3 id=&quot;why-does-the-garbage-collector-run&quot;&gt;Why does the garbage collector run? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#why-does-the-garbage-collector-run&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Memory in JavaScript is automatically managed for the developer by the VM. V8 uses a common garbage collection system where memory is divided into two (or more) &lt;a href=&quot;http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Generational_GC_.28ephemeral_GC.29&quot; rel=&quot;noopener&quot;&gt;generations&lt;/a&gt;. The young generation holds objects that have recently been allocated. If an object survives long enough it is moved to the old generation.&lt;/p&gt;
&lt;p&gt;The young generation is collected at a much higher frequency than the old generation is collected. This is by design, as young generation collection is much cheaper. It is often safe to assume that frequent GC pauses are caused by young generation collection.&lt;/p&gt;
&lt;p&gt;In V8 the young memory space is divided into two equally sized contiguous blocks of memory. Only one of these two blocks of memory is in use at any given time and it is called the to space. While there is remaining memory in the to space, allocating a new object is cheap. A cursor in the to space is moved forward the number of bytes needed for the new object. This continues until the to space is exhausted. At this point the program is stopped and collection begins.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;V8 young memory&quot; decoding=&quot;async&quot; height=&quot;346&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 545px) 545px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/pR8BlH788T0cNWdO8Yls.png?auto=format&amp;w=1090 1090w&quot; width=&quot;545&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;At this point the from space and to space are swapped. What was the to space and is now the from space, is scanned from beginning to end and any objects which are still alive are copied into the to space or are promoted to the old generation heap. If you want details, I suggest you read about &lt;a href=&quot;http://en.wikipedia.org/wiki/Cheney&#39;s_algorithm&quot; rel=&quot;noopener&quot;&gt;Cheney&#39;s Algorithm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Intuitively you should understand that every time an object is allocated either implicitly or explicitly (via a call to new, [], or {}) your application is moving closer and closer to a garbage collection and the dreaded application pause.&lt;/p&gt;
&lt;h3 id=&quot;is-10mbsec-of-garbage-expected-for-this-application&quot;&gt;Is 10MB/sec of garbage expected for this application? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#is-10mbsec-of-garbage-expected-for-this-application&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In short, no. The developer is not doing anything to expect 10MB/sec of garbage.&lt;/p&gt;
&lt;h2 id=&quot;suspects&quot;&gt;Suspects &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#suspects&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The next phase of the investigation is to determine potential suspects and then whittle them down.&lt;/p&gt;
&lt;h3 id=&quot;suspect-#1&quot;&gt;Suspect #1 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#suspect-#1&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Calling new during the frame. Remember that each object that is allocated moves you ever closer to a GC pause.  Applications running at high frame rates in particular should strive for zero allocations per frame. Usually this requires a carefully thought out, application specific, object recycling system. The V8 detectives checked with the Oz team and they were not calling new. In fact the Oz team was already well aware of this requirement and said &amp;quot;That would be embarrassing&amp;quot;. Scratch this one off the list.&lt;/p&gt;
&lt;h3 id=&quot;suspect-#2&quot;&gt;Suspect #2 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#suspect-#2&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Modifying the &amp;quot;shape&amp;quot; of an object outside of the constructor. This happens whenever a new property is added to an object outside of the constructor.  This creates a new &lt;a href=&quot;https://developers.google.com/v8/design&quot; rel=&quot;noopener&quot;&gt;hidden class&lt;/a&gt; for the object. When optimized code sees this new hidden class a deopt will be triggered, unoptimized code will execute until the code is classified as hot and optimized again. This de-optimization,re-optimization churn will result in jank but does not strictly correlate with excessive garbage creation. After a careful audit of the code, it was confirmed that object shapes were static, thus suspect #2 was ruled out.&lt;/p&gt;
&lt;h3 id=&quot;suspect-#3&quot;&gt;Suspect #3 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#suspect-#3&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Arithmetic in unoptimized code. In unoptimized code all computation results in actual objects being allocated. For example, this snippet:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; d&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; c &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; c &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3.3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;point&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Results in 5 HeapNumber objects being created. The first three are for the variables, a, b, and c. The 4th is for the anonymous value (a * b) and the 5th is from #4 * c; The 5th is ultimately assigned to point.x.&lt;/p&gt;
&lt;p&gt;Oz does thousands of these operations per frame. If any of these computations occur in functions which are never optimized, they could be the cause of the garbage. Because computations in unoptimized allocate
memory even for temporary results.&lt;/p&gt;
&lt;h3 id=&quot;suspect-#4&quot;&gt;Suspect #4 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#suspect-#4&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Storing a double precision number to a property. A HeapNumber object must be created to store the number and the property altered to point at this new object. Altering the property to point at the HeapNumber will not produce garbage. However, it is possible that there are many double precision numbers being stored as object properties. The code is full of statements like the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;sprite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In optimized code, every time x is assigned a freshly computed value, a seemingly innocuous statement, a new HeapNumber object is implicitly allocated, bringing us closer to a garbage collection pause.&lt;/p&gt;
&lt;p&gt;Note that by using a &lt;a href=&quot;http://www.khronos.org/registry/typedarray/specs/latest/&quot; rel=&quot;noopener&quot;&gt;typed array&lt;/a&gt; (or a regular array which only has held doubles) you can avoid this specific problem entirely as the storage for the double precision number is allocated only once and repeatedly changing the value does not require new storage to be allocated.&lt;/p&gt;
&lt;p&gt;Suspect #4 is a possibility.&lt;/p&gt;
&lt;h2 id=&quot;forensics&quot;&gt;Forensics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#forensics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point the detectives have two possible suspects: storing heap numbers as object properties and arithmetic computation happening inside unoptimized functions. It was time to head to the lab and determine definitively which suspect was guilty. NOTE: In this section I will be using a reproduction of the problem found in the actual Oz source code. This reproduction is orders of magnitude smaller than the original code, thus easier to reason about.&lt;/p&gt;
&lt;h3 id=&quot;experiment-#1&quot;&gt;Experiment #1 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#experiment-#1&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Checking for suspect #3 (arithmetic computation inside unoptimized functions). The V8 JavaScript engine has a logging system builtin which can provide great insight into what is happening under the hood.&lt;/p&gt;
&lt;p&gt;Starting with Chrome not running at all, launching Chrome with the flags:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;--no-sandbox --js-flags&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;--prof --noprof-lazy --log-timer-events&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;and then fully quitting Chrome will result in a v8.log file in the current directory.&lt;/p&gt;
&lt;p&gt;In order to interpret the contents of v8.log, you must &lt;a href=&quot;https://code.google.com/p/v8/wiki/Source&quot; rel=&quot;noopener&quot;&gt;download&lt;/a&gt; the same version of v8 that your Chrome is using (check about:version), and &lt;a href=&quot;https://developers.google.com/v8/build&quot; rel=&quot;noopener&quot;&gt;build it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After successfully building v8, you can process the log using the tick processor:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ tools/linux-tick-processor /path/to/v8.log&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;(Substitute mac or windows for linux depending on your platform.)
(This tool must be run from the top level source directory in v8.)&lt;/p&gt;
&lt;p&gt;The tick processor displays a text based table of JavaScript functions which had the most ticks:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;JavaScript&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;:&lt;br /&gt;ticks  total  nonlib   name&lt;br /&gt;&lt;span class=&quot;token number&quot;&gt;167&lt;/span&gt;   &lt;span class=&quot;token number&quot;&gt;61.2&lt;/span&gt;%   &lt;span class=&quot;token number&quot;&gt;61.2&lt;/span&gt;%  LazyCompile: *opt demo.js:12&lt;br /&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;   &lt;span class=&quot;token number&quot;&gt;14.7&lt;/span&gt;%   &lt;span class=&quot;token number&quot;&gt;14.7&lt;/span&gt;%  LazyCompile: unopt demo.js:20&lt;br /&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;5.5&lt;/span&gt;%    &lt;span class=&quot;token number&quot;&gt;5.5&lt;/span&gt;%  Stub: KeyedLoadElementStub&lt;br /&gt; &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;4.8&lt;/span&gt;%    &lt;span class=&quot;token number&quot;&gt;4.8&lt;/span&gt;%  Stub: BinaryOpStub_MUL_Alloc_Number+Smi&lt;br /&gt;  &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;2.2&lt;/span&gt;%    &lt;span class=&quot;token number&quot;&gt;2.2&lt;/span&gt;%  Stub: BinaryOpStub_ADD_OverwriteRight_Number+Number&lt;br /&gt;  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;1.5&lt;/span&gt;%    &lt;span class=&quot;token number&quot;&gt;1.5&lt;/span&gt;%  Stub: KeyedStoreElementStub&lt;br /&gt;  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;1.5&lt;/span&gt;%    &lt;span class=&quot;token number&quot;&gt;1.5&lt;/span&gt;%  KeyedLoadIC:  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;0.7&lt;/span&gt;%    &lt;span class=&quot;token number&quot;&gt;0.7&lt;/span&gt;%  KeyedStoreIC:  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;0.4&lt;/span&gt;%    &lt;span class=&quot;token number&quot;&gt;0.4&lt;/span&gt;%  LazyCompile: ~main demo.js:30&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can see demo.js had three functions: opt, unopt, and main. Optimized functions have an asterisk (*) next to their names. Observe that the function opt is optimized and unopt is unoptimized.&lt;/p&gt;
&lt;p&gt;Another important tool in the V8 detective&#39;s tool bag is plot-timer-event. It can be executed like so:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ tools/plot-timer-event /path/to/v8.log&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;After being run, a png file called timer-events.png is in the current directory. Opening it up you should see something that looks like this:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Timer events&quot; decoding=&quot;async&quot; height=&quot;300&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ycghUWKsSE0VSyih8QmE.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Aside from the graph along the bottom, data is displayed in rows. The X axis is time (ms). The left hand side includes labels for each row:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Timer events Y axis&quot; decoding=&quot;async&quot; height=&quot;568&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 524px) 524px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/PrC9GkRVxrrpdKPGAxh7.png?auto=format&amp;w=1048 1048w&quot; width=&quot;524&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The V8.Execute row has black vertical line drawn on it at each profile tick where V8 was executing JavaScript code. V8.GCScavenger has a blue vertical line drawn on it at each profile tick where V8 was performing a new generation collection. Similarly for the rest of the V8 states.&lt;/p&gt;
&lt;p&gt;One of the most important rows is the &amp;quot;code kind being executed&amp;quot;. It will be green whenever optimized code is executing and mix of red and blue when unoptimized code is being executed. The following screenshot shows the transition from optimized to unoptimized and then back to optimized code:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Code kind being executed&quot; decoding=&quot;async&quot; height=&quot;43&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/u24agPudgPibL91vBf6E.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Ideally, but never immediately, this line will be solid green. Meaning that your program has transitioned into an optimized steady state. Unoptimized code will always run slower than optimized code.&lt;/p&gt;
&lt;p&gt;If you&#39;ve gone to this length it&#39;s worth noting that you can work much quicker by refactoring your application so that it can run in the v8 debug shell: d8. Using d8 gives you faster iteration times with the tick-processor and plot-timer-event tools. Another side effect of using d8 is that it becomes easier to isolate actual problem, reducing the amount of noise present in the data.&lt;/p&gt;
&lt;p&gt;Looking at the timer events plot from the Oz source code, showed a transition from optimized to unoptimized code and, while executing unoptimized code many new generation collections were triggered, similar to the following screenshot (note time has been removed in the middle):&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Timer events plot&quot; decoding=&quot;async&quot; height=&quot;325&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 333px) 333px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/svYDqOn9SLBhh3nETaL4.png?auto=format&amp;w=666 666w&quot; width=&quot;333&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;If you look closely you can see that the black lines indicating when V8 is executing JavaScript code are missing at precisely the same profile tick times as the new generation collections (blue lines). This demonstrates clearly that while garbage is being collected, the script is paused.&lt;/p&gt;
&lt;p&gt;Looking at the tick processor output from the Oz source code, the top function (updateSprites) was not optimized. In other words, the function in which the program spent the most time was also unoptimized. This strongly indicates that suspect #3 is the culprit. The source for updateSprites contained loops that looked like these:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateSprites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;dt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sprite &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; sprites&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        sprite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// 20 more lines of arithmetic computation.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Knowing V8 as well as they do, they immediately recognized that the for-i-in loop construct is sometimes not optimized by V8. In other words, if a function contains a for-i-in loop construct, it may not be optimized. This is a special case today, and will likely change in the future, that is, V8 may one day optimize this loop construct. Since we aren&#39;t V8 detectives and don&#39;t know V8 like the back of our hands, how can we determine why updateSprites was not optimized?&lt;/p&gt;
&lt;h3 id=&quot;experiment-#2&quot;&gt;Experiment #2 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#experiment-#2&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Running Chrome with this flag:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;--js-flags&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;--trace-deopt --trace-opt-verbose&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;displays a verbose log of optimization and deoptimization data. Searching through the data for updateSprites we find:&lt;/p&gt;
&lt;p&gt;[disabled optimization for updateSprites, reason: ForInStatement is not fast case]&lt;/p&gt;
&lt;p&gt;Just as the detectives hypothesized, the for-i-in loop construct was the reason.&lt;/p&gt;
&lt;h2 id=&quot;case-closed&quot;&gt;Case Closed &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#case-closed&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After discovering the reason updateSprites was not optimized, the fix was simple, simply move the computation into its own function, that is:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateSprite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;sprite&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    sprite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; dt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// 20 more lines of arithmetic computation.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateSprites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;dt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sprite &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; sprites&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;updateSprite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sprite&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;updateSprite will be optimized, resulting in far fewer HeapNumber objects, resulting in less frequent GC pauses. It should be easy for you to confirm this by performing the same experiments with new code.  The careful reader will notice that double numbers are still being stored as properties. If profiling indicates it is worth it, changing position to be an array of doubles or a typed data array would further reduce the number of objects being created.&lt;/p&gt;
&lt;h2 id=&quot;epilogue&quot;&gt;Epilogue &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/performance-mystery/#epilogue&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Oz developers didn&#39;t stop there. Armed with the tools and techniques shared with them by the V8 detectives, they were able to find a few other functions that were stuck in deoptimization hell and factored the computation code into leaf functions which were optimized, resulting in even better performance.&lt;/p&gt;
&lt;p&gt;Get out there and start solving some performance crimes!&lt;/p&gt;
</content>
    <author>
      <name>John McCutchan</name>
    </author>
  </entry>
  
  <entry>
    <title>Effectively managing memory at Gmail scale</title>
    <link href="https://web.dev/effectivemanagement/"/>
    <updated>2013-06-12T00:00:00Z</updated>
    <id>https://web.dev/effectivemanagement/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While JavaScript employs garbage collection for automatic memory management, it is not a substitute for effective memory management in applications. JavaScript applications suffer from the same memory related problems that native applications do, such as memory leaks and bloat, yet they must also deal with garbage collection pauses. Large-scale applications like Gmail encounter the same problems facing your smaller applications. Read on to learn how the Gmail team used Chrome DevTools to identify, isolate, and fix their memory problems.&lt;/p&gt;
&lt;h2 id=&quot;google-io-2013-session&quot;&gt;Google I/O 2013 Session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#google-io-2013-session&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We presented this material at Google I/O 2013. Check out the video below:&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;x9Jlu_h_Lyw&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;h2 id=&quot;gmail,-we-have-a-problem&quot;&gt;Gmail, we have a problem… &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#gmail,-we-have-a-problem&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Gmail team was facing a serious problem. Anecdotes of Gmail tabs consuming multiple gigabytes of memory on resource-constrained laptops and desktops were being heard increasingly frequently, often with a conclusion of bringing the entire browser down. Stories of CPUs being pinned at 100%, unresponsive apps, and Chrome sad tabs (&amp;quot;He&#39;s dead, Jim.&amp;quot;). The team was at a loss as to how to even begin diagnosing the problem, let alone fix it. They had no idea how widespread the problem was and the available tools didn&#39;t scale up to large applications. The team joined forces with the Chrome teams, and together they developed new techniques to triage memory problems, improved existing tools, and enabled the collection of memory data from the field. But, before getting to the tools, let&#39;s cover the basics of JavaScript memory management.&lt;/p&gt;
&lt;h2 id=&quot;memory-management-basics&quot;&gt;Memory Management Basics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#memory-management-basics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before you can effectively manage memory in JavaScript you must understand the fundamentals. This section will cover primitive types, the object graph, and provide definitions for memory bloat in general and a memory leak in JavaScript. Memory in JavaScript can be conceptualized as a graph and because of this &lt;a href=&quot;http://en.wikipedia.org/wiki/Graph_theory&quot; rel=&quot;noopener&quot;&gt;Graph theory&lt;/a&gt; plays a part in JavaScript memory management and the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots/&quot; rel=&quot;noopener&quot;&gt;Heap Profiler&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;primitive-types&quot;&gt;Primitive Types &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#primitive-types&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;JavaScript has three primitive types:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Number (e.g. 4, 3.14159)&lt;/li&gt;
&lt;li&gt;Boolean (true or false)&lt;/li&gt;
&lt;li&gt;String (&amp;quot;Hello World&amp;quot;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These primitive types cannot reference any other values. In the object graph these values are always leaf or terminating nodes, meaning they never have an outgoing edge.&lt;/p&gt;
&lt;p&gt;There is only one container type: the Object. In JavaScript the Object is an &lt;a href=&quot;http://en.wikipedia.org/wiki/Associative_array&quot;&gt;associative array&lt;/a&gt;. A non-empty object is an inner node with outgoing edges to other values (nodes).&lt;/p&gt;
&lt;h3 id=&quot;what-about-arrays&quot;&gt;What About Arrays? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#what-about-arrays&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An Array in JavaScript is actually an Object that has numeric keys. This is a simplification, because JavaScript runtimes will optimize Array-like Objects and represent them under the hood as arrays.&lt;/p&gt;
&lt;h4 id=&quot;terminology&quot;&gt;Terminology &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#terminology&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Value - An instance of a primitive type, Object, Array, etc.&lt;/li&gt;
&lt;li&gt;Variable - A name that references a value.&lt;/li&gt;
&lt;li&gt;Property - A name in an Object that references a value.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;object-graph&quot;&gt;Object Graph &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#object-graph&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;All values in JavaScript are part of the object graph. The graph begins with roots, for example, the &lt;a href=&quot;https://developer.mozilla.org/docs/DOM/window&quot; rel=&quot;noopener&quot;&gt;window object&lt;/a&gt;. Managing the lifetime of GC roots is not in your control, as they are created by the browser and destroyed when the page is unloaded. Global variables are actually properties on window.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Object graph&quot; decoding=&quot;async&quot; height=&quot;376&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Jfzt0p3AgPh36QYiKoy7.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;when-does-a-value-become-garbage&quot;&gt;When Does a Value Become Garbage? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#when-does-a-value-become-garbage&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A value becomes garbage when there is no path from a root to the value. In other words, starting at the roots and exhaustively searching all Object properties and variables that are alive in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Call_stack&quot; rel=&quot;noopener&quot;&gt;stack frame&lt;/a&gt;, a value cannot be reached, it has become garbage.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Garbage graph&quot; decoding=&quot;async&quot; height=&quot;290&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 566px) 566px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/obCcXCpk9znOJlkNYo3P.png?auto=format&amp;w=1132 1132w&quot; width=&quot;566&quot; /&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;what-is-a-memory-leak-in-javascript&quot;&gt;What is a Memory Leak in JavaScript? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#what-is-a-memory-leak-in-javascript&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A memory leak in JavaScript most commonly occurs when there are DOM nodes that are not reachable from the page&#39;s DOM tree, but are still referenced by a JavaScript object. While modern browsers are making it increasingly difficult to inadvertently create leaks, it&#39;s still easier than one might think. Let&#39;s say you append an element to the DOM tree like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;email&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;displayList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;And later, you remove the element from the display list:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;displayList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeAllChildren&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As long as &lt;code&gt;email&lt;/code&gt; exists, the DOM element referenced by message will not be removed, even though it is now detached from the page&#39;s DOM tree.&lt;/p&gt;
&lt;h3 id=&quot;what-is-bloat&quot;&gt;What is Bloat? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#what-is-bloat&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Your page is &lt;a href=&quot;http://en.wikipedia.org/wiki/Software_bloat&quot; rel=&quot;noopener&quot;&gt;bloated&lt;/a&gt; when you&#39;re using more memory than necessary for optimal page speed. Indirectly, memory leaks also cause bloat but that is not by design. An application cache that does not have any size bound is a common source of memory bloat. Also, your page can be bloated by host data, for example, pixel data loaded from images.&lt;/p&gt;
&lt;h3 id=&quot;what-is-garbage-collection&quot;&gt;What is Garbage Collection? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#what-is-garbage-collection&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Garbage collection is how memory is reclaimed in JavaScript. The browser decides when this happens. During a collection, all script execution on your page is suspended while live values are discovered by a traversal of the object graph starting at the GC roots. All the values which are not &lt;a href=&quot;http://en.wikipedia.org/wiki/Reachability&quot; rel=&quot;noopener&quot;&gt;reachable&lt;/a&gt; are classified as garbage. Memory for garbage values is reclaimed by the memory manager.&lt;/p&gt;
&lt;h2 id=&quot;v8-garbage-collector-in-detail&quot;&gt;V8 Garbage Collector in Detail &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#v8-garbage-collector-in-detail&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To help further understand how garbage collection happens, let&#39;s take a look at the V8 garbage collector in detail. V8 uses a generational collector. Memory is divided into two generations: the young and the old. Allocation and collection within the young generation is fast and frequent. Allocation and collection within the old generation is slower and less frequent.&lt;/p&gt;
&lt;h3 id=&quot;generational-collector&quot;&gt;Generational Collector &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#generational-collector&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;V8 uses a two generation collector. The age of an value is defined as the number of bytes allocated since it was allocated. In practice, the age of an value is often approximated by the number of young generation collections that it survived. After a value is sufficiently old it is tenured into the old generation.&lt;/p&gt;
&lt;p&gt;In practice, freshly allocated values do not live long. A study of Smalltalk programs, showed that only 7% of values survive after a young generation collection. Similar studies across runtimes found that on average between, 90% and 70%  of freshly allocated values are never tenured into the old generation.&lt;/p&gt;
&lt;h3 id=&quot;young-generation&quot;&gt;Young Generation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#young-generation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The young generation heap in V8 is split into two spaces, named from and to. Memory is allocated from the to space. Allocating is very fast, until, the  to space is full at which point a young generation collection is triggered. Young generation collection first swaps the from and to space, the old to space (now the from space) is scanned and all live values are copied into the to space or tenured into the old generation. A typical young generation collection will take on the order of 10 milliseconds (ms).&lt;/p&gt;
&lt;p&gt;Intuitively, you should understand that each allocation your application makes brings you closer exhausting the to space and incurring a GC pause. Game developers, take note: to ensure a 16ms frame time (required to achieve 60 frames per second), your application must make zero allocations, because a single young generation collection will eat up most of the frame time.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Young generation heap&quot; decoding=&quot;async&quot; height=&quot;381&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 637px) 637px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vTvyvQYDtjgTpXNaHEuC.png?auto=format&amp;w=1274 1274w&quot; width=&quot;637&quot; /&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;old-generation&quot;&gt;Old Generation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#old-generation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The old generation heap in V8 uses a &lt;a href=&quot;http://en.wikipedia.org/wiki/Mark-compact_algorithm&quot; rel=&quot;noopener&quot;&gt;mark-compact algorithm&lt;/a&gt; for collection. Old generation allocations occur whenever a value is tenured from the young generation to the old generation. Whenever an old generation collection occurs a young generation collection is done as well. Your application will paused on the order of seconds. In practice this is acceptable because old generation collections are infrequent.&lt;/p&gt;
&lt;h3 id=&quot;v8-gc-summary&quot;&gt;V8 GC Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#v8-gc-summary&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Automatic memory management with garbage collection is great for developer productivity, but, each time you allocate a value, you move ever closer to a garbage collection pause. Garbage collection pauses can ruin the feel of your application by introducing jank. Now that you understand how JavaScript manages memory, you can make the right choices for your application.&lt;/p&gt;
&lt;h2 id=&quot;fixing-gmail&quot;&gt;Fixing Gmail &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#fixing-gmail&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Over the past year, numerous features and bug fixes have made their way into the Chrome DevTools making them more powerful than ever. In addition, the browser itself made a key change to the performance.memory API making it possible for Gmail and any other application to collect memory statistics from the field. Armed with these awesome tools, what once seemed like an impossible task soon became an exciting game of tracking down culprits.&lt;/p&gt;
&lt;h3 id=&quot;tools-and-techniques&quot;&gt;Tools and Techniques &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#tools-and-techniques&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;field-data-and-performancememory-api&quot;&gt;Field Data and performance.memory API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#field-data-and-performancememory-api&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;As of Chrome 22, the &lt;a href=&quot;http://docs.webplatform.org/wiki/apis/timing/properties/memory&quot; rel=&quot;noopener&quot;&gt;performance.memory API&lt;/a&gt; is enabled by default.  For long-running applications like Gmail, data from real users is invaluable. This information allows us to distinguish between power users-- those who spend 8-16 hours a day on Gmail, receiving hundreds of messages a day-- from more average users who spend a few minutes a day in Gmail, receiving a dozen or so messages a week.&lt;/p&gt;
&lt;p&gt;This API returns three pieces of data:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;jsHeapSizeLimit - The amount of memory (in bytes) that the JavaScript heap is limited to.&lt;/li&gt;
&lt;li&gt;totalJSHeapSize - The amount of memory (in bytes) that the JavaScript heap has allocated including free space.&lt;/li&gt;
&lt;li&gt;usedJSHeapSize - The amount of memory (in bytes) currently being used.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One thing to keep in mind is that the API returns memory values for the entire Chrome process. Although it is not the default mode, under certain circumstances, Chrome may open multiple tabs in the same renderer process. This means that the values returned by performance.memory may contain the memory footprint of other browser tabs in addition to the one containing your app.&lt;/p&gt;
&lt;h4 id=&quot;measuring-memory-at-scale&quot;&gt;Measuring Memory At Scale &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#measuring-memory-at-scale&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Gmail instrumented their JavaScript to use the performance.memory API to collect memory information approximately once every 30 minutes.  Because many Gmail users leave the app up for days at a time, the team was able to track memory growth over time as well as overall memory footprint statistics. Within a few days of instrumenting Gmail to collect memory information from a random sampling of users, the team had enough data to understand how widespread the memory problems were among average users. They set a baseline and used the stream of incoming data to track progress toward the goal of reducing memory consumption. Eventually this data would also be used to catch any memory regressions.&lt;/p&gt;
&lt;p&gt;Beyond tracking purposes, the field measurements also provide a keen insight into the correlation between memory footprint and application performance.  Contrary to the popular belief that &amp;quot;more memory results in better performance&amp;quot;, the Gmail team found that the larger the memory footprint, the longer latencies were for common Gmail actions. Armed with this revelation, they were more motivated than ever to rein in their memory consumption.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Measuring Memory At Scale&quot; decoding=&quot;async&quot; height=&quot;186&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 562px) 562px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/kEqrgQ3eeXVke53FRBwK.png?auto=format&amp;w=1124 1124w&quot; width=&quot;562&quot; /&gt;
&lt;/figure&gt;
&lt;h4 id=&quot;identifying-a-memory-problem-with-the-devtools-timeline&quot;&gt;Identifying a Memory Problem with the DevTools Timeline &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#identifying-a-memory-problem-with-the-devtools-timeline&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The first step in solving any performance problem is to prove that the problem exists, create a reproducible test, and take a baseline measurement of the problem. Without a reproducible program, you cannot reliably measure the problem. Without a baseline measurement you don&#39;t know by how much you&#39;ve improved performance.&lt;/p&gt;
&lt;p&gt;The DevTools Timeline panel is an ideal candidate for proving that the problem exists. It gives a complete overview of where time is spent when loading and interacting with your web app or page. All events, from loading resources to parsing JavaScript, calculating styles, garbage collection pauses, and repainting are plotted on a timeline. For the purposes of investigating memory issues, the Timeline panel also has a Memory mode which tracks total allocated memory, number of DOM nodes, number of window objects, and the number of event listeners allocated.&lt;/p&gt;
&lt;h4 id=&quot;proving-a-problem-exists&quot;&gt;Proving a problem exists &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#proving-a-problem-exists&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Start by identifying a sequence of actions you suspect to be leaking memory. Start recording the timeline, and perform the sequence of actions.  Use the trash can button at the bottom to force a full garbage collection.  If, after a few iterations, you see a &lt;a href=&quot;http://en.wikipedia.org/wiki/Sawtooth_wave&quot; rel=&quot;noopener&quot;&gt;sawtooth&lt;/a&gt; shaped graph, you are allocating lots of shortly lived objects. But if the sequence of actions is not expected to result in any retained memory, and the DOM node count does not drop down back to the baseline where you began, you have good reason to suspect there is a leak.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Sawtooth shaped graph&quot; decoding=&quot;async&quot; height=&quot;347&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 565px) 565px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eSh0rNSTOPQOiz9jadTw.png?auto=format&amp;w=1130 1130w&quot; width=&quot;565&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Once you&#39;ve confirmed that the problem exists, you can get help identifying the source of the problem from the DevTools Heap Profiler.&lt;/p&gt;
&lt;h4 id=&quot;finding-memory-leaks-with-the-devtools-heap-profiler&quot;&gt;Finding Memory Leaks with the DevTools Heap Profiler &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#finding-memory-leaks-with-the-devtools-heap-profiler&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The Profiler panel provides both a CPU profiler and a Heap profiler. Heap profiling works by taking a snapshot of the object graph. Before a snapshot is taken both the young and old generations are garbage collected. In other words, you will only see values which were alive when the snapshot was taken.&lt;/p&gt;
&lt;p&gt;There is too much functionality in the Heap profiler to cover sufficiently in this article, but &lt;a href=&quot;https://developer.chrome.com/devtools/docs/javascript-memory-profiling&quot; rel=&quot;noopener&quot;&gt;detailed documentation&lt;/a&gt; can be found on the Chrome Developers site. We&#39;ll focus here on the Heap Allocation profiler.&lt;/p&gt;
&lt;h4 id=&quot;using-the-heap-allocation-profiler&quot;&gt;Using the Heap Allocation Profiler &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#using-the-heap-allocation-profiler&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The Heap Allocation profiler combines the detailed snapshot information of the Heap Profiler with the incremental updating and tracking of the Timeline panel. Open the Profiles panel, start a &lt;strong&gt;Record Heap Allocations&lt;/strong&gt; profile, perform a sequence of actions, then stop the recording for analysis. The allocation profiler takes heap snapshots periodically throughout the recording (as frequently as every 50 ms!) and one final snapshot at the end of the recording.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Heap allocation profiler&quot; decoding=&quot;async&quot; height=&quot;321&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 616px) 616px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/4oCSZ0RNIBiSKYh9KvVo.png?auto=format&amp;w=1232 1232w&quot; width=&quot;616&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The bars at the top indicate when new objects are found in the heap.  The height of each bar corresponds to the size of the recently allocated objects, and the color of the bars indicate whether or not those objects are still live in the final heap snapshot: blue bars indicate objects that are still live at the end of the timeline, gray bars indicate objects that were allocated during the timeline, but have since been garbage collected.&lt;/p&gt;
&lt;p&gt;In the example above, an action was performed 10 times.  The sample program caches five objects, so the last five blue bars are expected.  But the leftmost blue bar indicates a potential problem. You can then use the sliders in the timeline above to zoom in on that particular snapshot and see the objects that were recently allocated at that point.  Clicking on a specific object in the heap will show its retaining tree in the bottom portion of the heap snapshot. Examining the retaining path to the object should give you enough information to understand why the object was not collected, and you can make the necessary code changes to remove the unnecessary reference.&lt;/p&gt;
&lt;h2 id=&quot;resolving-gmails-memory-crisis&quot;&gt;Resolving Gmail&#39;s Memory Crisis &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#resolving-gmails-memory-crisis&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By using the tools and techniques discussed above, the Gmail team was able to identify a few categories of bugs:  unbounded caches, infinitely growing arrays of callbacks waiting for something to happen that never actually happens, and event listeners unintentionally retaining their targets. By fixing these issues, the overall memory usage of Gmail was dramatically reduced. Users in the 99% percent used 80% less memory than before and the memory consumption of the median users dropped by nearly 50%.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Gmail memory usage&quot; decoding=&quot;async&quot; height=&quot;420&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/r90bVuT8hhy4GiHeRBZd.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Because Gmail used less memory the GC pause latency was reduced, increasing the overall user experience.&lt;/p&gt;
&lt;p&gt;Also of note, with the Gmail team collecting statistics on memory usage, they were able to uncover garbage collection regressions inside Chrome. Specifically, two fragmentation bugs were discovered when Gmail&#39;s memory data began showing a dramatic increase in the gap between total memory allocated and live memory.&lt;/p&gt;
&lt;h2 id=&quot;call-to-action&quot;&gt;Call to Action &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#call-to-action&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ask yourself these questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How much memory is my app using?
It&#39;s possible that you are using too much memory which contrary to popular belief has a net negative on overall application performance. It&#39;s hard to know exactly what the right number is, but, be sure to verify that any extra caching your page is using has a measurable performance impact.&lt;/li&gt;
&lt;li&gt;Is my page leak free?
If your page has memory leaks it can not only impact your page&#39;s performance but other tabs as well. Use the object tracker to help narrow in on any leaks.&lt;/li&gt;
&lt;li&gt;How frequently is my page GCing?
You can see any GC pause using &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/performance-reference/&quot; rel=&quot;noopener&quot;&gt;Timeline panel&lt;/a&gt; in &lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome Developer Tools&lt;/a&gt;. If your page is GCing frequently, chances are you are allocating too frequently, churning through your young generation memory.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/effectivemanagement/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We started off in a crisis. Covered the core basics of memory management in JavaScript and V8 in particular. You learned how to use the tools, including the new object tracker feature available in the latest builds of Chrome. The Gmail team, armed with this knowledge, solved their memory usage problem and saw improved performance. You can do the same with your web apps!&lt;/p&gt;
</content>
    <author>
      <name>John McCutchan</name>
    </author><author>
      <name>Loreena Lee</name>
    </author>
  </entry>
  
  <entry>
    <title>Profiling mobile HTML5 apps with Chrome DevTools</title>
    <link href="https://web.dev/mobile-profiling/"/>
    <updated>2013-03-26T00:00:00Z</updated>
    <id>https://web.dev/mobile-profiling/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Today, the most important thing you can do for your site is make sure it
performs well when visited from a phone or tablet. Read on and learn how
to optimize your site for the mobile browser using Chrome DevTools and an
Android device.&lt;/p&gt;
&lt;h2 id=&quot;why-is-optimizing-for-the-mobile-web-so-important&quot;&gt;Why is Optimizing for the Mobile Web so Important? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#why-is-optimizing-for-the-mobile-web-so-important&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;performance&quot;&gt;Performance &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#performance&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Mobile devices are getting faster CPUs, more RAM, faster GPUs, and faster
network access as we transition from 2G and 3G to 4G. Despite the drumbeat
of progress, mobile devices are underpowered when compared to our
computers. In more concrete terms, loading network resources takes longer,
unpacking images takes longer, painting the page takes longer, executing
scripts takes longer. It’s safe to assume that your page runs 5 to 10
times slower on a mobile device.&lt;/p&gt;
&lt;h3 id=&quot;battery&quot;&gt;Battery &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#battery&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Mobile devices are exclusively battery powered. Users of mobile devices want
that battery to last as long as possible. A sub-optimal site will drain a
battery much quicker than needed. Minimize network traffic and reduce paints
to reduce battery drain. When you fetch a resource, the WiFi or cell radio
must be on, which drains battery. When the browser paints an element, the CPU
and GPU usage spikes, which also drains battery.&lt;/p&gt;
&lt;h3 id=&quot;engagement&quot;&gt;Engagement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#engagement&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Performance is there to increase the metric that matters most to you. In
Facebook we care about scrolling. In an A/B test, we slowed down
scrolling from 60fps down to 30fps. Engagement collapsed. We said okay,
therefore scrolling matters.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;Facebook at Edge Conference&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mobile users expect to be able to get in and out quickly. The fastest site will get the most engagement.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;managing-performance&quot;&gt;Managing Performance &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#managing-performance&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chrome ships with a great &lt;a href=&quot;xhttps://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;set of developer tools.&lt;/a&gt;
This article teaches you how to use these tools to profile your mobile site. If you’re already familiar with Chrome DevTools, great! If not, check these great tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/blog/profiling-long-paint-times-with-devtools-continuous-painting-mode&quot; rel=&quot;noopener&quot;&gt;Profiling long paint times&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.igvita.com/slides/2012/devtools-tips-and-tricks/#1&quot; rel=&quot;noopener&quot;&gt;DevTools Tips and Tricks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;more…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that you’re caught up, let’s see how to accelerate your mobile site with DevTools. If this is the first time you’ve used Chrome DevTools for Android check out the getting  started guide at the bottom of the article.&lt;/p&gt;
&lt;h2 id=&quot;using-chrome-devtools-remotely&quot;&gt;Using Chrome DevTools Remotely &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#using-chrome-devtools-remotely&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With your Android device tethered to your computer. In desktop Chrome, navigate
to &lt;a href=&quot;http://localhost:9222/&quot; rel=&quot;noopener&quot;&gt;http://localhost:9222&lt;/a&gt; and on your
Android device, open up your site.
You will be taken to a list of open tabs on your Android device.
Pick your page from the list of ‘Inspectable pages’.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Inspectable Pages&quot; decoding=&quot;async&quot; height=&quot;333&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;and you will be taken to Chrome DevTools for that page.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Remote DevTools&quot; decoding=&quot;async&quot; height=&quot;141&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 628px) 628px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/wONT2x7fBMWgLtpXwRAR.png?auto=format&amp;w=1256 1256w&quot; width=&quot;628&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Aww… that familiar Chrome DevTools toolbar is right there.
&lt;strong&gt;The most important thing to understand about remote Chrome DevTools is that they are the same DevTools you’re using today on your desktop.&lt;/strong&gt;
The only difference isthat your Android device is only responsible for the page, while your desktop
is responsible for DevTools. &lt;strong&gt;Under the hood, the same data is collected and the same functionality is available.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As an example, I visited &lt;a href=&quot;http://www.sfgate.com/movies&quot; rel=&quot;noopener&quot;&gt;www.sfgate.com/movies&lt;/a&gt; on my phone. Using Chrome DevTools on my desktop I hovered over a
&lt;strong&gt;div&lt;/strong&gt; in the &lt;strong&gt;Elements tool&lt;/strong&gt; and, just like it is on the desktop, the &lt;strong&gt;div&lt;/strong&gt; is visually highlighted on my Android device.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Source code snippet.&quot; decoding=&quot;async&quot; height=&quot;169&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 511px) 511px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/w5XukOsNXf2IX7J1mO3F.png?auto=format&amp;w=1022 1022w&quot; width=&quot;511&quot; /&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt=&quot;Div highlighted.&quot; decoding=&quot;async&quot; height=&quot;1280&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 720px) 720px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/g56Z03Tsh6OwUR4R4wps.png?auto=format&amp;w=1440 1440w&quot; width=&quot;720&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The  &lt;strong&gt;Elements tool&lt;/strong&gt; can also be used to
toggle styles on and off, which will come in handy when we attempt to
investigate paint times.&lt;/p&gt;
&lt;h2 id=&quot;shedding-light-on-network-access&quot;&gt;Shedding Light on Network Access &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#shedding-light-on-network-access&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Network performance is import, and it’s even more important on the mobile web.
Mobile devices are often on slower connections than our desktop and
laptop computers. To make sure you’re doing the right thing, take a
network snapshot by going to the
&lt;strong&gt;Network tool&lt;/strong&gt; and pressing record.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Network tool.&quot; decoding=&quot;async&quot; height=&quot;228&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Nh8l3gfgPZ4KXpkBvjvh.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The screenshot shows the network traffic resulting from a Google search.
Observe the network requests your site makes and find ways to minimize them.
If your site makes polling requests to the server, you may want to pay
attention to user activity and avoid polling when the user has been idle.
The &lt;strong&gt;Network tool&lt;/strong&gt; allows you to view the
raw HTTP headers, useful in case mobile networks are altering them at all.&lt;/p&gt;
&lt;h2 id=&quot;optimizing-paint-times&quot;&gt;Optimizing Paint Times &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#optimizing-paint-times&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the biggest bottlenecks in mobile web browsers is painting your page.
Painting is the process of drawing an element on the page with the specified
styling. When one element is expensive to paint it slows down painting of the
whole page. Chrome makes an attempt at caching previously painted elements in
an offscreen buffer. But, on mobile the amount of GPU RAM available is limited,
limiting the number of elements that can be cached off screen. The side effect
is &lt;strong&gt;more paints&lt;/strong&gt; and
&lt;strong&gt;each paint is slower than the desktop.&lt;/strong&gt;
In order to have responsive scrolling, you must minimize paint times.&lt;/p&gt;
&lt;p&gt;Chrome 25 includes
&lt;strong&gt;continuous page repaint mode.&lt;/strong&gt;
Continuous page repaint mode never caches painted elements and, instead,
&lt;strong&gt;paints all elements each frame&lt;/strong&gt;. By
forcing all elements to be painted each frame, it is possible to perform A/B
testing of paint times by toggling elements on and off, and styles on and off.
While the process is manual, it’s an invaluable tool for tracking down how
expensive painting each element on your page is. The first rule of optimization
club is &lt;strong&gt;measure what you’re trying to optimize to get a baseline&lt;/strong&gt;. Let’s work through a simple example.&lt;/p&gt;
&lt;p&gt;First, enable continuous page repaint mode:&lt;/p&gt;
&lt;figure&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/T4FyVKpzu4WKF1kBNvXepbi08t52/Za5mvsDJtw6JW5CZy8gn.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;After enabling, a graph will be visible in the upper right corner of your
Android device. The x-axis of the graph is time, divided into frames. The
y-axis of the graph measures paint time, in milliseconds. You can see that,
on my device, the page takes 14 milliseconds to paint. The minimum and maximum
paint times are also shown along with GPU memory used.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Before&quot; decoding=&quot;async&quot; height=&quot;1280&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 720px) 720px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NAfLdfXcl5gFyi8bYKsj.png?auto=format&amp;w=1440 1440w&quot; width=&quot;720&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;As an experiment, I set the style on the selected element to be &lt;code&gt;display: none&lt;/code&gt;. Let’s see how expensive
the page is to paint now.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;After.&quot; decoding=&quot;async&quot; height=&quot;1280&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 720px) 720px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uJ0rlan3lXxym949h8PB.png?auto=format&amp;w=1440 1440w&quot; width=&quot;720&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Paint times went from around 14 milliseconds per frame down to 4 milliseconds
per frame. In other words, painting that one element took approximately 10
milliseconds. By following the process of toggling elements on and off and
styles on and off you can quickly narrow in on the expensive parts of your
page. Remember, faster paint times means less jank, a longer battery and more
engagement from your users. When you’re ready to dig deeper, be sure to read &lt;a href=&quot;http://updates.html5rocks.com/2013/02/Profiling-Long-Paint-Times-with-DevTools-Continuous-Painting-Mode&quot; rel=&quot;noopener&quot;&gt;this great article on continuous page repaint mode.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;advanced-features&quot;&gt;Advanced Features &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#advanced-features&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;abouttracing&quot;&gt;about:tracing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#abouttracing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Many of the more advanced developer features available in desktop Chrome are
also available in Android Chrome too. For example,
&lt;strong&gt;about:gpu-internals&lt;/strong&gt;, &lt;strong&gt;about:appcache-internals&lt;/strong&gt;, and &lt;strong&gt;about:net-internals&lt;/strong&gt; are available.
When investigating a particularly tricky problem you sometimes need more data in order to narrow in on the cause of
your problem. On the desktop, you might be using about:tracing. If you’re not
already familiar with &lt;strong&gt;about:tracing&lt;/strong&gt;, watch
&lt;a href=&quot;https://www.youtube.com/watch?v=nxXkquTPng8&quot; rel=&quot;noopener&quot;&gt;my video on using and exploring the &lt;strong&gt;about:tracing&lt;/strong&gt; profiling tool&lt;/a&gt;. It is
possible to capture the same data from
Android Chrome, follow these steps to get started:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download &lt;a href=&quot;https://github.com/johnmccutchan/adb_trace&quot; rel=&quot;noopener&quot;&gt;adb_trace.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Run adb_trace.py from the command line&lt;/li&gt;
&lt;li&gt;Use Chrome on Android&lt;/li&gt;
&lt;li&gt;Press enter on the command line, shutting down the adb_trace.py script.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After adb_trace.py completes you will have a JSON file that you can load in
desktop Chrome’s  &lt;strong&gt;about:tracing&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;starting-guide&quot;&gt;Starting Guide &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#starting-guide&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we&#39;ve reviewed what remote Chrome DevTools can do, let&#39;s cover how
to get started in your remote debugging session. If you haven’t used them
before,&lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/&quot; rel=&quot;noopener&quot;&gt;read detailed instructions on how to get started&lt;/a&gt;. If you’ve already
used them, but have forgotten exactly how to use them, I’ve listed shortened
instructions here as well.&lt;/p&gt;
&lt;h3 id=&quot;1-install-android-sdk&quot;&gt;1. Install Android SDK &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#1-install-android-sdk&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You might be wondering why you have to &lt;a href=&quot;http://developer.android.com/sdk/index.html&quot; rel=&quot;noopener&quot;&gt;install the Android SDK&lt;/a&gt;
when you are developing for the web. Included in the SDK is adb (Android Debug Bridge).
Desktop Chrome needs to be able to communicate with your Android device.
Chrome doesn’t talk directly with the Android device, instead it routes
communication through adb.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;ADB bridge.&quot; decoding=&quot;async&quot; height=&quot;269&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 591px) 591px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/mYiiyLxpmIxHCpN7mpj2.png?auto=format&amp;w=1182 1182w&quot; width=&quot;591&quot; /&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;2-enable-usb-debugging-on-your-device&quot;&gt;2. Enable USB debugging on your device &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#2-enable-usb-debugging-on-your-device&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;
&lt;img alt=&quot;Enable USB Debugging&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/cpkkGI3jIGPBo3WCaUFZ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The option for enabling USB debugging can be found in Android Settings. &lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/#troubleshooting&quot; rel=&quot;noopener&quot;&gt;Enable it&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;3-connect-to-the-device&quot;&gt;3. Connect to the device &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#3-connect-to-the-device&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you haven’t already, connect your Android device to your desktop via USB.
If this is the first time you’ve used USB debugging you will be given the
following prompt:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Allow USB Debugging&quot; decoding=&quot;async&quot; height=&quot;641&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uC0NCAA2FjRlo3DAAC3w.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;If you will be doing remote debug sessions frequently,
I recommend checking ‘Always allow from this computer’.&lt;/p&gt;
&lt;h3 id=&quot;4-verify-that-your-device-is-properly-connected&quot;&gt;4. Verify that your device is properly connected &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#4-verify-that-your-device-is-properly-connected&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Run &lt;strong&gt;adb devices&lt;/strong&gt; from your command prompt.
You should see your device listed.&lt;/p&gt;
&lt;h3 id=&quot;5-enable-usb-debugging-in-chrome&quot;&gt;5. Enable USB debugging in Chrome &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#5-enable-usb-debugging-in-chrome&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Open &lt;strong&gt;Settings &amp;gt; Advanced &amp;gt; DevTools&lt;/strong&gt; and
check the &lt;strong&gt;Enable USB Web debugging&lt;/strong&gt; option as shown here:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Allow USB Debugging&quot; decoding=&quot;async&quot; height=&quot;711&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yNf71EghRpVrnnJhNYwz.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;6-creating-a-devtools-connection-to-your-android-device&quot;&gt;6. Creating a DevTools connection to your Android device &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#6-creating-a-devtools-connection-to-your-android-device&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Run the following command:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;adb forward tcp:9222 localabstract:chrome_devtools_remote&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;creates a bridge between your desktop machine and your Android device via adb.
If you run into any issues getting to this point, read over the detailed setup
instructions &lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/&quot; rel=&quot;noopener&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;7-verifying-youre-good-to-go&quot;&gt;7. Verifying you&#39;re good to go &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#7-verifying-youre-good-to-go&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Verify that your device is properly connected by opening Chrome on your
desktop and navigating to &lt;a href=&quot;http://localhost:9222/&quot; rel=&quot;noopener&quot;&gt;http://localhost:9222&lt;/a&gt;. If you get a
404, another error, or don’t see something like the following:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Inspectable pages.&quot; decoding=&quot;async&quot; height=&quot;333&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/3vu176K580FJNGlxX99G.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Read over the detailed setup instructions &lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/&quot; rel=&quot;noopener&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-profiling/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mobile users are often in a hurry and need to quickly get that important piece of information from your page. It is your duty as a mobile site builder to ensure that the page loads quickly and performs well on mobile. If not, user engagement will drop. Remote Chrome DevTools are functionally equivalent to their desktop counterparts. The UI is similar enough that you don’t need to learn a new set of tools. In other words, your work flow carries over. Remember,
&lt;strong&gt;Facebook isn’t invincible to performance problems and your site isn’t either. Performant sites get more user engagement.&lt;/strong&gt;&lt;/p&gt;
</content>
    <author>
      <name>John McCutchan</name>
    </author>
  </entry>
  
  <entry>
    <title>Pointer lock and first person shooter controls</title>
    <link href="https://web.dev/pointerlock-intro/"/>
    <updated>2012-08-17T00:00:00Z</updated>
    <id>https://web.dev/pointerlock-intro/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Pointer Lock API helps properly implement first-person shooter controls in a browser game. Without relative mouse movement the player&#39;s cursor could, for example, hit the right edge of the screen and any further movements to the right would be discounted - the view would not continue to pan right, and the player would not be able to pursue the bad guys and strafe them with his machine gun. The player is going to get fragged and become frustrated. With pointer lock this suboptimal behaviour can&#39;t happen.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/API/Pointer_Lock_API&quot; rel=&quot;noopener&quot;&gt;Pointer Lock API&lt;/a&gt; allows your application to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get access to raw mouse data including relative mouse movements&lt;/li&gt;
&lt;li&gt;Route all mouse events to a specific element&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a side effect of enabling pointer lock, the mouse cursor is hidden allowing you to choose to draw an application-specific pointer if you desire, or leave the mouse pointer hidden so that the user can move the frame with the mouse. Relative mouse movement is the mouse pointer position&#39;s delta from the previous frame regardless of absolute position.  For example, if the mouse pointer moved from (640, 480) to (520, 490) the relative movement was (-120, 10). See below for an interactive example showing raw mouse position deltas.&lt;/p&gt;
&lt;p&gt;This tutorial covers two topics: the nuts and bolts of activating and processing pointer lock events, and implementing the first-person shooter control scheme. That&#39;s right, when you&#39;re finished reading this article you will know how to use pointer lock and implement Quake-style controls for your very own browser game!&lt;/p&gt;
&lt;h3 id=&quot;browser-compatibility&quot;&gt;Browser compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 37, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      37
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 50, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      50
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 13, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 10.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      10.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Element/requestPointerLock#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;pointer-lock-mechanics&quot;&gt;Pointer Lock Mechanics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#pointer-lock-mechanics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;feature-detection&quot;&gt;Feature Detection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#feature-detection&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To determine if the user&#39;s browser supports pointer lock you need to check for &lt;code&gt;pointerLockElement&lt;/code&gt; or a vendor-prefixed version in the document object. In code:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; havePointerLock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pointerLockElement&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; document &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;mozPointerLockElement&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; document &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;webkitPointerLockElement&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Currently pointer lock is only available in Firefox and Chrome. Opera and IE do not yet support it.&lt;/p&gt;
&lt;h3 id=&quot;activating&quot;&gt;Activating &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#activating&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Activating pointer lock is a two step process. First your application requests pointer lock be enabled for a specific element, and immediately after the user gives permission, a &lt;code&gt;pointerlockchange&lt;/code&gt; event fires. The user can cancel pointer lock at any time by pressing the escape key. Your application can also progrmamatically exit pointer lock. When pointer lock is cancelled a &lt;code&gt;pointerlockchange&lt;/code&gt; event fires.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;requestPointerLock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;requestPointerLock &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;			     element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mozRequestPointerLock &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;			     element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webkitRequestPointerLock&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Ask the browser to lock the pointer&lt;/span&gt;&lt;br /&gt;element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestPointerLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Ask the browser to release the pointer&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exitPointerLock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exitPointerLock &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;			   document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mozExitPointerLock &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;			   document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webkitExitPointerLock&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exitPointerLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The above code is all it takes. When the browser locks the pointer a bubble will popup letting the user know that your application has locked the pointer and instructing them that they can cancel it by pressing the &#39;Esc&#39; key.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Pointer Lock info bar in Chrome.&quot; decoding=&quot;async&quot; height=&quot;50&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 443px) 443px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhdPKkAI2RlF9SK4W0Vb.png?auto=format&amp;w=886 886w&quot; width=&quot;443&quot; /&gt;
&lt;figcaption&gt;Pointer Lock info bar in Chrome.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;event-handling&quot;&gt;Event Handling &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#event-handling&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are two events that your application must add listeners for. The first is &lt;code&gt;pointerlockchange&lt;/code&gt;, which fires whenever a change in pointer lock state occurs. The second is &lt;code&gt;mousemove&lt;/code&gt; which fires whenever the mouse has moved.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Hook pointer lock state change events&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pointerlockchange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; changeCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;mozpointerlockchange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; changeCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webkitpointerlockchange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; changeCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Hook mouse move events&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mousemove&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;moveCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Because of vendor-prefixing, you will have to listen for &lt;code&gt;pointerlockchange&lt;/code&gt;, &lt;code&gt;mozpointerlockchange&lt;/code&gt;, and &lt;code&gt;webkitpointerlockchange&lt;/code&gt;events to be cross-browser compatible. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Inside your &lt;code&gt;pointerlockchange&lt;/code&gt; callback you must check if the pointer has just been locked or unlocked. Determining if pointer lock was enabled is simple: check if document.pointerLockElement is equal to the element that pointer lock was requested for. If it is, your application successfully locked the pointer and if it is not, the pointer was unlocked by the user or your own code.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pointerLockElement &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; requestedElement &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mozPointerLockElement &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; requestedElement &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webkitPointerLockElement &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; requestedElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Pointer was just locked&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Enable the mousemove listener&lt;/span&gt;&lt;br /&gt;  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mousemove&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;moveCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Pointer was just unlocked&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Disable the mousemove listener&lt;/span&gt;&lt;br /&gt;  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mousemove&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;moveCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unlockHook&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;When pointer lock is enabled &lt;code&gt;clientX&lt;/code&gt;, &lt;code&gt;clientY&lt;/code&gt;, &lt;code&gt;screenX&lt;/code&gt;, and &lt;code&gt;screenY&lt;/code&gt; remain constant. &lt;code&gt;movementX&lt;/code&gt; and &lt;code&gt;movementY&lt;/code&gt; are updated with the number of pixels the pointer would have moved since the last event was delivered. In pseudo-code:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;movementX &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentCursorPositionX &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; previousCursorPositionX&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;movementY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentCursorPositionY &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; previousCursorPositionY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Inside the &lt;code&gt;mousemove&lt;/code&gt; callback relative mouse motion data can be extracted from the event&#39;s &lt;code&gt;movementX&lt;/code&gt; and &lt;code&gt;movementY&lt;/code&gt; fields.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;moveCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; movementX &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;movementX &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;      e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mozMovementX          &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;      e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webkitMovementX       &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  movementY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;movementY &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;      e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mozMovementY      &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;      e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webkitMovementY   &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The movement fields also have vendor-prefixes &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;catching-errors&quot;&gt;Catching errors &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#catching-errors&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If an error is raised by either entering or exiting pointer lock the &lt;code&gt;pointerlockerror&lt;/code&gt; event fires. There is no data attached to this event.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pointerlockerror&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; errorCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;mozpointerlockerror&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; errorCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webkitpointerlockerror&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; errorCallback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;full-screen-required&quot;&gt;Full-screen Required? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#full-screen-required&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Originally pointer lock was tied to the FullScreen API. Meaning that an element must be in fullscreen mode before it can have the pointer locked to it. That is no longer true and pointer lock can be used for any element in your application full-screen or not.&lt;/p&gt;
&lt;h2 id=&quot;first-person-shooter-controls-example&quot;&gt;First-Person Shooter Controls Example &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#first-person-shooter-controls-example&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we have pointer lock enabled and receiving events, it&#39;s time for a practical example. Have you ever wanted to know how the controls in Quake work? Strap in because I&#39;m about to explain them with code!&lt;/p&gt;
&lt;p&gt;First-person shooter controls are built around four core mechanics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Moving forward and backward along the current look vector&lt;/li&gt;
&lt;li&gt;Moving left and right along the current strafe vector&lt;/li&gt;
&lt;li&gt;Rotating the view yaw (left and right)&lt;/li&gt;
&lt;li&gt;Rotating the view pitch (up and down)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A game implementing this control scheme needs only three pieces of data: camera position, camera look vector, and a constant up vector. The up vector is always (0, 1, 0). All four of the above mechanics just manipulate the camera position and camera look vector in different ways.&lt;/p&gt;
&lt;h3 id=&quot;movement&quot;&gt;Movement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#movement&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First on deck is movement. In the demo below movement is mapped to the standard W, A, S, and D keys. The W and S keys drive the camera forward and backward. While the A and D keys drive the camera to the left and right. Moving the camera forward and backward is simple:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Forward direction&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; forwardDirection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cameraLookVector&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Speed&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; forwardSpeed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dt &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; cameraSpeed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Forward or backward depending on keys held&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; forwardScale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;forwardScale &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; keyState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;W&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;forwardScale &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; keyState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;S&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Scale movement&lt;/span&gt;&lt;br /&gt;vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;forwardDirection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; forwardScale &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; forwardSpeed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Add scaled movement to camera position&lt;/span&gt;&lt;br /&gt;vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cameraPosition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; forwardDirection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Strafing left and right requires a strafe direction. The strafe direction can be computed using the cross product:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Strafe direction&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; strafeDirection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cross&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cameraLookVector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cameraUpVector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; strafeDirection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Once you have the strafe direction, implementing strafe movement is the same as moving forward or backward.&lt;/p&gt;
&lt;p&gt;Next up is rotating the view.&lt;/p&gt;
&lt;h3 id=&quot;yaw&quot;&gt;Yaw &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#yaw&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Yaw or the horizontal rotation of the camera view is just a rotation around the constant up vector. Below is general code for rotating the camera look vector around an arbitrary axis. It works by constructing a &lt;a href=&quot;http://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation&quot; rel=&quot;noopener&quot;&gt;quaternion&lt;/a&gt; representing the rotation of &lt;code&gt;deltaAngle&lt;/code&gt; radians around &lt;code&gt;axis&lt;/code&gt; and then uses the quaternion to rotate the camera look vector:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Extract camera look vector&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; frontDirection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subtract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lookAtPoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eyePoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; frontDirection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frontDirection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; q &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; quat4&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Construct quaternion&lt;/span&gt;&lt;br /&gt;quat4&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromAngleAxis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deltaAngle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; axis&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Rotate camera look vector&lt;/span&gt;&lt;br /&gt;quat4&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;multiplyVec3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;q&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; frontDirection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Update camera look vector&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lookAtPoint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eyePoint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;vec3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lookAtPoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; frontDirection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;pitch&quot;&gt;Pitch &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#pitch&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Implementing pitch or the vertical rotation of the camera view is similar but instead of a rotation around the up vector you apply a rotation around the strafe vector. The first step is to compute the strafe vector and then rotate the camera look vector around that axis.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Pointer Lock API allows you to take control of the mouse cursor. If you&#39;re making web games your players will love it when they stop getting fragged because they excitedly moved mouse out of the window and your game stopped getting mouse updates. Usage is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;pointerlockchange&lt;/code&gt; event listener to track the state of pointer lock&lt;/li&gt;
&lt;li&gt;Request pointer lock for a specific element&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;mousemove&lt;/code&gt; event listener to get updates&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;external-demos&quot;&gt;External Demos &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#external-demos&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://media.tojicode.com/q3bsp/&quot; rel=&quot;noopener&quot;&gt;Quake 3 Map Viewer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=onD-avVQXFk&quot; rel=&quot;noopener&quot;&gt;Video demonstration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot;&gt;References &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pointerlock-intro/#references&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/API/Mouse_Lock_API&quot; rel=&quot;noopener&quot;&gt;Mozilla Developer Network API documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>John McCutchan</name>
    </author>
  </entry>
</feed>
