1 # Performance best practices for Firefox front-end engineers
3 This guide will help Firefox developers working on front-end code
4 produce code which is as performant as possible—not just on its own, but
5 in terms of its impact on other parts of Firefox. Always keep in mind
6 the side effects your changes may have, from blocking other tasks, to
7 interfering with other user interface elements.
9 ## Avoid the main thread where possible
11 The main thread is where we process user events and do painting. It's
12 also important to note that most of our JavaScript runs on the main
13 thread, so it's easy for script to cause delays in event processing or
14 painting. That means that the more code we can get off of the main
15 thread, the more that thread can respond to user events, paint, and
16 generally be responsive to the user.
18 You might want to consider using a Worker if you need to do some
19 computation that can be done off of the main thread. If you need more
20 elevated privileges than a standard worker allows, consider using a
21 ChromeWorker, which is a Firefox-only API which lets you create
22 workers with more elevated privileges.
24 ## Use requestIdleCallback()
26 If you simply cannot avoid doing some kind of long job on the main
27 thread, try to break it up into smaller pieces that you can run when the
28 browser has a free moment to spare, and the user isn't doing anything.
29 You can do that using **requestIdleCallback()** and the [Cooperative
30 Scheduling of Background Tasks API](https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API),
31 and doing it only when we have a free second where presumably the user
32 isn’t doing something.
34 See also the blog post [Collective scheduling with requestIdleCallback](https://hacks.mozilla.org/2016/11/cooperative-scheduling-with-requestidlecallback/).
36 As of [bug 1353206](https://bugzilla.mozilla.org/show_bug.cgi?id=1353206),
37 you can also schedule idle events in non-DOM contexts by using
38 **Services.tm.idleDispatchToMainThread**. See the
39 **nsIThreadManager.idl** file for more details.
43 If you’re adding a new XUL *\<xul:popup\>* or *\<xul:panel\>* to a
44 document, set the **hidden** attribute to **true** by default. By doing
45 so, you cause the binding applied on demand rather than at load time,
46 which makes initial construction of the XUL document faster.
48 ## Get familiar with the pipeline that gets pixels to the screen
50 Learn how pixels you draw make their way to the screen. Knowing the path
51 they will take through the various layers of the browser engine will
52 help you optimize your code to avoid pitfalls.
54 The rendering process goes through the following steps:
56 ![This is the pipeline that a browser uses to get pixels to the screen](img/rendering.png)
58 The above image is used under [Creative Commons Attribution 3.0](https://creativecommons.org/licenses/by/3.0/),
59 courtesy of [this page](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing)
60 from our friends at Google, which itself is well worth the read.
62 For a very down-to-earth explanation of the Style, Layout, Paint and
63 Composite steps of the pipeline, [this Hacks blog post](https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/)
64 does a great job of explaining it.
66 To achieve a 60 FPS frame rate, all of the above has to happen in 16
67 milliseconds or less, every frame.
69 Note that **requestAnimationFrame()** lets you queue up JavaScript to
70 **run right before the style flush occurs**. This allows you to put all
71 of your DOM writes (most importantly, anything that could change the
72 size or position of things in the DOM) just before the style and layout
73 steps of the pipeline, combining all the style and layout calculations
74 into a single batch so it all happens once, in a single frame tick,
75 instead of across multiple frames.
77 See [Detecting and avoiding synchronous reflow](#detecting-and-avoiding-synchronous-reflow)
78 below for more information.
80 This also means that *requestAnimationFrame()* is **not a good place**
81 to put queries for layout or style information.
83 ## Detecting and avoiding synchronous style flushes
85 ### What are style flushes?
86 When CSS is applied to a document (HTML or XUL, it doesn’t matter), the
87 browser does calculations to figure out which CSS styles will apply to
88 each element. This happens the first time the page loads and the CSS is
89 initially applied, but can happen again if JavaScript modifies the DOM.
91 JavaScript code might, for example, change DOM node attributes (either
92 directly or by adding or removing classes from elements), and can also
93 add, remove, or delete DOM nodes. Because styles are normally scoped to
94 the entire document, the cost of doing these style calculations is
95 proportional to the number of DOM nodes in the document (and the number
96 of styles being applied).
98 It is expected that over time, script will update the DOM, requiring us
99 to recalculate styles. Normally, the changes to the DOM just result in
100 the standard style calculation occurring immediately after the
101 JavaScript has finished running during the 16ms window, inside the
102 "Style" step. That's the ideal scenario.
104 However, it's possible for script to do things that force multiple style
105 calculations (or **style flushes**) to occur synchronously during the
106 JavaScript part of the 16 ms window. The more of them there are, the
107 more likely they'll exceed the 16ms frame budget. If that happens, some
108 of them will be postponed until the next frame (or possibly multiple
109 frames, if necessary), this skipping of frames is called **jank**.
111 Generally speaking, you force a synchronous style flush any time you
112 query for style information after the DOM has changed within the same
113 frame tick. Depending on whether or not [the style information you’re
114 asking for has something to do with size or position](https://gist.github.com/paulirish/5d52fb081b3570c81e3a)
115 you may also cause a layout recalculation (also referred to as *layout
116 flush* or *reflow*), which is also an expensive step see [Detecting
117 and avoiding synchronous reflow](#detecting-and-avoiding-synchronous-reflow) below.
119 To avoid this: avoid reading style information if you can. If you *must*
120 read style information, do so at the very beginning of the frame, before
121 any changes have been made to the DOM since the last time a style flush
124 Historically, there hasn't been an easy way of doing this - however,
125 [bug 1434376](https://bugzilla.mozilla.org/show_bug.cgi?id=1434376)
126 has landed some ChromeOnly helpers to the window binding to
129 If you want to queue up some JavaScript to run after the next *natural*
130 style and layout flush, try:
133 // Suppose we want to get the computed "display" style of some node without
134 // causing a style flush. We could do it this way:
135 async function nodeIsDisplayNone(node) {
136 let display = await window.promiseDocumentFlushed(() => {
137 // Do _not_ under any circumstances write to the DOM in one of these
139 return window.getComputedStyle(node).display;
142 return display == "none";
145 See [Detecting and avoiding synchronous reflow](#detecting-and-avoiding-synchronous-reflow)
146 for a more advanced example of getting layout information, and then
147 setting it safely, without causing flushes.
149 bestpractices.html#detecting-and-avoiding-synchronous-reflow
152 *promiseDocumentFlushed* is only available to privileged script, and
153 should be called on the inner window of a top-level frame. Calling it on
154 the outer window of a subframe is not supported, and calling it from
155 within the inner window of a subframe might cause the callback to fire
156 even though a style and layout flush will still be required. These
157 gotchas should be fixed by
158 [bug 1441173](https://bugzilla.mozilla.org/show_bug.cgi?id=1441173).
160 For now, it is up to you as the consumer of this API to not accidentally
161 write to the DOM within the *promiseDocumentFlushed* callback. Doing
162 so might cause flushes to occur for other *promiseDocumentFlushed*
163 callbacks that are scheduled to fire in the same tick of the refresh
165 [bug 1441168](https://bugzilla.mozilla.org/show_bug.cgi?id=1441168)
166 tracks work to make it impossible to modify the DOM within a
167 *promiseDocumentFlushed* callback.
169 ### Writing tests to ensure you don’t add more synchronous style flushes
171 Unlike reflow, there isn’t a “observer” mechanism for style
172 recalculations. However, as of Firefox 49, the
173 *nsIDOMWindowUtils.elementsRestyled* attribute records a count of how
174 many style calculations have occurred for a particular DOM window.
176 It should be possible to write a test that gets the
177 *nsIDOMWindowUtils* for a browser window, records the number of
178 styleFlushes, then **synchronously calls the function** that you want to
179 test, and immediately after checks the styleFlushes attribute again. If
180 the value went up, your code caused synchronous style flushes to occur.
182 Note that your test and function *must be called synchronously* in order
183 for this test to be accurate. If you ever go back to the event loop (by
184 yielding, waiting for an event, etc), style flushes unrelated to your
185 code are likely to run, and your test will give you a false positive.
187 ## Detecting and avoiding synchronous reflow
189 This is also sometimes called “sync layout”, "sync layout flushes" or
190 “sync layout calculations”
192 *Sync reflow* is a term bandied about a lot, and has negative
193 connotations. It's not unusual for an engineer to have only the vaguest
194 sense of what it is—and to only know to avoid it. This section will
195 attempt to demystify things.
197 The first time a document (XUL or HTML) loads, we parse the markup, and
198 then apply styles. Once the styles have been calculated, we then need to
199 calculate where things are going to be placed on the page. This layout
200 step can be seen in the “16ms” pipeline graphic above, and occurs just
201 before we paint things to be composited for the user to see.
203 It is expected that over time, script will update the DOM, requiring us
204 to recalculate styles, and then update layout. Normally, however, the
205 changes to the DOM just result in the standard style calculation that
206 occurs immediately after the JavaScript has finished running during the
209 ### Interruptible reflow
211 Since [the early days](https://bugzilla.mozilla.org/show_bug.cgi?id=67752), Gecko has
212 had the notion of interruptible reflow. This is a special type of
213 **content-only** reflow that checks at particular points whether or not
214 it should be interrupted (usually to respond to user events).
216 Because **interruptible reflows can only be interrupted when laying out
217 content, and not chrome UI**, the rest of this section is offered only
220 When an interruptible reflow is interrupted, what really happens is that
221 certain layout operations can be skipped in order to paint and process
224 When an interruptible reflow is interrupted, the best-case scenario is
225 that all layout is skipped, and the layout operation ends.
227 The worst-case scenario is that none of the layout can be skipped
228 despite being interrupted, and the entire layout calculation occurs.
230 Reflows that are triggered "naturally" by the 16ms tick are all
231 considered interruptible. Despite not actually being interuptible when
232 laying out chrome UI, striving for interruptible layout is always good
233 practice because uninterruptible layout has the potential to be much
234 worse (see next section).
236 **To repeat, only interruptible reflows in web content can be
239 ### Uninterruptible reflow
241 Uninterruptible reflow is what we want to **avoid at all costs**.
242 Uninterruptible reflow occurs when some DOM node’s styles have changed
243 such that the size or position of one or more nodes in the document will
244 need to be updated, and then **JavaScript asks for the size or position
245 of anything**. Since everything is pending a reflow, the answer isn't
246 available, so everything stalls until the reflow is complete and the
247 script can be given an answer. Flushing layout also means that styles
248 must be flushed to calculate the most up-to-date state of things, so
249 it's a double-whammy.
251 Here’s a simple example, cribbed from [this blog post by Paul
252 Rouget](http://paulrouget.com/e/fxoshud):
255 div1.style.margin = "200px"; // Line 1
256 var height1 = div1.clientHeight; // Line 2
257 div2.classList.add("foobar"); // Line 3
258 var height2 = div2.clientHeight; // Line 4
259 doSomething(height1, height2); // Line 5
261 At line 1, we’re setting some style information on a DOM node that’s
262 going to result in a reflow - but (at just line 1) it’s okay, because
263 that reflow will happen after the style calculation.
265 Note line 2 though - we’re asking for the height of some DOM node. This
266 means that Gecko needs to synchronously calculate layout (and styles)
267 using an uninterruptible reflow in order to answer the question that
268 JavaScript is asking (“What is the *clientHeight* of *div1*?”).
270 It’s possible for our example to avoid this synchronous, uninterruptible
271 reflow by moving lines 2 and 4 above line 1. Assuming there weren’t any
272 style changes requiring size or position recalculation above line 1, the
273 *clientHeight* information should be cached since the last reflow, and
274 will not result in a new layout calculation.
276 If you can avoid querying for the size or position of things in
277 JavaScript, that’s the safest option—especially because it’s always
278 possible that something earlier in this tick of JavaScript execution
279 caused a style change in the DOM without you knowing it.
281 Note that given the same changes to the DOM of a chrome UI document, a
282 single synchronous uninterruptible reflow is no more computationally
283 expensive than an interruptible reflow triggered by the 16ms tick. It
284 is, however, advantageous to strive for reflow to only occur in the one
285 place (the layout step of the 16ms tick) as opposed to multiple times
286 during the 16ms tick (which has a higher probability of running through
289 ### How do I avoid triggering uninterruptible reflow?
291 Here's a [list of things that JavaScript can ask for that can cause
292 uninterruptible reflow](https://gist.github.com/paulirish/5d52fb081b3570c81e3a), to
293 help you think about the problem. Note that some items in the list may
294 be browser-specific or subject to change, and that an item not occurring
295 explicitly in the list doesn't mean it doesn't cause reflow. For
296 instance, at time of writing accessing *event.rangeOffset* [triggers
297 reflow](https://searchfox.org/mozilla-central/rev/6bfadf95b4a6aaa8bb3b2a166d6c3545983e179a/dom/events/UIEvent.cpp#215-226)
298 in Gecko, and does not occur in the earlier link. If you're unsure
299 whether something causes reflow, check!
301 Note how abundant the properties in that first list are. This means that
302 when enumerating properties on DOM objects (e.g. elements/nodes, events,
303 windows, etc.) **accessing the value of each enumerated property will
304 almost certainly (accidentally) cause uninterruptible reflow**, because
305 a lot of DOM objects have one or even several properties that do so.
307 If you require size or position information, you have a few options.
309 [bug 1434376](https://bugzilla.mozilla.org/show_bug.cgi?id=1434376)
310 has landed a helper in the window binding to make it easier for
311 privileged code to queue up JavaScript to run when we know that the DOM
312 is not dirty, and size, position, and style information is cheap to
317 async function matchWidth(elem, otherElem) {
318 let width = await window.promiseDocumentFlushed(() => {
319 // Do _not_ under any circumstances write to the DOM in one of these
321 return elem.clientWidth;
324 requestAnimationFrame(() => {
325 otherElem.style.width = `${width}px`;
329 Please see the section on *promiseDocumentFlushed* in [Detecting and
330 avoiding synchronous style flushes](#detecting-and-avoiding-synchronous-style-flushes)
331 for more information on how to use the API.
333 Note that queries for size and position information are only expensive
334 if the DOM has been written to. Otherwise, we're doing a cheap look-up
335 of cached information. If we work hard to move all DOM writes into
336 *requestAnimationFrame()*, then we can be sure that all size and
337 position queries are cheap.
339 It's also possible (though less infallible than
340 *promiseDocumentFlushed*) to queue JavaScript to run very soon after
341 the frame has been painted, where the likelihood is highest that the DOM
342 has not been written to, and layout and style information queries are
343 still cheap. This can be done by using a *setTimeout* or dispatching a
344 runnable inside a *requestAnimationFrame* callback, for example:
347 requestAnimationFrame(() => {
349 // This code will be run ASAP after Style and Layout information have
350 // been calculated and the paint has occurred. Unless something else
351 // has dirtied the DOM very early, querying for style and layout information
352 // here should be cheap.
356 // Or, if you are running in privileged JavaScript and want to avoid the timer overhead,
357 // you could also use:
359 requestAnimationFrame(() => {
360 Services.tm.dispatchToMainThread(() => {
361 // Same-ish as above.
365 This also implies that *querying for size and position information* in
366 *requestAnimationFrame()* has a high probability of causing a
369 ### Other useful methods
371 Below you'll find some suggestions for other methods which may come in
372 handy when you need to do things without incurring synchronous reflow.
373 These methods generally return the most-recently-calculated value for
374 the requested value, which means the value may no longer be current, but
375 may still be "close enough" for your needs. Unless you need precisely
376 accurate information, they can be valuable tools in your performance
379 #### nsIDOMWindowUtils.getBoundsWithoutFlushing()
381 *getBoundsWithoutFlushing()* does exactly what its name suggests: it
382 allows you to get the bounds rectangle for a DOM node contained in a
383 window without flushing layout. This means that the information you get
384 is potentially out-of-date, but allows you to avoid a sync reflow. If
385 you can make do with information that may not be quite current, this can
388 #### nsIDOMWindowUtils.getRootBounds()
390 Like *getBoundsWithoutFlushing()*, *getRootBounds()* lets you get
391 the dimensions of the window without risking a synchronous reflow.
393 #### nsIDOMWindowUtils.getScrollXY()
395 Returns the window's scroll offsets without taking the chance of causing
398 ### Writing tests to ensure you don’t add more unintentional reflow
401 [nsIReflowObserver](https://dxr.mozilla.org/mozilla-central/source/docshell/base/nsIReflowObserver.idl)
402 lets us detect both interruptible and uninterruptible reflows. A number
403 of tests have been written that exercise various functions of the
404 browser [opening tabs](http://searchfox.org/mozilla-central/rev/78cefe75fb43195e7f5aee1d8042b8d8fc79fc70/browser/base/content/test/general/browser_tabopen_reflows.js),
405 [opening windows](http://searchfox.org/mozilla-central/source/browser/base/content/test/general/browser_windowopen_reflows.js)
406 and ensure that we don’t add new uninterruptible reflows accidentally
407 while those actions occur.
409 You should add tests like this for your feature if you happen to be
412 ## Detecting over-painting with paint flashing
414 Painting is, in general, cheaper than both style calculation and layout
415 calculation; still, the more you can avoid, the better. Generally
416 speaking, the larger an area that needs to be repainted, the longer it
417 takes. Similarly, the more things that need to be repainted, the longer
420 Our graphics team has added a handy feature to help you detect when and
421 where paints are occurring. This feature is called “paint flashing,” and
422 it can be activated for both web content and the browser chrome. Paint
423 flashing tints each region being painted with a randomly selected color
424 so that it’s more easy to see what on the screen is being painted.
426 - You can activate paint flashing for browser chrome by setting
427 *nglayout.debug.paint_flashing_chrome* to *true*.
429 - You can activate paint flashing for web content by setting
430 *nglayout.debug.paint_flashing* to *true*.
432 After enabling these, exercise your function and see what’s painting.
433 See a lot of flashing / colors? That means a lot of painting is going
434 on. The worst case is called **over-painting**. This is when you draw
435 multiple times over the same space. Unless transparency is involved, all
436 but the last painting will be overwritten, becoming unnecessary. If you
437 can find ways to avoid doing this, you can save substantial time.
439 Keep in mind that painting occurs on the main thread. Remember, too,
440 that the goal is to have as little happen on the main thread as
441 possible. That means that finding and removing (when possible)
442 over-painting is a good place to start reducing your burden on the main
443 thread, which will in turn improve performance.
445 Perhaps you’re animating something that requires a repaint? For example,
446 transitioning the *background-color* of a DOM node from red to blue
447 will result in a repaint for every frame of the animation, and paint
448 flashing will reveal that. Consider using a different animation that can
449 be accelerated by the GPU. These GPU-accelerated animations occur off of
450 the main thread, and have a much higher probability of running at 60 FPS
451 (see the section below called [Use the compositor for animations](#use-the-compositor-for-animations)
452 for further details).
454 Perhaps you’re touching some DOM nodes in such a way that unexpected
455 repaints are occurring in an area that don’t need it. Best to
456 investigate and try to remove those as best you can. Sometimes, our
457 graphics layer invalidates regions in ways that might not be clear to
458 you, and a section outside of the thing that just repainted will also
459 repaint. Sometimes this can be addressed by ensuring that the thing
460 changing is on its own layer (though this comes at a memory cost). You
461 can put something on its own layer by setting its *z-index*, or by
462 setting the *will-change* on the node, though this should be used
465 If you’re unsure why something is repainting, consider talking to our
466 always helpful graphics team in the [gfx room](https://chat.mozilla.org/#/room/%23gfx:mozilla.org) on
467 [Matrix](https://wiki.mozilla.org/Matrix), and they can probably
468 advise you. Note that a significant number of the graphics team members
469 are in the US Eastern Time zone (UTC-5 or UTC-4 during Daylight Saving
470 Time), so let that information guide your timing when you ask questions
471 in the [gfx room](https://chat.mozilla.org/#/room/%23gfx:mozilla.org)
474 ## Adding nodes using DocumentFragments
476 Sometimes you need to add several DOM nodes as part of an existing DOM
477 tree. For example, when using XUL *\<xul:menupopup\>s*, you often have
478 script which dynamically inserts *\<xul:menuitem\>s*. Inserting items
479 into the DOM has a cost. If you're adding a number of children to a DOM
480 node in a loop, it's often more efficient to batch them into a single
481 insertion by creating a *DocumentFragment*, adding the new nodes to
482 that, then inserting the *DocumentFragment* as a child of the desired
485 A *DocumentFragment* is maintained in memory outside the DOM itself,
486 so changes don't cause reflow. The API is straightforward:
488 1. Create the *DocumentFragment* by calling
489 *Document.createDocumentFragment()*.
491 2. Create each child element (by calling *Document.createElement()*
492 for example), and add each one to the fragment by calling
493 *DocumentFragment.appendChild()*.
495 3. Once the fragment is populated, append the fragment to the DOM by
496 calling *appendChild()* on the parent element for the new elements.
498 This example has been cribbed from [davidwalsh’s blog
499 post](https://davidwalsh.name/documentfragment):
502 // Create the fragment
504 var frag = document.createDocumentFragment();
506 // Create numerous list items, add to fragment
508 for(var x = 0; x < 10; x++) {
509 var li = document.createElement("li");
510 li.innerHTML = "List item " + x;
511 frag.appendChild(li);
514 // Mass-add the fragment nodes to the list
516 listNode.appendChild(frag);
518 The above is strictly cheaper than individually adding each node to the
521 ## The Gecko profiler add-on is your friend
523 The Gecko profiler is your best friend when diagnosing performance
524 problems and looking for bottlenecks. There’s plenty of excellent
525 documentation on MDN about the Gecko profiler:
527 - [Basic instructions for gathering and sharing a performance profile](reporting_a_performance_problem.md)
529 - [Advanced profile analysis](https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Profiling_with_the_Built-in_Profiler)
531 ## Don’t guess—measure.
533 If you’re working on a performance improvement, this should go without
534 saying: ensure that what you care about is actually improving by
535 measuring before and after.
537 Landing a speculative performance enhancement is the same thing as
538 landing speculative bug fixes—these things need to be tested. Even if
539 that means instrumenting a function with a *Date.now()* recording at
540 the entrance, and another *Date.now()* at the exit points in order to
541 measure processing time changes.
543 Prove to yourself that you’ve actually improved something by measuring
546 ### Use the performance API
549 API](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API)
550 is very useful for taking high-resolution measurements. This is usually
551 much better than using your own hand-rolled timers to measure how long
552 things take. You access the API through *Window.performance*.
554 Also, the Gecko profiler back-end is in the process of being modified to
555 expose things like markers (from *window.performance.mark()*).
557 ### Use the compositor for animations
559 Performing animations on the main thread should be treated as
560 **deprecated**. Avoid doing it. Instead, animate using
561 *Element.animate()*. See the article [Animating like you just don't
562 care](https://hacks.mozilla.org/2016/08/animating-like-you-just-dont-care-with-element-animate/)
563 for more information on how to do this.
565 ### Explicitly define start and end animation values
567 Some optimizations in the animation code of Gecko are based on an
568 expectation that the *from* (0%) and the *to* (100%) values will be
569 explicitly defined in the *@keyframes* definition. Even though these
570 values may be inferred through the use of initial values or the cascade,
571 the offscreen animation optimizations are dependent on the explicit
572 definition. See [this comment](https://bugzilla.mozilla.org/show_bug.cgi?id=1419096#c18)
573 and a few previous comments on that bug for more information.
575 ## Use IndexedDB for storage
577 [AppCache](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/en-US/docs/Web/HTML/Using_the_application_cache)
579 [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage)
580 are synchronous storage APIs that will block the main thread when you
581 use them. Avoid them at all costs!
583 [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB)
584 is preferable, as the API is asynchronous (all disk operations occur off
585 of the main thread), and can be accessed from web workers.
587 IndexedDB is also arguably better than storing and retrieving JSON from
588 a file—particularly if the JSON encoding or decoding is occurring on the
589 main thread. IndexedDB will do JavaScript object serialization and
590 deserialization for you using the [structured clone
591 algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
592 meaning that you can stash [things like maps, sets, dates, blobs, and
593 more](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#Supported_types)
594 without having to do conversions for JSON compatibility.
596 A Promise-based wrapper for IndexedDB,
597 [IndexedDB.jsm](http://searchfox.org/mozilla-central/source/toolkit/modules/IndexedDB.jsm)
598 is available for chrome code.
600 ## Test on weak hardware
602 For the folks paid to work on Firefox, we tend to have pretty powerful
603 hardware for development. This is great, because it reduces build times,
604 and means we can do our work faster.
606 We should remind ourselves that the majority of our user base is
607 unlikely to have similar hardware. Look at the [Firefox Hardware
608 Report](https://data.firefox.com/dashboard/hardware) to get
609 a sense of what our users are working with. Test on slower machines to
610 make it more obvious to yourself if what you’ve written impacts the
611 performance of the browser.
613 ## Consider loading scripts with the subscript loader asynchronously
615 If you've ever used the subscript loader, you might not know that it can
616 load scripts asynchronously, and return a Promise once they're loaded.
620 Services.scriptloader.loadSubScriptWithOptions(myScriptURL, { async: true }).then(() => {
621 console.log("Script at " + myScriptURL + " loaded asynchronously!");