merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 12 May 2015 16:10:32 +0200
changeset 263022 bedce1b405a38273d190853039000b78cc183bb6
parent 262859 2ad2f521c01704384b2f5c7cf1dbfa7a452cc8e2 (current diff)
parent 263021 6afb088b0cff22dcbdf88ba0d5c1291889d1e7e8 (diff)
child 263023 42db79f3cd6ba7fd8e331a490da007904d88870d
child 263031 5f6263cb1e504c462ba7f304f93b93b6d153f026
child 263084 ed36c68799441edb22044213af40e406d8d1f131
child 263109 a652bc38f0b1c21bae4f6fac8c7aa1e51306c0aa
push id8157
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:36:23 +0000
treeherdermozilla-aurora@d480e05bd276 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone41.0a1
merge mozilla-inbound to mozilla-central a=merge
browser/base/content/test/general/browser.ini
dom/base/MultipartFileImpl.cpp
dom/base/MultipartFileImpl.h
dom/ipc/ContentChild.cpp
dom/media/tests/mochitest/test_peerConnection_addCandidateInHaveLocalOffer.html
dom/plugins/ipc/interpose/Makefile.in
intl/unicharutil/util/Makefile.in
layout/base/tests/marionette/test_selectioncarets_multiplerange.py
memory/mozalloc/Makefile.in
rdf/util/Makefile.in
testing/web-platform/meta/IndexedDB/idbcursor_advance_index.htm.ini
testing/web-platform/meta/IndexedDB/idbcursor_advance_objectstore.htm.ini
tools/profiler/JSStreamWriter.cpp
tools/profiler/JSStreamWriter.h
tools/profiler/tests/gtest/JSStreamWriterTest.cpp
xpcom/glue/Makefile.in
xpcom/glue/standalone/Makefile.in
xpcom/glue/standalone/staticruntime/Makefile.in
xpcom/glue/staticruntime/Makefile.in
xpcom/typelib/xpt/Makefile.in
--- a/README.txt
+++ b/README.txt
@@ -20,11 +20,8 @@ are accessible on Google Groups, or news
 
 You can download nightly development builds from the Mozilla FTP server.
 Keep in mind that nightly builds, which are used by Mozilla developers for
 testing, may be buggy. Firefox nightlies, for example, can be found at:
 
     ftp://ftp.mozilla.org/pub/firefox/nightly/latest-trunk/
             - or -
     http://nightly.mozilla.org/
-
-
->>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -131,17 +131,17 @@ skip-if = e10s # Bug 1093153 - no about:
 [browser_autocomplete_a11y_label.js]
 skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir (works on its own)
 [browser_autocomplete_enter_race.js]
 [browser_autocomplete_no_title.js]
 [browser_autocomplete_autoselect.js]
 [browser_autocomplete_oldschool_wrap.js]
 [browser_autocomplete_tag_star_visibility.js]
 [browser_backButtonFitts.js]
-skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug 1099154: test touches content (attempts to add an event listener directly to the contentWindow)
+skip-if = os != "win" # The Fitts Law back button is only supported on Windows (bug 571454)
 [browser_beforeunload_duplicate_dialogs.js]
 skip-if = e10s # bug 967873 means permitUnload doesn't work in e10s mode
 [browser_blob-channelname.js]
 [browser_bookmark_titles.js]
 skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
 [browser_bug304198.js]
 [browser_bug321000.js]
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
@@ -152,17 +152,16 @@ skip-if = true # browser_bug321000.js is
 [browser_bug386835.js]
 [browser_bug405137.js]
 [browser_bug406216.js]
 [browser_bug409481.js]
 [browser_bug409624.js]
 [browser_bug413915.js]
 [browser_bug416661.js]
 [browser_bug417483.js]
-skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
 [browser_bug419612.js]
 [browser_bug422590.js]
 [browser_bug423833.js]
 skip-if = true # bug 428712
 [browser_bug424101.js]
 [browser_bug427559.js]
 [browser_bug431826.js]
 [browser_bug432599.js]
@@ -243,17 +242,16 @@ skip-if = buildapp == 'mulet'
 [browser_bug676619.js]
 skip-if = buildapp == 'mulet' || os == "mac" # mac: Intermittent failures, bug 925225
 [browser_bug678392.js]
 skip-if = e10s # bug 1102331 - does focus things on the content window which break in e10s mode
 [browser_bug710878.js]
 [browser_bug719271.js]
 [browser_bug724239.js]
 [browser_bug734076.js]
-skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
 [browser_bug735471.js]
 [browser_bug749738.js]
 [browser_bug763468_perwindowpb.js]
 [browser_bug767836_perwindowpb.js]
 [browser_bug771331.js]
 [browser_bug783614.js]
 [browser_bug817947.js]
 [browser_bug822367.js]
@@ -414,17 +412,17 @@ skip-if = e10s # Bug 1100664 - test reli
 [browser_tabs_owner.js]
 [browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js]
 run-if = e10s
 [browser_trackingUI.js]
 support-files =
   trackingPage.html
   benignPage.html
 [browser_typeAheadFind.js]
-skip-if = buildapp == 'mulet' || e10s # Bug 921935 - focusmanager issues with e10s (test calls waitForFocus)
+skip-if = buildapp == 'mulet'
 [browser_unknownContentType_title.js]
 [browser_unloaddialogs.js]
 skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs, which it doesn't
 [browser_urlHighlight.js]
 [browser_urlbarAutoFillTrimURLs.js]
 [browser_urlbarCopying.js]
 [browser_urlbarEnter.js]
 [browser_urlbarEnterAfterMouseOver.js]
--- a/browser/base/content/test/general/browser_backButtonFitts.js
+++ b/browser/base/content/test/general/browser_backButtonFitts.js
@@ -1,30 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-function test () {
-  waitForExplicitFinish();
-  var firstLocation = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
-  gBrowser.selectedTab = gBrowser.addTab(firstLocation);
-  gBrowser.selectedBrowser.addEventListener("pageshow", function onPageShow1() {
-    gBrowser.selectedBrowser.removeEventListener("pageshow", onPageShow1);
-    gBrowser.selectedBrowser.contentWindow.history.pushState("page2", "page2", "page2");
-    window.maximize();
+add_task(function* () {
+  let firstLocation = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, firstLocation);
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    // Push the state before maximizing the window and clicking below.
+    content.history.pushState("page2", "page2", "page2");
 
-    // Find where the nav-bar is vertically.
-    var navBar = document.getElementById("nav-bar");
-    var boundingRect = navBar.getBoundingClientRect();
-    var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2);
-    var xPixel = 0; // Use the first pixel of the screen since it is maximized.
+    // While in the child process, add a listener for the popstate event here. This
+    // event will fire when the mouse click happens.
+    content.addEventListener("popstate", function onPopState() {
+      content.removeEventListener("popstate", onPopState, false);
+      sendAsyncMessage("Test:PopStateOccurred", { location: content.document.location.href });
+    }, false);
+  });
+
+  window.maximize();
 
-    gBrowser.selectedBrowser.contentWindow.addEventListener("popstate", function onPopState() {
-      gBrowser.selectedBrowser.contentWindow.removeEventListener("popstate", onPopState);
-      is(gBrowser.selectedBrowser.contentDocument.location.href, firstLocation,
-         "Clicking the first pixel should have navigated back.");
-      window.restore();
-      gBrowser.removeCurrentTab();
-      finish();
+  // Find where the nav-bar is vertically.
+  var navBar = document.getElementById("nav-bar");
+  var boundingRect = navBar.getBoundingClientRect();
+  var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2);
+  var xPixel = 0; // Use the first pixel of the screen since it is maximized.
+
+  let resultLocation = yield new Promise(resolve => {
+    messageManager.addMessageListener("Test:PopStateOccurred", function statePopped(message) {
+      messageManager.removeMessageListener("Test:PopStateOccurred", statePopped);
+      resolve(message.data.location);
     });
+
     EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window);
   });
-}
+
+  is(resultLocation, firstLocation, "Clicking the first pixel should have navigated back.");
+  window.restore();
+
+  gBrowser.removeCurrentTab();
+});
--- a/browser/base/content/test/general/browser_bug417483.js
+++ b/browser/base/content/test/general/browser_bug417483.js
@@ -1,26 +1,30 @@
-function test() {
-  waitForExplicitFinish();
-  
-  var htmlContent = "data:text/html, <iframe src='data:text/html,text text'></iframe>";
-  gBrowser.addEventListener("pageshow", onPageShow, false);
+add_task(function* () {
+  let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true);
+  const htmlContent = "data:text/html, <iframe src='data:text/html,text text'></iframe>";
   gBrowser.loadURI(htmlContent);
-}
+  yield loadedPromise;
 
-function onPageShow() {
-    gBrowser.removeEventListener("pageshow", onPageShow, false);
-    var frame = content.frames[0];
-    var sel = frame.getSelection();
-    var range = frame.document.createRange();
-    var tn = frame.document.body.childNodes[0];
-    range.setStart(tn , 4);
-    range.setEnd(tn , 5);
+  yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+    let frame = content.frames[0];
+    let sel = frame.getSelection();
+    let range = frame.document.createRange();
+    let tn = frame.document.body.childNodes[0];
+    range.setStart(tn, 4);
+    range.setEnd(tn, 5);
     sel.addRange(range);
     frame.focus();
-    
-    document.popupNode = frame.document.body;
-    var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-    var contextMenu = new nsContextMenu(contentAreaContextMenu);
+  });
+
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
 
-    ok(document.getElementById("frame-sep").hidden, "'frame-sep' should be hidden if the selection contains only spaces");
-    finish();
-}
+  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+  yield BrowserTestUtils.synthesizeMouse("frame", 5, 5,
+        { type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+  yield popupShownPromise;  
+
+  ok(document.getElementById("frame-sep").hidden, "'frame-sep' should be hidden if the selection contains only spaces");
+
+  let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+  contentAreaContextMenu.hidePopup();
+  yield popupHiddenPromise;  
+});
--- a/browser/base/content/test/general/browser_bug734076.js
+++ b/browser/base/content/test/general/browser_bug734076.js
@@ -1,104 +1,108 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test() {
-  waitForExplicitFinish();
-
-  let tab = gBrowser.selectedTab = gBrowser.addTab();
-  registerCleanupFunction(function () {
-    gBrowser.removeTab(tab);
-  });
+add_task(function* ()
+{
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, null, false);
 
   let browser = tab.linkedBrowser;
   browser.stop(); // stop the about:blank load
 
   let writeDomainURL = encodeURI("data:text/html,<script>document.write(document.domain);</script>");
+
   let tests = [
     {
       name: "view background image",
       url: "http://mochi.test:8888/",
-      go: function (cb) {
-        let contentBody = browser.contentDocument.body;
-        contentBody.style.backgroundImage = "url('" + writeDomainURL + "')";
-        doOnLoad(function () {
-          let domain = browser.contentDocument.body.textContent;
-          is(domain, "", "no domain was inherited for view background image");
-          cb();
+      element: "body",
+      go: function () {
+        return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+          let contentBody = content.document.body;
+          contentBody.style.backgroundImage = "url('" + arg.writeDomainURL + "')";
+
+          return "context-viewbgimage";
         });
-
-        doContextCommand(contentBody, "context-viewbgimage");
+      },
+      verify: function () {
+        return ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+          return [content.document.body.textContent, "no domain was inherited for view background image"];
+        });
       }
     },
     {
       name: "view image",
       url: "http://mochi.test:8888/",
-      go: function (cb) {
-        doOnLoad(function () {
-          let domain = browser.contentDocument.body.textContent;
-          is(domain, "", "no domain was inherited for view image");
-          cb();
-        });
+      element: "img",
+      go: function () {
+        return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+          let doc = content.document;
+          let img = doc.createElement("img");
+          img.setAttribute("src", arg.writeDomainURL);
+          doc.body.insertBefore(img, doc.body.firstChild);
 
-        let doc = browser.contentDocument;
-        let img = doc.createElement("img");
-        img.setAttribute("src", writeDomainURL);
-        doc.body.appendChild(img);
-
-        doContextCommand(img, "context-viewimage");
+          return "context-viewimage";
+        });
+      },
+      verify: function () {
+        return ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+          return [content.document.body.textContent, "no domain was inherited for view image"];
+        });
       }
     },
     {
       name: "show only this frame",
       url: "http://mochi.test:8888/",
-      go: function (cb) {
-        doOnLoad(function () {
-          let domain = browser.contentDocument.body.textContent;
-          is(domain, "", "no domain was inherited for 'show only this frame'");
-          cb();
-        });
+      element: "iframe",
+      go: function () {
+        return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+          let doc = content.document;
+          let iframe = doc.createElement("iframe");
+          iframe.setAttribute("src", arg.writeDomainURL);
+          doc.body.insertBefore(iframe, doc.body.firstChild);
 
-        let doc = browser.contentDocument;
-        let iframe = doc.createElement("iframe");
-        iframe.setAttribute("src", writeDomainURL);
-        doc.body.appendChild(iframe);
-
-        iframe.addEventListener("load", function onload() {
-          doContextCommand(iframe.contentDocument.body,
-                           "context-showonlythisframe");
-        }, false);
+          // Wait for the iframe to load.
+          return new Promise(resolve => {
+            iframe.addEventListener("load", function onload() {
+              iframe.removeEventListener("load", onload, true);
+              resolve("context-showonlythisframe");
+            }, true);
+          });
+        });
+      },
+      verify: function () {
+        return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+          return [content.document.body.textContent, "no domain was inherited for 'show only this frame'"];
+        });
       }
     }
   ];
 
-  function doOnLoad(cb) {
-    browser.addEventListener("load", function onLoad(e) {
-      if (e.target != browser.contentDocument)
-        return;
-      browser.removeEventListener("load", onLoad, true);
-      cb();
-    }, true);
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+  for (let test of tests) {
+    let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+    gBrowser.loadURI(test.url);
+    yield loadedPromise;
+
+    info("Run subtest " + test.name);
+    let commandToRun = yield test.go();
+
+    let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+    yield BrowserTestUtils.synthesizeMouse(test.element, 3, 3,
+          { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+    yield popupShownPromise;
+
+    let loadedAfterCommandPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+    document.getElementById(commandToRun).click();
+    yield loadedAfterCommandPromise;
+
+    let result = yield test.verify();
+    ok(!result[0], result[1]);
+
+    let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+    contentAreaContextMenu.hidePopup();
+    yield popupHiddenPromise;
   }
 
-  function doNext() {
-    let test = tests.shift();
-    if (test) {
-      info("Running test: " + test.name);
-      doOnLoad(function () {
-        test.go(function () {
-          executeSoon(doNext);
-        });
-      });
-      browser.contentDocument.location = test.url;
-    } else {
-      executeSoon(finish);
-    }
-  }
-
-  doNext();
-}
-
-function doContextCommand(aNode, aCmd) {
-  EventUtils.sendMouseEvent({ type: "contextmenu" }, aNode);
-  document.getElementById(aCmd).click();
-  document.getElementById("contentAreaContextMenu").hidePopup();
-}
+  gBrowser.removeCurrentTab();
+});
--- a/browser/base/content/test/general/browser_tabopen_reflows.js
+++ b/browser/base/content/test/general/browser_tabopen_reflows.js
@@ -41,16 +41,25 @@ const EXPECTED_REFLOWS = [
     "TabItems_link@chrome://browser/content/tabview.js|" +
     "TabItems_init/this._eventListeners.open@chrome://browser/content/tabview.js|",
 
   // SessionStore.getWindowDimensions()
   "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" +
     "ssi_updateWindowFeatures/<@resource:///modules/sessionstore/SessionStore.jsm|" +
     "ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" +
     "ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|",
+
+  // selection change notification may cause querying the focused editor content
+  // by IME and that will cause reflow.
+  "select@chrome://global/content/bindings/textbox.xml|" +
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
+    "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
+    "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
+    "BrowserOpenTab@chrome://browser/content/browser.js|",
+
 ];
 
 const PREF_PRELOAD = "browser.newtab.preload";
 const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
 
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new tabs.
--- a/browser/base/content/test/general/browser_typeAheadFind.js
+++ b/browser/base/content/test/general/browser_typeAheadFind.js
@@ -1,28 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-let testWindow = null;
+add_task(function *() {
+  let testWindow = yield BrowserTestUtils.openNewBrowserWindow();
 
-function test() {
-  waitForExplicitFinish();
+  testWindow.gBrowser.loadURI("data:text/html,<h1>A Page</h1>");
+  yield BrowserTestUtils.browserLoaded(testWindow.gBrowser.selectedBrowser);
+
+  yield SimpleTest.promiseFocus(testWindow.gBrowser.selectedBrowser);
 
-  testWindow = OpenBrowserWindow();
-  whenDelayedStartupFinished(testWindow, function () {
-    let selectedBrowser = testWindow.gBrowser.selectedBrowser;
-    selectedBrowser.addEventListener("load", function onLoad() {
-      selectedBrowser.removeEventListener("load", onLoad, true);
-      ok(true, "load listener called");
-      waitForFocus(onFocus, testWindow.content);
-    }, true);
-    testWindow.gBrowser.loadURI("data:text/html,<h1>A Page</h1>");
-  });
-}
+  ok(!testWindow.gFindBarInitialized, "find bar is not initialized");
 
-function onFocus() {
-  ok(!testWindow.gFindBarInitialized, "find bar is not initialized");
+  let findBarOpenPromise = promiseWaitForEvent(testWindow.gBrowser, "findbaropen");
   EventUtils.synthesizeKey("/", {}, testWindow);
+  yield findBarOpenPromise;
+
   ok(testWindow.gFindBarInitialized, "find bar is now initialized");
-  testWindow.close();
-  finish();
-}
+
+  yield BrowserTestUtils.closeWindow(testWindow);
+});
--- a/browser/devtools/performance/modules/actors.js
+++ b/browser/devtools/performance/modules/actors.js
@@ -163,16 +163,22 @@ ProfilerFrontFacade.prototype = {
     return data;
   }),
 
   /**
    * Returns profile data from now since `startTime`.
    */
   getProfile: Task.async(function *(options) {
     let profilerData = yield (actorCompatibilityBridge("getProfile").call(this, options));
+    // If the backend is not deduped, dedupe it ourselves, as rest of the code
+    // expects a deduped profile.
+    if (profilerData.profile.meta.version === 2) {
+      RecordingUtils.deflateProfile(profilerData.profile);
+    }
+
     // If the backend does not support filtering by start and endtime on platform (< Fx40),
     // do it on the client (much slower).
     if (!this.traits.filterable) {
       RecordingUtils.filterSamples(profilerData.profile, options.startTime || 0);
     }
 
     return profilerData;
   }),
--- a/browser/devtools/performance/modules/io.js
+++ b/browser/devtools/performance/modules/io.js
@@ -99,16 +99,19 @@ let PerformanceIO = {
       }
       if (!isValidSerializerVersion(recordingData.version)) {
         deferred.reject(new Error("Unsupported recording data file version."));
         return;
       }
       if (recordingData.version === PERF_TOOL_SERIALIZER_LEGACY_VERSION) {
         recordingData = convertLegacyData(recordingData);
       }
+      if (recordingData.profile.meta.version === 2) {
+        RecordingUtils.deflateProfile(recordingData.profile);
+      }
       deferred.resolve(recordingData);
     });
 
     return deferred.promise;
   }
 };
 
 exports.PerformanceIO = PerformanceIO;
--- a/browser/devtools/performance/modules/recording-utils.js
+++ b/browser/devtools/performance/modules/recording-utils.js
@@ -18,35 +18,36 @@ exports.RecordingUtils = {};
  *
  * @param object profile
  *        The profiler data received from the backend.
  * @param number profilerStartTime
  *        The earliest acceptable sample time (in milliseconds).
  */
 exports.RecordingUtils.filterSamples = function(profile, profilerStartTime) {
   let firstThread = profile.threads[0];
-
-  firstThread.samples = firstThread.samples.filter(e => {
-    return e.time >= profilerStartTime;
+  const TIME_SLOT = firstThread.samples.schema.time;
+  firstThread.samples.data = firstThread.samples.data.filter(e => {
+    return e[TIME_SLOT] >= profilerStartTime;
   });
 }
 
 /**
  * Offsets all the samples in the provided profiler data by the specified time.
  *
  * @param object profile
  *        The profiler data received from the backend.
  * @param number timeOffset
  *        The amount of time to offset by (in milliseconds).
  */
 exports.RecordingUtils.offsetSampleTimes = function(profile, timeOffset) {
   let firstThread = profile.threads[0];
-
-  for (let sample of firstThread.samples) {
-    sample.time -= timeOffset;
+  const TIME_SLOT = firstThread.samples.schema.time;
+  let samplesData = firstThread.samples.data;
+  for (let i = 0; i < samplesData.length; i++) {
+    samplesData[i][TIME_SLOT] -= timeOffset;
   }
 }
 
 /**
  * Offsets all the markers in the provided timeline data by the specified time.
  *
  * @param array markers
  *        The markers array received from the backend.
@@ -74,68 +75,132 @@ exports.RecordingUtils.offsetMarkerTimes
 exports.RecordingUtils.offsetAndScaleTimestamps = function(timestamps, timeOffset, timeScale) {
   for (let i = 0, len = timestamps.length; i < len; i++) {
     timestamps[i] -= timeOffset;
     timestamps[i] /= timeScale;
   }
 }
 
 /**
- * Cache used in `RecordingUtils.getSamplesFromAllocations`.
+ * Cache used in `RecordingUtils.getProfileThreadFromAllocations`.
  */
-let gSamplesFromAllocationCache = new WeakMap();
+let gProfileThreadFromAllocationCache = new WeakMap();
 
 /**
  * Converts allocation data from the memory actor to something that follows
  * the same structure as the samples data received from the profiler.
  *
  * @see MemoryActor.prototype.getAllocations for more information.
  *
  * @param object allocations
  *        A list of { sites, timestamps, frames, counts } arrays.
- * @return array
- *         The samples data.
+ * @return object
+ *         The "profile" describing the allocations log.
  */
-exports.RecordingUtils.getSamplesFromAllocations = function(allocations) {
-  let cached = gSamplesFromAllocationCache.get(allocations);
+exports.RecordingUtils.getProfileThreadFromAllocations = function(allocations) {
+  let cached = gProfileThreadFromAllocationCache.get(allocations);
   if (cached) {
     return cached;
   }
 
   let { sites, timestamps, frames, counts } = allocations;
-  let samples = [];
-
-  for (let i = 0, len = sites.length; i < len; i++) {
-    let site = sites[i];
-    let timestamp = timestamps[i];
-    let frame = frames[site];
-    let count = counts[site];
-
-    let sample = { time: timestamp, frames: [] };
-    samples.push(sample);
+  let uniqueStrings = new UniqueStrings();
 
-    while (frame) {
-      let source = frame.source + ":" + frame.line + ":" + frame.column;
-      let funcName = frame.functionDisplayName || "";
+  // Convert allocation frames to the the stack and frame tables expected by
+  // the profiler format.
+  //
+  // Since the allocations log is already presented as a tree, we would be
+  // wasting time if we jumped through the same hoops as deflateProfile below
+  // and instead use the existing structure of the allocations log to build up
+  // the profile JSON.
+  //
+  // The allocations.frames array corresponds roughly to the profile stack
+  // table: a trie of all stacks. We could work harder to further deduplicate
+  // each individual frame as the profiler does, but it is not necessary for
+  // correctness.
+  let stackTable = new Array(frames.length);
+  let frameTable = new Array(frames.length);
 
-      sample.frames.push({
-        location: funcName ? funcName + " (" + source + ")" : source,
-        allocations: count
-      });
+  // Array used to concat the location.
+  let locationConcatArray = new Array(5);
 
-      site = frame.parent;
-      frame = frames[site];
-      count = counts[site];
+  for (let i = 0; i < frames.length; i++) {
+    let frame = frames[i];
+    if (!frame) {
+      stackTable[i] = frameTable[i] = null;
+      continue;
     }
 
-    sample.frames.reverse();
+    let prefix = frame.parent;
+
+    // Schema:
+    //   [prefix, frame]
+    stackTable[i] = [frames[prefix] ? prefix : null, i];
+
+    // Schema:
+    //   [location]
+    //
+    // The only field a frame will have in an allocations profile is location.
+    //
+    // If frame.functionDisplayName is present, the format is
+    //   "functionDisplayName (source:line:column)"
+    // Otherwise, it is
+    //   "source:line:column"
+    //
+    // A static array is used to join to save memory on intermediate strings.
+    locationConcatArray[0] = frame.source;
+    locationConcatArray[1] = ":";
+    locationConcatArray[2] = String(frame.line);
+    locationConcatArray[3] = ":";
+    locationConcatArray[4] = String(frame.column);
+    locationConcatArray[5] = "";
+
+    let location = locationConcatArray.join("");
+    let funcName = frame.functionDisplayName;
+
+    if (funcName) {
+      locationConcatArray[0] = funcName;
+      locationConcatArray[1] = " (";
+      locationConcatArray[2] = location;
+      locationConcatArray[3] = ")";
+      locationConcatArray[4] = "";
+      locationConcatArray[5] = "";
+      location = locationConcatArray.join("");
+    }
+
+    frameTable[i] = [uniqueStrings.getOrAddStringIndex(location)];
   }
 
-  gSamplesFromAllocationCache.set(allocations, samples);
-  return samples;
+  let samples = new Array(sites.length);
+  let writePos = 0;
+  for (let i = 0; i < sites.length; i++) {
+    // Schema:
+    //   [stack, time]
+    //
+    // Originally, sites[i] indexes into the frames array. Note that in the
+    // loop above, stackTable[sites[i]] and frames[sites[i]] index the same
+    // information.
+    let stackIndex = sites[i];
+    if (frames[stackIndex]) {
+      samples[writePos++] = [stackIndex, timestamps[i]];
+    }
+  }
+  samples.length = writePos;
+
+  let thread = {
+    name: "allocations",
+    samples: samplesWithSchema(samples),
+    stackTable: stackTableWithSchema(stackTable),
+    frameTable: frameTableWithSchema(frameTable),
+    stringTable: uniqueStrings.stringTable,
+    allocationsTable: counts
+  };
+
+  gProfileThreadFromAllocationCache.set(allocations, thread);
+  return thread;
 }
 
 /**
  * Gets the current timeline blueprint without the hidden markers.
  *
  * @param blueprint
  *        The default timeline blueprint.
  * @param array hiddenMarkers
@@ -174,8 +239,333 @@ exports.RecordingUtils.getFilteredBluepr
       if (markerDetails.group > removedGroup) {
         markerDetails.group--;
       }
     }
   }
 
   return filteredBlueprint;
 };
+
+/**
+ * Deduplicates a profile by deduplicating stacks, frames, and strings.
+ *
+ * This is used to adapt version 2 profiles from the backend to version 3, for
+ * use with older Geckos (like B2G).
+ *
+ * Note that the schemas used by this must be kept in sync with schemas used
+ * by the C++ UniqueStacks class in tools/profiler/ProfileEntry.cpp.
+ *
+ * @param object profile
+ *               A profile with version 2.
+ */
+exports.RecordingUtils.deflateProfile = function deflateProfile(profile) {
+  profile.threads = profile.threads.map((thread) => {
+    let uniqueStacks = new UniqueStacks();
+    return deflateThread(thread, uniqueStacks);
+  });
+
+  profile.meta.version = 3;
+};
+
+/**
+ * Given an array of frame objects, deduplicates each frame as well as all
+ * prefixes in the stack. Returns the index of the deduplicated stack.
+ *
+ * @param object frames
+ *               Array of frame objects.
+ * @param UniqueStacks uniqueStacks
+ * @return number index
+ */
+function deflateStack(frames, uniqueStacks) {
+  // Deduplicate every prefix in the stack by keeping track of the current
+  // prefix hash.
+  let prefixIndex = null;
+  for (let i = 0; i < frames.length; i++) {
+    let frameIndex = uniqueStacks.getOrAddFrameIndex(frames[i]);
+    prefixIndex = uniqueStacks.getOrAddStackIndex(prefixIndex, frameIndex);
+  }
+  return prefixIndex;
+}
+
+/**
+ * Given an array of sample objects, deduplicate each sample's stack and
+ * convert the samples to a table with a schema. Returns the deflated samples.
+ *
+ * @param object samples
+ *               Array of samples
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateSamples(samples, uniqueStacks) {
+  // Schema:
+  //   [stack, time, responsiveness, rss, uss, frameNumber, power]
+
+  let deflatedSamples = new Array(samples.length);
+  for (let i = 0; i < samples.length; i++) {
+    let sample = samples[i];
+    deflatedSamples[i] = [
+      deflateStack(sample.frames, uniqueStacks),
+      sample.time,
+      sample.responsiveness,
+      sample.rss,
+      sample.uss,
+      sample.frameNumber,
+      sample.power
+    ];
+  }
+
+  return samplesWithSchema(deflatedSamples);
+}
+
+/**
+ * Given an array of marker objects, convert the markers to a table with a
+ * schema. Returns the deflated markers.
+ *
+ * If a marker contains a backtrace as its payload, the backtrace stack is
+ * deduplicated in the context of the profile it's in.
+ *
+ * @param object markers
+ *               Array of markers
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateMarkers(markers, uniqueStacks) {
+  // Schema:
+  //   [name, time, data]
+
+  let deflatedMarkers = new Array(markers.length);
+  for (let i = 0; i < markers.length; i++) {
+    let marker = markers[i];
+    if (marker.data && marker.data.type === "tracing" && marker.data.stack) {
+      marker.data.stack = deflateThread(marker.data.stack, uniqueStacks);
+    }
+
+    deflatedMarkers[i] = [
+      uniqueStacks.getOrAddStringIndex(marker.name),
+      marker.time,
+      marker.data
+    ];
+  }
+
+  let slot = 0;
+  return {
+    schema: {
+      name: slot++,
+      time: slot++,
+      data: slot++
+    },
+    data: deflatedMarkers
+  };
+}
+
+/**
+ * Deflate a thread.
+ *
+ * @param object thread
+ *               The profile thread.
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateThread(thread, uniqueStacks) {
+  return {
+    name: thread.name,
+    tid: thread.tid,
+    samples: deflateSamples(thread.samples, uniqueStacks),
+    markers: deflateMarkers(thread.markers, uniqueStacks),
+    stackTable: uniqueStacks.getStackTableWithSchema(),
+    frameTable: uniqueStacks.getFrameTableWithSchema(),
+    stringTable: uniqueStacks.getStringTable()
+  };
+}
+exports.RecordingUtils.deflateThread = deflateThread;
+
+function stackTableWithSchema(data) {
+  let slot = 0;
+  return {
+    schema: {
+      prefix: slot++,
+      frame: slot++
+    },
+    data: data
+  };
+}
+
+function frameTableWithSchema(data) {
+  let slot = 0;
+  return {
+    schema: {
+      location: slot++,
+      implementation: slot++,
+      optimizations: slot++,
+      line: slot++,
+      category: slot++
+    },
+    data: data
+  };
+}
+
+function samplesWithSchema(data) {
+  let slot = 0;
+  return {
+    schema: {
+      stack: slot++,
+      time: slot++,
+      responsiveness: slot++,
+      rss: slot++,
+      uss: slot++,
+      frameNumber: slot++,
+      power: slot++
+    },
+    data: data
+  };
+}
+
+/**
+ * A helper class to deduplicate strings.
+ */
+function UniqueStrings() {
+  this.stringTable = [];
+  this._stringHash = Object.create(null);
+}
+
+UniqueStrings.prototype.getOrAddStringIndex = function(s) {
+  if (!s) {
+    return null;
+  }
+
+  let stringHash = this._stringHash;
+  let stringTable = this.stringTable;
+  let index = stringHash[s];
+  if (index !== undefined) {
+    return index;
+  }
+
+  index = stringTable.length;
+  stringHash[s] = index;
+  stringTable.push(s);
+  return index;
+};
+
+exports.RecordingUtils.UniqueStrings = UniqueStrings;
+
+/**
+ * A helper class to deduplicate old-version profiles.
+ *
+ * The main functionality provided is deduplicating frames and stacks.
+ *
+ * For example, given 2 stacks
+ *   [A, B, C]
+ * and
+ *   [A, B, D]
+ *
+ * There are 4 unique frames: A, B, C, and D.
+ * There are 4 unique prefixes: [A], [A, B], [A, B, C], [A, B, D]
+ *
+ * For the example, the output of using UniqueStacks is:
+ *
+ * Frame table:
+ *   [A, B, C, D]
+ *
+ * That is, A has id 0, B has id 1, etc.
+ *
+ * Since stack prefixes are themselves deduplicated (shared), stacks are
+ * represented as a tree, or more concretely, a pair of ids, the prefix and
+ * the leaf.
+ *
+ * Stack table:
+ *   [
+ *     [null, 0],
+ *     [0,    1],
+ *     [1,    2],
+ *     [1,    3]
+ *   ]
+ *
+ * That is, [A] has id 0 and value [null, 0]. This means it has no prefix, and
+ * has the leaf frame 0, which resolves to A in the frame table.
+ *
+ * [A, B] has id 1 and value [0, 1]. This means it has prefix 0, which is [A],
+ * and leaf 1, thus [A, B].
+ *
+ * [A, B, C] has id 2 and value [1, 2]. This means it has prefix 1, which in
+ * turn is [A, B], and leaf 2, thus [A, B, C].
+ *
+ * [A, B, D] has id 3 and value [1, 3]. Note how it shares the prefix 1 with
+ * [A, B, C].
+ */
+function UniqueStacks() {
+  this._frameTable = [];
+  this._stackTable = [];
+  this._frameHash = Object.create(null);
+  this._stackHash = Object.create(null);
+  this._uniqueStrings = new UniqueStrings();
+}
+
+UniqueStacks.prototype.getStackTableWithSchema = function() {
+  return stackTableWithSchema(this._stackTable);
+};
+
+UniqueStacks.prototype.getFrameTableWithSchema = function() {
+  return frameTableWithSchema(this._frameTable);
+};
+
+UniqueStacks.prototype.getStringTable = function() {
+  return this._uniqueStrings.stringTable;
+};
+
+UniqueStacks.prototype.getOrAddFrameIndex = function(frame) {
+  // Schema:
+  //   [location, implementation, optimizations, line, category]
+
+  let frameHash = this._frameHash;
+  let frameTable = this._frameTable;
+
+  let locationIndex = this.getOrAddStringIndex(frame.location);
+  let implementationIndex = this.getOrAddStringIndex(frame.implementation);
+
+  // Super dumb.
+  let hash = `${locationIndex} ${implementationIndex || ""} ${frame.line || ""} ${frame.category || ""}`;
+
+  let index = frameHash[hash];
+  if (index !== undefined) {
+    return index;
+  }
+
+  index = frameTable.length;
+  frameHash[hash] = index;
+  frameTable.push([
+    this.getOrAddStringIndex(frame.location),
+    this.getOrAddStringIndex(frame.implementation),
+    // Don't bother with JIT optimization info for deflating old profile data
+    // format to the new format.
+    null,
+    frame.line,
+    frame.category
+  ]);
+  return index;
+};
+
+UniqueStacks.prototype.getOrAddStackIndex = function(prefixIndex, frameIndex) {
+  // Schema:
+  //   [prefix, frame]
+
+  let stackHash = this._stackHash;
+  let stackTable = this._stackTable;
+
+  // Also super dumb.
+  let hash = prefixIndex + " " + frameIndex;
+
+  let index = stackHash[hash];
+  if (index !== undefined) {
+    return index;
+  }
+
+  index = stackTable.length;
+  stackHash[hash] = index;
+  stackTable.push([prefixIndex, frameIndex]);
+  return index;
+};
+
+UniqueStacks.prototype.getOrAddStringIndex = function(s) {
+  return this._uniqueStrings.getOrAddStringIndex(s);
+};
+
+exports.RecordingUtils.UniqueStacks = UniqueStacks;
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -124,15 +124,16 @@ support-files =
 [browser_profiler_tree-view-01.js]
 [browser_profiler_tree-view-02.js]
 [browser_profiler_tree-view-03.js]
 [browser_profiler_tree-view-04.js]
 [browser_profiler_tree-view-05.js]
 [browser_profiler_tree-view-06.js]
 [browser_profiler_tree-view-07.js]
 [browser_profiler_tree-view-08.js]
+[browser_profiler-frame-utils-01.js]
 [browser_timeline-blueprint.js]
 [browser_timeline-filters.js]
 [browser_timeline-waterfall-background.js]
 [browser_timeline-waterfall-generic.js]
 [browser_timeline-waterfall-sidebar.js]
 # remove in bug 1160313
 [browser_retro-test.js]
--- a/browser/devtools/performance/test/browser_perf-allocations-to-samples.js
+++ b/browser/devtools/performance/test/browser_perf-allocations-to-samples.js
@@ -5,17 +5,17 @@
  * Tests if allocations data received from the memory actor is properly
  * converted to something that follows the same structure as the samples data
  * received from the profiler.
  */
 
 function test() {
   let { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
 
-  let output = RecordingUtils.getSamplesFromAllocations(TEST_DATA);
+  let output = RecordingUtils.getProfileThreadFromAllocations(TEST_DATA);
   is(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");
 
   finish();
 }
 
 let TEST_DATA = {
   sites: [0, 0, 1, 2, 3],
   timestamps: [50, 100, 150, 200, 250],
@@ -38,42 +38,65 @@ let TEST_DATA = {
       column: 6,
       functionDisplayName: null,
       parent: 2
     }
   ],
   counts: [11, 22, 33, 44]
 };
 
-let EXPECTED_OUTPUT = [{
-  time: 50,
-  frames: []
-}, {
-  time: 100,
-  frames: []
-}, {
-  time: 150,
-  frames: [{
-    location: "x (A:1:2)",
-    allocations: 22
-  }]
-}, {
-  time: 200,
-  frames: [{
-    location: "x (A:1:2)",
-    allocations: 22
-  }, {
-    location: "y (B:3:4)",
-    allocations: 33
-  }]
-}, {
-  time: 250,
-  frames: [{
-    location: "x (A:1:2)",
-    allocations: 22
-  }, {
-    location: "y (B:3:4)",
-    allocations: 33
-  }, {
-    location: "C:5:6",
-    allocations: 44
-  }]
-}];
+let EXPECTED_OUTPUT = {
+  name: "allocations",
+  samples: {
+    "schema": {
+      "stack": 0,
+      "time": 1,
+      "responsiveness": 2,
+      "rss": 3,
+      "uss": 4,
+      "frameNumber": 5,
+      "power": 6
+    },
+    data: [
+      [ 1, 150 ],
+      [ 2, 200 ],
+      [ 3, 250 ]
+    ]
+  },
+  stackTable: {
+    "schema": {
+      "prefix": 0,
+      "frame": 1
+    },
+    "data": [
+      null,
+      [ null, 1 ], // x (A:1:2)
+      [ 1, 2 ],    // x (A:1:2) > y (B:3:4)
+      [ 2, 3 ]     // x (A:1:2) > y (B:3:4) > C:5:6
+    ]
+  },
+  frameTable: {
+    "schema": {
+      "location": 0,
+      "implementation": 1,
+      "optimizations": 2,
+      "line": 3,
+      "category": 4
+    },
+    data: [
+      null,
+      [ 0 ],
+      [ 1 ],
+      [ 2 ]
+    ]
+  },
+  "stringTable": [
+    "x (A:1:2)",
+    "y (B:3:4)",
+    "C:5:6"
+  ],
+  "allocationsTable": [
+    11,
+    22,
+    33,
+    44
+  ]
+};
--- a/browser/devtools/performance/test/browser_perf-compatibility-02.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-02.js
@@ -41,21 +41,22 @@ let test = Task.async(function*() {
   isEmptyArray(allocations.frames, "allocations.frames");
   isEmptyArray(allocations.counts, "allocations.counts");
 
   let sampleCount = 0;
 
   for (let thread of profile.threads) {
     info("Checking thread: " + thread.name);
 
-    for (let sample of thread.samples) {
+    for (let sample of thread.samples.data) {
       sampleCount++;
 
-      if (sample.frames[0].location != "(root)") {
-        ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
+      let stack = getInflatedStackLocations(thread, sample);
+      if (stack[0] != "(root)") {
+        ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
       }
     }
   }
 
   ok(sampleCount > 0,
     "At least some samples have been iterated over, checking for root nodes.");
 
   is($("#overview-pane").hidden, true,
--- a/browser/devtools/performance/test/browser_perf-compatibility-04.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-04.js
@@ -40,21 +40,22 @@ let test = Task.async(function*() {
   isEmptyArray(allocations.frames, "allocations.frames");
   isEmptyArray(allocations.counts, "allocations.counts");
 
   let sampleCount = 0;
 
   for (let thread of profile.threads) {
     info("Checking thread: " + thread.name);
 
-    for (let sample of thread.samples) {
+    for (let sample of thread.samples.data) {
       sampleCount++;
 
-      if (sample.frames[0].location != "(root)") {
-        ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
+      let stack = getInflatedStackLocations(thread, sample);
+      if (stack[0] != "(root)") {
+        ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
       }
     }
   }
 
   ok(sampleCount > 0,
     "At least some samples have been iterated over, checking for root nodes.");
 
   is($("#overview-pane").hidden, false,
--- a/browser/devtools/performance/test/browser_perf-compatibility-05.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-05.js
@@ -47,29 +47,30 @@ function spawnTest () {
   let secondRecording = yield front.startRecording();
   let secondRecordingStartTime = secondRecording._profilerStartTime;
   info("Started profiling at: " + secondRecordingStartTime);
 
   busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
 
   yield front.stopRecording(secondRecording);
   let secondRecordingProfile = secondRecording.getProfile();
-  let secondRecordingSamples = secondRecordingProfile.threads[0].samples;
+  let secondRecordingSamples = secondRecordingProfile.threads[0].samples.data;
 
   isnot(secondRecording._profilerStartTime, 0,
     "The profiling start time should not be 0 on the second recording.");
   ok(secondRecording.getDuration() >= WAIT_TIME,
     "The second recording duration is correct.");
 
-  info("Second profile's first sample time: " + secondRecordingSamples[0].time);
-  ok(secondRecordingSamples[0].time < secondRecordingStartTime,
+  const TIME_SLOT = secondRecordingProfile.threads[0].samples.schema.time;
+  info("Second profile's first sample time: " + secondRecordingSamples[0][TIME_SLOT]);
+  ok(secondRecordingSamples[0][TIME_SLOT] < secondRecordingStartTime,
     "The second recorded sample times were normalized.");
-  ok(secondRecordingSamples[0].time > 0,
+  ok(secondRecordingSamples[0][TIME_SLOT] > 0,
     "The second recorded sample times were normalized correctly.");
-  ok(!secondRecordingSamples.find(e => e.time + secondRecordingStartTime <= firstRecording.getDuration()),
+  ok(!secondRecordingSamples.find(e => e[TIME_SLOT] + secondRecordingStartTime <= firstRecording.getDuration()),
     "There should be no samples from the first recording in the second one, " +
     "even though the total number of frames did not overflow.");
 
   target.client.request = request;
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-data-massaging-01.js
+++ b/browser/devtools/performance/test/browser_perf-data-massaging-01.js
@@ -32,26 +32,27 @@ function spawnTest () {
   let secondRecording = yield front.startRecording();
   let secondRecordingStartTime = secondRecording._profilerStartTime;
   info("Started profiling at: " + secondRecordingStartTime);
 
   busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
 
   yield front.stopRecording(secondRecording);
   let secondRecordingProfile = secondRecording.getProfile();
-  let secondRecordingSamples = secondRecordingProfile.threads[0].samples;
+  let secondRecordingSamples = secondRecordingProfile.threads[0].samples.data;
 
   isnot(secondRecording._profilerStartTime, 0,
     "The profiling start time should not be 0 on the second recording.");
   ok(secondRecording.getDuration() >= WAIT_TIME,
     "The second recording duration is correct.");
 
-  ok(secondRecordingSamples[0].time < secondRecordingStartTime,
+  const TIME_SLOT = secondRecordingProfile.threads[0].samples.schema.time;
+  ok(secondRecordingSamples[0][TIME_SLOT] < secondRecordingStartTime,
     "The second recorded sample times were normalized.");
-  ok(secondRecordingSamples[0].time > 0,
+  ok(secondRecordingSamples[0][TIME_SLOT] > 0,
     "The second recorded sample times were normalized correctly.");
-  ok(!secondRecordingSamples.find(e => e.time + secondRecordingStartTime <= firstRecording.getDuration()),
+  ok(!secondRecordingSamples.find(e => e[TIME_SLOT] + secondRecordingStartTime <= firstRecording.getDuration()),
     "There should be no samples from the first recording in the second one, " +
     "even though the total number of frames did not overflow.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-data-samples.js
+++ b/browser/devtools/performance/test/browser_perf-data-samples.js
@@ -18,21 +18,22 @@ function spawnTest () {
 
   yield front.stopRecording(rec);
   let profile = rec.getProfile();
   let sampleCount = 0;
 
   for (let thread of profile.threads) {
     info("Checking thread: " + thread.name);
 
-    for (let sample of thread.samples) {
+    for (let sample of thread.samples.data) {
       sampleCount++;
 
-      if (sample.frames[0].location != "(root)") {
-        ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
+      let stack = getInflatedStackLocations(thread, sample);
+      if (stack[0] != "(root)") {
+        ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
       }
     }
   }
 
   ok(sampleCount > 0,
     "At least some samples have been iterated over, checking for root nodes.");
 
   yield teardown(panel);
--- a/browser/devtools/performance/test/browser_perf-events-calltree.js
+++ b/browser/devtools/performance/test/browser_perf-events-calltree.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the call tree up/down events work for js calltree and memory calltree.
  */
-let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
+const { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
+const { RecordingUtils } = devtools.require("devtools/performance/recording-utils")
 function spawnTest () {
   let focus = 0;
   let focusEvent = () => focus++;
 
   Services.prefs.setBoolPref(MEMORY_PREF, true);
 
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, DetailsView, JsCallTreeView, MemoryCallTreeView } = panel.panelWin;
@@ -19,17 +20,17 @@ function spawnTest () {
 
   // Make a recording just so the performance tool is in the correct state
   yield startRecording(panel);
   let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
   yield stopRecording(panel);
   yield rendered;
 
   // Mock the profile used so we can get a deterministic tree created
-  let threadNode = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gProfile.threads[0]);
   JsCallTreeView._populateCallTree(threadNode);
   JsCallTreeView.emit(EVENTS.JS_CALL_TREE_RENDERED);
 
   JsCallTreeView.on("focus", focusEvent);
 
   click(panel.panelWin, $("#js-calltree-view .call-tree-item"));
   fireKey("VK_DOWN");
   fireKey("VK_DOWN");
@@ -39,41 +40,49 @@ function spawnTest () {
   JsCallTreeView.off("focus", focusEvent);
 
   is(focus, 4, "several focus events are fired for the js calltree.");
 
   yield teardown(panel);
   finish();
 };
 
-let gSamples = [{
-  time: 5,
-  frames: [
-    { category: 8,  location: "(root)" },
-    { category: 8,  location: "A (http://foo/bar/baz:12)" },
-    { category: 16, location: "B (http://foo/bar/baz:34)" },
-    { category: 32, location: "C (http://foo/bar/baz:56)" }
-  ]
-}, {
-  time: 5 + 1,
-  frames: [
-    { category: 8,  location: "(root)" },
-    { category: 8,  location: "A (http://foo/bar/baz:12)" },
-    { category: 16, location: "B (http://foo/bar/baz:34)" },
-    { category: 64, location: "D (http://foo/bar/baz:78)" }
-  ]
-}, {
-  time: 5 + 1 + 2,
-  frames: [
-    { category: 8,  location: "(root)" },
-    { category: 8,  location: "A (http://foo/bar/baz:12)" },
-    { category: 16, location: "B (http://foo/bar/baz:34)" },
-    { category: 64, location: "D (http://foo/bar/baz:78)" }
-  ]
-}, {
-  time: 5 + 1 + 2 + 7,
-  frames: [
-    { category: 8,   location: "(root)" },
-    { category: 8,   location: "A (http://foo/bar/baz:12)" },
-    { category: 128, location: "E (http://foo/bar/baz:90)" },
-    { category: 256, location: "F (http://foo/bar/baz:99)" }
-  ]
-}];
+let gProfile = {
+  meta: { version: 2 },
+  threads: [{
+    samples: [{
+      time: 5,
+      frames: [
+        { category: 8,  location: "(root)" },
+        { category: 8,  location: "A (http://foo/bar/baz:12)" },
+        { category: 16, location: "B (http://foo/bar/baz:34)" },
+        { category: 32, location: "C (http://foo/bar/baz:56)" }
+      ]
+    }, {
+      time: 5 + 1,
+      frames: [
+        { category: 8,  location: "(root)" },
+        { category: 8,  location: "A (http://foo/bar/baz:12)" },
+        { category: 16, location: "B (http://foo/bar/baz:34)" },
+        { category: 64, location: "D (http://foo/bar/baz:78)" }
+      ]
+    }, {
+      time: 5 + 1 + 2,
+      frames: [
+        { category: 8,  location: "(root)" },
+        { category: 8,  location: "A (http://foo/bar/baz:12)" },
+        { category: 16, location: "B (http://foo/bar/baz:34)" },
+        { category: 64, location: "D (http://foo/bar/baz:78)" }
+      ]
+    }, {
+      time: 5 + 1 + 2 + 7,
+      frames: [
+        { category: 8,   location: "(root)" },
+        { category: 8,   location: "A (http://foo/bar/baz:12)" },
+        { category: 128, location: "E (http://foo/bar/baz:90)" },
+        { category: 256, location: "F (http://foo/bar/baz:99)" }
+      ]
+    }],
+    markers: []
+  }]
+};
+
+RecordingUtils.deflateProfile(gProfile);
--- a/browser/devtools/performance/test/browser_perf-jit-model-01.js
+++ b/browser/devtools/performance/test/browser_perf-jit-model-01.js
@@ -2,69 +2,118 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that JITOptimizations track optimization sites and create
  * an OptimizationSiteProfile when adding optimization sites, like from the
  * FrameNode, and the returning of that data is as expected.
  */
 
+const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+
 function test() {
   let { JITOptimizations } = devtools.require("devtools/shared/profiler/jit");
 
-  let jit = new JITOptimizations(gOpts);
+  let rawSites = [];
+  rawSites.push(gRawSite2);
+  rawSites.push(gRawSite2);
+  rawSites.push(gRawSite1);
+  rawSites.push(gRawSite1);
+  rawSites.push(gRawSite2);
+  rawSites.push(gRawSite3);
 
-  jit.addOptimizationSite(1);
-  jit.addOptimizationSite(1);
-  jit.addOptimizationSite(0);
-  jit.addOptimizationSite(0);
-  jit.addOptimizationSite(1);
-  jit.addOptimizationSite(2);
-
-  let sites = jit.getOptimizationSites();
+  let jit = new JITOptimizations(rawSites, gStringTable.stringTable);
+  let sites = jit.optimizationSites;
 
   let [first, second, third] = sites;
 
-  is(first.id, 1, "Ordered by samples count, descending");
+  is(first.id, 0, "site id is array index");
   is(first.samples, 3, "first OptimizationSiteProfile has correct sample count");
-  is(first.data, gOpts[1], "includes OptimizationSite as reference under `data`");
-  is(second.id, 0, "Ordered by samples count, descending");
+  is(first.data.line, 34, "includes OptimizationSite as reference under `data`");
+  is(second.id, 1, "site id is array index");
   is(second.samples, 2, "second OptimizationSiteProfile has correct sample count");
-  is(second.data, gOpts[0], "includes OptimizationSite as reference under `data`");
-  is(third.id, 2, "Ordered by samples count, descending");
+  is(second.data.line, 12, "includes OptimizationSite as reference under `data`");
+  is(third.id, 2, "site id is array index");
   is(third.samples, 1, "third OptimizationSiteProfile has correct sample count");
-  is(third.data, gOpts[2], "includes OptimizationSite as reference under `data`");
+  is(third.data.line, 78, "includes OptimizationSite as reference under `data`");
 
   finish();
 }
 
-let gOpts = [{
+let gStringTable = new RecordingUtils.UniqueStrings();
+
+function uniqStr(s) {
+  return gStringTable.getOrAddStringIndex(s);
+}
+
+let gRawSite1 = {
   line: 12,
   column: 2,
-  types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
-    { keyedBy: "primitive", location: "self-hosted" }
-  ]}],
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Inlined", strategy: "SomeGetter3" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+      keyedBy: uniqStr("constructor"),
+      name: uniqStr("Foo"),
+      location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+      keyedBy: uniqStr("primitive"),
+      location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Inlined"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+let gRawSite2 = {
   line: 34,
-  types: [{ mirType: "Int32", site: "Receiver" }], // use no types
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Failure3", strategy: "SomeGetter3" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Int32"),
+    site: uniqStr("Receiver")
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Failure3"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+let gRawSite3 = {
   line: 78,
-  types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
-    { keyedBy: "primitive", location: "self-hosted" }
-  ]}],
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "GenericSuccess", strategy: "SomeGetter3" },
-  ]
-}];
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+      keyedBy: uniqStr("constructor"),
+      name: uniqStr("Foo"),
+      location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+      keyedBy: uniqStr("primitive"),
+      location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("GenericSuccess"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
--- a/browser/devtools/performance/test/browser_perf-jit-model-02.js
+++ b/browser/devtools/performance/test/browser_perf-jit-model-02.js
@@ -1,29 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that JITOptimizations create OptimizationSites, and the underlying
  * OptimizationSites methods work as expected.
  */
 
+const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+
 function test() {
   let { JITOptimizations, OptimizationSite } = devtools.require("devtools/shared/profiler/jit");
 
-  let jit = new JITOptimizations(gOpts);
+  let rawSites = [];
+  rawSites.push(gRawSite2);
+  rawSites.push(gRawSite2);
+  rawSites.push(gRawSite1);
+  rawSites.push(gRawSite1);
+  rawSites.push(gRawSite2);
+  rawSites.push(gRawSite3);
 
-  jit.addOptimizationSite(1);
-  jit.addOptimizationSite(1);
-  jit.addOptimizationSite(0);
-  jit.addOptimizationSite(0);
-  jit.addOptimizationSite(1);
-  jit.addOptimizationSite(2);
-
-  let sites = jit.getOptimizationSites();
+  let jit = new JITOptimizations(rawSites, gStringTable.stringTable);
+  let sites = jit.optimizationSites;
 
   let [first, second, third] = sites;
 
   /* hasSuccessfulOutcome */
   is(first.hasSuccessfulOutcome(), false, "optSite.hasSuccessfulOutcome() returns expected (1)");
   is(second.hasSuccessfulOutcome(), true, "optSite.hasSuccessfulOutcome() returns expected (2)");
   is(third.hasSuccessfulOutcome(), true, "optSite.hasSuccessfulOutcome() returns expected (3)");
 
@@ -35,43 +37,96 @@ function test() {
   /* getIonTypes */
   is(first.getIonTypes().length, 1, "optSite.getIonTypes() has the correct amount of IonTypes (1)");
   is(second.getIonTypes().length, 2, "optSite.getIonTypes() has the correct amount of IonTypes (2)");
   is(third.getIonTypes().length, 1, "optSite.getIonTypes() has the correct amount of IonTypes (3)");
 
   finish();
 }
 
-let gOpts = [{
+
+let gStringTable = new RecordingUtils.UniqueStrings();
+
+function uniqStr(s) {
+  return gStringTable.getOrAddStringIndex(s);
+}
+
+let gRawSite1 = {
   line: 12,
   column: 2,
-  types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
-    { keyedBy: "constructor", location: "A (http://foo/bar/baz:12)" }
-  ]}, { mirType: "Int32", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "primitive", location: "self-hosted" }
-  ]}],
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Inlined", strategy: "SomeGetter3" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+      keyedBy: uniqStr("constructor"),
+      name: uniqStr("Foo"),
+      location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+      keyedBy: uniqStr("constructor"),
+      location: uniqStr("A (http://foo/bar/baz:12)")
+    }]
+  }, {
+    mirType: uniqStr("Int32"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+      keyedBy: uniqStr("primitive"),
+      location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Inlined"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+let gRawSite2 = {
   line: 34,
-  types: [{ mirType: "Int32", site: "Receiver" }], // use no types
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Int32"),
+    site: uniqStr("Receiver")
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")]
+    ]
+  }
+};
+
+let gRawSite3 = {
   line: 78,
-  types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
-    { keyedBy: "primitive", location: "self-hosted" }
-  ]}],
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "GenericSuccess", strategy: "SomeGetter3" },
-  ]
-}];
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+      keyedBy: uniqStr("constructor"),
+      name: uniqStr("Foo"),
+      location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+      keyedBy: uniqStr("primitive"),
+      location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("GenericSuccess"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
--- a/browser/devtools/performance/test/browser_perf-jit-view-01.js
+++ b/browser/devtools/performance/test/browser_perf-jit-view-01.js
@@ -1,41 +1,47 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the JIT Optimizations view renders optimization data
  * if on, and displays selected frames on focus.
  */
 
+const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+
 Services.prefs.setBoolPref(INVERT_PREF, false);
 
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
   let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
 
-  let profilerData = { threads: [{samples: gSamples, optimizations: gOpts}] };
+  let profilerData = { threads: [gThread] }
 
   is(Services.prefs.getBoolPref(JIT_PREF), false, "show JIT Optimizations pref off by default");
 
   // Make two recordings, so we have one to switch to later, as the
   // second one will have fake sample data
   yield startRecording(panel);
   yield stopRecording(panel);
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
   yield DetailsView.selectView("js-calltree");
 
   yield injectAndRenderProfilerData();
 
-  yield checkFrame(1, [0, 1]);
-  yield checkFrame(2, [2]);
+  // gRawSite1 and gRawSite2 are both optimizations on A, so they'll have
+  // indices in descending order of # of samples.
+  yield checkFrame(1, [{ i: 0, opt: gRawSite1 }, { i: 1, opt: gRawSite2 }]);
+
+  // gRawSite3 is the only optimization on B, so it'll have index 0.
+  yield checkFrame(2, [{ i: 0, opt: gRawSite3 }]);
   yield checkFrame(3);
 
   let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
   let reset = once(JITOptimizationsView, EVENTS.OPTIMIZATIONS_RESET);
   RecordingsView.selectedIndex = 0;
   yield Promise.all([select, reset]);
   ok(true, "JITOptimizations view correctly reset when switching recordings.");
 
@@ -59,127 +65,200 @@ function spawnTest () {
     Services.prefs.setBoolPref(JIT_PREF, true);
     is($("#jit-optimizations-view").hidden, false, "JIT Optimizations should be visible when pref is on");
     ok($("#jit-optimizations-view").classList.contains("empty"),
       "JIT Optimizations view has empty message when no frames selected.");
 
      Services.prefs.setBoolPref(JIT_PREF, false);
   }
 
-  function *checkFrame (frameIndex, expectedOptsIndex=[]) {
+  function *checkFrame (frameIndex, expectedOpts=[]) {
     // Click the frame
     let rendered = once(JITOptimizationsView, EVENTS.OPTIMIZATIONS_RENDERED);
     mousedown(window, $$(".call-tree-item")[frameIndex]);
     Services.prefs.setBoolPref(JIT_PREF, true);
     yield rendered;
     ok(true, "JITOptimizationsView rendered when enabling with the current frame node selected");
 
     let isEmpty = $("#jit-optimizations-view").classList.contains("empty");
-    if (expectedOptsIndex.length === 0) {
+    if (expectedOpts.length === 0) {
       ok(isEmpty, "JIT Optimizations view has an empty message when selecting a frame without opt data.");
       return;
     } else {
       ok(!isEmpty, "JIT Optimizations view has no empty message.");
     }
 
     // Get the frame info for the first opt site, since all opt sites
     // share the same frame info
-    let frameInfo = gOpts[expectedOptsIndex[0]]._testFrameInfo;
+    let frameInfo = expectedOpts[0].opt._testFrameInfo;
 
     let { $headerName, $headerLine, $headerFile } = JITOptimizationsView;
     ok(!$headerName.hidden, "header function name should be shown");
     ok(!$headerLine.hidden, "header line should be shown");
     ok(!$headerFile.hidden, "header file should be shown");
     is($headerName.textContent, frameInfo.name, "correct header function name.");
     is($headerLine.textContent, frameInfo.line, "correct header line");
     is($headerFile.textContent, frameInfo.file, "correct header file");
 
     // Need the value of the optimizations in its array, as its
     // an index used internally by the view to uniquely ID the opt
-    for (let i of expectedOptsIndex) {
-      let opt = gOpts[i];
+    for (let { i, opt } of expectedOpts) {
       let { types: ionTypes, attempts } = opt;
 
       // Check attempts
-      is($$(`.tree-widget-container li[data-id='["${i}","${i}-attempts"]'] .tree-widget-children .tree-widget-item`).length, attempts.length,
-        `found ${attempts.length} attempts`);
+      is($$(`.tree-widget-container li[data-id='["${i}","${i}-attempts"]'] .tree-widget-children .tree-widget-item`).length, attempts.data.length,
+        `found ${attempts.data.length} attempts`);
 
       for (let j = 0; j < ionTypes.length; j++) {
         ok($(`.tree-widget-container li[data-id='["${i}","${i}-types","${i}-types-${j}"]']`),
           "found an ion type row");
       }
 
       // The second and third optimization should display optimization failures.
       let warningIcon = $(`.tree-widget-container li[data-id='["${i}"]'] .opt-icon[severity=warning]`);
-      if (i === 1 || i === 2) {
+      if (opt === gRawSite2 || opt === gRawSite3) {
         ok(warningIcon, "did find a warning icon for all strategies failing.");
       } else {
         ok(!warningIcon, "did not find a warning icon for no successful strategies");
       }
     }
   }
 }
 
-let gSamples = [{
-  time: 5,
-  frames: [
-    { location: "(root)" },
-    { location: "A (http://foo/bar/baz:12)", optsIndex: 0 },
-    { location: "B (http://foo/bar/boo:34)", optsIndex: 2 },
-    { location: "C (http://foo/bar/baz:56)" }
-  ]
-}, {
-  time: 5 + 1,
-  frames: [
-    { location: "(root)" },
-    { location: "A (http://foo/bar/baz:12)" },
-    { location: "B (http://foo/bar/boo:34)" },
-  ]
-}, {
-  time: 5 + 1 + 2,
-  frames: [
-    { location: "(root)" },
-    { location: "A (http://foo/bar/baz:12)", optsIndex: 1 },
-    { location: "B (http://foo/bar/boo:34)" },
-  ]
-}, {
-  time: 5 + 1 + 2 + 7,
-  frames: [
-    { location: "(root)" },
-    { location: "A (http://foo/bar/baz:12)", optsIndex: 0 },
-    { location: "E (http://foo/bar/baz:90)" },
-    { location: "F (http://foo/bar/baz:99)" }
-  ]
-}];
+let gUniqueStacks = new RecordingUtils.UniqueStacks();
+
+function uniqStr(s) {
+  return gUniqueStacks.getOrAddStringIndex(s);
+}
 
-// Array of OptimizationSites
-let gOpts = [{
+// Since deflateThread doesn't handle deflating optimization info, use
+// placeholder names A_O1, B_O3, and A_O2, which will be used to manually
+// splice deduped opts into the profile.
+let gThread = RecordingUtils.deflateThread({
+  samples: [{
+    time: 0,
+    frames: [
+      { location: "(root)" }
+    ]
+  }, {
+    time: 5,
+    frames: [
+      { location: "(root)" },
+      { location: "A_O1" },
+      { location: "B_O3" },
+      { location: "C (http://foo/bar/baz:56)" }
+    ]
+  }, {
+    time: 5 + 1,
+    frames: [
+      { location: "(root)" },
+      { location: "A (http://foo/bar/baz:12)" },
+      { location: "B (http://foo/bar/boo:34)" },
+    ]
+  }, {
+    time: 5 + 1 + 2,
+    frames: [
+      { location: "(root)" },
+      { location: "A_O2" },
+      { location: "B (http://foo/bar/boo:34)" },
+    ]
+  }, {
+    time: 5 + 1 + 2 + 7,
+    frames: [
+      { location: "(root)" },
+      { location: "A_O1" },
+      { location: "E (http://foo/bar/baz:90)" },
+      { location: "F (http://foo/bar/baz:99)" }
+    ]
+  }],
+  markers: []
+}, gUniqueStacks);
+
+// 3 RawOptimizationSites
+let gRawSite1 = {
   _testFrameInfo: { name: "A", line: "12", file: "@baz" },
   line: 12,
   column: 2,
-  types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
-    { keyedBy: "primitive", location: "self-hosted" }
-  ]}],
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Inlined", strategy: "SomeGetter3" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+        keyedBy: uniqStr("constructor"),
+        name: uniqStr("Foo"),
+        location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+        keyedBy: uniqStr("primitive"),
+        location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Inlined"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+let gRawSite2 = {
   _testFrameInfo: { name: "A", line: "12", file: "@baz" },
   line: 12,
-  types: [{ mirType: "Int32", site: "Receiver" }], // use no types
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Failure3", strategy: "SomeGetter3" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Int32"),
+    site: uniqStr("Receiver")
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Failure3"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+let gRawSite3 = {
   _testFrameInfo: { name: "B", line: "34", file: "@boo" },
   line: 34,
-  types: [{ mirType: "Int32", site: "Receiver" }], // use no types
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Failure3", strategy: "SomeGetter3" },
-  ]
-}];
+  types: [{
+    mirType: uniqStr("Int32"),
+    site: uniqStr("Receiver")
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Failure3"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+gThread.frameTable.data.forEach((frame) => {
+  const LOCATION_SLOT = gThread.frameTable.schema.location;
+  const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
+
+  let l = gThread.stringTable[frame[LOCATION_SLOT]];
+  switch (l) {
+  case "A_O1":
+    frame[LOCATION_SLOT] = uniqStr("A (http://foo/bar/baz:12)");
+    frame[OPTIMIZATIONS_SLOT] = gRawSite1;
+    break;
+  case "A_O2":
+    frame[LOCATION_SLOT] = uniqStr("A (http://foo/bar/baz:12)");
+    frame[OPTIMIZATIONS_SLOT] = gRawSite2;
+    break;
+  case "B_O3":
+    frame[LOCATION_SLOT] = uniqStr("B (http://foo/bar/boo:34)");
+    frame[OPTIMIZATIONS_SLOT] = gRawSite3;
+    break;
+  }
+});
--- a/browser/devtools/performance/test/browser_perf-jit-view-02.js
+++ b/browser/devtools/performance/test/browser_perf-jit-view-02.js
@@ -1,26 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the JIT Optimizations view does not display information
  * for meta nodes when viewing "content only".
  */
 
-let { CATEGORY_MASK } = devtools.require("devtools/shared/profiler/global");
+const { CATEGORY_MASK } = devtools.require("devtools/shared/profiler/global");
+const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+
 Services.prefs.setBoolPref(INVERT_PREF, false);
 Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
 
 function spawnTest () {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
   let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
 
-  let profilerData = { threads: [{samples: gSamples, optimizations: gOpts}] };
+  let profilerData = { threads: [gThread] };
 
   is(Services.prefs.getBoolPref(JIT_PREF), false, "show JIT Optimizations pref off by default");
 
   // Make two recordings, so we have one to switch to later, as the
   // second one will have fake sample data
   yield startRecording(panel);
   yield stopRecording(panel);
 
@@ -65,45 +67,99 @@ function spawnTest () {
     Services.prefs.setBoolPref(JIT_PREF, true);
     ok($("#jit-optimizations-view").classList.contains("empty"),
       "JIT Optimizations view has empty message when no frames selected.");
 
      Services.prefs.setBoolPref(JIT_PREF, false);
   }
 }
 
-let gSamples = [{
-  time: 5,
-  frames: [
-    { location: "(root)" },
-    { location: "A (http://foo/bar/baz:12)", optsIndex: 0 }
-  ]
-}, {
-  time: 5 + 1,
-  frames: [
-    { location: "(root)" },
-    { location: "A (http://foo/bar/baz:12)", optsIndex: 0 },
-    { location: "JS", optsIndex: 1, category: CATEGORY_MASK("js") },
-  ]
-}];
+let gUniqueStacks = new RecordingUtils.UniqueStacks();
+
+function uniqStr(s) {
+  return gUniqueStacks.getOrAddStringIndex(s);
+}
 
-// Array of OptimizationSites
-let gOpts = [{
+let gThread = RecordingUtils.deflateThread({
+  samples: [{
+    time: 0,
+    frames: [
+      { location: "(root)" }
+    ]
+  }, {
+    time: 5,
+    frames: [
+      { location: "(root)" },
+      { location: "A (http://foo/bar/baz:12)" }
+    ]
+  }, {
+    time: 5 + 1,
+    frames: [
+      { location: "(root)" },
+      { location: "A (http://foo/bar/baz:12)" },
+      { location: "JS", category: CATEGORY_MASK("js") },
+    ]
+  }],
+  markers: []
+}, gUniqueStacks);
+
+// 3 RawOptimizationSites
+let gRawSite1 = {
   line: 12,
   column: 2,
-  types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
-    { keyedBy: "primitive", location: "self-hosted" }
-  ]}],
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Inlined", strategy: "SomeGetter3" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+        keyedBy: uniqStr("constructor"),
+        name: uniqStr("Foo"),
+        location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+        keyedBy: uniqStr("primitive"),
+        location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Inlined"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+let gRawSite2 = {
   line: 22,
-  types: [{ mirType: "Int32", site: "Receiver" }], // use no types
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Failure3", strategy: "SomeGetter3" },
-  ]
-}];
+  types: [{
+    mirType: uniqStr("Int32"),
+    site: uniqStr("Receiver")
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Failure3"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+gThread.frameTable.data.forEach((frame) => {
+  const LOCATION_SLOT = gThread.frameTable.schema.location;
+  const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
+
+  let l = gThread.stringTable[frame[LOCATION_SLOT]];
+  switch (l) {
+  case "A (http://foo/bar/baz:12)":
+    frame[OPTIMIZATIONS_SLOT] = gRawSite1;
+    break;
+  case "JS":
+    frame[OPTIMIZATIONS_SLOT] = gRawSite2;
+    break;
+  }
+});
--- a/browser/devtools/performance/test/browser_perf-options-flatten-tree-recursion-01.js
+++ b/browser/devtools/performance/test/browser_perf-options-flatten-tree-recursion-01.js
@@ -13,47 +13,47 @@ function spawnTest () {
   yield startRecording(panel);
   yield busyWait(100);
 
   yield stopRecording(panel);
   let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
   yield DetailsView.selectView("js-flamegraph");
   yield rendered;
 
-  let samples1 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;
-  let rendering1 = FlameGraphUtils._cache.get(samples1);
+  let thread1 = PerformanceController.getCurrentRecording().getProfile().threads[0];
+  let rendering1 = FlameGraphUtils._cache.get(thread1);
 
-  ok(samples1,
+  ok(thread1,
     "The samples were retrieved from the controller.");
   ok(rendering1,
     "The rendering data was cached.");
 
   rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(FLATTEN_PREF, false);
   yield rendered;
 
   ok(true, "JsFlameGraphView rerendered when toggling flatten-tree-recursion.");
 
-  let samples2 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;
-  let rendering2 = FlameGraphUtils._cache.get(samples2);
+  let thread2 = PerformanceController.getCurrentRecording().getProfile().threads[0];
+  let rendering2 = FlameGraphUtils._cache.get(thread2);
 
-  is(samples1, samples2,
+  is(thread1, thread2,
     "The same samples data should be retrieved from the controller (1).");
   isnot(rendering1, rendering2,
     "The rendering data should be different because other options were used (1).");
 
   rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(FLATTEN_PREF, true);
   yield rendered;
 
   ok(true, "JsFlameGraphView rerendered when toggling back flatten-tree-recursion.");
 
-  let samples3 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;
-  let rendering3 = FlameGraphUtils._cache.get(samples3);
+  let thread3 = PerformanceController.getCurrentRecording().getProfile().threads[0];
+  let rendering3 = FlameGraphUtils._cache.get(thread3);
 
-  is(samples2, samples3,
+  is(thread2, thread3,
     "The same samples data should be retrieved from the controller (2).");
   isnot(rendering2, rendering3,
     "The rendering data should be different because other options were used (2).");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/performance/test/browser_perf-options-flatten-tree-recursion-02.js
+++ b/browser/devtools/performance/test/browser_perf-options-flatten-tree-recursion-02.js
@@ -16,55 +16,55 @@ function spawnTest () {
   yield busyWait(100);
 
   let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
   yield stopRecording(panel);
   yield DetailsView.selectView("memory-flamegraph");
   yield rendered;
 
   let allocations1 = PerformanceController.getCurrentRecording().getAllocations();
-  let samples1 = RecordingUtils.getSamplesFromAllocations(allocations1);
-  let rendering1 = FlameGraphUtils._cache.get(samples1);
+  let thread1 = RecordingUtils.getProfileThreadFromAllocations(allocations1);
+  let rendering1 = FlameGraphUtils._cache.get(thread1);
 
   ok(allocations1,
     "The allocations were retrieved from the controller.");
-  ok(samples1,
-    "The samples were retrieved from the utility funcs.");
+  ok(thread1,
+    "The allocations profile was synthesized by the utility funcs.");
   ok(rendering1,
     "The rendering data was cached.");
 
   rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(FLATTEN_PREF, false);
   yield rendered;
 
   ok(true, "MemoryFlameGraphView rerendered when toggling flatten-tree-recursion.");
 
   let allocations2 = PerformanceController.getCurrentRecording().getAllocations();
-  let samples2 = RecordingUtils.getSamplesFromAllocations(allocations2);
-  let rendering2 = FlameGraphUtils._cache.get(samples2);
+  let thread2 = RecordingUtils.getProfileThreadFromAllocations(allocations2);
+  let rendering2 = FlameGraphUtils._cache.get(thread2);
 
   is(allocations1, allocations2,
     "The same allocations data should be retrieved from the controller (1).");
-  is(samples1, samples2,
-    "The same samples data should be retrieved from the utility funcs. (1).");
+  is(thread1, thread2,
+    "The same allocations profile should be retrieved from the utility funcs. (1).");
   isnot(rendering1, rendering2,
     "The rendering data should be different because other options were used (1).");
 
   rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
   Services.prefs.setBoolPref(FLATTEN_PREF, true);
   yield rendered;
 
   ok(true, "MemoryFlameGraphView rerendered when toggling back flatten-tree-recursion.");
 
   let allocations3 = PerformanceController.getCurrentRecording().getAllocations();
-  let samples3 = RecordingUtils.getSamplesFromAllocations(allocations3);
-  let rendering3 = FlameGraphUtils._cache.get(samples3);
+  let thread3 = RecordingUtils.getProfileThreadFromAllocations(allocations3);
+  let rendering3 = FlameGraphUtils._cache.get(thread3);
 
   is(allocations2, allocations3,
     "The same allocations data should be retrieved from the controller (2).");
-  is(samples2, samples3,
-    "The same samples data should be retrieved from the utility funcs. (2).");
+  is(thread2, thread3,
+    "The same allocations profile should be retrieved from the utility funcs. (2).");
   isnot(rendering2, rendering3,
     "The rendering data should be different because other options were used (2).");
 
   yield teardown(panel);
   finish();
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_profiler-frame-utils-01.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that frame-utils isContent and parseLocation work as intended
+ * when parsing over frames from the profiler.
+ */
+
+const CONTENT_LOCATIONS = [
+  "hello/<.world (https://foo/bar.js:123:987)",
+  "hello/<.world (http://foo/bar.js:123:987)",
+  "hello/<.world (http://foo/bar.js:123)",
+  "hello/<.world (http://foo/bar.js#baz:123:987)",
+  "hello/<.world (http://foo/#bar:123:987)",
+  "hello/<.world (http://foo/:123:987)",
+  "hello/<.world (app://myfxosapp/file.js:100:1)",
+].map(argify);
+
+const CHROME_LOCATIONS = [
+  { location: "Startup::XRE_InitChildProcess", line: 456, column: 123 },
+  { location: "chrome://browser/content/content.js", line: 456, column: 123 },
+  "setTimeout_timer (resource://gre/foo.js:123:434)",
+  "hello/<.world (jar:file://Users/mcurie/Dev/jetpacks.js)",
+  "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
+  "EnterJIT",
+].map(argify);
+
+function test() {
+  const { isContent, parseLocation } = devtools.require("devtools/shared/profiler/frame-utils");
+
+  for (let frame of CONTENT_LOCATIONS) {
+    ok(isContent.apply(null, frameify(frame)), `${frame[0]} should be considered a content frame.`);
+  }
+
+  for (let frame of CHROME_LOCATIONS) {
+    ok(!isContent.apply(null, frameify(frame)), `${frame[0]} should not be considered a content frame.`);
+  }
+
+  // functionName, fileName, hostName, url, line, column
+  const FIELDS = ["functionName", "fileName", "hostName", "url", "line", "column"];
+  const PARSED_CONTENT = [
+    ["hello/<.world", "bar.js", "foo", "https://foo/bar.js", 123, 987],
+    ["hello/<.world", "bar.js", "foo", "http://foo/bar.js", 123, 987],
+    ["hello/<.world", "bar.js", "foo", "http://foo/bar.js", 123, null],
+    ["hello/<.world", "bar.js#baz", "foo", "http://foo/bar.js#baz", 123, 987],
+    ["hello/<.world", "#bar", "foo", "http://foo/#bar", 123, 987],
+    ["hello/<.world", "/", "foo", "http://foo/", 123, 987],
+    ["hello/<.world", "file.js", "myfxosapp", "app://myfxosapp/file.js", 100, 1],
+  ];
+
+  for (let i = 0; i < PARSED_CONTENT.length; i++) {
+    let parsed = parseLocation.apply(null, CONTENT_LOCATIONS[i]);
+    for (let j = 0; j < FIELDS.length; j++) {
+      is(parsed[FIELDS[j]], PARSED_CONTENT[i][j], `${CONTENT_LOCATIONS[i]} was parsed to correct ${FIELDS[j]}`);
+    }
+  }
+
+  const PARSED_CHROME = [
+    ["Startup::XRE_InitChildProcess", null, null, null, 456, 123],
+    ["chrome://browser/content/content.js", null, null, null, 456, 123],
+    ["setTimeout_timer", "foo.js", null, "resource://gre/foo.js", 123, 434],
+    ["hello/<.world (jar:file://Users/mcurie/Dev/jetpacks.js)", null, null, null, null, null],
+    ["hello/<.world", "baz.js", "bar", "http://bar/baz.js", 123, 987],
+    ["EnterJIT", null, null, null, null, null],
+  ];
+
+  for (let i = 0; i < PARSED_CHROME.length; i++) {
+    let parsed = parseLocation.apply(null, CHROME_LOCATIONS[i]);
+    for (let j = 0; j < FIELDS.length; j++) {
+      is(parsed[FIELDS[j]], PARSED_CHROME[i][j], `${CHROME_LOCATIONS[i]} was parsed to correct ${FIELDS[j]}`);
+    }
+  }
+
+  finish();
+}
+
+/**
+ * Takes either a string or an object and turns it into an array that
+ * parseLocation.apply expects.
+ */
+function argify (val) {
+  if (typeof val === "string") {
+    return [val];
+  } else {
+    return [val.location, val.line, val.column];
+  }
+}
+
+/**
+ * Takes the result of argify and turns it into an array that can be passed to
+ * isContent.apply.
+ */
+function frameify(val) {
+  return [{ location: val[0] }];
+}
--- a/browser/devtools/performance/test/browser_profiler_tree-frame-node.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-frame-node.js
@@ -4,20 +4,23 @@
 /**
  * Verifies if FrameNodes retain and parse their data appropriately.
  */
 
 function test() {
   let { FrameNode } = devtools.require("devtools/shared/profiler/tree-model");
   let { CATEGORY_OTHER } = devtools.require("devtools/shared/profiler/global");
 
-  let frame1 = new FrameNode({
+  let frame1 = new FrameNode("hello/<.world (http://foo/bar.js:123:987)", {
     location: "hello/<.world (http://foo/bar.js:123:987)",
-    line: 456
-  });
+    line: 456,
+    isContent: FrameNode.isContent({
+      location: "hello/<.world (http://foo/bar.js:123:987)"
+    })
+  }, false);
 
   is(frame1.getInfo().nodeType, "Frame",
     "The first frame node has the correct type.");
   is(frame1.getInfo().functionName, "hello/<.world",
     "The first frame node has the correct function name.");
   is(frame1.getInfo().fileName, "bar.js",
     "The first frame node has the correct file name.");
   is(frame1.getInfo().hostName, "foo",
@@ -28,20 +31,23 @@ function test() {
     "The first frame node has the correct line.");
   is(frame1.getInfo().column, 987,
     "The first frame node has the correct column.");
   is(frame1.getInfo().categoryData.toSource(), "({})",
     "The first frame node has the correct category data.");
   is(frame1.getInfo().isContent, true,
     "The first frame node has the correct content flag.");
 
-  let frame2 = new FrameNode({
+  let frame2 = new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", {
     location: "hello/<.world (http://foo/bar.js#baz:123:987)",
-    line: 456
-  });
+    line: 456,
+    isContent: FrameNode.isContent({
+      location: "hello/<.world (http://foo/bar.js#baz:123:987)"
+    })
+  }, false);
 
   is(frame2.getInfo().nodeType, "Frame",
     "The second frame node has the correct type.");
   is(frame2.getInfo().functionName, "hello/<.world",
     "The second frame node has the correct function name.");
   is(frame2.getInfo().fileName, "bar.js#baz",
     "The second frame node has the correct file name.");
   is(frame2.getInfo().hostName, "foo",
@@ -52,20 +58,23 @@ function test() {
     "The second frame node has the correct line.");
   is(frame2.getInfo().column, 987,
     "The second frame node has the correct column.");
   is(frame2.getInfo().categoryData.toSource(), "({})",
     "The second frame node has the correct category data.");
   is(frame2.getInfo().isContent, true,
     "The second frame node has the correct content flag.");
 
-  let frame3 = new FrameNode({
+  let frame3 = new FrameNode("hello/<.world (http://foo/#bar:123:987)", {
     location: "hello/<.world (http://foo/#bar:123:987)",
-    line: 456
-  });
+    line: 456,
+    isContent: FrameNode.isContent({
+      location: "hello/<.world (http://foo/#bar:123:987)"
+    })
+  }, false);
 
   is(frame3.getInfo().nodeType, "Frame",
     "The third frame node has the correct type.");
   is(frame3.getInfo().functionName, "hello/<.world",
     "The third frame node has the correct function name.");
   is(frame3.getInfo().fileName, "#bar",
     "The third frame node has the correct file name.");
   is(frame3.getInfo().hostName, "foo",
@@ -76,20 +85,23 @@ function test() {
     "The third frame node has the correct line.");
   is(frame3.getInfo().column, 987,
     "The third frame node has the correct column.");
   is(frame3.getInfo().categoryData.toSource(), "({})",
     "The third frame node has the correct category data.");
   is(frame3.getInfo().isContent, true,
     "The third frame node has the correct content flag.");
 
-  let frame4 = new FrameNode({
+  let frame4 = new FrameNode("hello/<.world (http://foo/:123:987)", {
     location: "hello/<.world (http://foo/:123:987)",
-    line: 456
-  });
+    line: 456,
+    isContent: FrameNode.isContent({
+      location: "hello/<.world (http://foo/:123:987)"
+    })
+  }, false);
 
   is(frame4.getInfo().nodeType, "Frame",
     "The fourth frame node has the correct type.");
   is(frame4.getInfo().functionName, "hello/<.world",
     "The fourth frame node has the correct function name.");
   is(frame4.getInfo().fileName, "/",
     "The fourth frame node has the correct file name.");
   is(frame4.getInfo().hostName, "foo",
@@ -100,20 +112,23 @@ function test() {
     "The fourth frame node has the correct line.");
   is(frame4.getInfo().column, 987,
     "The fourth frame node has the correct column.");
   is(frame4.getInfo().categoryData.toSource(), "({})",
     "The fourth frame node has the correct category data.");
   is(frame4.getInfo().isContent, true,
     "The fourth frame node has the correct content flag.");
 
-  let frame5 = new FrameNode({
+  let frame5 = new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", {
     location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
-    line: 456
-  });
+    line: 456,
+    isContent: FrameNode.isContent({
+      location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)"
+    })
+  }, false);
 
   is(frame5.getInfo().nodeType, "Frame",
     "The fifth frame node has the correct type.");
   is(frame5.getInfo().functionName, "hello/<.world",
     "The fifth frame node has the correct function name.");
   is(frame5.getInfo().fileName, "baz.js",
     "The fifth frame node has the correct file name.");
   is(frame5.getInfo().hostName, "bar",
@@ -124,45 +139,49 @@ function test() {
     "The fifth frame node has the correct line.");
   is(frame5.getInfo().column, 987,
     "The fifth frame node has the correct column.");
   is(frame5.getInfo().categoryData.toSource(), "({})",
     "The fifth frame node has the correct category data.");
   is(frame5.getInfo().isContent, false,
     "The fifth frame node has the correct content flag.");
 
-  let frame6 = new FrameNode({
+  let frame6 = new FrameNode("Foo::Bar::Baz", {
     location: "Foo::Bar::Baz",
     line: 456,
-    column: 123,
-    category: CATEGORY_OTHER
-  });
+    category: CATEGORY_OTHER,
+    isContent: FrameNode.isContent({
+      location: "Foo::Bar::Baz",
+      category: CATEGORY_OTHER
+    })
+  }, false);
 
   is(frame6.getInfo().nodeType, "Frame",
     "The sixth frame node has the correct type.");
   is(frame6.getInfo().functionName, "Foo::Bar::Baz",
     "The sixth frame node has the correct function name.");
   is(frame6.getInfo().fileName, null,
     "The sixth frame node has the correct file name.");
   is(frame6.getInfo().hostName, null,
     "The sixth frame node has the correct host name.");
   is(frame6.getInfo().url, null,
     "The sixth frame node has the correct url.");
   is(frame6.getInfo().line, 456,
     "The sixth frame node has the correct line.");
-  is(frame6.getInfo().column, 123,
-    "The sixth frame node has the correct column.");
   is(frame6.getInfo().categoryData.abbrev, "other",
     "The sixth frame node has the correct category data.");
   is(frame6.getInfo().isContent, false,
     "The sixth frame node has the correct content flag.");
 
-  let frame7 = new FrameNode({
-    location: "EnterJIT"
-  });
+  let frame7 = new FrameNode("EnterJIT", {
+    location: "EnterJIT",
+    isContent: FrameNode.isContent({
+      location: "EnterJIT"
+    })
+  }, false);
 
   is(frame7.getInfo().nodeType, "Frame",
     "The seventh frame node has the correct type.");
   is(frame7.getInfo().functionName, "EnterJIT",
     "The seventh frame node has the correct function name.");
   is(frame7.getInfo().fileName, null,
     "The seventh frame node has the correct file name.");
   is(frame7.getInfo().hostName, null,
@@ -173,27 +192,27 @@ function test() {
     "The seventh frame node has the correct line.");
   is(frame7.getInfo().column, null,
     "The seventh frame node has the correct column.");
   is(frame7.getInfo().categoryData.abbrev, "js",
     "The seventh frame node has the correct category data.");
   is(frame7.getInfo().isContent, false,
     "The seventh frame node has the correct content flag.");
 
-  let frame8 = new FrameNode({
+  let frame8 = new FrameNode("chrome://browser/content/content.js", {
     location: "chrome://browser/content/content.js",
     line: 456,
     column: 123
-  });
+  }, false);
 
   is(frame8.getInfo().hostName, null,
     "The eighth frame node has the correct host name.");
 
-  let frame9 = new FrameNode({
+  let frame9 = new FrameNode("hello/<.world (resource://gre/foo.js:123:434)", {
     location: "hello/<.world (resource://gre/foo.js:123:434)",
     line: 456
-  });
+  }, false);
 
   is(frame9.getInfo().hostName, null,
     "The ninth frame node has the correct host name.");
 
   finish();
 }
--- a/browser/devtools/performance/test/browser_profiler_tree-model-01.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-01.js
@@ -1,180 +1,119 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if a call tree model can be correctly computed from a samples array.
  */
 
 function test() {
-  let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
+  const { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
 
   // Create a root node from a given samples array.
 
-  let root = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gThread);
+  let root = getFrameNodePath(threadNode, "(root)");
 
   // Test the root node.
 
-  is(root.duration, 18,
+  is(threadNode.getInfo().nodeType, "Thread",
+    "The correct node type was retrieved for the root node.");
+
+  is(root.duration, 20,
     "The correct duration was calculated for the root node.");
-  is(root.getInfo().nodeType, "Thread",
-    "The correct node type was retrieved for the root node.");
   is(root.getInfo().functionName, "(root)",
     "The correct function name was retrieved for the root node.");
   is(root.getInfo().categoryData.toSource(), "({})",
     "The correct empty category data was retrieved for the root node.");
 
-  is(Object.keys(root.calls).length, 1,
+  is(root.calls.length, 1,
     "The correct number of child calls were calculated for the root node.");
-  is(Object.keys(root.calls)[0], "A",
+  ok(getFrameNodePath(root, "A"),
     "The root node's only child call is correct.");
 
   // Test all the descendant nodes.
 
-  is(Object.keys(root.calls.A.calls).length, 2,
-    "The correct number of child calls were calculated for the '.A' node.");
-  is(Object.keys(root.calls.A.calls)[0], "B",
-    "The '.A' node's first child call is correct.");
-  is(Object.keys(root.calls.A.calls)[1], "E",
-    "The '.A' node's second child call is correct.");
-
-  is(Object.keys(root.calls.A.calls.B.calls).length, 2,
-    "The correct number of child calls were calculated for the '.A.B' node.");
-  is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
-    "The '.A.B' node's first child call is correct.");
-  is(Object.keys(root.calls.A.calls.B.calls)[1], "D",
-    "The '.A.B' node's second child call is correct.");
-
-  is(Object.keys(root.calls.A.calls.E.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.E' node.");
-  is(Object.keys(root.calls.A.calls.E.calls)[0], "F",
-    "The '.A.E' node's only child call is correct.");
+  is(getFrameNodePath(root, "A").calls.length, 2,
+    "The correct number of child calls were calculated for the 'A' node.");
+  ok(getFrameNodePath(root, "A > B"),
+    "The 'A' node has a 'B' child call.");
+  ok(getFrameNodePath(root, "A > E"),
+    "The 'A' node has a 'E' child call.");
 
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.B.C' node.");
-  is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.B.D' node.");
-  is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.E.F' node.");
-
-  // Insert new nodes in the tree.
+  is(getFrameNodePath(root, "A > B").calls.length, 2,
+    "The correct number of child calls were calculated for the 'A > B' node.");
+  ok(getFrameNodePath(root, "A > B > C"),
+    "The 'A > B' node has a 'C' child call.");
+  ok(getFrameNodePath(root, "A > B > D"),
+    "The 'A > B' node has a 'D' child call.");
 
-  root.insert({
-    time: 20,
-    frames: [
-      { location: "(root)" },
-      { location: "A" },
-      { location: "B" },
-      { location: "C" },
-      { location: "D" },
-      { location: "E" },
-      { location: "F" },
-      { location: "G" }
-    ]
-  });
-
-  // Retest the root node.
-
-  is(root.duration, 20,
-    "The correct duration was recalculated for the root node.");
+  is(getFrameNodePath(root, "A > E").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A > E' node.");
+  ok(getFrameNodePath(root, "A > E > F"),
+    "The 'A > E' node has a 'F' child call.");
 
-  is(Object.keys(root.calls).length, 1,
-    "The correct number of child calls were calculated for the root node.");
-  is(Object.keys(root.calls)[0], "A",
-    "The root node's only child call is correct.");
-
-  // Retest all the descendant nodes.
+  is(getFrameNodePath(root, "A > B > C").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A > B > C' node.");
+  ok(getFrameNodePath(root, "A > B > C > D"),
+    "The 'A > B > C' node has a 'D' child call.");
 
-  is(Object.keys(root.calls.A.calls).length, 2,
-    "The correct number of child calls were calculated for the '.A' node.");
-  is(Object.keys(root.calls.A.calls)[0], "B",
-    "The '.A' node's first child call is correct.");
-  is(Object.keys(root.calls.A.calls)[1], "E",
-    "The '.A' node's second child call is correct.");
-
-  is(Object.keys(root.calls.A.calls.B.calls).length, 2,
-    "The correct number of child calls were calculated for the '.A.B' node.");
-  is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
-    "The '.A.B' node's first child call is correct.");
-  is(Object.keys(root.calls.A.calls.B.calls)[1], "D",
-    "The '.A.B' node's second child call is correct.");
-
-  is(Object.keys(root.calls.A.calls.E.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.E' node.");
-  is(Object.keys(root.calls.A.calls.E.calls)[0], "F",
-    "The '.A.E' node's only child call is correct.");
+  is(getFrameNodePath(root, "A > B > C > D").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A > B > C > D' node.");
+  ok(getFrameNodePath(root, "A > B > C > D > E"),
+    "The 'A > B > C > D' node has a 'E' child call.");
 
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.B.C' node.");
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls)[0], "D",
-    "The '.A.B.C' node's only child call is correct.");
-
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.B.C.D' node.");
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls)[0], "E",
-    "The '.A.B.C.D' node's only child call is correct.");
+  is(getFrameNodePath(root, "A > B > C > D > E").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A > B > C > D > E' node.");
+  ok(getFrameNodePath(root, "A > B > C > D > E > F"),
+    "The 'A > B > C > D > E' node has a 'F' child call.");
 
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.B.C.D.E' node.");
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls)[0], "F",
-    "The '.A.B.C.D.E' node's only child call is correct.");
+  is(getFrameNodePath(root, "A > B > C > D > E > F").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A > B > C > D > E > F' node.");
+  ok(getFrameNodePath(root, "A > B > C > D > E > F > G"),
+    "The 'A > B > C > D > E > F' node has a 'G' child call.");
 
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.B.C.D.E.F' node.");
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls)[0], "G",
-    "The '.A.B.C.D.E.F' node's only child call is correct.");
-
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.B.D.E.F.G' node.");
-  is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.B.D' node.");
-  is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.E.F' node.");
+  is(getFrameNodePath(root, "A > B > C > D > E > F > G").calls.length, 0,
+    "The correct number of child calls were calculated for the 'A > B > C > D > E > F > G' node.");
+  is(getFrameNodePath(root, "A > B > D").calls.length, 0,
+    "The correct number of child calls were calculated for the 'A > B > D' node.");
+  is(getFrameNodePath(root, "A > E > F").calls.length, 0,
+    "The correct number of child calls were calculated for the 'A > E > F' node.");
 
   // Check the location, sample times, duration and samples of the root.
 
-  is(root.calls.A.location, "A",
-    "The '.A' node has the correct location.");
-  is(root.calls.A.sampleTimes.toSource(),
-    "[{start:5, end:10}, {start:11, end:17}, {start:18, end:25}, {start:20, end:22}]",
-    "The '.A' node has the correct sample times.");
-  is(root.calls.A.duration, 20,
-    "The '.A' node has the correct duration in milliseconds.");
-  is(root.calls.A.samples, 4,
-    "The '.A' node has the correct number of samples.");
+  is(getFrameNodePath(root, "A").location, "A",
+    "The 'A' node has the correct location.");
+  is(getFrameNodePath(root, "A").duration, 20,
+    "The 'A' node has the correct duration in milliseconds.");
+  is(getFrameNodePath(root, "A").samples, 4,
+    "The 'A' node has the correct number of samples.");
 
   // ...and the rightmost leaf.
 
-  is(root.calls.A.calls.E.calls.F.location, "F",
-    "The '.A.E.F' node has the correct location.");
-  is(root.calls.A.calls.E.calls.F.sampleTimes.toSource(),
-    "[{start:18, end:25}]",
-    "The '.A.E.F' node has the correct sample times.");
-  is(root.calls.A.calls.E.calls.F.duration, 7,
-    "The '.A.E.F' node has the correct duration in milliseconds.");
-  is(root.calls.A.calls.E.calls.F.samples, 1,
-    "The '.A.E.F' node has the correct number of samples.");
+  is(getFrameNodePath(root, "A > E > F").location, "F",
+    "The 'A > E > F' node has the correct location.");
+  is(getFrameNodePath(root, "A > E > F").duration, 7,
+    "The 'A > E > F' node has the correct duration in milliseconds.");
+  is(getFrameNodePath(root, "A > E > F").samples, 1,
+    "The 'A > E > F' node has the correct number of samples.");
 
   // ...and the leftmost leaf.
 
-  is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.location, "G",
-    "The '.A.B.C.D.E.F.G' node has the correct location.");
-  is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.sampleTimes.toSource(),
-    "[{start:20, end:22}]",
-    "The '.A.B.C.D.E.F.G' node has the correct sample times.");
-  is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.duration, 2,
-    "The '.A.B.C.D.E.F.G' node has the correct duration in milliseconds.");
-  is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.samples, 1,
-    "The '.A.B.C.D.E.F.G' node has the correct number of samples.");
+  is(getFrameNodePath(root, "A > B > C > D > E > F > G").location, "G",
+    "The 'A > B > C > D > E > F > G' node has the correct location.");
+  is(getFrameNodePath(root, "A > B > C > D > E > F > G").duration, 2,
+    "The 'A > B > C > D > E > F > G' node has the correct duration in milliseconds.");
+  is(getFrameNodePath(root, "A > B > C > D > E > F > G").samples, 1,
+    "The 'A > B > C > D > E > F > G' node has the correct number of samples.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { location: "(root)" },
     { location: "A" },
     { location: "B" },
     { location: "C" }
   ]
 }, {
@@ -188,9 +127,21 @@ let gSamples = [{
 }, {
   time: 5 + 6 + 7,
   frames: [
     { location: "(root)" },
     { location: "A" },
     { location: "E" },
     { location: "F" }
   ]
-}];
+}, {
+  time: 20,
+  frames: [
+    { location: "(root)" },
+    { location: "A" },
+    { location: "B" },
+    { location: "C" },
+    { location: "D" },
+    { location: "E" },
+    { location: "F" },
+    { location: "G" }
+  ]
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-model-02.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-02.js
@@ -5,55 +5,55 @@
  * Tests if a call tree model ignores samples with no timing information.
  */
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
 
   // Create a root node from a given samples array.
 
-  let root = new ThreadNode(gSamples);
+  let root = getFrameNodePath(new ThreadNode(gThread), "(root)");
 
   // Test the root node.
 
   is(root.duration, 5,
     "The correct duration was calculated for the root node.");
 
-  is(Object.keys(root.calls).length, 1,
+  is(root.calls.length, 1,
     "The correct number of child calls were calculated for the root node.");
-  is(Object.keys(root.calls)[0], "A",
+  ok(getFrameNodePath(root, "A"),
     "The root node's only child call is correct.");
 
   // Test all the descendant nodes.
 
-  is(Object.keys(root.calls.A.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A' node.");
-  is(Object.keys(root.calls.A.calls)[0], "B",
-    "The '.A.B' node's only child call is correct.");
+  is(getFrameNodePath(root, "A").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A' node.");
+  ok(getFrameNodePath(root, "A > B"),
+    "The 'A' node's only child call is correct.");
 
-  is(Object.keys(root.calls.A.calls.B.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.B' node.");
-  is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
-    "The '.A.B' node's only child call is correct.");
+  is(getFrameNodePath(root, "A > B").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A > B' node.");
+  ok(getFrameNodePath(root, "A > B > C"),
+    "The 'A > B' node's only child call is correct.");
 
-  is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.B.C' node.");
+  is(getFrameNodePath(root, "A > B > C").calls.length, 0,
+    "The correct number of child calls were calculated for the 'A > B > C' node.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { location: "(root)" },
     { location: "A" },
     { location: "B" },
     { location: "C" }
   ]
 }, {
   time: null,
   frames: [
     { location: "(root)" },
     { location: "A" },
     { location: "B" },
     { location: "D" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-model-03.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-03.js
@@ -5,57 +5,62 @@
  * Tests if a call tree model can be correctly computed from a samples array,
  * while at the same time filtering by duration.
  */
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
 
   // Create a root node from a given samples array, filtering by time.
-
-  let root = new ThreadNode(gSamples, { startTime: 11, endTime: 18 });
+  //
+  // Filtering from 5 to 18 includes the 2nd and 3rd samples. The 2nd sample
+  // starts exactly on 5 and ends at 11. The 3rd sample starts at 11 and ends
+  // exactly at 18.
+  let startTime = 5;
+  let endTime = 18;
+  let root = getFrameNodePath(new ThreadNode(gThread, { startTime, endTime }), "(root)");
 
   // Test the root node.
 
-  is(root.duration, 18,
+  is(root.duration, endTime - startTime,
     "The correct duration was calculated for the root node.");
 
-  is(Object.keys(root.calls).length, 1,
+  is(root.calls.length, 1,
     "The correct number of child calls were calculated for the root node.");
-  is(Object.keys(root.calls)[0], "A",
+  ok(getFrameNodePath(root, "A"),
     "The root node's only child call is correct.");
 
   // Test all the descendant nodes.
 
-  is(Object.keys(root.calls.A.calls).length, 2,
-    "The correct number of child calls were calculated for the '.A' node.");
-  is(Object.keys(root.calls.A.calls)[0], "B",
-    "The '.A' node's first child call is correct.");
-  is(Object.keys(root.calls.A.calls)[1], "E",
-    "The '.A' node's second child call is correct.");
+  is(getFrameNodePath(root, "A").calls.length, 2,
+    "The correct number of child calls were calculated for the 'A' node.");
+  ok(getFrameNodePath(root, "A > B"),
+    "The 'A' node has a 'B' child call.");
+  ok(getFrameNodePath(root, "A > E"),
+    "The 'A' node has a 'E' child call.");
 
-  is(Object.keys(root.calls.A.calls.B.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.B' node.");
-  is(Object.keys(root.calls.A.calls.B.calls)[0], "D",
-    "The '.A.B' node's only child call is correct.");
+  is(getFrameNodePath(root, "A > B").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A > B' node.");
+  ok(getFrameNodePath(root, "A > B > D"),
+    "The 'A > B' node's only child call is correct.");
 
-  is(Object.keys(root.calls.A.calls.E.calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.E' node.");
-  is(Object.keys(root.calls.A.calls.E.calls)[0], "F",
-    "The '.A.E' node's only child call is correct.");
+  is(getFrameNodePath(root, "A > E").calls.length, 1,
+    "The correct number of child calls were calculated for the 'A > E' node.");
+  ok(getFrameNodePath(root, "A > E > F"),
+    "The 'A > E' node's only child call is correct.");
 
-  is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.B.D' node.");
-  is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
-    "The correct number of child calls were calculated for the '.A.E.F' node.");
+  is(getFrameNodePath(root, "A > B > D").calls.length, 0,
+    "The correct number of child calls were calculated for the 'A > B > D' node.");
+  is(getFrameNodePath(root, "A > E > F").calls.length, 0,
+    "The correct number of child calls were calculated for the 'A > E > F' node.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { location: "(root)" },
     { location: "A" },
     { location: "B" },
     { location: "C" }
   ]
 }, {
@@ -78,9 +83,9 @@ let gSamples = [{
   time: 5 + 6 + 7 + 8,
   frames: [
     { location: "(root)" },
     { location: "A" },
     { location: "B" },
     { location: "C" },
     { location: "D" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-model-04.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-04.js
@@ -6,54 +6,56 @@
  * while at the same time filtering by duration and content-only frames.
  */
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
 
   // Create a root node from a given samples array, filtering by time.
 
-  let root = new ThreadNode(gSamples, { startTime: 11, endTime: 18, contentOnly: true });
+  let startTime = 5;
+  let endTime = 18;
+  let root = getFrameNodePath(new ThreadNode(gThread, { startTime: startTime, endTime: endTime, contentOnly: true }), "(root)");
 
   // Test the root node.
 
-  is(root.duration, 18,
+  is(root.duration, endTime - startTime,
     "The correct duration was calculated for the root node.");
 
-  is(Object.keys(root.calls).length, 2,
+  is(root.calls.length, 2,
     "The correct number of child calls were calculated for the root node.");
-  is(Object.keys(root.calls)[0], "http://D",
-    "The root node's first child call is correct.");
-  is(Object.keys(root.calls)[1], "http://A",
-    "The root node's second child call is correct.");
+  ok(getFrameNodePath(root, "http://D"),
+    "The root has a 'http://D' child call.");
+  ok(getFrameNodePath(root, "http://A"),
+    "The root has a 'http://A' child call.");
 
   // Test all the descendant nodes.
 
-  is(Object.keys(root.calls["http://A"].calls).length, 1,
-    "The correct number of child calls were calculated for the '.A' node.");
-  is(Object.keys(root.calls["http://A"].calls)[0], "https://E",
-    "The '.A' node's only child call is correct.");
+  is(getFrameNodePath(root, "http://A").calls.length, 1,
+    "The correct number of child calls were calculated for the 'http://A' node.");
+  ok(getFrameNodePath(root, "http://A > https://E"),
+    "The 'http://A' node's only child call is correct.");
 
-  is(Object.keys(root.calls["http://A"].calls["https://E"].calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.E' node.");
-  is(Object.keys(root.calls["http://A"].calls["https://E"].calls)[0], "file://F",
-    "The '.A.E' node's only child call is correct.");
+  is(getFrameNodePath(root, "http://A > https://E").calls.length, 1,
+    "The correct number of child calls were calculated for the 'http://A > http://E' node.");
+  ok(getFrameNodePath(root, "http://A > https://E > file://F"),
+    "The 'http://A > https://E' node's only child call is correct.");
 
-  is(Object.keys(root.calls["http://A"].calls["https://E"].calls["file://F"].calls).length, 1,
-    "The correct number of child calls were calculated for the '.A.E.F' node.");
-  is(Object.keys(root.calls["http://A"].calls["https://E"].calls["file://F"].calls)[0], "app://H",
-    "The '.A.E.F' node's only child call is correct.");
+  is(getFrameNodePath(root, "http://A > https://E > file://F").calls.length, 1,
+    "The correct number of child calls were calculated for the 'http://A > https://E >> file://F' node.");
+  ok(getFrameNodePath(root, "http://A > https://E > file://F > app://H"),
+    "The 'http://A > https://E >> file://F' node's only child call is correct.");
 
-  is(Object.keys(root.calls["http://D"].calls).length, 0,
-    "The correct number of child calls were calculated for the '.D' node.");
+  is(getFrameNodePath(root, "http://D").calls.length, 0,
+    "The correct number of child calls were calculated for the 'http://D' node.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { location: "(root)" },
     { location: "http://A" },
     { location: "http://B" },
     { location: "http://C" }
   ]
 }, {
@@ -78,9 +80,9 @@ let gSamples = [{
   time: 5 + 6 + 7 + 8,
   frames: [
     { location: "(root)" },
     { location: "http://A" },
     { location: "http://B" },
     { location: "http://C" },
     { location: "http://D" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-model-05.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-05.js
@@ -3,17 +3,17 @@
 
 /**
  * Tests if an inverted call tree model can be correctly computed from a samples
  * array.
  */
 
 let time = 1;
 
-let samples = [{
+let gThread = synthesizeProfileForTest([{
   time: time++,
   frames: [
     { location: "(root)" },
     { location: "A" },
     { location: "B" },
     { location: "C" }
   ]
 }, {
@@ -35,45 +35,45 @@ let samples = [{
 }, {
   time: time++,
   frames: [
     { location: "(root)" },
     { location: "A" },
     { location: "B" },
     { location: "F" }
   ]
-}];
+}]);
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
 
-  let root = new ThreadNode(samples, { invertTree: true });
+  let root = new ThreadNode(gThread, { invertTree: true });
 
-  is(Object.keys(root.calls).length, 2,
+  is(root.calls.length, 2,
      "Should get the 2 youngest frames, not the 1 oldest frame");
 
-  let C = root.calls.C;
+  let C = getFrameNodePath(root, "C");
   ok(C, "Should have C as a child of the root.");
 
-  is(Object.keys(C.calls).length, 3,
+  is(C.calls.length, 3,
      "Should have 3 frames that called C.");
-  ok(C.calls.B, "B called C.");
-  ok(C.calls.D, "D called C.");
-  ok(C.calls.E, "E called C.");
+  ok(getFrameNodePath(C, "B"), "B called C.");
+  ok(getFrameNodePath(C, "D"), "D called C.");
+  ok(getFrameNodePath(C, "E"), "E called C.");
 
-  is(Object.keys(C.calls.B.calls).length, 1);
-  ok(C.calls.B.calls.A, "A called B called C");
-  is(Object.keys(C.calls.D.calls).length, 1);
-  ok(C.calls.D.calls.A, "A called D called C");
-  is(Object.keys(C.calls.E.calls).length, 1);
-  ok(C.calls.E.calls.A, "A called E called C");
+  is(getFrameNodePath(C, "B").calls.length, 1);
+  ok(getFrameNodePath(C, "B > A"), "A called B called C");
+  is(getFrameNodePath(C, "D").calls.length, 1);
+  ok(getFrameNodePath(C, "D > A"), "A called D called C");
+  is(getFrameNodePath(C, "E").calls.length, 1);
+  ok(getFrameNodePath(C, "E > A"), "A called E called C");
 
-  let F = root.calls.F;
+  let F = getFrameNodePath(root, "F");
   ok(F, "Should have F as a child of the root.");
 
-  is(Object.keys(F.calls).length, 1);
-  ok(F.calls.B, "B called F");
+  is(F.calls.length, 1);
+  ok(getFrameNodePath(F, "B"), "B called F");
 
-  is(Object.keys(F.calls.B.calls).length, 1);
-  ok(F.calls.B.calls.A, "A called B called F");
+  is(getFrameNodePath(F, "B").calls.length, 1);
+  ok(getFrameNodePath(F, "B > A"), "A called B called F");
 
   finish();
 }
--- a/browser/devtools/performance/test/browser_profiler_tree-model-06.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-06.js
@@ -1,103 +1,181 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that when constructing FrameNodes, if optimization data is available,
  * the FrameNodes have the correct optimization data after iterating over samples.
  */
 
+const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+
+let gUniqueStacks = new RecordingUtils.UniqueStacks();
+
+function uniqStr(s) {
+  return gUniqueStacks.getOrAddStringIndex(s);
+}
+
 let time = 1;
 
-let samples = [{
-  time: time++,
-  frames: [
-    { location: "(root)" },
-    { location: "A", optsIndex: 0 },
-    { location: "B" },
-    { location: "C" }
-  ]
-}, {
-  time: time++,
-  frames: [
-    { location: "(root)" },
-    { location: "A", optsIndex: 0 },
-    { location: "D" },
-    { location: "C" }
-  ]
-}, {
-  time: time++,
-  frames: [
-    { location: "(root)" },
-    { location: "A", optsIndex: 1 },
-    { location: "E", optsIndex: 2 },
-    { location: "C" }
-  ],
-}, {
-  time: time++,
-  frames: [
-    { location: "(root)" },
-    { location: "A" },
-    { location: "B" },
-    { location: "F" }
-  ]
-}];
+let gThread = RecordingUtils.deflateThread({
+  samples: [{
+    time: 0,
+    frames: [
+      { location: "(root)" }
+    ]
+  }, {
+    time: time++,
+    frames: [
+      { location: "(root)" },
+      { location: "A_O1" },
+      { location: "B" },
+      { location: "C" }
+    ]
+  }, {
+    time: time++,
+    frames: [
+      { location: "(root)" },
+      { location: "A_O1" },
+      { location: "D" },
+      { location: "C" }
+    ]
+  }, {
+    time: time++,
+    frames: [
+      { location: "(root)" },
+      { location: "A_O2" },
+      { location: "E_O3" },
+      { location: "C" }
+    ],
+  }, {
+    time: time++,
+    frames: [
+      { location: "(root)" },
+      { location: "A" },
+      { location: "B" },
+      { location: "F" }
+    ]
+  }],
+  markers: []
+}, gUniqueStacks);
 
-// Array of OptimizationSites
-let gOpts = [{
+// 3 OptimizationSites
+let gRawSite1 = {
   line: 12,
   column: 2,
-  types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
-    { keyedBy: "primitive", location: "self-hosted" }
-  ]}],
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Inlined", strategy: "SomeGetter3" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+        keyedBy: uniqStr("constructor"),
+        name: uniqStr("Foo"),
+        location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+        keyedBy: uniqStr("primitive"),
+        location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Inlined"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+let gRawSite2 = {
   line: 34,
-  types: [{ mirType: "Int32", site: "Receiver" }], // use no types
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "Failure3", strategy: "SomeGetter3" },
-  ]
-}, {
+  types: [{
+    mirType: uniqStr("Int32"),
+    site: uniqStr("Receiver")
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("Failure3"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+let gRawSite3 = {
   line: 78,
-  types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
-    { keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
-    { keyedBy: "primitive", location: "self-hosted" }
-  ]}],
-  attempts: [
-    { outcome: "Failure1", strategy: "SomeGetter1" },
-    { outcome: "Failure2", strategy: "SomeGetter2" },
-    { outcome: "GenericSuccess", strategy: "SomeGetter3" },
-  ]
-}];
+  types: [{
+    mirType: uniqStr("Object"),
+    site: uniqStr("A (http://foo/bar/bar:12)"),
+    typeset: [{
+      keyedBy: uniqStr("constructor"),
+      name: uniqStr("Foo"),
+      location: uniqStr("A (http://foo/bar/baz:12)")
+    }, {
+      keyedBy: uniqStr("primitive"),
+      location: uniqStr("self-hosted")
+    }]
+  }],
+  attempts: {
+    schema: {
+      outcome: 0,
+      strategy: 1
+    },
+    data: [
+      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
+      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
+      [uniqStr("GenericSuccess"), uniqStr("SomeGetter3")]
+    ]
+  }
+};
+
+gThread.frameTable.data.forEach((frame) => {
+  const LOCATION_SLOT = gThread.frameTable.schema.location;
+  const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
+
+  let l = gThread.stringTable[frame[LOCATION_SLOT]];
+  switch (l) {
+  case "A_O1":
+    frame[LOCATION_SLOT] = uniqStr("A");
+    frame[OPTIMIZATIONS_SLOT] = gRawSite1;
+    break;
+  case "A_O2":
+    frame[LOCATION_SLOT] = uniqStr("A");
+    frame[OPTIMIZATIONS_SLOT] = gRawSite2;
+    break;
+  case "E_O3":
+    frame[LOCATION_SLOT] = uniqStr("E");
+    frame[OPTIMIZATIONS_SLOT] = gRawSite3;
+    break;
+  }
+});
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
 
-  let root = new ThreadNode(samples, { optimizations: gOpts });
+  let root = new ThreadNode(gThread);
 
-  let A = root.calls.A;
+  let A = getFrameNodePath(root, "(root) > A");
 
   let opts = A.getOptimizations();
-  let sites = opts.getOptimizationSites();
+  let sites = opts.optimizationSites;
   is(sites.length, 2, "Frame A has two optimization sites.");
   is(sites[0].samples, 2, "first opt site has 2 samples.");
   is(sites[1].samples, 1, "second opt site has 1 sample.");
 
-  let E = A.calls.E;
+  let E = getFrameNodePath(A, "E");
   opts = E.getOptimizations();
-  sites = opts.getOptimizationSites();
+  sites = opts.optimizationSites;
   is(sites.length, 1, "Frame E has one optimization site.");
   is(sites[0].samples, 1, "first opt site has 1 samples.");
 
-  let D = A.calls.D;
+  let D = getFrameNodePath(A, "D");
   ok(!D.getOptimizations(),
     "frames that do not have any opts data do not have JITOptimizations instances.");
 
   finish();
 }
--- a/browser/devtools/performance/test/browser_profiler_tree-model-07.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-07.js
@@ -8,52 +8,54 @@
 let { CATEGORY_MASK } = devtools.require("devtools/shared/profiler/global");
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
   let url = (n) => `http://content/${n}`;
 
   // Create a root node from a given samples array.
 
-  let root = new ThreadNode(gSamples, { contentOnly: true });
+  let root = getFrameNodePath(new ThreadNode(gThread, { contentOnly: true }), "(root)");
 
   /*
    * should have a tree like:
    * root
    *   - (JS)
    *   - A
    *     - (GC)
    *     - B
    *       - C
    *       - D
    *     - E
    *       - F
-   *       - (JS)
+   *         - (JS)
    */
 
   // Test the root node.
 
-  is(Object.keys(root.calls).length, 2, "root has 2 children");
-  ok(root.calls[url("A")], "root has content child");
-  ok(root.calls["64"], "root has platform generalized child");
-  is(Object.keys(root.calls["64"].calls).length, 0, "platform generalized child is a leaf.");
+  is(root.calls.length, 2, "root has 2 children");
+  ok(getFrameNodePath(root, url("A")), "root has content child");
+  ok(getFrameNodePath(root, "64"), "root has platform generalized child");
+  is(getFrameNodePath(root, "64").calls.length, 0, "platform generalized child is a leaf.");
+
+  ok(getFrameNodePath(root, `${url("A")} > 128`), "A has platform generalized child of another type");
+  is(getFrameNodePath(root, `${url("A")} > 128`).calls.length, 0, "second generalized type is a leaf.");
 
-  ok(root.calls[url("A")].calls["128"], "A has platform generalized child of another type");
-  is(Object.keys(root.calls[url("A")].calls["128"].calls).length, 0, "second generalized type is a leaf.");
+  ok(getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 64`),
+     "a second leaf of the first generalized type exists deep in the tree.");
+  ok(getFrameNodePath(root, `${url("A")} > 128`), "A has platform generalized child of another type");
 
-  ok(root.calls[url("A")].calls[url("E")].calls[url("F")].calls["64"],
-    "a second leaf of the first generalized type exists deep in the tree.");
-  ok(root.calls[url("A")].calls["128"], "A has platform generalized child of another type");
+  is(getFrameNodePath(root, "64").category,
+     getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 64`).category,
+     "generalized frames of same type are duplicated in top-down view");
 
-  is(root.calls["64"].category, root.calls[url("A")].calls[url("E")].calls[url("F")].calls["64"].category,
-    "generalized frames of same type are duplicated in top-down view");
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
     { location: "http://content/B" },
     { location: "http://content/C" }
   ]
 }, {
@@ -83,9 +85,9 @@ let gSamples = [{
   ]
 }, {
   time: 5 + 25,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
     { location: "contentZ", category: CATEGORY_MASK("gc", 1) },
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-view-01.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-01.js
@@ -5,17 +5,19 @@
  * Tests if the profiler's tree view implementation works properly and
  * creates the correct column structure.
  */
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
   let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
 
-  let threadNode = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gThread);
+  // Don't display the synthesized (root) and the real (root) node twice.
+  threadNode.calls = threadNode.calls[0].calls;
   let treeRoot = new CallView({ frame: threadNode });
 
   let container = document.createElement("vbox");
   treeRoot.autoExpandDepth = 0;
   treeRoot.attachTo(container);
 
   is(container.childNodes.length, 1,
     "The container node should have one child available.");
@@ -55,17 +57,17 @@ function test() {
   is(container.childNodes[0].childNodes[5].getAttribute("type"), "function",
     "The root node in the tree has a function cell.");
   is(container.childNodes[0].childNodes[5].style.MozMarginStart, "0px",
     "The root node in the tree has the correct indentation.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { category: 8,  location: "(root)" },
     { category: 8,  location: "A (http://foo/bar/baz:12)" },
     { category: 16, location: "B (http://foo/bar/baz:34)" },
     { category: 32, location: "C (http://foo/bar/baz:56)" }
   ]
 }, {
@@ -87,9 +89,9 @@ let gSamples = [{
 }, {
   time: 5 + 1 + 2 + 7,
   frames: [
     { category: 8,   location: "(root)" },
     { category: 8,   location: "A (http://foo/bar/baz:12)" },
     { category: 128, location: "E (http://foo/bar/baz:90)" },
     { category: 256, location: "F (http://foo/bar/baz:99)" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-view-02.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-02.js
@@ -7,17 +7,19 @@
  */
 
 let { CATEGORY_MASK } = devtools.require("devtools/shared/profiler/global");
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
   let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
 
-  let threadNode = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gThread);
+  // Don't display the synthesized (root) and the real (root) node twice.
+  threadNode.calls = threadNode.calls[0].calls;
   let treeRoot = new CallView({ frame: threadNode });
 
   let container = document.createElement("vbox");
   treeRoot.autoExpandDepth = 0;
   treeRoot.attachTo(container);
 
   let $$fun = node => container.querySelectorAll(".call-tree-cell[type=function] > " + node);
   let $$dur = i => container.querySelectorAll(".call-tree-cell[type=duration]")[i];
@@ -120,17 +122,17 @@ function test() {
   is($$fun(".call-tree-host")[3].getAttribute("value"), "foo",
     "The .A.E node's function cell displays the correct host.");
   is($$fun(".call-tree-category")[3].getAttribute("value"), "GC",
     "The .A.E node's function cell displays the correct category.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { category: CATEGORY_MASK('other'),  location: "(root)" },
     { category: CATEGORY_MASK('other'),  location: "A (http://foo/bar/baz:12)" },
     { category: CATEGORY_MASK('css'),    location: "B (http://foo/bar/baz:34)" },
     { category: CATEGORY_MASK('js'),     location: "C (http://foo/bar/baz:56)" }
   ]
 }, {
@@ -152,9 +154,9 @@ let gSamples = [{
 }, {
   time: 5 + 1 + 2 + 7,
   frames: [
     { category: CATEGORY_MASK('other'),   location: "(root)" },
     { category: CATEGORY_MASK('other'),   location: "A (http://foo/bar/baz:12)" },
     { category: CATEGORY_MASK('gc', 2),   location: "E (http://foo/bar/baz:90)" },
     { category: CATEGORY_MASK('network'), location: "F (http://foo/bar/baz:99)" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-view-03.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-03.js
@@ -5,17 +5,19 @@
  * Tests if the profiler's tree view implementation works properly and
  * creates the correct column structure and can auto-expand all nodes.
  */
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
   let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
 
-  let threadNode = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gThread);
+  // Don't display the synthesized (root) and the real (root) node twice.
+  threadNode.calls = threadNode.calls[0].calls;
   let treeRoot = new CallView({ frame: threadNode });
 
   let container = document.createElement("vbox");
   treeRoot.attachTo(container);
 
   let $$fun = i => container.querySelectorAll(".call-tree-cell[type=function]")[i];
   let $$name = i => container.querySelectorAll(".call-tree-cell[type=function] > .call-tree-name")[i];
   let $$duration = i => container.querySelectorAll(".call-tree-cell[type=duration]")[i];
@@ -68,17 +70,17 @@ function test() {
   is($$duration(5).getAttribute("value"), "7 ms",
     "The .A.E node's function cell displays the correct duration.");
   is($$duration(6).getAttribute("value"), "7 ms",
     "The .A.E.F node's function cell displays the correct duration.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { category: 8,  location: "(root)" },
     { category: 8,  location: "A (http://foo/bar/baz:12)" },
     { category: 16, location: "B (http://foo/bar/baz:34)" },
     { category: 32, location: "C (http://foo/bar/baz:56)" }
   ]
 }, {
@@ -100,10 +102,10 @@ let gSamples = [{
 }, {
   time: 5 + 1 + 2 + 7,
   frames: [
     { category: 8,   location: "(root)" },
     { category: 8,   location: "A (http://foo/bar/baz:12)" },
     { category: 128, location: "E (http://foo/bar/baz:90)" },
     { category: 256, location: "F (http://foo/bar/baz:99)" }
   ]
-}];
+}]);
 
--- a/browser/devtools/performance/test/browser_profiler_tree-view-04.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-04.js
@@ -7,17 +7,19 @@
  */
 
 let { CATEGORY_MASK } = devtools.require("devtools/shared/profiler/global");
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
   let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
 
-  let threadNode = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gThread);
+  // Don't display the synthesized (root) and the real (root) node twice.
+  threadNode.calls = threadNode.calls[0].calls;
   let treeRoot = new CallView({ frame: threadNode });
 
   let container = document.createElement("vbox");
   treeRoot.attachTo(container);
 
   is(treeRoot.target.getAttribute("origin"), "chrome",
     "The root node's 'origin' attribute is correct.");
   is(treeRoot.target.getAttribute("category"), "",
@@ -74,17 +76,17 @@ function test() {
   is(functionCell.childNodes[6].tagName, "spacer",
     "The seventh node displayed for function cells is correct.");
   is(functionCell.childNodes[7].className, "plain call-tree-category",
     "The eight node displayed for function cells is correct.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { category: CATEGORY_MASK('other'),  location: "(root)" },
     { category: CATEGORY_MASK('other'),  location: "A (http://foo/bar/baz:12)" },
     { category: CATEGORY_MASK('css'),    location: "B (http://foo/bar/baz:34)" },
     { category: CATEGORY_MASK('js'),     location: "C (http://foo/bar/baz:56)" }
   ]
 }, {
@@ -106,9 +108,9 @@ let gSamples = [{
 }, {
   time: 5 + 1 + 2 + 7,
   frames: [
     { category: CATEGORY_MASK('other'),   location: "(root)" },
     { category: CATEGORY_MASK('other'),   location: "A (http://foo/bar/baz:12)" },
     { category: CATEGORY_MASK('gc', 2),   location: "E (http://foo/bar/baz:90)" },
     { category: CATEGORY_MASK('network'), location: "F (http://foo/bar/baz:99)" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-view-05.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-05.js
@@ -5,17 +5,19 @@
  * Tests if the profiler's tree view implementation works properly and
  * can toggle categories hidden or visible.
  */
 
 function test() {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
   let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
 
-  let threadNode = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gThread);
+  // Don't display the synthesized (root) and the real (root) node twice.
+  threadNode.calls = threadNode.calls[0].calls;
   let treeRoot = new CallView({ frame: threadNode });
 
   let container = document.createElement("vbox");
   treeRoot.attachTo(container);
 
   let categories = container.querySelectorAll(".call-tree-category");
   is(categories.length, 7,
     "The call tree displays a correct number of categories.");
@@ -26,17 +28,17 @@ function test() {
   is(categories.length, 7,
     "The call tree displays the same number of categories.");
   ok(container.hasAttribute("categories-hidden"),
     "All categories should now be hidden in the tree.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { category: 8,  location: "(root)" },
     { category: 8,  location: "A (http://foo/bar/baz:12)" },
     { category: 16, location: "B (http://foo/bar/baz:34)" },
     { category: 32, location: "C (http://foo/bar/baz:56)" }
   ]
 }, {
@@ -58,9 +60,9 @@ let gSamples = [{
 }, {
   time: 5 + 1 + 2 + 7,
   frames: [
     { category: 8,   location: "(root)" },
     { category: 8,   location: "A (http://foo/bar/baz:12)" },
     { category: 128, location: "E (http://foo/bar/baz:90)" },
     { category: 256, location: "F (http://foo/bar/baz:99)" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-view-06.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-06.js
@@ -5,17 +5,19 @@
  * Tests if the profiler's tree view implementation works properly and
  * correctly emits events when certain DOM nodes are clicked.
  */
 
 function spawnTest () {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
   let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
 
-  let threadNode = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gThread);
+  // Don't display the synthesized (root) and the real (root) node twice.
+  threadNode.calls = threadNode.calls[0].calls;
   let treeRoot = new CallView({ frame: threadNode });
 
   let container = document.createElement("vbox");
   treeRoot.attachTo(container);
 
   let A = treeRoot.getChild();
   let B = A.getChild();
   let D = B.getChild();
@@ -24,17 +26,17 @@ function spawnTest () {
   EventUtils.sendMouseEvent({ type: "mousedown" }, D.target.querySelector(".call-tree-url"));
 
   let eventItem = yield receivedLinkEvent;
   is(eventItem, D, "The 'link' event target is correct.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { category: 8,  location: "(root)" },
     { category: 8,  location: "A (http://foo/bar/baz:12)" },
     { category: 16, location: "B (http://foo/bar/baz:34)" },
     { category: 32, location: "C (http://foo/bar/baz:56)" }
   ]
 }, {
@@ -56,9 +58,9 @@ let gSamples = [{
 }, {
   time: 5 + 1 + 2 + 7,
   frames: [
     { category: 8,   location: "(root)" },
     { category: 8,   location: "A (http://foo/bar/baz:12)" },
     { category: 128, location: "E (http://foo/bar/baz:90)" },
     { category: 256, location: "F (http://foo/bar/baz:99)" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-view-07.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-07.js
@@ -5,17 +5,19 @@
  * Tests if the profiler's tree view implementation works properly and
  * has the correct 'root', 'parent', 'level' etc. accessors on child nodes.
  */
 
 function spawnTest () {
   let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
   let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
 
-  let threadNode = new ThreadNode(gSamples);
+  let threadNode = new ThreadNode(gThread);
+  // Don't display the synthesized (root) and the real (root) node twice.
+  threadNode.calls = threadNode.calls[0].calls;
   let treeRoot = new CallView({ frame: threadNode });
 
   let container = document.createElement("vbox");
   container.id = "call-tree-container";
   treeRoot.attachTo(container);
 
   let A = treeRoot.getChild();
   let B = A.getChild();
@@ -28,17 +30,17 @@ function spawnTest () {
   is(D.level, 3,
     "The .A.B.D node has the correct level.");
   is(D.target.className, "call-tree-item",
     "The .A.B.D node has the correct target node.");
   is(D.container.id, "call-tree-container",
     "The .A.B.D node has the correct container node.");
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { category: 8,  location: "(root)" },
     { category: 8,  location: "A (http://foo/bar/baz:12)" },
     { category: 16, location: "B (http://foo/bar/baz:34)" },
     { category: 32, location: "C (http://foo/bar/baz:56)" }
   ]
 }, {
@@ -60,9 +62,9 @@ let gSamples = [{
 }, {
   time: 5 + 1 + 2 + 7,
   frames: [
     { category: 8,   location: "(root)" },
     { category: 8,   location: "A (http://foo/bar/baz:12)" },
     { category: 128, location: "E (http://foo/bar/baz:90)" },
     { category: 256, location: "F (http://foo/bar/baz:99)" }
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/browser_profiler_tree-view-08.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-view-08.js
@@ -21,17 +21,19 @@ function test() {
    *       - D
    *     - E
    *       - F
    *         - (JS)
    *     - (GC)
    *   - (JS)
    */
 
-  let threadNode = new ThreadNode(gSamples, { contentOnly: true });
+  let threadNode = new ThreadNode(gThread, { contentOnly: true });
+  // Don't display the synthesized (root) and the real (root) node twice.
+  threadNode.calls = threadNode.calls[0].calls;
   let treeRoot = new CallView({ frame: threadNode, autoExpandDepth: 10 });
 
   let container = document.createElement("vbox");
   treeRoot.attachTo(container);
 
   let A = treeRoot.getChild(0);
   let JS = treeRoot.getChild(1);
   let GC = A.getChild(1);
@@ -56,17 +58,17 @@ function test() {
   is(GC.target.getAttribute("tooltiptext"), "GC",
     "Generalized GC node has correct category");
   is(GC.target.querySelector(".call-tree-name").getAttribute("value"), "GC",
     "Generalized GC node has correct display value as just the category name.");
 
   finish();
 }
 
-let gSamples = [{
+let gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
     { location: "http://content/B" },
     { location: "http://content/C" }
   ]
 }, {
@@ -96,9 +98,9 @@ let gSamples = [{
   ]
 }, {
   time: 5 + 25,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
     { location: "contentZ", category: CATEGORY_MASK("gc", 1) },
   ]
-}];
+}]);
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -512,8 +512,71 @@ function reload (aTarget, aEvent = "navi
 * Forces cycle collection and GC, used in AudioNode destruction tests.
 */
 function forceCC () {
   info("Triggering GC/CC...");
   SpecialPowers.DOMWindowUtils.cycleCollect();
   SpecialPowers.DOMWindowUtils.garbageCollect();
   SpecialPowers.DOMWindowUtils.garbageCollect();
 }
+
+/**
+ * Inflate a particular sample's stack and return an array of strings.
+ */
+function getInflatedStackLocations(thread, sample) {
+  let stackTable = thread.stackTable;
+  let frameTable = thread.frameTable;
+  let stringTable = thread.stringTable;
+  let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
+  let STACK_PREFIX_SLOT = stackTable.schema.prefix;
+  let STACK_FRAME_SLOT = stackTable.schema.frame;
+  let FRAME_LOCATION_SLOT = frameTable.schema.location;
+
+  // Build the stack from the raw data and accumulate the locations in
+  // an array.
+  let stackIndex = sample[SAMPLE_STACK_SLOT];
+  let locations = [];
+  while (stackIndex !== null) {
+    let stackEntry = stackTable.data[stackIndex];
+    let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
+    locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
+    stackIndex = stackEntry[STACK_PREFIX_SLOT];
+  }
+
+  // The profiler tree is inverted, so reverse the array.
+  return locations.reverse();
+}
+
+/**
+ * Get a path in a FrameNode call tree.
+ */
+function getFrameNodePath(root, path) {
+  let calls = root.calls;
+  let node;
+  for (let key of path.split(" > ")) {
+    node = calls.find((node) => node.key == key);
+    if (!node) {
+      break;
+    }
+    calls = node.calls;
+  }
+  return node;
+}
+
+/**
+ * Synthesize a profile for testing.
+ */
+function synthesizeProfileForTest(samples) {
+  const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+
+  samples.unshift({
+    time: 0,
+    frames: [
+      { location: "(root)" }
+    ]
+  });
+
+  let uniqueStacks = new RecordingUtils.UniqueStacks();
+  return RecordingUtils.deflateThread({
+    samples: samples,
+    markers: []
+  }, uniqueStacks);
+}
--- a/browser/devtools/performance/views/details-js-call-tree.js
+++ b/browser/devtools/performance/views/details-js-call-tree.js
@@ -5,17 +5,18 @@
 
 /**
  * CallTree view containing profiler call tree, controlled by DetailsView.
  */
 let JsCallTreeView = Heritage.extend(DetailsSubview, {
 
   rerenderPrefs: [
     "invert-call-tree",
-    "show-platform-data"
+    "show-platform-data",
+    "flatten-tree-recursion"
   ],
 
   rangeChangeDebounceTime: 75, // ms
 
   /**
    * Sets up the view with event binding.
    */
   initialize: function () {
@@ -42,17 +43,18 @@ let JsCallTreeView = Heritage.extend(Det
    * Method for handling all the set up for rendering a new call tree.
    *
    * @param object interval [optional]
    *        The { startTime, endTime }, in milliseconds.
    */
   render: function (interval={}) {
     let options = {
       contentOnly: !PerformanceController.getOption("show-platform-data"),
-      invertTree: PerformanceController.getOption("invert-call-tree")
+      invertTree: PerformanceController.getOption("invert-call-tree"),
+      flattenRecursion: PerformanceController.getOption("flatten-tree-recursion")
     };
     let recording = PerformanceController.getCurrentRecording();
     let profile = recording.getProfile();
     let threadNode = this._prepareCallTree(profile, interval, options);
     this._populateCallTree(threadNode, options);
     this.emit(EVENTS.JS_CALL_TREE_RENDERED);
   },
 
@@ -70,39 +72,44 @@ let JsCallTreeView = Heritage.extend(Det
     });
   },
 
   /**
    * Called when the recording is stopped and prepares data to
    * populate the call tree.
    */
   _prepareCallTree: function (profile, { startTime, endTime }, options) {
-    let threadSamples = profile.threads[0].samples;
-    let optimizations = profile.threads[0].optimizations;
-    let { contentOnly, invertTree } = options;
+    let thread = profile.threads[0];
+    let { contentOnly, invertTree, flattenRecursion } = options;
+    let threadNode = new ThreadNode(thread, { startTime, endTime, contentOnly, invertTree, flattenRecursion });
 
-    let threadNode = new ThreadNode(threadSamples,
-      { startTime, endTime, contentOnly, invertTree, optimizations });
+    // Real profiles from nsProfiler (i.e. not synthesized from allocation
+    // logs) always have a (root) node. Go down one level in the uninverted
+    // view to avoid displaying both the synthesized root node and the (root)
+    // node from the profiler.
+    if (!invertTree) {
+      threadNode.calls = threadNode.calls[0].calls;
+    }
 
     return threadNode;
   },
 
   /**
    * Renders the call tree.
    */
   _populateCallTree: function (frameNode, options={}) {
     // If we have an empty profile (no samples), then don't invert the tree, as
     // it would hide the root node and a completely blank call tree space can be
     // mis-interpreted as an error.
     let inverted = options.invertTree && frameNode.samples > 0;
 
     let root = new CallView({
       frame: frameNode,
       inverted: inverted,
-      // Root nodes are hidden in inverted call trees.
+      // The synthesized root node is hidden in inverted call trees.
       hidden: inverted,
       // Call trees should only auto-expand when not inverted. Passing undefined
       // will default to the CALL_TREE_AUTO_EXPAND depth.
       autoExpandDepth: inverted ? 0 : undefined
     });
 
     // Bind events.
     root.on("link", this._onLink);
--- a/browser/devtools/performance/views/details-js-flamegraph.js
+++ b/browser/devtools/performance/views/details-js-flamegraph.js
@@ -53,22 +53,22 @@ let JsFlameGraphView = Heritage.extend(D
    *
    * @param object interval [optional]
    *        The { startTime, endTime }, in milliseconds.
    */
   render: function (interval={}) {
     let recording = PerformanceController.getCurrentRecording();
     let duration = recording.getDuration();
     let profile = recording.getProfile();
-    let samples = profile.threads[0].samples;
+    let thread = profile.threads[0];
 
-    let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
-      invertStack: PerformanceController.getOption("invert-flame-graph"),
+    let data = FlameGraphUtils.createFlameGraphDataFromThread(thread, {
+      invertTree: PerformanceController.getOption("invert-flame-graph"),
       flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
-      filterFrames: !PerformanceController.getOption("show-platform-data") && FrameNode.isContent,
+      contentOnly: !PerformanceController.getOption("show-platform-data"),
       showIdleBlocks: PerformanceController.getOption("show-idle-blocks") && L10N.getStr("table.idle")
     });
 
     this.graph.setData({ data,
       bounds: {
         startTime: 0,
         endTime: duration
       },
@@ -90,18 +90,18 @@ let JsFlameGraphView = Heritage.extend(D
   },
 
   /**
    * Called whenever a pref is changed and this view needs to be rerendered.
    */
   _onRerenderPrefChanged: function() {
     let recording = PerformanceController.getCurrentRecording();
     let profile = recording.getProfile();
-    let samples = profile.threads[0].samples;
-    FlameGraphUtils.removeFromCache(samples);
+    let thread = profile.threads[0];
+    FlameGraphUtils.removeFromCache(thread);
   },
 
   /**
    * Called when `devtools.theme` changes.
    */
   _onThemeChanged: function (_, theme) {
     this.graph.setTheme(theme);
     this.graph.refresh({ force: true });
--- a/browser/devtools/performance/views/details-memory-call-tree.js
+++ b/browser/devtools/performance/views/details-memory-call-tree.js
@@ -60,20 +60,20 @@ let MemoryCallTreeView = Heritage.extend
     });
   },
 
   /**
    * Called when the recording is stopped and prepares data to
    * populate the call tree.
    */
   _prepareCallTree: function (allocations, { startTime, endTime }, options) {
-    let samples = RecordingUtils.getSamplesFromAllocations(allocations);
+    let thread = RecordingUtils.getProfileThreadFromAllocations(allocations);
     let invertTree = PerformanceController.getOption("invert-call-tree");
 
-    let threadNode = new ThreadNode(samples,
+    let threadNode = new ThreadNode(thread,
       { startTime, endTime, invertTree });
 
     // If we have an empty profile (no samples), then don't invert the tree, as
     // it would hide the root node and a completely blank call tree space can be
     // mis-interpreted as an error.
     options.inverted = invertTree && threadNode.samples > 0;
 
     return threadNode;
--- a/browser/devtools/performance/views/details-memory-flamegraph.js
+++ b/browser/devtools/performance/views/details-memory-flamegraph.js
@@ -53,18 +53,18 @@ let MemoryFlameGraphView = Heritage.exte
    * @param object interval [optional]
    *        The { startTime, endTime }, in milliseconds.
    */
   render: function (interval={}) {
     let recording = PerformanceController.getCurrentRecording();
     let duration = recording.getDuration();
     let allocations = recording.getAllocations();
 
-    let samples = RecordingUtils.getSamplesFromAllocations(allocations);
-    let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
+    let thread = RecordingUtils.getProfileThreadFromAllocations(allocations);
+    let data = FlameGraphUtils.createFlameGraphDataFromThread(thread, {
       invertStack: PerformanceController.getOption("invert-flame-graph"),
       flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
       showIdleBlocks: PerformanceController.getOption("show-idle-blocks") && L10N.getStr("table.idle")
     });
 
     this.graph.setData({ data,
       bounds: {
         startTime: 0,
@@ -88,18 +88,18 @@ let MemoryFlameGraphView = Heritage.exte
   },
 
   /**
    * Called whenever a pref is changed and this view needs to be rerendered.
    */
   _onRerenderPrefChanged: function() {
     let recording = PerformanceController.getCurrentRecording();
     let allocations = recording.getAllocations();
-    let samples = RecordingUtils.getSamplesFromAllocations(allocations);
-    FlameGraphUtils.removeFromCache(samples);
+    let thread = RecordingUtils.getProfileThreadFromAllocations(allocations);
+    FlameGraphUtils.removeFromCache(thread);
   },
 
   /**
    * Called when `devtools.theme` changes.
    */
   _onThemeChanged: function (_, theme) {
     this.graph.setTheme(theme);
     this.graph.refresh({ force: true });
--- a/browser/devtools/performance/views/jit-optimizations.js
+++ b/browser/devtools/performance/views/jit-optimizations.js
@@ -134,17 +134,17 @@ let JITOptimizationsView = {
     // case of only showing content, reset the view.
     if (!frameNode.hasOptimizations() || frameNode.isMetaCategory) {
       this.reset();
       return;
     }
     this.el.classList.remove("empty");
 
     // An array of sorted OptimizationSites.
-    let sites = frameNode.getOptimizations().getOptimizationSites();
+    let sites = frameNode.getOptimizations().optimizationSites;
 
     for (let site of sites) {
       this._renderSite(view, site, frameData);
     }
 
     this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
   },
 
@@ -182,17 +182,17 @@ let JITOptimizationsView = {
     let { id, data: { types }} = site;
     // Cast `id` to a string so TreeWidget doesn't think it does not exist
     id = id + "";
     for (let i = 0; i < types.length; i++) {
       let ionType = types[i];
 
       let ionNode = this._createIonNode(ionType);
       view.add([id, `${id}-types`, { id: `${id}-types-${i}`, node: ionNode }]);
-      for (let observedType of (ionType.types || [])) {
+      for (let observedType of (ionType.typeset || [])) {
         let node = this._createObservedTypeNode(observedType);
         view.add([id, `${id}-types`, `${id}-types-${i}`, { node }]);
       }
     }
   },
 
   /**
    * Creates an element for insertion in the raw view for an OptimizationSite.
@@ -245,27 +245,18 @@ let JITOptimizationsView = {
    *
    * @see browser/devtools/shared/profiler/jit.js
    * @param {IonType} ionType
    * @return {Element}
    */
 
   _createIonNode: function (ionType) {
     let node = document.createElement("span");
-    let icon = document.createElement("span");
-    let typeNode = document.createElement("span");
-    let siteNode = document.createElement("span");
-
-    typeNode.textContent = ionType.mirType;
-    typeNode.className = "opt-ion-type";
-    siteNode.textContent = `(${ionType.site})`;
-    siteNode.className = "opt-ion-type-site";
-    node.appendChild(typeNode);
-    node.appendChild(siteNode);
-
+    node.textContent = `${ionType.site} : ${ionType.mirType}`;
+    node.className = "opt-ion-type";
     return node;
   },
 
   /**
    * Creates an element for insertion in the raw view for an ObservedType.
    *
    * @see browser/devtools/shared/profiler/jit.js
    * @param {ObservedType} type
--- a/browser/devtools/shared/profiler/frame-utils.js
+++ b/browser/devtools/shared/profiler/frame-utils.js
@@ -4,133 +4,287 @@
 "use strict";
 
 const { Ci } = require("chrome");
 const { extend } = require("sdk/util/object");
 loader.lazyRequireGetter(this, "Services");
 loader.lazyRequireGetter(this, "CATEGORY_OTHER",
   "devtools/shared/profiler/global", true);
 
+// Character codes used in various parsing helper functions.
+const CHAR_CODE_A = "a".charCodeAt(0);
+const CHAR_CODE_C = "c".charCodeAt(0);
+const CHAR_CODE_E = "e".charCodeAt(0);
+const CHAR_CODE_F = "f".charCodeAt(0);
+const CHAR_CODE_H = "h".charCodeAt(0);
+const CHAR_CODE_I = "i".charCodeAt(0);
+const CHAR_CODE_J = "j".charCodeAt(0);
+const CHAR_CODE_L = "l".charCodeAt(0);
+const CHAR_CODE_M = "m".charCodeAt(0);
+const CHAR_CODE_O = "o".charCodeAt(0);
+const CHAR_CODE_P = "p".charCodeAt(0);
+const CHAR_CODE_R = "r".charCodeAt(0);
+const CHAR_CODE_S = "s".charCodeAt(0);
+const CHAR_CODE_T = "t".charCodeAt(0);
+const CHAR_CODE_U = "u".charCodeAt(0);
+const CHAR_CODE_0 = "0".charCodeAt(0);
+const CHAR_CODE_9 = "9".charCodeAt(0);
+
+const CHAR_CODE_LPAREN = "(".charCodeAt(0);
+const CHAR_CODE_COLON = ":".charCodeAt(0);
+const CHAR_CODE_SLASH = "/".charCodeAt(0);
+
 // The cache used in the `nsIURL` function.
 const gNSURLStore = new Map();
-const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"];
-const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"];
+
+// The cache used to store inflated frames.
+const gInflatedFrameStore = new WeakMap();
 
 /**
  * Parses the raw location of this function call to retrieve the actual
  * function name, source url, host name, line and column.
  */
-exports.parseLocation = function parseLocation (frame) {
+exports.parseLocation = function parseLocation(location, fallbackLine, fallbackColumn) {
   // Parse the `location` for the function name, source url, line, column etc.
-  let lineAndColumn = frame.location.match(/((:\d+)*)\)?$/)[1];
-  let [, line, column] = lineAndColumn.split(":");
-  line = line || frame.line;
-  column = column || frame.column;
+
+  let line, column, url;
+
+  // These two indices are used to extract the resource substring, which is
+  // location[firstParenIndex + 1 .. lineAndColumnIndex].
+  //
+  // The resource substring is extracted iff a line number was found. There
+  // may be no parentheses, in which case the substring starts at 0.
+  //
+  // For example, take "foo (bar.js:1)".
+  //                        ^      ^
+  //                        |      -----+
+  //                        +-------+   |
+  //                                |   |
+  // firstParenIndex will point to -+   |
+  //                                    |
+  // lineAndColumnIndex will point to --+
+  //
+  // For an example without parentheses, take "bar.js:2".
+  //                                          ^      ^
+  //                                          |      |
+  // firstParenIndex will point to -----------+      |
+  //                                                 |
+  // lineAndColumIndex will point to ----------------+
+  let firstParenIndex = -1;
+  let lineAndColumnIndex = -1;
+
+  // Compute firstParenIndex and lineAndColumnIndex. If lineAndColumnIndex is
+  // found, also extract the line and column.
+  for (let i = 0; i < location.length; i++) {
+    let c = location.charCodeAt(i);
+
+    // The url and line information might be inside parentheses.
+    if (c === CHAR_CODE_LPAREN) {
+      if (firstParenIndex < 0) {
+        firstParenIndex = i;
+      }
+      continue;
+    }
 
-  let firstParenIndex = frame.location.indexOf("(");
-  let lineAndColumnIndex = frame.location.indexOf(lineAndColumn);
-  let resource = frame.location.substring(firstParenIndex + 1, lineAndColumnIndex);
+    // Look for numbers after colons, twice. Firstly for the line, secondly
+    // for the column.
+    if (c === CHAR_CODE_COLON) {
+      if (isNumeric(location.charCodeAt(i + 1))) {
+        // If we found a line number, remember when it starts.
+        if (lineAndColumnIndex < 0) {
+          lineAndColumnIndex = i;
+        }
+
+        let start = ++i;
+        let length = 1;
+        while (isNumeric(location.charCodeAt(++i))) {
+          length++;
+        }
+
+        if (!line) {
+          line = location.substr(start, length);
+
+          // Unwind a character due to the isNumeric loop above.
+          --i;
 
-  let url = resource.split(" -> ").pop();
-  let uri = nsIURL(url);
+          // There still might be a column number, continue looking.
+          continue;
+        }
+
+        column = location.substr(start, length);
+
+        // We've gotten both a line and a column, stop looking.
+        break;
+      }
+    }
+  }
+
+  let uri;
+  if (lineAndColumnIndex > 0) {
+    let resource = location.substring(firstParenIndex + 1, lineAndColumnIndex);
+    url = resource.split(" -> ").pop();
+    if (url) {
+      uri = nsIURL(url);
+    }
+  }
+
   let functionName, fileName, hostName;
 
   // If the URI digged out from the `location` is valid, this is a JS frame.
   if (uri) {
-    functionName = frame.location.substring(0, firstParenIndex - 1);
+    functionName = location.substring(0, firstParenIndex - 1);
     fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
     hostName = getHost(url, uri.host);
   } else {
-    functionName = frame.location;
+    functionName = location;
     url = null;
   }
 
   return {
     functionName: functionName,
     fileName: fileName,
     hostName: hostName,
     url: url,
-    line: line,
-    column: column
+    line: line || fallbackLine,
+    column: column || fallbackColumn
   };
-},
+};
 
 /**
- * Determines if the given frame is the (root) frame.
+ * Checks if the specified function represents a chrome or content frame.
  *
- * @param object frame
+ * @param string location
+ *        The location of the frame.
+ * @param number category [optional]
+ *        If a chrome frame, the category.
  * @return boolean
+ *         True if a content frame, false if a chrome frame.
  */
-exports.isRoot = function isRoot({ location }) {
-  return location === "(root)";
+function isContent({ location, category }) {
+  // Only C++ stack frames have associated category information.
+  if (category) {
+    return false;
+  }
+
+  // Locations in frames with function names look like:
+  //   "functionName (foo://bar)".
+  // Look for the starting left parenthesis, then try to match a
+  // scheme name.
+  for (let i = 0; i < location.length; i++) {
+    if (location.charCodeAt(i) === CHAR_CODE_LPAREN) {
+      return isContentScheme(location, i + 1);
+    }
+  }
+
+  // If there was no left parenthesis, try matching from the start.
+  return isContentScheme(location, 0);
+}
+exports.isContent = isContent;
+
+/**
+ * Get caches to cache inflated frames and computed frame keys of a frame
+ * table.
+ *
+ * @param object framesTable
+ * @return object
+ */
+exports.getInflatedFrameCache = function getInflatedFrameCache(frameTable) {
+  let inflatedCache = gInflatedFrameStore.get(frameTable);
+  if (inflatedCache !== undefined) {
+    return inflatedCache;
+  }
+
+  // Fill with nulls to ensure no holes.
+  inflatedCache = Array.from({ length: frameTable.data.length }, () => null);
+  gInflatedFrameStore.set(frameTable, inflatedCache);
+  return inflatedCache;
 };
 
 /**
-* Checks if the specified function represents a chrome or content frame.
-*
-* @param object frame
-*        The { category, location } properties of the frame.
-* @return boolean
-*         True if a content frame, false if a chrome frame.
-*/
-exports.isContent = function isContent (frame) {
-  if (exports.isRoot(frame)) {
-    return true;
+ * Get or add an inflated frame to a cache.
+ *
+ * @param object cache
+ * @param number index
+ * @param object frameTable
+ * @param object stringTable
+ * @param object allocationsTable
+ */
+exports.getOrAddInflatedFrame = function getOrAddInflatedFrame(cache, index, frameTable, stringTable, allocationsTable) {
+  let inflatedFrame = cache[index];
+  if (inflatedFrame === null) {
+    inflatedFrame = cache[index] = new InflatedFrame(index, frameTable, stringTable, allocationsTable);
   }
-
-  // Only C++ stack frames have associated category information.
-  const { category, location } = frame;
-  return !!(!category &&
-    !CHROME_SCHEMES.find(e => location.includes(e)) &&
-    CONTENT_SCHEMES.find(e => location.includes(e)));
-}
+  return inflatedFrame;
+};
 
 /**
- * This filters out platform data frames in a sample. With latest performance
- * tool in Fx40, when displaying only content, we still filter out all platform data,
- * except we generalize platform data that are leaves. We do this because of two
- * observations:
- *
- * 1. The leaf is where time is _actually_ being spent, so we _need_ to show it
- * to developers in some way to give them accurate profiling data. We decide to
- * split the platform into various category buckets and just show time spent in
- * each bucket.
- *
- * 2. The calls leading to the leaf _aren't_ where we are spending time, but
- * _do_ give the developer context for how they got to the leaf where they _are_
- * spending time. For non-platform hackers, the non-leaf platform frames don't
- * give any meaningful context, and so we can safely filter them out.
- *
- * Example transformations:
- * Before: PlatformA -> PlatformB -> ContentA -> ContentB
- * After:  ContentA -> ContentB
+ * An intermediate data structured used to hold inflated frames.
  *
- * Before: PlatformA -> ContentA -> PlatformB -> PlatformC
- * After:  ContentA -> Category(PlatformC)
+ * @param number index
+ * @param object frameTable
+ * @param object stringTable
+ * @param object allocationsTable
  */
-exports.filterPlatformData = function filterPlatformData (frames) {
-  let result = [];
-  let last = frames.length - 1;
-  let frame;
+function InflatedFrame(index, frameTable, stringTable, allocationsTable) {
+  const LOCATION_SLOT = frameTable.schema.location;
+  const OPTIMIZATIONS_SLOT = frameTable.schema.optimizations;
+  const LINE_SLOT = frameTable.schema.line;
+  const CATEGORY_SLOT = frameTable.schema.category;
 
-  for (let i = 0; i < frames.length; i++) {
-    frame = frames[i];
-    if (exports.isContent(frame)) {
-      result.push(frame);
-    } else if (last === i) {
-      // Extend here so we're not destructively editing
-      // the original profiler data. Set isMetaCategory `true`,
-      // and ensure we have a category set by default, because that's how
-      // the generalized frame nodes are organized.
-      result.push(extend({ isMetaCategory: true, category: CATEGORY_OTHER }, frame));
-    }
+  let frame = frameTable.data[index];
+  let category = frame[CATEGORY_SLOT];
+  this.location = stringTable[frame[LOCATION_SLOT]];
+  this.optimizations = frame[OPTIMIZATIONS_SLOT];
+  this.line = frame[LINE_SLOT];
+  this.column = undefined;
+  this.category = category;
+  this.metaCategory = category || CATEGORY_OTHER;
+  this.allocations = allocationsTable ? allocationsTable[index] : 0;
+  this.isContent = isContent(this);
+};
+
+/**
+ * Gets the frame key (i.e., equivalence group) according to options. Content
+ * frames are always identified by location. Chrome frames are identified by
+ * location if content-only filtering is off. If content-filtering is on, they
+ * are identified by their category.
+ *
+ * @param object options
+ * @return string
+ */
+InflatedFrame.prototype.getFrameKey = function getFrameKey(options) {
+  if (this.isContent || !options.contentOnly || options.isRoot) {
+    options.isMetaCategoryOut = false;
+    return this.location;
   }
 
-  return result;
-}
+  if (options.isLeaf) {
+    // We only care about leaf platform frames if we are displaying content
+    // only. If no category is present, give the default category of
+    // CATEGORY_OTHER.
+    //
+    // 1. The leaf is where time is _actually_ being spent, so we _need_ to
+    // show it to developers in some way to give them accurate profiling
+    // data. We decide to split the platform into various category buckets
+    // and just show time spent in each bucket.
+    //
+    // 2. The calls leading to the leaf _aren't_ where we are spending time,
+    // but _do_ give the developer context for how they got to the leaf
+    // where they _are_ spending time. For non-platform hackers, the
+    // non-leaf platform frames don't give any meaningful context, and so we
+    // can safely filter them out.
+    options.isMetaCategoryOut = true;
+    return this.metaCategory;
+  }
+
+  // Return an empty string denoting that this frame should be skipped.
+  return "";
+};
+
+exports.InflatedFrame = InflatedFrame;
 
 /**
  * Helper for getting an nsIURL instance out of a string.
  */
 function nsIURL(url) {
   let cached = gNSURLStore.get(url);
   // If we cached a valid URI, or `null` in the case
   // of a failure, return it.
@@ -144,20 +298,111 @@ function nsIURL(url) {
     // if it's invalid, but accessing the host can throw as well
     uri.host;
   } catch(e) {
     // The passed url string is invalid.
     uri = null;
   }
   gNSURLStore.set(url, uri);
   return uri;
-}
+};
 
 /**
  * Takes a `host` string from an nsIURL instance and
  * returns the same string, or null, if it's an invalid host.
  */
 function getHost (url, hostName) {
-  if (CHROME_SCHEMES.find(e => url.indexOf(e) === 0)) {
-    return null;
+  return isChromeScheme(url, 0) ? null : hostName;
+}
+
+// For the functions below, we assume that we will never access the location
+// argument out of bounds, which is indeed the vast majority of cases.
+//
+// They are written this way because they are hot. Each frame is checked for
+// being content or chrome when processing the profile.
+
+function isColonSlashSlash(location, i) {
+  return location.charCodeAt(++i) === CHAR_CODE_COLON &&
+         location.charCodeAt(++i) === CHAR_CODE_SLASH &&
+         location.charCodeAt(++i) === CHAR_CODE_SLASH;
+}
+
+function isContentScheme(location, i) {
+  let firstChar = location.charCodeAt(i);
+
+  switch (firstChar) {
+  case CHAR_CODE_H: // "http://" or "https://"
+    if (location.charCodeAt(++i) === CHAR_CODE_T &&
+        location.charCodeAt(++i) === CHAR_CODE_T &&
+        location.charCodeAt(++i) === CHAR_CODE_P) {
+      if (location.charCodeAt(i + 1) === CHAR_CODE_S) {
+        ++i;
+      }
+      return isColonSlashSlash(location, i);
+    }
+    return false;
+
+  case CHAR_CODE_F: // "file://"
+    if (location.charCodeAt(++i) === CHAR_CODE_I &&
+        location.charCodeAt(++i) === CHAR_CODE_L &&
+        location.charCodeAt(++i) === CHAR_CODE_E) {
+      return isColonSlashSlash(location, i);
+    }
+    return false;
+
+  case CHAR_CODE_A: // "app://"
+    if (location.charCodeAt(++i) == CHAR_CODE_P &&
+        location.charCodeAt(++i) == CHAR_CODE_P) {
+      return isColonSlashSlash(location, i);
+    }
+    return false;
+
+  default:
+    return false;
   }
-  return hostName;
 }
+
+function isChromeScheme(location, i) {
+  let firstChar = location.charCodeAt(i);
+
+  switch (firstChar) {
+  case CHAR_CODE_C: // "chrome://"
+    if (location.charCodeAt(++i) === CHAR_CODE_H &&
+        location.charCodeAt(++i) === CHAR_CODE_R &&
+        location.charCodeAt(++i) === CHAR_CODE_O &&
+        location.charCodeAt(++i) === CHAR_CODE_M &&
+        location.charCodeAt(++i) === CHAR_CODE_E) {
+      return isColonSlashSlash(location, i);
+    }
+    return false;
+
+  case CHAR_CODE_R: // "resource://"
+    if (location.charCodeAt(++i) === CHAR_CODE_E &&
+        location.charCodeAt(++i) === CHAR_CODE_S &&
+        location.charCodeAt(++i) === CHAR_CODE_O &&
+        location.charCodeAt(++i) === CHAR_CODE_U &&
+        location.charCodeAt(++i) === CHAR_CODE_R &&
+        location.charCodeAt(++i) === CHAR_CODE_C &&
+        location.charCodeAt(++i) === CHAR_CODE_E) {
+      return isColonSlashSlash(location, i);
+    }
+    return false;
+
+  case CHAR_CODE_J: // "jar:file://"
+    if (location.charCodeAt(++i) === CHAR_CODE_A &&
+        location.charCodeAt(++i) === CHAR_CODE_R &&
+        location.charCodeAt(++i) === CHAR_CODE_COLON &&
+        location.charCodeAt(++i) === CHAR_CODE_F &&
+        location.charCodeAt(++i) === CHAR_CODE_I &&
+        location.charCodeAt(++i) === CHAR_CODE_L &&
+        location.charCodeAt(++i) === CHAR_CODE_E) {
+      return isColonSlashSlash(location, i);
+    }
+    return false;
+
+  default:
+    return false;
+  }
+}
+
+function isNumeric(c) {
+  return c >= CHAR_CODE_0 && c <= CHAR_CODE_9;
+}
--- a/browser/devtools/shared/profiler/jit.js
+++ b/browser/devtools/shared/profiler/jit.js
@@ -8,55 +8,58 @@ const SUCCESSFUL_OUTCOMES = [
   "GenericSuccess", "Inlined", "DOM", "Monomorphic", "Polymorphic"
 ];
 
 /**
  * Model representing JIT optimization sites from the profiler
  * for a frame (represented by a FrameNode). Requires optimization data from
  * a profile, which is an array of RawOptimizationSites.
  *
- * When the ThreadNode for the profile iterates over the samples' frames, a JITOptimization
- * model is attached to each frame node, with each sample of the frame, usually with each
- * sample containing different optimization information for the same frame (one sample may
- * pick up optimization X on line Y in the frame, with the next sample containing optimization Z
- * on line W in the same frame, as each frame is only function.
+ * When the ThreadNode for the profile iterates over the samples' frames, each
+ * frame's optimizations are accumulated in their respective FrameNodes. Each
+ * FrameNode may contain many different optimization sites. One sample may
+ * pick up optimization X on line Y in the frame, with the next sample
+ * containing optimization Z on line W in the same frame, as each frame is
+ * only function.
  *
- * Each RawOptimizationSite can be sampled multiple times, which multiple calls to
- * JITOptimizations#addOptimizationSite handles. An OptimizationSite contains
- * a record of how many times the RawOptimizationSite was sampled, as well as the unique id
- * based off of the original profiler array, and the RawOptimizationSite itself as a reference.
+ * An OptimizationSite contains a record of how many times the
+ * RawOptimizationSite was sampled, as well as the unique id based off of the
+ * original profiler array, and the RawOptimizationSite itself as a reference.
  * @see browser/devtools/shared/profiler/tree-model.js
  *
- *
  * @struct RawOptimizationSite
  * A structure describing a location in a script that was attempted to be optimized.
  * Contains all the IonTypes observed, and the sequence of OptimizationAttempts that
  * were attempted, and the line and column in the script. This is retrieved from the
  * profiler after a recording, and our base data structure. Should always be referenced,
  * and unmodified.
  *
+ * Note that propertyName is an index into a string table, which needs to be
+ * provided in order for the raw optimization site to be inflated.
+ *
  * @type {Array<IonType>} types
  * @type {Array<OptimizationAttempt>} attempts
+ * @type {?number} propertyName
  * @type {number} line
  * @type {number} column
  *
  *
  * @struct IonType
  * IonMonkey attempts to classify each value in an optimization site by some type.
  * Based off of the observed types for a value (like a variable that could be a
  * string or an instance of an object), it determines what kind of type it should be classified
  * as. Each IonType here contains an array of all ObservedTypes under `types`,
  * the Ion type that IonMonkey decided this value should be (Int32, Object, etc.) as `mirType`,
  * and the component of this optimization type that this value refers to -- like
  * a "getter" optimization, `a[b]`, has site `a` (the "Receiver") and `b` (the "Index").
  *
  * Generally the more ObservedTypes, the more deoptimized this OptimizationSite is.
  * There could be no ObservedTypes, in which case `types` is undefined.
  *
- * @type {?Array<ObservedType>} types
+ * @type {?Array<ObservedType>} typeset
  * @type {string} site
  * @type {string} mirType
  *
  *
  * @struct ObservedType
  * When IonMonkey attempts to determine what type a value is, it checks on each sample.
  * The ObservedType can be thought of in more of JavaScripty-terms, rather than C++.
  * The `keyedBy` property is a high level description of the type, like "primitive",
@@ -106,26 +109,27 @@ const SUCCESSFUL_OUTCOMES = [
  * @param {Array<RawOptimizationSite>} optimizations
  * @param {number} optsIndex
  *
  * @type {RawOptimizationSite} data
  * @type {number} samples
  * @type {number} id
  */
 
-const OptimizationSite = exports.OptimizationSite = function (optimizations, optsIndex) {
-  this.id = optsIndex;
-  this.data = optimizations[optsIndex];
-  this.samples = 0;
+const OptimizationSite = exports.OptimizationSite = function (id, opts) {
+  this.id = id;
+  this.data = opts;
+  this.samples = 1;
 };
 
 /**
  * Returns a boolean indicating if the passed in OptimizationSite
  * has a "good" outcome at the end of its attempted strategies.
  *
+ * @param {Array<string>} stringTable
  * @return {boolean}
  */
 
 OptimizationSite.prototype.hasSuccessfulOutcome = function () {
   let attempts = this.getAttempts();
   let lastOutcome = attempts[attempts.length - 1].outcome;
   return OptimizationSite.isSuccessfulOutcome(lastOutcome);
 };
@@ -150,56 +154,87 @@ OptimizationSite.prototype.getIonTypes =
   return this.data.types;
 };
 
 
 /**
  * Constructor for JITOptimizations. A collection of OptimizationSites for a frame.
  *
  * @constructor
- * @param {Array<RawOptimizationSite>} optimizations
- *        Array of RawOptimizationSites from the profiler. Do not modify this!
+ * @param {Array<RawOptimizationSite>} rawSites
+ *                                     Array of raw optimization sites.
+ * @param {Array<string>} stringTable
+ *                        Array of strings from the profiler used to inflate
+ *                        JIT optimizations. Do not modify this!
  */
 
-const JITOptimizations = exports.JITOptimizations = function (optimizations) {
-  this._opts = optimizations;
-  // Hash of OptimizationSites observed for this frame.
-  this._optSites = {};
-};
+const JITOptimizations = exports.JITOptimizations = function (rawSites, stringTable) {
+  // Build a histogram of optimization sites.
+  let sites = [];
 
-/**
- * Called when a sample detects an optimization on this frame. Takes an `optsIndex`,
- * referring to an optimization in the stored `this._opts` array. Creates a histogram
- * of optimization site data by creating or incrementing an OptimizationSite
- * for each observed optimization.
- *
- * @param {Number} optsIndex
- */
+  for (let rawSite of rawSites) {
+    let existingSite = sites.find((site) => site.data === rawSite);
+    if (existingSite) {
+      existingSite.samples++;
+    } else {
+      sites.push(new OptimizationSite(sites.length, rawSite));
+    }
+  }
+
+  // Inflate the optimization information.
+  for (let site of sites) {
+    let data = site.data;
+    let STRATEGY_SLOT = data.attempts.schema.strategy;
+    let OUTCOME_SLOT = data.attempts.schema.outcome;
 
-JITOptimizations.prototype.addOptimizationSite = function (optsIndex) {
-  let op = this._optSites[optsIndex] || (this._optSites[optsIndex] = new OptimizationSite(this._opts, optsIndex));
-  op.samples++;
-};
+    site.data = {
+      attempts: data.attempts.data.map((a) => {
+        return {
+          strategy: stringTable[a[STRATEGY_SLOT]],
+          outcome: stringTable[a[OUTCOME_SLOT]]
+        }
+      }),
 
-/**
- * Returns an array of OptimizationSites, sorted from most to least times sampled.
- *
- * @return {Array<OptimizationSite>}
- */
+      types: data.types.map((t) => {
+        return {
+          typeset: maybeTypeset(t.typeset, stringTable),
+          site: stringTable[t.site],
+          mirType: stringTable[t.mirType]
+        };
+      }),
 
-JITOptimizations.prototype.getOptimizationSites = function () {
-  let opts = [];
-  for (let opt of Object.keys(this._optSites)) {
-    opts.push(this._optSites[opt]);
+      propertyName: maybeString(stringTable, data.propertyName),
+      line: data.line,
+      column: data.column
+    };
   }
-  return opts.sort((a, b) => b.samples - a.samples);
+
+  this.optimizationSites = sites.sort((a, b) => b.samples - a.samples);;
 };
 
 /**
  * Takes an "outcome" string from an OptimizationAttempt and returns
  * a boolean indicating whether or not its a successful outcome.
  *
  * @return {boolean}
  */
 
 OptimizationSite.isSuccessfulOutcome = JITOptimizations.isSuccessfulOutcome = function (outcome) {
   return !!~SUCCESSFUL_OUTCOMES.indexOf(outcome);
 };
+
+function maybeString(stringTable, index) {
+  return index ? stringTable[index] : undefined;
+}
+
+function maybeTypeset(typeset, stringTable) {
+  if (!typeset) {
+    return undefined;
+  }
+  return typeset.map((ty) => {
+    return {
+      keyedBy: maybeString(stringTable, ty.keyedBy),
+      name: maybeString(stringTable, ty.name),
+      location: maybeString(stringTable, ty.location),
+      line: ty.line
+    };
+  });
+}
--- a/browser/devtools/shared/profiler/tree-model.js
+++ b/browser/devtools/shared/profiler/tree-model.js
@@ -8,127 +8,323 @@ const {Cc, Ci, Cu, Cr} = require("chrome
 loader.lazyRequireGetter(this, "L10N",
   "devtools/shared/profiler/global", true);
 loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
   "devtools/shared/profiler/global", true);
 loader.lazyRequireGetter(this, "CATEGORIES",
   "devtools/shared/profiler/global", true);
 loader.lazyRequireGetter(this, "CATEGORY_JIT",
   "devtools/shared/profiler/global", true);
+loader.lazyRequireGetter(this, "CATEGORY_OTHER",
+  "devtools/shared/profiler/global", true);
 loader.lazyRequireGetter(this, "JITOptimizations",
   "devtools/shared/profiler/jit", true);
 loader.lazyRequireGetter(this, "FrameUtils",
   "devtools/shared/profiler/frame-utils");
 
 exports.ThreadNode = ThreadNode;
 exports.FrameNode = FrameNode;
 exports.FrameNode.isContent = FrameUtils.isContent;
 
 /**
  * A call tree for a thread. This is essentially a linkage between all frames
  * of all samples into a single tree structure, with additional information
  * on each node, like the time spent (in milliseconds) and samples count.
  *
- * Example:
- * {
- *   duration: number,
- *   calls: {
- *     "FunctionName (url:line)": {
- *       line: number,
- *       category: number,
- *       samples: number,
- *       duration: number,
- *       calls: {
- *         ...
- *       }
- *     }, // FrameNode
- *     ...
- *   }
- * } // ThreadNode
- *
- * @param object threadSamples
- *        The raw samples array received from the backend.
+ * @param object thread
+ *        The raw thread object received from the backend. Contains samples,
+ *        stackTable, frameTable, and stringTable.
  * @param object options
  *        Additional supported options, @see ThreadNode.prototype.insert
  *          - number startTime [optional]
  *          - number endTime [optional]
  *          - boolean contentOnly [optional]
  *          - boolean invertTree [optional]
- *          - object optimizations [optional]
- *            The raw tracked optimizations array received from the backend.
+ *          - boolean flattenRecursion [optional]
  */
-function ThreadNode(threadSamples, options = {}) {
+function ThreadNode(thread, options = {}) {
   this.samples = 0;
   this.duration = 0;
-  this.calls = {};
-  this._previousSampleTime = 0;
+  this.calls = [];
+
+  // Maps of frame to their self counts and duration.
+  this.selfCount = Object.create(null);
+  this.selfDuration = Object.create(null);
+
+  let { samples, stackTable, frameTable, stringTable, allocationsTable } = thread;
 
-  for (let sample of threadSamples) {
-    this.insert(sample, options);
+  // Nothing to do if there are no samples.
+  if (samples.data.length === 0) {
+    return;
+  }
+
+  this._buildInverted(samples, stackTable, frameTable, stringTable, allocationsTable, options);
+  if (!options.invertTree) {
+    this._uninvert();
   }
 }
 
 ThreadNode.prototype = {
   /**
-   * Adds function calls in the tree from a sample's frames.
+   * Build an inverted call tree from profile samples. The format of the
+   * samples is described in tools/profiler/ProfileEntry.h, under the heading
+   * "ThreadProfile JSON Format".
+   *
+   * The profile data is naturally presented inverted. Inverting the call tree
+   * is also the default in the Performance tool.
    *
-   * @param object sample
-   *        The { frames, time } sample, containing an array of frames and
-   *        the time the sample was taken. This sample is assumed to be older
-   *        than the most recently inserted one.
-   * @param object options [optional]
-   *        Additional supported options:
-   *          - number startTime: the earliest sample to start at (in milliseconds)
-   *          - number endTime: the latest sample to end at (in milliseconds)
-   *          - boolean contentOnly: if platform frames shouldn't be used
-   *          - boolean invertTree: if the call tree should be inverted
-   *          - object optimizations: The array of all indexable optimizations from the backend.
+   * @param object samples
+   *        The raw samples array received from the backend.
+   * @param object stackTable
+   *        The table of deduplicated stacks from the backend.
+   * @param object frameTable
+   *        The table of deduplicated frames from the backend.
+   * @param object stringTable
+   *        The table of deduplicated strings from the backend.
+   * @param object allocationsTable
+   *        The table of allocation counts from the backend. Indexed by frame
+   *        index.
+   * @param object options
+   *        Additional supported options
+   *          - number startTime [optional]
+   *          - number endTime [optional]
+   *          - boolean contentOnly [optional]
+   *          - boolean invertTree [optional]
    */
-  insert: function(sample, options = {}) {
-    let startTime = options.startTime || 0;
-    let endTime = options.endTime || Infinity;
-    let optimizations = options.optimizations;
-    let sampleTime = sample.time;
-    if (!sampleTime || sampleTime < startTime || sampleTime > endTime) {
-      return;
+  _buildInverted: function buildInverted(samples, stackTable, frameTable, stringTable, allocationsTable, options) {
+    function getOrAddFrameNode(calls, isLeaf, frameKey, inflatedFrame, isMetaCategory, leafTable) {
+      // Insert the inflated frame into the call tree at the current level.
+      let frameNode;
+
+      // Leaf nodes have fan out much greater than non-leaf nodes, thus the
+      // use of a hash table. Otherwise, do linear search.
+      //
+      // Note that this method is very hot, thus the manual looping over
+      // Array.prototype.find.
+      if (isLeaf) {
+        frameNode = leafTable[frameKey];
+      } else {
+        for (let i = 0; i < calls.length; i++) {
+          if (calls[i].key === frameKey) {
+            frameNode = calls[i];
+            break;
+          }
+        }
+      }
+
+      if (!frameNode) {
+        frameNode = new FrameNode(frameKey, inflatedFrame, isMetaCategory);
+        if (isLeaf) {
+          leafTable[frameKey] = frameNode;
+        }
+        calls.push(frameNode);
+      }
+
+      return frameNode;
     }
 
-    let sampleFrames = sample.frames;
+    const SAMPLE_STACK_SLOT = samples.schema.stack;
+    const SAMPLE_TIME_SLOT = samples.schema.time;
+
+    const STACK_PREFIX_SLOT = stackTable.schema.prefix;
+    const STACK_FRAME_SLOT = stackTable.schema.frame;
+
+    const InflatedFrame = FrameUtils.InflatedFrame;
+    const getOrAddInflatedFrame = FrameUtils.getOrAddInflatedFrame;
+
+    let selfCount = this.selfCount;
+    let selfDuration = this.selfDuration;
+
+    let samplesData = samples.data;
+    let stacksData = stackTable.data;
+
+    // Caches.
+    let inflatedFrameCache = FrameUtils.getInflatedFrameCache(frameTable);
+    let leafTable = Object.create(null);
+
+    let startTime = options.startTime || 0
+    let endTime = options.endTime || Infinity;
+    let flattenRecursion = options.flattenRecursion;
+
+    // Take the timestamp of the first sample as prevSampleTime. 0 is
+    // incorrect due to circular buffer wraparound. If wraparound happens,
+    // then the first sample will have an incorrect, large duration.
+    let prevSampleTime = samplesData[0][SAMPLE_TIME_SLOT];
+
+    // Reused options object passed to InflatedFrame.prototype.getFrameKey.
+    let mutableFrameKeyOptions = {
+      contentOnly: options.contentOnly,
+      isRoot: false,
+      isLeaf: false,
+      isMetaCategoryOut: false
+    };
+
+    // Start iteration at the second sample, as we use the first sample to
+    // compute prevSampleTime.
+    for (let i = 1; i < samplesData.length; i++) {
+      let sample = samplesData[i];
+      let sampleTime = sample[SAMPLE_TIME_SLOT];
+
+      // A sample's end time is considered to be its time of sampling. Its
+      // start time is the sampling time of the previous sample.
+      //
+      // Thus, we compare sampleTime <= start instead of < to filter out
+      // samples that end exactly at the start time.
+      if (!sampleTime || sampleTime <= startTime || sampleTime > endTime) {
+        prevSampleTime = sampleTime;
+        continue;
+      }
+
+      let sampleDuration = sampleTime - prevSampleTime;
+      let stackIndex = sample[SAMPLE_STACK_SLOT];
+      let calls = this.calls;
+      let prevCalls = this.calls;
+      let prevFrameKey;
+      let isLeaf = mutableFrameKeyOptions.isLeaf = true;
 
-    if (!options.invertTree) {
-      // Remove the (root) node if the tree is not inverted: we will synthesize
-      // our own root in the view. However, for inverted trees, we wish to be
-      // able to differentiate between (root)->A->B->C and (root)->B->C stacks,
-      // so we need the root node in that case.
-      sampleFrames = sampleFrames.slice(1);
+      // Inflate the stack and build the FrameNode call tree directly.
+      //
+      // In the profiler data, each frame's stack is referenced by an index
+      // into stackTable.
+      //
+      // Each entry in stackTable is a pair [ prefixIndex, frameIndex ]. The
+      // prefixIndex is itself an index into stackTable, referencing the
+      // prefix of the current stack (that is, the younger frames). In other
+      // words, the stackTable is encoded as a trie of the inverted
+      // callstack. The frameIndex is an index into frameTable, describing the
+      // frame at the current depth.
+      //
+      // This algorithm inflates each frame in the frame table while walking
+      // the stack trie as described above.
+      //
+      // The frame key is then computed from the inflated frame /and/ the
+      // current depth in the FrameNode call tree.  That is, the frame key is
+      // not wholly determinable from just the inflated frame.
+      //
+      // For content frames, the frame key is just its location. For chrome
+      // frames, the key may be a metacategory or its location, depending on
+      // rendering options and its position in the FrameNode call tree.
+      //
+      // The frame key is then used to build up the inverted FrameNode call
+      // tree.
+      //
+      // Note that various filtering functions, such as filtering for content
+      // frames or flattening recursion, are inlined into the stack inflation
+      // loop. This is important for performance as it avoids intermediate
+      // structures and multiple passes.
+      while (stackIndex !== null) {
+        let stackEntry = stacksData[stackIndex];
+        let frameIndex = stackEntry[STACK_FRAME_SLOT];
+
+        // Fetch the stack prefix (i.e. older frames) index.
+        stackIndex = stackEntry[STACK_PREFIX_SLOT];
+
+        // Inflate the frame.
+        let inflatedFrame = getOrAddInflatedFrame(inflatedFrameCache, frameIndex, frameTable,
+                                                  stringTable, allocationsTable);
+
+        // Compute the frame key.
+        mutableFrameKeyOptions.isRoot = stackIndex === null;
+        let frameKey = inflatedFrame.getFrameKey(mutableFrameKeyOptions);
+
+        // Leaf frames are never skipped and require self count and duration
+        // bookkeeping.
+        if (isLeaf) {
+          // Tabulate self count and duration for the leaf frame. The frameKey
+          // is never empty for a leaf frame.
+          if (selfCount[frameKey] === undefined) {
+            selfCount[frameKey] = 0;
+            selfDuration[frameKey] = 0;
+          }
+          selfCount[frameKey]++;
+          selfDuration[frameKey] += sampleDuration;
+        } else {
+          // An empty frame key means this frame should be skipped.
+          if (frameKey === "") {
+            continue;
+          }
+        }
+
+        // If we shouldn't flatten the current frame into the previous one, advance a
+        // level in the call tree.
+        if (!flattenRecursion || frameKey !== prevFrameKey) {
+          calls = prevCalls;
+        }
+
+        let frameNode = getOrAddFrameNode(calls, isLeaf, frameKey, inflatedFrame,
+                                          mutableFrameKeyOptions.isMetaCategoryOut,
+                                          leafTable);
+
+        frameNode._countSample(prevSampleTime, sampleTime, inflatedFrame.optimizations,
+                               stringTable);
+
+        prevFrameKey = frameKey;
+        prevCalls = frameNode.calls;
+        isLeaf = mutableFrameKeyOptions.isLeaf = false;
+      }
+
+      this.duration += sampleDuration;
+      this.samples++;
+      prevSampleTime = sampleTime;
     }
+  },
 
-    // Filter out platform frames if only content-related function calls
-    // should be taken into consideration.
-    if (options.contentOnly) {
-      sampleFrames = FrameUtils.filterPlatformData(sampleFrames);
+  /**
+   * Uninverts the call tree after its having been built.
+   */
+  _uninvert: function uninvert() {
+    function mergeOrAddFrameNode(calls, node) {
+      // Unlike the inverted call tree, we don't use a root table for the top
+      // level, as in general, there are many fewer entry points than
+      // leaves. Instead, linear search is used regardless of level.
+      for (let i = 0; i < calls.length; i++) {
+        if (calls[i].key === node.key) {
+          let foundNode = calls[i];
+          foundNode._merge(node);
+          return foundNode.calls;
+        }
+      }
+      let copy = node._clone();
+      calls.push(copy);
+      return copy.calls;
     }
 
-    // If no frames remain after filtering, then this is a leaf node, no need
-    // to continue.
-    if (!sampleFrames.length) {
-      return;
-    }
-    // Invert the tree after filtering, if preferred.
-    if (options.invertTree) {
-      sampleFrames.reverse();
+    let workstack = [{ node: this, level: 0 }];
+    let spine = [];
+    let entry;
+
+    // The new root.
+    let rootCalls = [];
+
+    // Walk depth-first and keep the current spine (e.g., callstack).
+    while (entry = workstack.pop()) {
+      spine[entry.level] = entry;
+
+      let node = entry.node;
+      let calls = node.calls;
+
+      if (calls.length === 0) {
+        // We've bottomed out. Reverse the spine and add them to the
+        // uninverted call tree.
+        let uninvertedCalls = rootCalls;
+        for (let level = entry.level; level > 0; level--) {
+          let callee = spine[level];
+          uninvertedCalls = mergeOrAddFrameNode(uninvertedCalls, callee.node);
+        }
+      } else {
+        // We still have children. Continue the depth-first walk.
+        for (let i = 0; i < calls.length; i++) {
+          workstack.push({ node: calls[i], level: entry.level + 1 });
+        }
+      }
     }
 
-    let sampleDuration = sampleTime - this._previousSampleTime;
-    this._previousSampleTime = sampleTime;
-    this.samples++;
-    this.duration += sampleDuration;
-
-    FrameNode.prototype.insert(
-      sampleFrames, optimizations, 0, sampleTime, sampleDuration, this.calls);
+    // Replace the toplevel calls with rootCalls, which now contains the
+    // uninverted roots.
+    this.calls = rootCalls;
   },
 
   /**
    * Gets additional details about this node.
    * @return object
    */
   getInfo: function() {
     return {
@@ -150,86 +346,104 @@ ThreadNode.prototype = {
   hasOptimizations: function () {
     return null;
   }
 };
 
 /**
  * A function call node in a tree.
  *
+ * @param string frameKey
+ *        The key associated with this frame. The key determines identity of
+ *        the node.
  * @param string location
  *        The location of this function call. Note that this isn't sanitized,
  *        so it may very well (not?) include the function name, url, etc.
  * @param number line
  *        The line number inside the source containing this function call.
- * @param number column
- *        The column number inside the source containing this function call.
  * @param number category
  *        The category type of this function call ("js", "graphics" etc.).
  * @param number allocations
  *        The number of memory allocations performed in this frame.
+ * @param number isContent
+ *        Whether this frame is content.
  * @param boolean isMetaCategory
  *        Whether or not this is a platform node that should appear as a
  *        generalized meta category or not.
  */
-function FrameNode({ location, line, column, category, allocations, isMetaCategory }) {
+function FrameNode(frameKey, { location, line, category, allocations, isContent }, isMetaCategory) {
+  this.key = frameKey;
   this.location = location;
   this.line = line;
-  this.column = column;
   this.category = category;
-  this.allocations = allocations || 0;
-  this.sampleTimes = [];
+  this.allocations = allocations;
   this.samples = 0;
   this.duration = 0;
-  this.calls = {};
+  this.calls = [];
+  this.isContent = isContent;
   this._optimizations = null;
+  this._stringTable = null;
   this.isMetaCategory = isMetaCategory;
 }
 
 FrameNode.prototype = {
   /**
-   * Adds function calls in the tree from a sample's frames. For example, given
-   * the the frames below (which would account for three calls to `insert` on
-   * the root frame), the following tree structure is created:
+   * Count a sample as associated with this node.
    *
-   *                          A
-   *   A -> B -> C           / \
-   *   A -> B -> D    ~>    B   E
-   *   A -> E -> F         / \   \
-   *                      C   D   F
-   * @param frames
-   *        The sample call stack.
-   * @param optimizations
-   *        The array of indexable optimizations.
-   * @param index
-   *        The index of the call in the stack representing this node.
-   * @param number time
-   *        The delta time (in milliseconds) when the frame was sampled.
-   * @param number duration
-   *        The amount of time spent executing all functions on the stack.
+   * @param number prevSampleTime
+   *               The time when the immediate previous sample was sampled.
+   * @param number sampleTime
+   *               The time when the current sample was sampled.
+   * @param object optimizationSite
+   *               Any JIT optimization information attached to the current
+   *               sample. Lazily inflated via stringTable.
+   * @param object stringTable
+   *               The string table used to inflate the optimizationSite.
    */
-  insert: function(frames, optimizations, index, time, duration, _store = this.calls) {
-    let frame = frames[index];
-    if (!frame) {
+  _countSample: function (prevSampleTime, sampleTime, optimizationSite, stringTable) {
+    this.samples++;
+    this.duration += sampleTime - prevSampleTime;
+
+    // Simply accumulate optimization sites for now. Processing is done lazily
+    // by JITOptimizations, if optimization information is actually displayed.
+    if (optimizationSite) {
+      let opts = this._optimizations;
+      if (opts === null) {
+        opts = this._optimizations = [];
+        this._stringTable = stringTable;
+      }
+      opts.push(optimizationSite);
+    }
+  },
+
+  _clone: function () {
+    let newNode = new FrameNode(this.key, this, this.isMetaCategory);
+    newNode._merge(this);
+    return newNode;
+  },
+
+  _merge: function (otherNode) {
+    if (this === otherNode) {
       return;
     }
-    // If we are only displaying content, then platform data will have
-    // a `isMetaCategory` property. Group by category (GC, Graphics, etc.)
-    // to group together frames so they're displayed only once, since we don't
-    // need the location anyway.
-    let key = frame.isMetaCategory ? frame.category : frame.location;
-    let child = _store[key] || (_store[key] = new FrameNode(frame));
-    child.sampleTimes.push({ start: time, end: time + duration });
-    child.samples++;
-    child.duration += duration;
-    if (optimizations && frame.optsIndex != null) {
-      let opts = child._optimizations || (child._optimizations = new JITOptimizations(optimizations));
-      opts.addOptimizationSite(frame.optsIndex);
+
+    this.samples += otherNode.samples;
+    this.duration += otherNode.duration;
+
+    if (otherNode._optimizations) {
+      let opts = this._optimizations;
+      if (opts === null) {
+        opts = this._optimizations = [];
+        this._stringTable = otherNode._stringTable;
+      }
+      let otherOpts = otherNode._optimizations;
+      for (let i = 0; i < otherOpts.length; i++) {
+        opts.push(otherOpts[i]);
+      }
     }
-    child.insert(frames, optimizations, index + 1, time, duration);
   },
 
   /**
    * Returns the parsed location and additional data describing
    * this frame. Uses cached data if possible.
    *
    * @return object
    *         The computed { name, file, url, line } properties for this
@@ -244,24 +458,28 @@ FrameNode.prototype = {
    * function name and source url.
    */
   _computeInfo: function() {
     // "EnterJIT" pseudoframes are special, not actually on the stack.
     if (this.location == "EnterJIT") {
       this.category = CATEGORY_JIT;
     }
 
+    if (this.isMetaCategory && !this.category) {
+      this.category = CATEGORY_OTHER;
+    }
+
     // Since only C++ stack frames have associated category information,
     // default to an "unknown" category otherwise.
     let categoryData = CATEGORY_MAPPINGS[this.category] || {};
 
-    let parsedData = FrameUtils.parseLocation(this);
+    let parsedData = FrameUtils.parseLocation(this.location, this.line, this.column);
     parsedData.nodeType = "Frame";
     parsedData.categoryData = categoryData;
-    parsedData.isContent = FrameUtils.isContent(this);
+    parsedData.isContent = this.isContent;
     parsedData.isMetaCategory = this.isMetaCategory;
 
     return this._data = parsedData;
   },
 
   /**
    * Returns whether or not the frame node has an JITOptimizations model.
    *
@@ -273,11 +491,14 @@ FrameNode.prototype = {
 
   /**
    * Returns the underlying JITOptimizations model representing
    * the optimization attempts occuring in this frame.
    *
    * @return {JITOptimizations|null}
    */
   getOptimizations: function () {
-    return this._optimizations;
+    if (!this._optimizations) {
+      return null;
+    }
+    return new JITOptimizations(this._optimizations, this._stringTable);
   }
 };
--- a/browser/devtools/shared/profiler/tree-view.js
+++ b/browser/devtools/shared/profiler/tree-view.js
@@ -120,45 +120,33 @@ CallView.prototype = Heritage.extend(Abs
 
     let frameInfo = this.frame.getInfo();
     let framePercentage = this._getPercentage(this.frame.samples);
 
     let selfPercentage;
     let selfDuration;
     let totalAllocations;
 
-    if (!this._getChildCalls().length) {
-      if (this.visibleCells.selfPercentage) {
-        selfPercentage = framePercentage;
-      }
-      if (this.visibleCells.selfDuration) {
-        selfDuration = this.frame.duration;
-      }
+    let frameKey = this.frame.key;
+    if (this.visibleCells.selfPercentage) {
+      selfPercentage = this._getPercentage(this.root.frame.selfCount[frameKey]);
+    }
+    if (this.visibleCells.selfDuration) {
+      selfDuration = this.root.frame.selfDuration[frameKey];
+    }
+
+    if (!this.frame.calls.length) {
       if (this.visibleCells.allocations) {
         totalAllocations = this.frame.allocations;
       }
     } else {
-      // Avoid performing costly computations if the respective columns
-      // won't be shown anyway.
-      if (this.visibleCells.selfPercentage) {
-        let childrenPercentage = sum([this._getPercentage(c.samples) for (c of this._getChildCalls())]);
-        selfPercentage = clamp(framePercentage - childrenPercentage, 0, 100);
-      }
-      if (this.visibleCells.selfDuration) {
-        let childrenDuration = sum([c.duration for (c of this._getChildCalls())]);
-        selfDuration = this.frame.duration - childrenDuration;
-      }
       if (this.visibleCells.allocations) {
-        let childrenAllocations = sum([c.allocations for (c of this._getChildCalls())]);
+        let childrenAllocations = this.frame.calls.reduce((acc, node) => acc + node.allocations, 0);
         totalAllocations = this.frame.allocations + childrenAllocations;
       }
-      if (this.inverted) {
-        selfPercentage = framePercentage - selfPercentage;
-        selfDuration = this.frame.duration - selfDuration;
-      }
     }
 
     if (this.visibleCells.duration) {
       var durationCell = this._createTimeCell(this.frame.duration);
     }
     if (this.visibleCells.selfDuration) {
       var selfDurationCell = this._createTimeCell(selfDuration, true);
     }
@@ -227,31 +215,24 @@ CallView.prototype = Heritage.extend(Abs
   /**
    * Calculate what percentage of all samples the given number of samples is.
    */
   _getPercentage: function(samples) {
     return samples / this.root.frame.samples * 100;
   },
 
   /**
-   * Return an array of this frame's child calls.
-   */
-  _getChildCalls: function() {
-    return Object.keys(this.frame.calls).map(k => this.frame.calls[k]);
-  },
-
-  /**
    * Populates this node in the call tree with the corresponding "callees".
    * These are defined in the `frame` data source for this call view.
    * @param array:AbstractTreeItem children
    */
   _populateSelf: function(children) {
     let newLevel = this.level + 1;
 
-    for (let newFrame of this._getChildCalls()) {
+    for (let newFrame of this.frame.calls) {
       children.push(new CallView({
         caller: this,
         frame: newFrame,
         level: newLevel,
         inverted: this.inverted
       }));
     }
 
--- a/browser/devtools/shared/test/browser_flame-graph-utils-01.js
+++ b/browser/devtools/shared/test/browser_flame-graph-utils-01.js
@@ -1,24 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that text metrics and data conversion from profiler samples
 // widget work properly in the flame graph.
 
 let {FlameGraphUtils, FLAME_GRAPH_BLOCK_HEIGHT} = devtools.require("devtools/shared/widgets/FlameGraph");
+let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
 function* performTest() {
-  let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA);
+  let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
 
   ok(out, "Some data was outputted properly");
   is(out.length, 10, "The outputted length is correct.");
 
   info("Got flame graph data:\n" + out.toSource() + "\n");
 
   for (let i = 0; i < out.length; i++) {
     let found = out[i];
@@ -37,17 +38,17 @@ function* performTest() {
       is(found.blocks[j].height, expected.blocks[j].height,
         "The expected block height is correct for this frame.");
       is(found.blocks[j].text, expected.blocks[j].text,
         "The expected block text is correct for this frame.");
     }
   }
 }
 
-let TEST_DATA = [{
+let TEST_DATA = synthesizeProfileForTest([{
   frames: [{
     location: "M"
   }, {
     location: "N",
   }, {
     location: "P"
   }],
   time: 50,
@@ -91,17 +92,17 @@ let TEST_DATA = [{
   frames: [{
     location: "X"
   }, {
     location: "Y",
   }, {
     location: "Z"
   }],
   time: 500
-}];
+}]);
 
 let EXPECTED_OUTPUT = [{
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: [{
     srcData: {
--- a/browser/devtools/shared/test/browser_flame-graph-utils-02.js
+++ b/browser/devtools/shared/test/browser_flame-graph-utils-02.js
@@ -7,17 +7,17 @@ let {FlameGraphUtils, FLAME_GRAPH_BLOCK_
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
 function* performTest() {
-  let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
+  let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
     flattenRecursion: true
   });
 
   ok(out, "Some data was outputted properly");
   is(out.length, 10, "The outputted length is correct.");
 
   info("Got flame graph data:\n" + out.toSource() + "\n");
 
@@ -38,32 +38,32 @@ function* performTest() {
       is(found.blocks[j].height, expected.blocks[j].height,
         "The expected block height is correct for this frame.");
       is(found.blocks[j].text, expected.blocks[j].text,
         "The expected block text is correct for this frame.");
     }
   }
 }
 
-let TEST_DATA = [{
+let TEST_DATA = synthesizeProfileForTest([{
   frames: [{
     location: "A"
   }, {
     location: "A"
   }, {
     location: "A"
   }, {
     location: "B",
   }, {
     location: "B",
   }, {
     location: "C"
   }],
   time: 50,
-}];
+}]);
 
 let EXPECTED_OUTPUT = [{
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: [{
     srcData: {
@@ -84,17 +84,27 @@ let EXPECTED_OUTPUT = [{
     },
     x: 0,
     y: FLAME_GRAPH_BLOCK_HEIGHT,
     width: 50,
     height: FLAME_GRAPH_BLOCK_HEIGHT,
     text: "B"
   }]
 }, {
-  blocks: []
+  blocks: [{
+    srcData: {
+      startTime: 0,
+      rawLocation: "C"
+    },
+    x: 0,
+    y: FLAME_GRAPH_BLOCK_HEIGHT * 2,
+    width: 50,
+    height: FLAME_GRAPH_BLOCK_HEIGHT,
+    text: "C"
+  }]
 }, {
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: []
--- a/browser/devtools/shared/test/browser_flame-graph-utils-03.js
+++ b/browser/devtools/shared/test/browser_flame-graph-utils-03.js
@@ -8,25 +8,27 @@ let {FrameNode} = devtools.require("devt
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
 function* performTest() {
-  let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
-    filterFrames: FrameNode.isContent
+  let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
+    contentOnly: true
   });
 
   ok(out, "Some data was outputted properly");
   is(out.length, 10, "The outputted length is correct.");
 
   info("Got flame graph data:\n" + out.toSource() + "\n");
 
+  dump(JSON.stringify(out, undefined, 2));
+
   for (let i = 0; i < out.length; i++) {
     let found = out[i];
     let expected = EXPECTED_OUTPUT[i];
 
     is(found.blocks.length, expected.blocks.length,
       "The correct number of blocks were found in this bucket.");
 
     for (let j = 0; j < found.blocks.length; j++) {
@@ -39,30 +41,30 @@ function* performTest() {
       is(found.blocks[j].height, expected.blocks[j].height,
         "The expected block height is correct for this frame.");
       is(found.blocks[j].text, expected.blocks[j].text,
         "The expected block text is correct for this frame.");
     }
   }
 }
 
-let TEST_DATA = [{
+let TEST_DATA = synthesizeProfileForTest([{
   frames: [{
     location: "http://A"
   }, {
     location: "https://B"
   }, {
     location: "file://C",
   }, {
     location: "chrome://D"
   }, {
     location: "resource://E"
   }],
   time: 50,
-}];
+}]);
 
 let EXPECTED_OUTPUT = [{
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: [{
     srcData: {
@@ -87,17 +89,27 @@ let EXPECTED_OUTPUT = [{
   }]
 }, {
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: []
 }, {
-  blocks: []
+  blocks: [{
+    srcData: {
+      startTime: 0,
+      rawLocation: "Gecko"
+    },
+    x: 0,
+    y: FLAME_GRAPH_BLOCK_HEIGHT * 3,
+    width: 50,
+    height: FLAME_GRAPH_BLOCK_HEIGHT,
+    text: "Gecko"
+  }]
 }, {
   blocks: []
 }, {
   blocks: [{
     srcData: {
       startTime: 0,
       rawLocation: "https://B"
     },
--- a/browser/devtools/shared/test/browser_flame-graph-utils-04.js
+++ b/browser/devtools/shared/test/browser_flame-graph-utils-04.js
@@ -8,20 +8,20 @@ let {FrameNode} = devtools.require("devt
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
 function* performTest() {
-  let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
+  let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
     flattenRecursion: true,
-    filterFrames: FrameNode.isContent,
-    showIdleBlocks: "\m/"
+    contentOnly: true,
+    showIdleBlocks: '\m/'
   });
 
   ok(out, "Some data was outputted properly");
   is(out.length, 10, "The outputted length is correct.");
 
   info("Got flame graph data:\n" + out.toSource() + "\n");
 
   for (let i = 0; i < out.length; i++) {
@@ -41,17 +41,17 @@ function* performTest() {
       is(found.blocks[j].height, expected.blocks[j].height,
         "The expected block height is correct for this frame.");
       is(found.blocks[j].text, expected.blocks[j].text,
         "The expected block text is correct for this frame.");
     }
   }
 }
 
-let TEST_DATA = [{
+let TEST_DATA = synthesizeProfileForTest([{
   frames: [{
     location: "http://A"
   }, {
     location: "http://A"
   }, {
     location: "http://A"
   }, {
     location: "https://B"
@@ -61,32 +61,28 @@ let TEST_DATA = [{
     location: "file://C",
   }, {
     location: "chrome://D"
   }, {
     location: "resource://E"
   }],
   time: 50
 }, {
-  frames: [{
-    location: "chrome://D"
-  }, {
-    location: "resource://E"
-  }],
+  frames: [],
   time: 100
 }, {
   frames: [{
     location: "http://A"
   }, {
     location: "https://B"
   }, {
     location: "file://C",
   }],
   time: 150
-}];
+}]);
 
 let EXPECTED_OUTPUT = [{
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: [{
     srcData: {
@@ -113,16 +109,26 @@ let EXPECTED_OUTPUT = [{
       startTime: 100,
       rawLocation: "http://A"
     },
     x: 100,
     y: 0,
     width: 50,
     height: FLAME_GRAPH_BLOCK_HEIGHT,
     text: "http://A"
+  }, {
+    srcData: {
+      startTime: 0,
+      rawLocation: "file://C"
+    },
+    x: 100,
+    y: FLAME_GRAPH_BLOCK_HEIGHT * 2,
+    width: 50,
+    height: FLAME_GRAPH_BLOCK_HEIGHT,
+    text: "file://C"
   }]
 }, {
   blocks: [{
     srcData: {
       startTime: 50,
       rawLocation: "\m/"
     },
     x: 50,
@@ -131,17 +137,27 @@ let EXPECTED_OUTPUT = [{
     height: FLAME_GRAPH_BLOCK_HEIGHT,
     text: "\m/"
   }]
 }, {
   blocks: []
 }, {
   blocks: []
 }, {
-  blocks: []
+  blocks: [{
+    srcData: {
+      startTime: 0,
+      rawLocation: "Gecko"
+    },
+    x: 0,
+    y: FLAME_GRAPH_BLOCK_HEIGHT * 3,
+    width: 50,
+    height: FLAME_GRAPH_BLOCK_HEIGHT,
+    text: "Gecko"
+  }]
 }, {
   blocks: []
 }, {
   blocks: [{
     srcData: {
       startTime: 0,
       rawLocation: "https://B"
     },
--- a/browser/devtools/shared/test/browser_flame-graph-utils-05.js
+++ b/browser/devtools/shared/test/browser_flame-graph-utils-05.js
@@ -7,36 +7,36 @@ let {FlameGraphUtils} = devtools.require
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
 function* performTest() {
-  let out1 = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA);
-  let out2 = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA);
+  let out1 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
+  let out2 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
   is(out1, out2, "The outputted data is identical.")
 
-  let out3 = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, { flattenRecursion: true });
+  let out3 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, { flattenRecursion: true });
   is(out2, out3, "The outputted data is still identical.");
 
   FlameGraphUtils.removeFromCache(TEST_DATA);
-  let out4 = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, { flattenRecursion: true });
+  let out4 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, { flattenRecursion: true });
   isnot(out3, out4, "The outputted data is not identical anymore.");
 }
 
-let TEST_DATA = [{
+let TEST_DATA = synthesizeProfileForTest([{
   frames: [{
     location: "A"
   }, {
     location: "A"
   }, {
     location: "A"
   }, {
     location: "B",
   }, {
     location: "B",
   }, {
     location: "C"
   }],
   time: 50,
-}];
+}]);
--- a/browser/devtools/shared/test/browser_flame-graph-utils-06.js
+++ b/browser/devtools/shared/test/browser_flame-graph-utils-06.js
@@ -8,17 +8,17 @@ let {FlameGraphUtils, FLAME_GRAPH_BLOCK_
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
 function* performTest() {
-  let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
+  let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
     flattenRecursion: true
   });
 
   ok(out, "Some data was outputted properly");
   is(out.length, 10, "The outputted length is correct.");
 
   info("Got flame graph data:\n" + out.toSource() + "\n");
 
@@ -39,24 +39,24 @@ function* performTest() {
       is(found.blocks[j].height, expected.blocks[j].height,
         "The expected block height is correct for this frame.");
       is(found.blocks[j].text, expected.blocks[j].text,
         "The expected block text is correct for this frame.");
     }
   }
 }
 
-let TEST_DATA = [{
+let TEST_DATA = synthesizeProfileForTest([{
   frames: [{
     location: "A (http://path/to/file.js:10:5"
   }, {
     location: "B (http://path/to/file.js:100:5"
   }],
   time: 50,
-}];
+}]);
 
 let EXPECTED_OUTPUT = [{
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: []
 }, {
@@ -69,17 +69,27 @@ let EXPECTED_OUTPUT = [{
     },
     x: 0,
     y: 0,
     width: 50,
     height: FLAME_GRAPH_BLOCK_HEIGHT,
     text: "A (file.js:10)"
   }]
 }, {
-  blocks: []
+  blocks: [{
+    srcData: {
+      startTime: 0,
+      rawLocation: "B (http://path/to/file.js:100:5)"
+    },
+    x: 0,
+    y: FLAME_GRAPH_BLOCK_HEIGHT,
+    width: 50,
+    height: FLAME_GRAPH_BLOCK_HEIGHT,
+    text: "B (file.js:100)"
+  }]
 }, {
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: []
 }, {
   blocks: []
--- a/browser/devtools/shared/test/head.js
+++ b/browser/devtools/shared/test/head.js
@@ -240,16 +240,34 @@ function* openAndCloseToolbox(nbOfTimes,
     yield new Promise(resolve => setTimeout(resolve, usageTime));
 
     info("Closing toolbox " + (i + 1));
     yield gDevTools.closeToolbox(target);
   }
 }
 
 /**
+ * Synthesize a profile for testing.
+ */
+function synthesizeProfileForTest(samples) {
+  const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+
+  samples.unshift({
+    time: 0,
+    frames: []
+  });
+
+  let uniqueStacks = new RecordingUtils.UniqueStacks();
+  return RecordingUtils.deflateThread({
+    samples: samples,
+    markers: []
+  }, uniqueStacks);
+}
+
+/**
  * Waits until a predicate returns true.
  *
  * @param function predicate
  *        Invoked once in a while until it returns true.
  * @param number interval [optional]
  *        How often the predicate is invoked, in milliseconds.
  */
 function waitUntil(predicate, interval = 10) {
--- a/browser/devtools/shared/widgets/FlameGraph.js
+++ b/browser/devtools/shared/widgets/FlameGraph.js
@@ -6,16 +6,19 @@
 const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm");
 const { AbstractCanvasGraph, GraphArea, GraphAreaDragger } = require("resource:///modules/devtools/Graphs.jsm");
 const { Promise } = require("resource://gre/modules/Promise.jsm");
 const { Task } = require("resource://gre/modules/Task.jsm");
 const { getColor } = require("devtools/shared/theme");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const FrameUtils = require("devtools/shared/profiler/frame-utils");
 
+loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
+  "devtools/shared/profiler/global", true);
+
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
 const L10N = new ViewHelpers.L10N();
 
 const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
 
 const GRAPH_WHEEL_ZOOM_SENSITIVITY = 0.00035;
 const GRAPH_WHEEL_SCROLL_SENSITIVITY = 0.5;
@@ -45,17 +48,17 @@ const FLAME_GRAPH_BLOCK_TEXT_PADDING_RIG
 /**
  * A flamegraph visualization. This implementation is responsable only with
  * drawing the graph, using a data source consisting of rectangles and
  * their corresponding widths.
  *
  * Example usage:
  *   let graph = new FlameGraph(node);
  *   graph.once("ready", () => {
- *     let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples);
+ *     let data = FlameGraphUtils.createFlameGraphDataFromThread(thread);
  *     let bounds = { startTime, endTime };
  *     graph.setData({ data, bounds });
  *   });
  *
  * Data source format:
  *   [
  *     {
  *       color: "string",
@@ -987,157 +990,218 @@ const COLOR_PALLETTE = Array.from(Array(
 /**
  * A collection of utility functions converting various data sources
  * into a format drawable by the FlameGraph.
  */
 let FlameGraphUtils = {
   _cache: new WeakMap(),
 
   /**
-   * Converts a list of samples from the profiler data to something that's
-   * drawable by a FlameGraph widget.
-   *
-   * The outputted data will be cached, so the next time this method is called
-   * the previous output is returned. If this is undesirable, or should the
-   * options change, use `removeFromCache`.
+   * Create data suitable for use with FlameGraph from a profile's samples.
+   * Iterate the profile's samples and keep a moving window of stack traces.
    *
-   * @param array samples
-   *        A list of { time, frames: [{ location }] } objects.
-   * @param object options [optional]
-   *        Additional options supported by this operation:
-   *          - invertStack: specifies if the frames array in every sample
-   *                         should be reversed
-   *          - flattenRecursion: specifies if identical consecutive frames
-   *                              should be omitted from the output
-   *          - filterFrames: predicate used for filtering all frames, passing
-   *                          in each frame, its index and the sample array
-   *          - showIdleBlocks: adds "idle" blocks when no frames are available
-   *                            using the provided localized text
-   * @param array out [optional]
-   *        An output storage to reuse for storing the flame graph data.
-   * @return array
-   *         The flame graph data.
+   * @param object thread
+   *               The raw thread object received from the backend.
+   * @param object options
+   *               Additional supported options,
+   *                 - boolean contentOnly [optional]
+   *                 - boolean invertTree [optional]
+   *                 - boolean flattenRecursion [optional]
+   *                 - string showIdleBlocks [optional]
+   * @return object
+   *         Data source usable by FlameGraph.
    */
-  createFlameGraphDataFromSamples: function(samples, options = {}, out = []) {
-    let cached = this._cache.get(samples);
+  createFlameGraphDataFromThread: function(thread, options = {}, out = []) {
+    let cached = this._cache.get(thread);
     if (cached) {
       return cached;
     }
 
     // 1. Create a map of colors to arrays, representing buckets of
     // blocks inside the flame graph pyramid sharing the same style.
 
-    let buckets = new Map();
-
-    for (let color of COLOR_PALLETTE) {
-      buckets.set(color, []);
-    }
+    let buckets = Array.from({ length: PALLETTE_SIZE }, () => []);
 
     // 2. Populate the buckets by iterating over every frame in every sample.
 
-    let prevTime = 0;
+    let { samples, stackTable, frameTable, stringTable } = thread;
+
+    const SAMPLE_STACK_SLOT = samples.schema.stack;
+    const SAMPLE_TIME_SLOT = samples.schema.time;
+
+    const STACK_PREFIX_SLOT = stackTable.schema.prefix;
+    const STACK_FRAME_SLOT = stackTable.schema.frame;
+
+    const getOrAddInflatedFrame = FrameUtils.getOrAddInflatedFrame;
+
+    let inflatedFrameCache = FrameUtils.getInflatedFrameCache(frameTable);
+    let labelCache = Object.create(null);
+
+    let samplesData = samples.data;
+    let stacksData = stackTable.data;
+
+    let flattenRecursion = options.flattenRecursion;
+
+    // Reused objects.
+    let mutableFrameKeyOptions = {
+      contentOnly: options.contentOnly,
+      isRoot: false,
+      isLeaf: false,
+      isMetaCategoryOut: false
+    };
+
+    // Take the timestamp of the first sample as prevTime. 0 is incorrect due
+    // to circular buffer wraparound. If wraparound happens, then the first
+    // sample will have an incorrect, large duration.
+    let prevTime = samplesData.length > 0 ? samplesData[0][SAMPLE_TIME_SLOT] : 0;
     let prevFrames = [];
+    let sampleFrames = [];
+    let sampleFrameKeys = [];
+
+    for (let i = 1; i < samplesData.length; i++) {
+      let sample = samplesData[i];
+      let time = sample[SAMPLE_TIME_SLOT];
+
+      let stackIndex = sample[SAMPLE_STACK_SLOT];
+      let prevFrameKey;
+
+      let stackDepth = 0;
 
-    for (let { frames, time } of samples) {
-      let frameIndex = 0;
+      // Inflate the stack and keep a moving window of call stacks.
+      //
+      // For reference, see the similar block comment in
+      // ThreadNode.prototype._buildInverted.
+      //
+      // In a similar fashion to _buildInverted, frames are inflated on the
+      // fly while stackwalking the stackTable trie. The exact same frame key
+      // is computed in both _buildInverted and here.
+      //
+      // Unlike _buildInverted, which builds a call tree directly, the flame
+      // graph inflates the stack into an array, as it maintains a moving
+      // window of stacks over time.
+      //
+      // Like _buildInverted, the various filtering functions are also inlined
+      // into stack inflation loop.
+      while (stackIndex !== null) {
+        let stackEntry = stacksData[stackIndex];
+        let frameIndex = stackEntry[STACK_FRAME_SLOT];
+
+        // Fetch the stack prefix (i.e. older frames) index.
+        stackIndex = stackEntry[STACK_PREFIX_SLOT];
 
-      // Flatten recursion if preferred, by removing consecutive frames
-      // sharing the same location.
-      if (options.flattenRecursion) {
-        frames = frames.filter(this._isConsecutiveDuplicate);
+        // Inflate the frame.
+        let inflatedFrame = getOrAddInflatedFrame(inflatedFrameCache, frameIndex,
+                                                  frameTable, stringTable);
+
+        mutableFrameKeyOptions.isRoot = stackIndex === null;
+        mutableFrameKeyOptions.isLeaf = stackDepth === 0;
+        let frameKey = inflatedFrame.getFrameKey(mutableFrameKeyOptions);
+
+        // If not skipping the frame, add it to the current level. The (root)
+        // node isn't useful for flame graphs.
+        if (frameKey !== "" && frameKey !== "(root)") {
+          // If the frame is a meta category, use the category label.
+          if (mutableFrameKeyOptions.isMetaCategoryOut) {
+            frameKey = CATEGORY_MAPPINGS[frameKey].label;
+          }
+
+          sampleFrames[stackDepth] = inflatedFrame;
+          sampleFrameKeys[stackDepth] = frameKey;
+
+          // If we shouldn't flatten the current frame into the previous one,
+          // increment the stack depth.
+          if (!flattenRecursion || frameKey !== prevFrameKey) {
+            stackDepth++;
+          }
+
+          prevFrameKey = frameKey;
+        }
       }
 
-      // Apply a provided filter function. This can be used, for example, to
-      // filter out platform frames if only content-related function calls
-      // should be taken into consideration.
-      if (options.filterFrames) {
-        frames = frames.filter(options.filterFrames);
-      }
-
-      // Invert the stack if preferred, reversing the frames array in place.
-      if (options.invertStack) {
-        frames.reverse();
+      // Uninvert frames in place if needed.
+      if (!options.invertTree) {
+        sampleFrames.length = stackDepth;
+        sampleFrames.reverse();
+        sampleFrameKeys.length = stackDepth;
+        sampleFrameKeys.reverse();
       }
 
       // If no frames are available, add a pseudo "idle" block in between.
-      if (options.showIdleBlocks && frames.length == 0) {
-        frames = [{ location: options.showIdleBlocks || "", idle: true }];
+      let isIdleFrame = false;
+      if (options.showIdleBlocks && stackDepth === 0) {
+        sampleFrames[0] = null;
+        sampleFrameKeys[0] = options.showIdleBlocks;
+        stackDepth = 1;
+        isIdleFrame = true;
       }
 
-      for (let frame of frames) {
-        let { location } = frame;
+      // Put each frame in a bucket.
+      for (let frameIndex = 0; frameIndex < stackDepth; frameIndex++) {
+        let key = sampleFrameKeys[frameIndex];
         let prevFrame = prevFrames[frameIndex];
 
         // Frames at the same location and the same depth will be reused.
         // If there is a block already created, change its width.
-        if (prevFrame && prevFrame.srcData.rawLocation == location) {
-          prevFrame.width = (time - prevFrame.srcData.startTime);
+        if (prevFrame && prevFrame.frameKey === key) {
+          prevFrame.width = (time - prevFrame.startTime);
         }
         // Otherwise, create a new block for this frame at this depth,
         // using a simple location based salt for picking a color.
         else {
-          let hash = this._getStringHash(location);
-          let color = COLOR_PALLETTE[hash % PALLETTE_SIZE];
-          let bucket = buckets.get(color);
+          let hash = this._getStringHash(key);
+          let bucket = buckets[hash % PALLETTE_SIZE];
+
+          let label;
+          if (isIdleFrame) {
+            label = key;
+          } else {
+            label = labelCache[key];
+            if (!label) {
+              label = labelCache[key] = this._formatLabel(key, sampleFrames[frameIndex]);
+            }
+          }
 
           bucket.push(prevFrames[frameIndex] = {
-            srcData: { startTime: prevTime, rawLocation: location },
+            startTime: prevTime,
+            frameKey: key,
             x: prevTime,
             y: frameIndex * FLAME_GRAPH_BLOCK_HEIGHT,
             width: time - prevTime,
             height: FLAME_GRAPH_BLOCK_HEIGHT,
-            text: this._formatLabel(frame)
+            text: label
           });
         }
-
-        frameIndex++;
       }
 
       // Previous frames at stack depths greater than the current sample's
       // maximum need to be nullified. It's nonsensical to reuse them.
-      prevFrames.length = frameIndex;
+      prevFrames.length = stackDepth;
       prevTime = time;
     }
 
     // 3. Convert the buckets into a data source usable by the FlameGraph.
     // This is a simple conversion from a Map to an Array.
 
-    for (let [color, blocks] of buckets) {
-      out.push({ color, blocks });
+    for (let i = 0; i < buckets.length; i++) {
+      out.push({ color: COLOR_PALLETTE[i], blocks: buckets[i] });
     }
 
-    this._cache.set(samples, out);
+    this._cache.set(thread, out);
     return out;
   },
 
   /**
    * Clears the cached flame graph data created for the given source.
    * @param any source
    */
   removeFromCache: function(source) {
     this._cache.delete(source);
   },
 
   /**
-   * Checks if the provided frame is the same as the next one in a sample.
-   *
-   * @param object e
-   *        An object containing a { location } property.
-   * @param number index
-   *        The index of the object in the parent array.
-   * @param array array
-   *        The parent array.
-   * @return boolean
-   *         True if the next frame shares the same location, false otherwise.
-   */
-  _isConsecutiveDuplicate: function(e, index, array) {
-    return index < array.length - 1 && e.location != array[index + 1].location;
-  },
-
-  /**
    * Very dumb hashing of a string. Used to pick colors from a pallette.
    *
    * @param string input
    * @return number
    */
   _getStringHash: function(input) {
     const STRING_HASH_PRIME1 = 7;
     const STRING_HASH_PRIME2 = 31;
@@ -1152,30 +1216,25 @@ let FlameGraphUtils = {
         return hash;
       }
     }
 
     return hash;
   },
 
   /**
-   * Takes a FrameNode and returns a string that should be displayed
-   * in its flame block.
+   * Takes a frame key and a frame, and returns a string that should be
+   * displayed in its flame block.
    *
-   * @param FrameNode frame
+   * @param string key
+   * @param object frame
    * @return string
    */
-  _formatLabel: function (frame) {
-    // If an idle block, just return the location which will just be "(idle)" text
-    // anyway.
-    if (frame.idle) {
-      return frame.location;
-    }
-
-    let { functionName, fileName, line } = FrameUtils.parseLocation(frame);
+  _formatLabel: function (key, frame) {
+    let { functionName, fileName, line } = FrameUtils.parseLocation(key, frame.line);
     let label = functionName;
 
     if (fileName) {
       label += ` (${fileName}${line != null ? (":" + line) : ""})`;
     }
 
     return label;
   }
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -1064,17 +1064,17 @@ let DirectoryLinksProvider = {
    * @return promise resolved upon disk write completion
    */
   _removeTileClick: function DirectoryLinksProvider_removeTileClick(url = "") {
     // remove trailing slash, to accomodate Places sending site urls ending with '/'
     let noTrailingSlashUrl = url.replace(/\/$/,"");
     let capObject = this._frequencyCaps[url] || this._frequencyCaps[noTrailingSlashUrl];
     // return resolved promise if capObject is not found
     if (!capObject) {
-      return Promise.resolve();;
+      return Promise.resolve();
     }
     // otherwise remove clicked flag
     delete capObject.clicked;
     return this._writeFrequencyCapFile();
   },
 
   /**
    * Removes all clicked flags from frequency cap object
--- a/browser/themes/shared/devtools/performance.inc.css
+++ b/browser/themes/shared/devtools/performance.inc.css
@@ -594,21 +594,16 @@
   display: block;
   overflow: hidden;
 }
 
 #jit-optimizations-raw-view .tree-widget-item[level="1"] {
   font-weight: 600;
 }
 
-#jit-optimizations-view .opt-ion-type-site {
-  -moz-margin-start: 4px !important;
-  opacity: 0.6;
-}
-
 #jit-optimizations-view .opt-outcome::before {
   content: "→";
   margin: 4px 0px;
   color: var(--theme-body-color);
 }
 #jit-optimizations-view .theme-selected .opt-outcome::before {
   color: var(--theme-selection-color);
 }
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -416,17 +416,17 @@ bool classHasAddRefRelease(const CXXReco
   if (it != refCountedClasses.end()) {
     return it->second.second;
   }
 
   bool seenAddRef = false;
   bool seenRelease = false;
   for (CXXRecordDecl::method_iterator method = D->method_begin();
        method != D->method_end(); ++method) {
-    std::string name = method->getNameAsString();
+    const auto &name = method->getName();
     if (name == "AddRef") {
       seenAddRef = true;
     } else if (name == "Release") {
       seenRelease = true;
     }
   }
   refCountedClasses[D] = std::make_pair(D, seenAddRef && seenRelease);
   return seenAddRef && seenRelease;
@@ -583,17 +583,17 @@ AST_MATCHER(BinaryOperator, isInSkScalar
   return llvm::sys::path::rbegin(FileName)->equals("SkScalar.h");
 }
 
 /// This matcher will match all accesses to AddRef or Release methods.
 AST_MATCHER(MemberExpr, isAddRefOrRelease) {
   ValueDecl *Member = Node.getMemberDecl();
   CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Member);
   if (Method) {
-    std::string Name = Method->getNameAsString();
+    const auto &Name = Method->getName();
     return Name == "AddRef" || Name == "Release";
   }
   return false;
 }
 
 /// This matcher will select classes which are refcounted.
 AST_MATCHER(QualType, isRefCounted) {
   return isClassRefCounted(Node);
--- a/build/stlport/Makefile.in
+++ b/build/stlport/Makefile.in
@@ -1,13 +1,9 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MODULES = stlport
 
-# Force to build a static library, instead of a fake library, without
-# installing it in dist/lib.
-NO_EXPAND_LIBS = 1
-
 include $(topsrcdir)/config/rules.mk
 
 CXXFLAGS += -fuse-cxa-atexit
--- a/build/stlport/moz.build
+++ b/build/stlport/moz.build
@@ -60,8 +60,12 @@ NO_VISIBILITY_FLAGS = True
 
 # Suppress warnings in third-party code.
 if CONFIG['GNU_CXX']:
     CXXFLAGS += [
         '-Wno-empty-body',
         '-Wno-type-limits',
         '-Wno-unused-local-typedefs',
     ]
+
+# Force to build a static library, instead of a fake library, without
+# installing it in dist/lib.
+NO_EXPAND_LIBS = True
--- a/build/unix/elfhack/inject/moz.build
+++ b/build/unix/elfhack/inject/moz.build
@@ -1,15 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-NO_DIST_INSTALL = True
+DIST_INSTALL = False
 
 if CONFIG['TARGET_CPU'].endswith('86'):
     cpu = 'x86'
 elif CONFIG['TARGET_CPU'].startswith('arm'):
     cpu = 'arm'
 else:
     cpu = CONFIG['TARGET_CPU']
 
--- a/build/unix/elfhack/moz.build
+++ b/build/unix/elfhack/moz.build
@@ -1,15 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-NO_DIST_INSTALL = True
+DIST_INSTALL = False
 DIRS += ['inject']
 
 if not CONFIG['CROSS_COMPILE']:
     SOURCES += [
         'dummy.c',
         'test-array.c',
         'test-ctors.c',
     ]
--- a/config/config.mk
+++ b/config/config.mk
@@ -181,17 +181,24 @@ ifneq (,$(MOZ_DEBUG)$(MOZ_DEBUG_SYMBOLS)
     endif
   else
     _DEBUG_ASFLAGS += $(MOZ_DEBUG_FLAGS)
   endif
   _DEBUG_CFLAGS += $(MOZ_DEBUG_FLAGS)
   _DEBUG_LDFLAGS += $(MOZ_DEBUG_LDFLAGS)
 endif
 
+ifeq ($(YASM),$(AS))
+# yasm doesn't like the GNU as flags we may already have in ASFLAGS, so reset.
+ASFLAGS := $(_DEBUG_ASFLAGS)
+# yasm doesn't like -c
+AS_DASH_C_FLAG=
+else
 ASFLAGS += $(_DEBUG_ASFLAGS)
+endif
 OS_CFLAGS += $(_DEBUG_CFLAGS)
 OS_CXXFLAGS += $(_DEBUG_CFLAGS)
 OS_LDFLAGS += $(_DEBUG_LDFLAGS)
 
 # XXX: What does this? Bug 482434 filed for better explanation.
 ifeq ($(OS_ARCH)_$(GNU_CC),WINNT_)
 ifdef MOZ_DEBUG
 ifneq (,$(MOZ_BROWSE_INFO)$(MOZ_BSCFILE))
@@ -419,21 +426,21 @@ HOST_CMMFLAGS += -fobjc-exceptions
 OS_COMPILE_CMFLAGS += -fobjc-exceptions
 OS_COMPILE_CMMFLAGS += -fobjc-exceptions
 ifeq ($(MOZ_WIDGET_TOOLKIT),uikit)
 OS_COMPILE_CMFLAGS += -fobjc-abi-version=2 -fobjc-legacy-dispatch
 OS_COMPILE_CMMFLAGS += -fobjc-abi-version=2 -fobjc-legacy-dispatch
 endif
 endif
 
-COMPILE_CFLAGS	= $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_CPPFLAGS) $(OS_COMPILE_CFLAGS) $(CFLAGS) $(MOZBUILD_CFLAGS) $(EXTRA_COMPILE_FLAGS)
-COMPILE_CXXFLAGS = $(if $(DISABLE_STL_WRAPPING),,$(STL_FLAGS)) $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_CPPFLAGS) $(OS_COMPILE_CXXFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(EXTRA_COMPILE_FLAGS)
-COMPILE_CMFLAGS = $(OS_COMPILE_CMFLAGS) $(MOZBUILD_CMFLAGS) $(EXTRA_COMPILE_FLAGS)
-COMPILE_CMMFLAGS = $(OS_COMPILE_CMMFLAGS) $(MOZBUILD_CMMFLAGS) $(EXTRA_COMPILE_FLAGS)
-ASFLAGS += $(EXTRA_ASSEMBLER_FLAGS)
+COMPILE_CFLAGS	= $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_CPPFLAGS) $(OS_COMPILE_CFLAGS) $(CFLAGS) $(MOZBUILD_CFLAGS)
+COMPILE_CXXFLAGS = $(if $(DISABLE_STL_WRAPPING),,$(STL_FLAGS)) $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_CPPFLAGS) $(OS_COMPILE_CXXFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS)
+COMPILE_CMFLAGS = $(OS_COMPILE_CMFLAGS) $(MOZBUILD_CMFLAGS)
+COMPILE_CMMFLAGS = $(OS_COMPILE_CMMFLAGS) $(MOZBUILD_CMMFLAGS)
+ASFLAGS += $(MOZBUILD_ASFLAGS)
 
 ifndef CROSS_COMPILE
 HOST_CFLAGS += $(RTL_FLAGS)
 endif
 
 #
 # Name of the binary code directories
 #
--- a/config/moz.build
+++ b/config/moz.build
@@ -2,17 +2,17 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Build Config')
 
-NO_DIST_INSTALL = True
+DIST_INSTALL = False
 # For sanity's sake, we compile nsinstall without the wrapped system
 # headers, so that we can use it to set up the wrapped system headers.
 NO_VISIBILITY_FLAGS = True
 
 CONFIGURE_SUBST_FILES += [
     'doxygen.cfg',
     'makefiles/test/Makefile',
     'tests/makefiles/autodeps/Makefile',
--- a/configure.in
+++ b/configure.in
@@ -68,17 +68,17 @@ PANGO_VERSION=1.22.0
 GTK2_VERSION=2.18.0
 GTK3_VERSION=3.4.0
 WINDRES_VERSION=2.14.90
 W32API_VERSION=3.14
 GNOMEUI_VERSION=2.2.0
 GCONF_VERSION=1.2.1
 STARTUP_NOTIFICATION_VERSION=0.8
 DBUS_VERSION=0.60
-SQLITE_VERSION=3.8.9
+SQLITE_VERSION=3.8.10.1
 
 MSMANIFEST_TOOL=
 
 dnl Set various checks
 dnl ========================================================
 MISSING_X=
 AC_PROG_AWK
 
@@ -3861,17 +3861,16 @@ MOZ_SCTP=
 MOZ_ANDROID_OMX=
 MOZ_MEDIA_NAVIGATOR=
 MOZ_OMX_PLUGIN=
 MOZ_VPX=
 MOZ_VPX_ERROR_CONCEALMENT=
 MOZ_WEBSPEECH=1
 VPX_AS=
 VPX_ASFLAGS=
-VPX_AS_DASH_C_FLAG=
 VPX_AS_CONVERSION=
 VPX_ASM_SUFFIX=
 VPX_X86_ASM=
 VPX_ARM_ASM=
 LIBJPEG_TURBO_AS=
 LIBJPEG_TURBO_ASFLAGS=
 LIBJPEG_TURBO_X86_ASM=
 LIBJPEG_TURBO_X64_ASM=
@@ -5479,17 +5478,16 @@ if test -n "$MOZ_VPX" -a -z "$MOZ_NATIVE
       fi # COMPILE_ENVIRONMENT and others
     ;;
     *:arm*)
       if test -n "$GNU_AS" ; then
         VPX_AS=$AS
         dnl These flags are a lie; they're just used to enable the requisite
         dnl opcodes; actual arch detection is done at runtime.
         VPX_ASFLAGS="-march=armv7-a -mfpu=neon"
-        VPX_DASH_C_FLAG="-c"
         VPX_AS_CONVERSION='$(PERL) $(topsrcdir)/media/libvpx/build/make/ads2gas.pl'
         VPX_ASM_SUFFIX="$ASM_SUFFIX"
         VPX_ARM_ASM=1
       fi
     ;;
     *:x86)
       if $CC -E -dM -</dev/null | grep -q __ELF__; then
         VPX_ASFLAGS="-f elf32 -rnasm -pnasm -DPIC"
@@ -6739,16 +6737,45 @@ then
         )
     ])
     AC_MSG_RESULT($ac_cv_sqlite_enable_unlock_notify)
     CFLAGS="$_SAVE_CFLAGS"
     LIBS="$_SAVE_LIBS"
     if test "x$ac_cv_sqlite_enable_unlock_notify" = "xno"; then
         AC_MSG_ERROR([System SQLite library is not compiled with SQLITE_ENABLE_UNLOCK_NOTIFY.])
     fi
+
+    dnl =========================================
+    dnl === SQLITE_ENABLE_DBSTAT_VTAB check ===
+    dnl =========================================
+    dnl check to see if the system SQLite package is compiled with
+    dnl SQLITE_ENABLE_DBSTAT_VTAB.
+    AC_MSG_CHECKING(for SQLITE_ENABLE_DBSTAT_VTAB support in system SQLite)
+    _SAVE_CFLAGS="$CFLAGS"
+    CFLAGS="$CFLAGS $SQLITE_CFLAGS"
+    _SAVE_LIBS="$LIBS"
+    LIBS="$LIBS $SQLITE_LIBS"
+    AC_CACHE_VAL(ac_cv_sqlite_dbstat_vtab,[
+        AC_TRY_RUN([
+            #include "sqlite3.h"
+
+            int main(int argc, char **argv){
+              return !sqlite3_compileoption_used("SQLITE_ENABLE_DBSTAT_VTAB");
+            }],
+            ac_cv_sqlite_dbstat_vtab=yes,
+            ac_cv_sqlite_dbstat_vtab=no,
+            ac_cv_sqlite_dbstat_vtab=no
+        )
+    ])
+    AC_MSG_RESULT($ac_cv_sqlite_dbstat_vtab)
+    CFLAGS="$_SAVE_CFLAGS"
+    LIBS="$_SAVE_LIBS"
+    if test "x$ac_cv_sqlite_dbstat_vtab" = "xno"; then
+        AC_MSG_ERROR([System SQLite library is not compiled with SQLITE_ENABLE_DBSTAT_VTAB.])
+    fi
 fi
 
 if test -n "$MOZ_NATIVE_SQLITE"; then
     AC_DEFINE(MOZ_NATIVE_SQLITE)
 fi
 AC_SUBST(MOZ_NATIVE_SQLITE)
 
 dnl ========================================================
@@ -8830,27 +8857,26 @@ AC_SUBST(MOZ_FMP4)
 AC_SUBST(MOZ_EME)
 AC_SUBST(MOZ_DIRECTSHOW)
 AC_SUBST(MOZ_ANDROID_OMX)
 AC_SUBST(MOZ_APPLEMEDIA)
 AC_SUBST(MOZ_OMX_PLUGIN)
 AC_SUBST(MOZ_VPX_ERROR_CONCEALMENT)
 AC_SUBST(MOZ_VPX)
 AC_SUBST(VPX_AS)
-AC_SUBST(VPX_ASFLAGS)
-AC_SUBST(VPX_DASH_C_FLAG)
+AC_SUBST_LIST(VPX_ASFLAGS)
 AC_SUBST(VPX_AS_CONVERSION)
 AC_SUBST(VPX_ASM_SUFFIX)
 AC_SUBST(VPX_X86_ASM)
 AC_SUBST(VPX_ARM_ASM)
 AC_SUBST(VPX_NEED_OBJ_INT_EXTRACT)
 AC_SUBST(MOZ_INSTRUMENT_EVENT_LOOP)
 AC_SUBST(MOZ_CODE_COVERAGE)
 AC_SUBST(LIBJPEG_TURBO_AS)
-AC_SUBST(LIBJPEG_TURBO_ASFLAGS)
+AC_SUBST_LIST(LIBJPEG_TURBO_ASFLAGS)
 AC_SUBST(LIBJPEG_TURBO_X86_ASM)
 AC_SUBST(LIBJPEG_TURBO_X64_ASM)
 AC_SUBST(LIBJPEG_TURBO_ARM_ASM)
 AC_SUBST(LIBJPEG_TURBO_ARM64_ASM)
 AC_SUBST(LIBJPEG_TURBO_MIPS_ASM)
 
 AC_SUBST(MOZ_PACKAGE_JSSHELL)
 AC_SUBST(MOZ_FOLD_LIBS)
--- a/db/sqlite3/src/moz.build
+++ b/db/sqlite3/src/moz.build
@@ -30,17 +30,18 @@ SOURCES += [
 # be overridden on a per-platform basis through the use of the PREF_TS_PAGESIZE
 # hidden preference.  If that preference is missing or invalid then this value
 # will be used.
 # -DSQLITE_MAX_SCHEMA_RETRY increases the times SQLite may try to reparse
 # statements when the schema changes. This is important when supporting lots of
 # concurrent connections, especially when they use shared cache.
 # Note: Be sure to update the configure.in checks when these change!
 for var in ('SQLITE_SECURE_DELETE', 'SQLITE_THREADSAFE', 'SQLITE_CORE',
-            'SQLITE_ENABLE_FTS3', 'SQLITE_ENABLE_UNLOCK_NOTIFY'):
+            'SQLITE_ENABLE_FTS3', 'SQLITE_ENABLE_UNLOCK_NOTIFY',
+            'SQLITE_ENABLE_DBSTAT_VTAB'):
     DEFINES[var] = 1
 
 DEFINES['SQLITE_DEFAULT_PAGE_SIZE'] = 32768
 DEFINES['SQLITE_MAX_DEFAULT_PAGE_SIZE'] = 32768
 DEFINES['SQLITE_MAX_SCHEMA_RETRY'] = 25
 
 # -DSQLITE_WIN32_GETVERSIONEX=0 avoids using deprecated functions.
 # SQLite will just assume we are running on NT kinds of Windows. That's fine
--- a/db/sqlite3/src/sqlite3.c
+++ b/db/sqlite3/src/sqlite3.c
@@ -1,11 +1,11 @@
 /******************************************************************************
 ** This file is an amalgamation of many separate C source files from SQLite
-** version 3.8.9.  By combining all the individual C code files into this 
+** version 3.8.10.1.  By combining all the individual C code files into this 
 ** single large file, the entire code can be compiled as a single translation
 ** unit.  This allows many compilers to do optimizations that would not be
 ** possible if the files were compiled separately.  Performance improvements
 ** of 5% or more are commonly seen when SQLite is compiled as a single
 ** translation unit.
 **
 ** This file is all you need to compile SQLite.  To use SQLite in other
 ** programs, you need this file and the "sqlite3.h" header file that defines
@@ -65,16 +65,17 @@
 #ifndef _MSVC_H_
 #define _MSVC_H_
 
 #if defined(_MSC_VER)
 #pragma warning(disable : 4054)
 #pragma warning(disable : 4055)
 #pragma warning(disable : 4100)
 #pragma warning(disable : 4127)
+#pragma warning(disable : 4130)
 #pragma warning(disable : 4152)
 #pragma warning(disable : 4189)
 #pragma warning(disable : 4206)
 #pragma warning(disable : 4210)
 #pragma warning(disable : 4232)
 #pragma warning(disable : 4244)
 #pragma warning(disable : 4305)
 #pragma warning(disable : 4306)
@@ -312,19 +313,19 @@ extern "C" {
 ** within its configuration management system.  ^The SQLITE_SOURCE_ID
 ** string contains the date and time of the check-in (UTC) and an SHA1
 ** hash of the entire source tree.
 **
 ** See also: [sqlite3_libversion()],
 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
 ** [sqlite_version()] and [sqlite_source_id()].
 */
-#define SQLITE_VERSION        "3.8.9"
-#define SQLITE_VERSION_NUMBER 3008009
-#define SQLITE_SOURCE_ID      "2015-04-08 12:16:33 8a8ffc862e96f57aa698f93de10dee28e69f6e09"
+#define SQLITE_VERSION        "3.8.10.1"
+#define SQLITE_VERSION_NUMBER 3008010
+#define SQLITE_SOURCE_ID      "2015-05-09 12:14:55 05b4b1f2a937c06c90db70c09890038f6c98ec40"
 
 /*
 ** CAPI3REF: Run-Time Library Version Numbers
 ** KEYWORDS: sqlite3_version, sqlite3_sourceid
 **
 ** These interfaces provide the same information as the [SQLITE_VERSION],
 ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros
 ** but are associated with the library instead of the header file.  ^(Cautious
@@ -471,16 +472,17 @@ typedef sqlite_uint64 sqlite3_uint64;
 ** substitute integer for floating-point.
 */
 #ifdef SQLITE_OMIT_FLOATING_POINT
 # define double sqlite3_int64
 #endif
 
 /*
 ** CAPI3REF: Closing A Database Connection
+** DESTRUCTOR: sqlite3
 **
 ** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors
 ** for the [sqlite3] object.
 ** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if
 ** the [sqlite3] object is successfully destroyed and all associated
 ** resources are deallocated.
 **
 ** ^If the database connection is associated with unfinalized prepared
@@ -522,16 +524,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_cl
 ** The type for a callback function.
 ** This is legacy and deprecated.  It is included for historical
 ** compatibility and is not documented.
 */
 typedef int (*sqlite3_callback)(void*,int,char**, char**);
 
 /*
 ** CAPI3REF: One-Step Query Execution Interface
+** METHOD: sqlite3
 **
 ** The sqlite3_exec() interface is a convenience wrapper around
 ** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()],
 ** that allows an application to run multiple statements of SQL
 ** without having to use a lot of C code. 
 **
 ** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded,
 ** semicolon-separate SQL statements passed into its 2nd argument,
@@ -1579,16 +1582,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_os
 ** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK].
 ** ^If the option is unknown or SQLite is unable to set the option
 ** then this routine returns a non-zero [error code].
 */
 SQLITE_API int SQLITE_CDECL sqlite3_config(int, ...);
 
 /*
 ** CAPI3REF: Configure database connections
+** METHOD: sqlite3
 **
 ** The sqlite3_db_config() interface is used to make configuration
 ** changes to a [database connection].  The interface is similar to
 ** [sqlite3_config()] except that the changes apply to a single
 ** [database connection] (specified in the first argument).
 **
 ** The second argument to sqlite3_db_config(D,V,...)  is the
 ** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code 
@@ -2076,25 +2080,27 @@ struct sqlite3_mem_methods {
 */
 #define SQLITE_DBCONFIG_LOOKASIDE       1001  /* void* int int */
 #define SQLITE_DBCONFIG_ENABLE_FKEY     1002  /* int int* */
 #define SQLITE_DBCONFIG_ENABLE_TRIGGER  1003  /* int int* */
 
 
 /*
 ** CAPI3REF: Enable Or Disable Extended Result Codes
+** METHOD: sqlite3
 **
 ** ^The sqlite3_extended_result_codes() routine enables or disables the
 ** [extended result codes] feature of SQLite. ^The extended result
 ** codes are disabled by default for historical compatibility.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_extended_result_codes(sqlite3*, int onoff);
 
 /*
 ** CAPI3REF: Last Insert Rowid
+** METHOD: sqlite3
 **
 ** ^Each entry in most SQLite tables (except for [WITHOUT ROWID] tables)
 ** has a unique 64-bit signed
 ** integer key called the [ROWID | "rowid"]. ^The rowid is always available
 ** as an undeclared column named ROWID, OID, or _ROWID_ as long as those
 ** names are not also used by explicitly declared columns. ^If
 ** the table has a column of type [INTEGER PRIMARY KEY] then that column
 ** is another alias for the rowid.
@@ -2136,16 +2142,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_ex
 ** then the value returned by [sqlite3_last_insert_rowid()] is
 ** unpredictable and might not equal either the old or the new
 ** last insert [rowid].
 */
 SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_last_insert_rowid(sqlite3*);
 
 /*
 ** CAPI3REF: Count The Number Of Rows Modified
+** METHOD: sqlite3
 **
 ** ^This function returns the number of rows modified, inserted or
 ** deleted by the most recently completed INSERT, UPDATE or DELETE
 ** statement on the database connection specified by the only parameter.
 ** ^Executing any other type of SQL statement does not modify the value
 ** returned by this function.
 **
 ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
@@ -2188,16 +2195,17 @@ SQLITE_API sqlite3_int64 SQLITE_STDCALL 
 ** If a separate thread makes changes on the same database connection
 ** while [sqlite3_changes()] is running then the value returned
 ** is unpredictable and not meaningful.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_changes(sqlite3*);
 
 /*
 ** CAPI3REF: Total Number Of Rows Modified
+** METHOD: sqlite3
 **
 ** ^This function returns the total number of rows inserted, modified or
 ** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed
 ** since the database connection was opened, including those executed as
 ** part of trigger programs. ^Executing any other type of SQL statement
 ** does not affect the value returned by sqlite3_total_changes().
 ** 
 ** ^Changes made as part of [foreign key actions] are included in the
@@ -2211,16 +2219,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_ch
 ** If a separate thread makes changes on the same database connection
 ** while [sqlite3_total_changes()] is running then the value
 ** returned is unpredictable and not meaningful.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_total_changes(sqlite3*);
 
 /*
 ** CAPI3REF: Interrupt A Long-Running Query
+** METHOD: sqlite3
 **
 ** ^This function causes any pending database operation to abort and
 ** return at its earliest opportunity. This routine is typically
 ** called in response to a user action such as pressing "Cancel"
 ** or Ctrl-C where the user wants a long query operation to halt
 ** immediately.
 **
 ** ^It is safe to call this routine from a thread different from the
@@ -2287,16 +2296,17 @@ SQLITE_API void SQLITE_STDCALL sqlite3_i
 ** UTF-16 string in native byte order.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_complete(const char *sql);
 SQLITE_API int SQLITE_STDCALL sqlite3_complete16(const void *sql);
 
 /*
 ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors
 ** KEYWORDS: {busy-handler callback} {busy handler}
+** METHOD: sqlite3
 **
 ** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X
 ** that might be invoked with argument P whenever
 ** an attempt is made to access a database table associated with
 ** [database connection] D when another thread
 ** or process has the table locked.
 ** The sqlite3_busy_handler() interface is used to implement
 ** [sqlite3_busy_timeout()] and [PRAGMA busy_timeout].
@@ -2346,16 +2356,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_co
 ** 
 ** A busy handler must not close the database connection
 ** or [prepared statement] that invoked the busy handler.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);
 
 /*
 ** CAPI3REF: Set A Busy Timeout
+** METHOD: sqlite3
 **
 ** ^This routine sets a [sqlite3_busy_handler | busy handler] that sleeps
 ** for a specified amount of time when a table is locked.  ^The handler
 ** will sleep multiple times until at least "ms" milliseconds of sleeping
 ** have accumulated.  ^After at least "ms" milliseconds of sleeping,
 ** the handler returns 0 which causes [sqlite3_step()] to return
 ** [SQLITE_BUSY].
 **
@@ -2368,16 +2379,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bu
 ** this routine, that other busy handler is cleared.)^
 **
 ** See also:  [PRAGMA busy_timeout]
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_busy_timeout(sqlite3*, int ms);
 
 /*
 ** CAPI3REF: Convenience Routines For Running Queries
+** METHOD: sqlite3
 **
 ** This is a legacy interface that is preserved for backwards compatibility.
 ** Use of this interface is not recommended.
 **
 ** Definition: A <b>result table</b> is memory data structure created by the
 ** [sqlite3_get_table()] interface.  A result table records the
 ** complete query results from one or more queries.
 **
@@ -2703,16 +2715,17 @@ SQLITE_API sqlite3_int64 SQLITE_STDCALL 
 ** non-NULL P then the pseudo-randomness is generated
 ** internally and without recourse to the [sqlite3_vfs] xRandomness
 ** method.
 */
 SQLITE_API void SQLITE_STDCALL sqlite3_randomness(int N, void *P);
 
 /*
 ** CAPI3REF: Compile-Time Authorization Callbacks
+** METHOD: sqlite3
 **
 ** ^This routine registers an authorizer callback with a particular
 ** [database connection], supplied in the first argument.
 ** ^The authorizer callback is invoked as SQL statements are being compiled
 ** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()],
 ** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()].  ^At various
 ** points during the compilation process, as logic is being created
 ** to perform various actions, the authorizer callback is invoked to
@@ -2859,16 +2872,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_se
 #define SQLITE_DROP_VTABLE          30   /* Table Name      Module Name     */
 #define SQLITE_FUNCTION             31   /* NULL            Function Name   */
 #define SQLITE_SAVEPOINT            32   /* Operation       Savepoint Name  */
 #define SQLITE_COPY                  0   /* No longer used */
 #define SQLITE_RECURSIVE            33   /* NULL            NULL            */
 
 /*
 ** CAPI3REF: Tracing And Profiling Functions
+** METHOD: sqlite3
 **
 ** These routines register callback functions that can be used for
 ** tracing and profiling the execution of SQL statements.
 **
 ** ^The callback function registered by sqlite3_trace() is invoked at
 ** various times when an SQL statement is being run by [sqlite3_step()].
 ** ^The sqlite3_trace() callback is invoked with a UTF-8 rendering of the
 ** SQL statement text as the statement first begins executing.
@@ -2891,16 +2905,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_se
 ** subject to change in future versions of SQLite.
 */
 SQLITE_API void *SQLITE_STDCALL sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*);
 SQLITE_API SQLITE_EXPERIMENTAL void *SQLITE_STDCALL sqlite3_profile(sqlite3*,
    void(*xProfile)(void*,const char*,sqlite3_uint64), void*);
 
 /*
 ** CAPI3REF: Query Progress Callbacks
+** METHOD: sqlite3
 **
 ** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback
 ** function X to be invoked periodically during long running calls to
 ** [sqlite3_exec()], [sqlite3_step()] and [sqlite3_get_table()] for
 ** database connection D.  An example use for this
 ** interface is to keep a GUI updated during a large query.
 **
 ** ^The parameter P is passed through as the only parameter to the 
@@ -2924,16 +2939,17 @@ SQLITE_API SQLITE_EXPERIMENTAL void *SQL
 ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their
 ** database connections for the meaning of "modify" in this paragraph.
 **
 */
 SQLITE_API void SQLITE_STDCALL sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
 
 /*
 ** CAPI3REF: Opening A New Database Connection
+** CONSTRUCTOR: sqlite3
 **
 ** ^These routines open an SQLite database file as specified by the 
 ** filename argument. ^The filename argument is interpreted as UTF-8 for
 ** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte
 ** order for sqlite3_open16(). ^(A [database connection] handle is usually
 ** returned in *ppDb, even if an error occurs.  The only exception is that
 ** if SQLite is unable to allocate memory to hold the [sqlite3] object,
 ** a NULL will be written into *ppDb instead of a pointer to the [sqlite3]
@@ -3209,16 +3225,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_op
 */
 SQLITE_API const char *SQLITE_STDCALL sqlite3_uri_parameter(const char *zFilename, const char *zParam);
 SQLITE_API int SQLITE_STDCALL sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
 SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_uri_int64(const char*, const char*, sqlite3_int64);
 
 
 /*
 ** CAPI3REF: Error Codes And Messages
+** METHOD: sqlite3
 **
 ** ^If the most recent sqlite3_* API call associated with 
 ** [database connection] D failed, then the sqlite3_errcode(D) interface
 ** returns the numeric [result code] or [extended result code] for that
 ** API call.
 ** If the most recent API call was successful,
 ** then the return value from sqlite3_errcode() is undefined.
 ** ^The sqlite3_extended_errcode()
@@ -3254,43 +3271,44 @@ SQLITE_API sqlite3_int64 SQLITE_STDCALL 
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_errcode(sqlite3 *db);
 SQLITE_API int SQLITE_STDCALL sqlite3_extended_errcode(sqlite3 *db);
 SQLITE_API const char *SQLITE_STDCALL sqlite3_errmsg(sqlite3*);
 SQLITE_API const void *SQLITE_STDCALL sqlite3_errmsg16(sqlite3*);
 SQLITE_API const char *SQLITE_STDCALL sqlite3_errstr(int);
 
 /*
-** CAPI3REF: SQL Statement Object
+** CAPI3REF: Prepared Statement Object
 ** KEYWORDS: {prepared statement} {prepared statements}
 **
-** An instance of this object represents a single SQL statement.
-** This object is variously known as a "prepared statement" or a
-** "compiled SQL statement" or simply as a "statement".
-**
-** The life of a statement object goes something like this:
+** An instance of this object represents a single SQL statement that
+** has been compiled into binary form and is ready to be evaluated.
+**
+** Think of each SQL statement as a separate computer program.  The
+** original SQL text is source code.  A prepared statement object 
+** is the compiled object code.  All SQL must be converted into a
+** prepared statement before it can be run.
+**
+** The life-cycle of a prepared statement object usually goes like this:
 **
 ** <ol>
-** <li> Create the object using [sqlite3_prepare_v2()] or a related
-**      function.
-** <li> Bind values to [host parameters] using the sqlite3_bind_*()
+** <li> Create the prepared statement object using [sqlite3_prepare_v2()].
+** <li> Bind values to [parameters] using the sqlite3_bind_*()
 **      interfaces.
 ** <li> Run the SQL by calling [sqlite3_step()] one or more times.
-** <li> Reset the statement using [sqlite3_reset()] then go back
+** <li> Reset the prepared statement using [sqlite3_reset()] then go back
 **      to step 2.  Do this zero or more times.
 ** <li> Destroy the object using [sqlite3_finalize()].
 ** </ol>
-**
-** Refer to documentation on individual methods above for additional
-** information.
 */
 typedef struct sqlite3_stmt sqlite3_stmt;
 
 /*
 ** CAPI3REF: Run-time Limits
+** METHOD: sqlite3
 **
 ** ^(This interface allows the size of various constructs to be limited
 ** on a connection by connection basis.  The first parameter is the
 ** [database connection] whose limit is to be set or queried.  The
 ** second parameter is one of the [limit categories] that define a
 ** class of constructs to be size limited.  The third parameter is the
 ** new limit for that construct.)^
 **
@@ -3392,16 +3410,18 @@ SQLITE_API int SQLITE_STDCALL sqlite3_li
 #define SQLITE_LIMIT_LIKE_PATTERN_LENGTH       8
 #define SQLITE_LIMIT_VARIABLE_NUMBER           9
 #define SQLITE_LIMIT_TRIGGER_DEPTH            10
 #define SQLITE_LIMIT_WORKER_THREADS           11
 
 /*
 ** CAPI3REF: Compiling An SQL Statement
 ** KEYWORDS: {SQL statement compiler}
+** METHOD: sqlite3
+** CONSTRUCTOR: sqlite3_stmt
 **
 ** To execute an SQL query, it must first be compiled into a byte-code
 ** program using one of these routines.
 **
 ** The first argument, "db", is a [database connection] obtained from a
 ** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or
 ** [sqlite3_open16()].  The database connection must not have been closed.
 **
@@ -3499,25 +3519,27 @@ SQLITE_API int SQLITE_STDCALL sqlite3_pr
   const void *zSql,       /* SQL statement, UTF-16 encoded */
   int nByte,              /* Maximum length of zSql in bytes. */
   sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
   const void **pzTail     /* OUT: Pointer to unused portion of zSql */
 );
 
 /*
 ** CAPI3REF: Retrieving Statement SQL
+** METHOD: sqlite3_stmt
 **
 ** ^This interface can be used to retrieve a saved copy of the original
 ** SQL text used to create a [prepared statement] if that statement was
 ** compiled using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()].
 */
 SQLITE_API const char *SQLITE_STDCALL sqlite3_sql(sqlite3_stmt *pStmt);
 
 /*
 ** CAPI3REF: Determine If An SQL Statement Writes The Database
+** METHOD: sqlite3_stmt
 **
 ** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if
 ** and only if the [prepared statement] X makes no direct changes to
 ** the content of the database file.
 **
 ** Note that [application-defined SQL functions] or
 ** [virtual tables] might change the database indirectly as a side effect.  
 ** ^(For example, if an application defines a function "eval()" that 
@@ -3539,16 +3561,17 @@ SQLITE_API const char *SQLITE_STDCALL sq
 ** sqlite3_stmt_readonly() to return true since, while those statements
 ** change the configuration of a database connection, they do not make 
 ** changes to the content of the database files on disk.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
 
 /*
 ** CAPI3REF: Determine If A Prepared Statement Has Been Reset
+** METHOD: sqlite3_stmt
 **
 ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the
 ** [prepared statement] S has been stepped at least once using 
 ** [sqlite3_step(S)] but has not run to completion and/or has not 
 ** been reset using [sqlite3_reset(S)].  ^The sqlite3_stmt_busy(S)
 ** interface returns false if S is a NULL pointer.  If S is not a 
 ** NULL pointer and is not a pointer to a valid [prepared statement]
 ** object, then the behavior is undefined and probably undesirable.
@@ -3613,16 +3636,17 @@ typedef struct Mem sqlite3_value;
 ** and/or [sqlite3_set_auxdata()].
 */
 typedef struct sqlite3_context sqlite3_context;
 
 /*
 ** CAPI3REF: Binding Values To Prepared Statements
 ** KEYWORDS: {host parameter} {host parameters} {host parameter name}
 ** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding}
+** METHOD: sqlite3_stmt
 **
 ** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
 ** literals may be replaced by a [parameter] that matches one of following
 ** templates:
 **
 ** <ul>
 ** <li>  ?
 ** <li>  ?NNN
@@ -3731,16 +3755,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bi
 SQLITE_API int SQLITE_STDCALL sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
 SQLITE_API int SQLITE_STDCALL sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64,
                          void(*)(void*), unsigned char encoding);
 SQLITE_API int SQLITE_STDCALL sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
 SQLITE_API int SQLITE_STDCALL sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
 
 /*
 ** CAPI3REF: Number Of SQL Parameters
+** METHOD: sqlite3_stmt
 **
 ** ^This routine can be used to find the number of [SQL parameters]
 ** in a [prepared statement].  SQL parameters are tokens of the
 ** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as
 ** placeholders for values that are [sqlite3_bind_blob | bound]
 ** to the parameters at a later time.
 **
 ** ^(This routine actually returns the index of the largest (rightmost)
@@ -3751,16 +3776,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bi
 ** See also: [sqlite3_bind_blob|sqlite3_bind()],
 ** [sqlite3_bind_parameter_name()], and
 ** [sqlite3_bind_parameter_index()].
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_bind_parameter_count(sqlite3_stmt*);
 
 /*
 ** CAPI3REF: Name Of A Host Parameter
+** METHOD: sqlite3_stmt
 **
 ** ^The sqlite3_bind_parameter_name(P,N) interface returns
 ** the name of the N-th [SQL parameter] in the [prepared statement] P.
 ** ^(SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA"
 ** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA"
 ** respectively.
 ** In other words, the initial ":" or "$" or "@" or "?"
 ** is included as part of the name.)^
@@ -3778,52 +3804,56 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bi
 ** See also: [sqlite3_bind_blob|sqlite3_bind()],
 ** [sqlite3_bind_parameter_count()], and
 ** [sqlite3_bind_parameter_index()].
 */
 SQLITE_API const char *SQLITE_STDCALL sqlite3_bind_parameter_name(sqlite3_stmt*, int);
 
 /*
 ** CAPI3REF: Index Of A Parameter With A Given Name
+** METHOD: sqlite3_stmt
 **
 ** ^Return the index of an SQL parameter given its name.  ^The
 ** index value returned is suitable for use as the second
 ** parameter to [sqlite3_bind_blob|sqlite3_bind()].  ^A zero
 ** is returned if no matching parameter is found.  ^The parameter
 ** name must be given in UTF-8 even if the original statement
 ** was prepared from UTF-16 text using [sqlite3_prepare16_v2()].
 **
 ** See also: [sqlite3_bind_blob|sqlite3_bind()],
 ** [sqlite3_bind_parameter_count()], and
 ** [sqlite3_bind_parameter_index()].
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
 
 /*
 ** CAPI3REF: Reset All Bindings On A Prepared Statement
+** METHOD: sqlite3_stmt
 **
 ** ^Contrary to the intuition of many, [sqlite3_reset()] does not reset
 ** the [sqlite3_bind_blob | bindings] on a [prepared statement].
 ** ^Use this routine to reset all host parameters to NULL.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_clear_bindings(sqlite3_stmt*);
 
 /*
 ** CAPI3REF: Number Of Columns In A Result Set
+** METHOD: sqlite3_stmt
 **
 ** ^Return the number of columns in the result set returned by the
 ** [prepared statement]. ^This routine returns 0 if pStmt is an SQL
 ** statement that does not return data (for example an [UPDATE]).
 **
 ** See also: [sqlite3_data_count()]
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_column_count(sqlite3_stmt *pStmt);
 
 /*
 ** CAPI3REF: Column Names In A Result Set
+** METHOD: sqlite3_stmt
 **
 ** ^These routines return the name assigned to a particular column
 ** in the result set of a [SELECT] statement.  ^The sqlite3_column_name()
 ** interface returns a pointer to a zero-terminated UTF-8 string
 ** and sqlite3_column_name16() returns a pointer to a zero-terminated
 ** UTF-16 string.  ^The first parameter is the [prepared statement]
 ** that implements the [SELECT] statement. ^The second parameter is the
 ** column number.  ^The leftmost column is number 0.
@@ -3843,16 +3873,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_co
 ** then the name of the column is unspecified and may change from
 ** one release of SQLite to the next.
 */
 SQLITE_API const char *SQLITE_STDCALL sqlite3_column_name(sqlite3_stmt*, int N);
 SQLITE_API const void *SQLITE_STDCALL sqlite3_column_name16(sqlite3_stmt*, int N);
 
 /*
 ** CAPI3REF: Source Of Data In A Query Result
+** METHOD: sqlite3_stmt
 **
 ** ^These routines provide a means to determine the database, table, and
 ** table column that is the origin of a particular result column in
 ** [SELECT] statement.
 ** ^The name of the database or table or column can be returned as
 ** either a UTF-8 or UTF-16 string.  ^The _database_ routines return
 ** the database name, the _table_ routines return the table name, and
 ** the origin_ routines return the column name.
@@ -3895,16 +3926,17 @@ SQLITE_API const char *SQLITE_STDCALL sq
 SQLITE_API const void *SQLITE_STDCALL sqlite3_column_database_name16(sqlite3_stmt*,int);
 SQLITE_API const char *SQLITE_STDCALL sqlite3_column_table_name(sqlite3_stmt*,int);
 SQLITE_API const void *SQLITE_STDCALL sqlite3_column_table_name16(sqlite3_stmt*,int);
 SQLITE_API const char *SQLITE_STDCALL sqlite3_column_origin_name(sqlite3_stmt*,int);
 SQLITE_API const void *SQLITE_STDCALL sqlite3_column_origin_name16(sqlite3_stmt*,int);
 
 /*
 ** CAPI3REF: Declared Datatype Of A Query Result
+** METHOD: sqlite3_stmt
 **
 ** ^(The first parameter is a [prepared statement].
 ** If this statement is a [SELECT] statement and the Nth column of the
 ** returned result set of that [SELECT] is a table column (not an
 ** expression or subquery) then the declared type of the table
 ** column is returned.)^  ^If the Nth column of the result set is an
 ** expression or subquery, then a NULL pointer is returned.
 ** ^The returned string is always UTF-8 encoded.
@@ -3927,16 +3959,17 @@ SQLITE_API const void *SQLITE_STDCALL sq
 ** is associated with individual values, not with the containers
 ** used to hold those values.
 */
 SQLITE_API const char *SQLITE_STDCALL sqlite3_column_decltype(sqlite3_stmt*,int);
 SQLITE_API const void *SQLITE_STDCALL sqlite3_column_decltype16(sqlite3_stmt*,int);
 
 /*
 ** CAPI3REF: Evaluate An SQL Statement
+** METHOD: sqlite3_stmt
 **
 ** After a [prepared statement] has been prepared using either
 ** [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or one of the legacy
 ** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function
 ** must be called one or more times to evaluate the statement.
 **
 ** The details of the behavior of the sqlite3_step() interface depend
 ** on whether the statement was prepared using the newer "v2" interface
@@ -4006,16 +4039,17 @@ SQLITE_API const void *SQLITE_STDCALL sq
 ** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces,
 ** then the more specific [error codes] are returned directly
 ** by sqlite3_step().  The use of the "v2" interface is recommended.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_step(sqlite3_stmt*);
 
 /*
 ** CAPI3REF: Number of columns in a result set
+** METHOD: sqlite3_stmt
 **
 ** ^The sqlite3_data_count(P) interface returns the number of columns in the
 ** current row of the result set of [prepared statement] P.
 ** ^If prepared statement P does not have results ready to return
 ** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of
 ** interfaces) then sqlite3_data_count(P) returns 0.
 ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer.
 ** ^The sqlite3_data_count(P) routine returns 0 if the previous call to
@@ -4059,16 +4093,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_da
 #else
 # define SQLITE_TEXT     3
 #endif
 #define SQLITE3_TEXT     3
 
 /*
 ** CAPI3REF: Result Values From A Query
 ** KEYWORDS: {column access functions}
+** METHOD: sqlite3_stmt
 **
 ** These routines form the "result set" interface.
 **
 ** ^These routines return information about a single column of the current
 ** result row of a query.  ^In every case the first argument is a pointer
 ** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*]
 ** that was returned from [sqlite3_prepare_v2()] or one of its variants)
 ** and the second argument is the index of the column for which information
@@ -4231,16 +4266,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_co
 SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_column_int64(sqlite3_stmt*, int iCol);
 SQLITE_API const unsigned char *SQLITE_STDCALL sqlite3_column_text(sqlite3_stmt*, int iCol);
 SQLITE_API const void *SQLITE_STDCALL sqlite3_column_text16(sqlite3_stmt*, int iCol);
 SQLITE_API int SQLITE_STDCALL sqlite3_column_type(sqlite3_stmt*, int iCol);
 SQLITE_API sqlite3_value *SQLITE_STDCALL sqlite3_column_value(sqlite3_stmt*, int iCol);
 
 /*
 ** CAPI3REF: Destroy A Prepared Statement Object
+** DESTRUCTOR: sqlite3_stmt
 **
 ** ^The sqlite3_finalize() function is called to delete a [prepared statement].
 ** ^If the most recent evaluation of the statement encountered no errors
 ** or if the statement is never been evaluated, then sqlite3_finalize() returns
 ** SQLITE_OK.  ^If the most recent evaluation of statement S failed, then
 ** sqlite3_finalize(S) returns the appropriate [error code] or
 ** [extended error code].
 **
@@ -4258,16 +4294,17 @@ SQLITE_API sqlite3_value *SQLITE_STDCALL
 ** a prepared statement after it has been finalized.  Any use of a prepared
 ** statement after it has been finalized can result in undefined and
 ** undesirable behavior such as segfaults and heap corruption.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_finalize(sqlite3_stmt *pStmt);
 
 /*
 ** CAPI3REF: Reset A Prepared Statement Object
+** METHOD: sqlite3_stmt
 **
 ** The sqlite3_reset() function is called to reset a [prepared statement]
 ** object back to its initial state, ready to be re-executed.
 ** ^Any SQL statement variables that had values bound to them using
 ** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values.
 ** Use [sqlite3_clear_bindings()] to reset the bindings.
 **
 ** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S
@@ -4287,16 +4324,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_fi
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_reset(sqlite3_stmt *pStmt);
 
 /*
 ** CAPI3REF: Create Or Redefine SQL Functions
 ** KEYWORDS: {function creation routines}
 ** KEYWORDS: {application-defined SQL function}
 ** KEYWORDS: {application-defined SQL functions}
+** METHOD: sqlite3
 **
 ** ^These functions (collectively known as "function creation routines")
 ** are used to add SQL functions or aggregates or to redefine the behavior
 ** of existing SQL functions or aggregates.  The only differences between
 ** these routines are the text encoding expected for
 ** the second parameter (the name of the function being created)
 ** and the presence or absence of a destructor callback for
 ** the application data pointer.
@@ -4456,16 +4494,17 @@ SQLITE_API SQLITE_DEPRECATED int SQLITE_
 SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_global_recover(void);
 SQLITE_API SQLITE_DEPRECATED void SQLITE_STDCALL sqlite3_thread_cleanup(void);
 SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),
                       void*,sqlite3_int64);
 #endif
 
 /*
 ** CAPI3REF: Obtaining SQL Function Parameter Values
+** METHOD: sqlite3_value
 **
 ** The C-language implementation of SQL functions and aggregates uses
 ** this set of interface routines to access the parameter values on
 ** the function or aggregate.
 **
 ** The xFunc (for scalar functions) or xStep (for aggregates) parameters
 ** to [sqlite3_create_function()] and [sqlite3_create_function16()]
 ** define callbacks that implement the SQL functions and aggregates.
@@ -4514,16 +4553,17 @@ SQLITE_API const unsigned char *SQLITE_S
 SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16(sqlite3_value*);
 SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16le(sqlite3_value*);
 SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16be(sqlite3_value*);
 SQLITE_API int SQLITE_STDCALL sqlite3_value_type(sqlite3_value*);
 SQLITE_API int SQLITE_STDCALL sqlite3_value_numeric_type(sqlite3_value*);
 
 /*
 ** CAPI3REF: Obtain Aggregate Function Context
+** METHOD: sqlite3_context
 **
 ** Implementations of aggregate SQL functions use this
 ** routine to allocate memory for storing their state.
 **
 ** ^The first time the sqlite3_aggregate_context(C,N) routine is called 
 ** for a particular aggregate function, SQLite
 ** allocates N of memory, zeroes out that memory, and returns a pointer
 ** to the new memory. ^On second and subsequent calls to
@@ -4558,41 +4598,44 @@ SQLITE_API int SQLITE_STDCALL sqlite3_va
 **
 ** This routine must be called from the same thread in which
 ** the aggregate SQL function is running.
 */
 SQLITE_API void *SQLITE_STDCALL sqlite3_aggregate_context(sqlite3_context*, int nBytes);
 
 /*
 ** CAPI3REF: User Data For Functions
+** METHOD: sqlite3_context
 **
 ** ^The sqlite3_user_data() interface returns a copy of
 ** the pointer that was the pUserData parameter (the 5th parameter)
 ** of the [sqlite3_create_function()]
 ** and [sqlite3_create_function16()] routines that originally
 ** registered the application defined function.
 **
 ** This routine must be called from the same thread in which
 ** the application-defined function is running.
 */
 SQLITE_API void *SQLITE_STDCALL sqlite3_user_data(sqlite3_context*);
 
 /*
 ** CAPI3REF: Database Connection For Functions
+** METHOD: sqlite3_context
 **
 ** ^The sqlite3_context_db_handle() interface returns a copy of
 ** the pointer to the [database connection] (the 1st parameter)
 ** of the [sqlite3_create_function()]
 ** and [sqlite3_create_function16()] routines that originally
 ** registered the application defined function.
 */
 SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3_context_db_handle(sqlite3_context*);
 
 /*
 ** CAPI3REF: Function Auxiliary Data
+** METHOD: sqlite3_context
 **
 ** These functions may be used by (non-aggregate) SQL functions to
 ** associate metadata with argument values. If the same value is passed to
 ** multiple invocations of the same SQL function during query execution, under
 ** some circumstances the associated metadata may be preserved.  An example
 ** of where this might be useful is in a regular-expression matching
 ** function. The compiled version of the regular expression can be stored as
 ** metadata associated with the pattern string.  
@@ -4655,16 +4698,17 @@ SQLITE_API void SQLITE_STDCALL sqlite3_s
 ** C++ compilers.
 */
 typedef void (*sqlite3_destructor_type)(void*);
 #define SQLITE_STATIC      ((sqlite3_destructor_type)0)
 #define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)
 
 /*
 ** CAPI3REF: Setting The Result Of An SQL Function
+** METHOD: sqlite3_context
 **
 ** These routines are used by the xFunc or xFinal callbacks that
 ** implement SQL functions and aggregates.  See
 ** [sqlite3_create_function()] and [sqlite3_create_function16()]
 ** for additional information.
 **
 ** These functions work very much like the [parameter binding] family of
 ** functions used to bind values to host parameters in prepared statements.
@@ -4790,16 +4834,17 @@ SQLITE_API void SQLITE_STDCALL sqlite3_r
 SQLITE_API void SQLITE_STDCALL sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*));
 SQLITE_API void SQLITE_STDCALL sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*));
 SQLITE_API void SQLITE_STDCALL sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*));
 SQLITE_API void SQLITE_STDCALL sqlite3_result_value(sqlite3_context*, sqlite3_value*);
 SQLITE_API void SQLITE_STDCALL sqlite3_result_zeroblob(sqlite3_context*, int n);
 
 /*
 ** CAPI3REF: Define New Collating Sequences
+** METHOD: sqlite3
 **
 ** ^These functions add, remove, or modify a [collation] associated
 ** with the [database connection] specified as the first argument.
 **
 ** ^The name of the collation is a UTF-8 string
 ** for sqlite3_create_collation() and sqlite3_create_collation_v2()
 ** and a UTF-16 string in native byte order for sqlite3_create_collation16().
 ** ^Collation names that compare equal according to [sqlite3_strnicmp()] are
@@ -4892,16 +4937,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_cr
   const void *zName,
   int eTextRep, 
   void *pArg,
   int(*xCompare)(void*,int,const void*,int,const void*)
 );
 
 /*
 ** CAPI3REF: Collation Needed Callbacks
+** METHOD: sqlite3
 **
 ** ^To avoid having to register all collation sequences before a database
 ** can be used, a single callback function may be registered with the
 ** [database connection] to be invoked whenever an undefined collation
 ** sequence is required.
 **
 ** ^If the function is registered using the sqlite3_collation_needed() API,
 ** then it is passed the names of undefined collation sequences as strings
@@ -5099,16 +5145,17 @@ SQLITE_API char *sqlite3_temp_directory;
 ** made NULL or made to point to memory obtained from [sqlite3_malloc]
 ** or else the use of the [data_store_directory pragma] should be avoided.
 */
 SQLITE_API char *sqlite3_data_directory;
 
 /*
 ** CAPI3REF: Test For Auto-Commit Mode
 ** KEYWORDS: {autocommit mode}
+** METHOD: sqlite3
 **
 ** ^The sqlite3_get_autocommit() interface returns non-zero or
 ** zero if the given database connection is or is not in autocommit mode,
 ** respectively.  ^Autocommit mode is on by default.
 ** ^Autocommit mode is disabled by a [BEGIN] statement.
 ** ^Autocommit mode is re-enabled by a [COMMIT] or [ROLLBACK].
 **
 ** If certain kinds of errors occur on a statement within a multi-statement
@@ -5121,68 +5168,73 @@ SQLITE_API char *sqlite3_data_directory;
 ** If another thread changes the autocommit status of the database
 ** connection while this routine is running, then the return value
 ** is undefined.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_get_autocommit(sqlite3*);
 
 /*
 ** CAPI3REF: Find The Database Handle Of A Prepared Statement
+** METHOD: sqlite3_stmt
 **
 ** ^The sqlite3_db_handle interface returns the [database connection] handle
 ** to which a [prepared statement] belongs.  ^The [database connection]
 ** returned by sqlite3_db_handle is the same [database connection]
 ** that was the first argument
 ** to the [sqlite3_prepare_v2()] call (or its variants) that was used to
 ** create the statement in the first place.
 */
 SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3_db_handle(sqlite3_stmt*);
 
 /*
 ** CAPI3REF: Return The Filename For A Database Connection
+** METHOD: sqlite3
 **
 ** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename
 ** associated with database N of connection D.  ^The main database file
 ** has the name "main".  If there is no attached database N on the database
 ** connection D, or if database N is a temporary or in-memory database, then
 ** a NULL pointer is returned.
 **
 ** ^The filename returned by this function is the output of the
 ** xFullPathname method of the [VFS].  ^In other words, the filename
 ** will be an absolute pathname, even if the filename used
 ** to open the database originally was a URI or relative pathname.
 */
 SQLITE_API const char *SQLITE_STDCALL sqlite3_db_filename(sqlite3 *db, const char *zDbName);
 
 /*
 ** CAPI3REF: Determine if a database is read-only
+** METHOD: sqlite3
 **
 ** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N
 ** of connection D is read-only, 0 if it is read/write, or -1 if N is not
 ** the name of a database on connection D.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
 
 /*
 ** CAPI3REF: Find the next prepared statement
+** METHOD: sqlite3
 **
 ** ^This interface returns a pointer to the next [prepared statement] after
 ** pStmt associated with the [database connection] pDb.  ^If pStmt is NULL
 ** then this interface returns a pointer to the first prepared statement
 ** associated with the database connection pDb.  ^If no prepared statement
 ** satisfies the conditions of this routine, it returns NULL.
 **
 ** The [database connection] pointer D in a call to
 ** [sqlite3_next_stmt(D,S)] must refer to an open database
 ** connection and in particular must not be a NULL pointer.
 */
 SQLITE_API sqlite3_stmt *SQLITE_STDCALL sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);
 
 /*
 ** CAPI3REF: Commit And Rollback Notification Callbacks
+** METHOD: sqlite3
 **
 ** ^The sqlite3_commit_hook() interface registers a callback
 ** function to be invoked whenever a transaction is [COMMIT | committed].
 ** ^Any callback set by a previous call to sqlite3_commit_hook()
 ** for the same database connection is overridden.
 ** ^The sqlite3_rollback_hook() interface registers a callback
 ** function to be invoked whenever a transaction is [ROLLBACK | rolled back].
 ** ^Any callback set by a previous call to sqlite3_rollback_hook()
@@ -5222,16 +5274,17 @@ SQLITE_API sqlite3_stmt *SQLITE_STDCALL 
 **
 ** See also the [sqlite3_update_hook()] interface.
 */
 SQLITE_API void *SQLITE_STDCALL sqlite3_commit_hook(sqlite3*, int(*)(void*), void*);
 SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
 
 /*
 ** CAPI3REF: Data Change Notification Callbacks
+** METHOD: sqlite3
 **
 ** ^The sqlite3_update_hook() interface registers a callback function
 ** with the [database connection] identified by the first argument
 ** to be invoked whenever a row is updated, inserted or deleted in
 ** a rowid table.
 ** ^Any callback set by a previous call to this function
 ** for the same database connection is overridden.
 **
@@ -5328,16 +5381,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_en
 ** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT].
 **
 ** See also: [sqlite3_db_release_memory()]
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_release_memory(int);
 
 /*
 ** CAPI3REF: Free Memory Used By A Database Connection
+** METHOD: sqlite3
 **
 ** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap
 ** memory as possible from database connection D. Unlike the
 ** [sqlite3_release_memory()] interface, this interface is in effect even
 ** when the [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is
 ** omitted.
 **
 ** See also: [sqlite3_release_memory()]
@@ -5405,16 +5459,17 @@ SQLITE_API sqlite3_int64 SQLITE_STDCALL 
 ** only.  All new applications should use the
 ** [sqlite3_soft_heap_limit64()] interface rather than this one.
 */
 SQLITE_API SQLITE_DEPRECATED void SQLITE_STDCALL sqlite3_soft_heap_limit(int N);
 
 
 /*
 ** CAPI3REF: Extract Metadata About A Column Of A Table
+** METHOD: sqlite3
 **
 ** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns
 ** information about column C of table T in database D
 ** on [database connection] X.)^  ^The sqlite3_table_column_metadata()
 ** interface returns SQLITE_OK and fills in the non-NULL pointers in
 ** the final five arguments with appropriate values if the specified
 ** column exists.  ^The sqlite3_table_column_metadata() interface returns
 ** SQLITE_ERROR and if the specified column does not exist.
@@ -5483,16 +5538,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_ta
   char const **pzCollSeq,     /* OUTPUT: Collation sequence name */
   int *pNotNull,              /* OUTPUT: True if NOT NULL constraint exists */
   int *pPrimaryKey,           /* OUTPUT: True if column part of PK */
   int *pAutoinc               /* OUTPUT: True if column is auto-increment */
 );
 
 /*
 ** CAPI3REF: Load An Extension
+** METHOD: sqlite3
 **
 ** ^This interface loads an SQLite extension library from the named file.
 **
 ** ^The sqlite3_load_extension() interface attempts to load an
 ** [SQLite extension] library contained in the file zFile.  If
 ** the file cannot be loaded directly, attempts are made to load
 ** with various operating-system specific extensions added.
 ** So for example, if "samplelib" cannot be loaded, then names like
@@ -5524,16 +5580,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_lo
   sqlite3 *db,          /* Load the extension into this database connection */
   const char *zFile,    /* Name of the shared library containing extension */
   const char *zProc,    /* Entry point.  Derived from zFile if 0 */
   char **pzErrMsg       /* Put error message here if not 0 */
 );
 
 /*
 ** CAPI3REF: Enable Or Disable Extension Loading
+** METHOD: sqlite3
 **
 ** ^So as not to open security holes in older applications that are
 ** unprepared to deal with [extension loading], and as a means of disabling
 ** [extension loading] while evaluating user-entered SQL, the following API
 ** is provided to turn the [sqlite3_load_extension()] mechanism on and off.
 **
 ** ^Extension loading is off by default.
 ** ^Call the sqlite3_enable_load_extension() routine with onoff==1
@@ -5773,16 +5830,17 @@ struct sqlite3_index_info {
 #define SQLITE_INDEX_CONSTRAINT_GT    4
 #define SQLITE_INDEX_CONSTRAINT_LE    8
 #define SQLITE_INDEX_CONSTRAINT_LT    16
 #define SQLITE_INDEX_CONSTRAINT_GE    32
 #define SQLITE_INDEX_CONSTRAINT_MATCH 64
 
 /*
 ** CAPI3REF: Register A Virtual Table Implementation
+** METHOD: sqlite3
 **
 ** ^These routines are used to register a new [virtual table module] name.
 ** ^Module names must be registered before
 ** creating a new [virtual table] using the module and before using a
 ** preexisting [virtual table] for the module.
 **
 ** ^The module name is registered on the [database connection] specified
 ** by the first parameter.  ^The name of the module is given by the 
@@ -5869,16 +5927,17 @@ struct sqlite3_vtab_cursor {
 ** [virtual table module] call this interface
 ** to declare the format (the names and datatypes of the columns) of
 ** the virtual tables they implement.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_declare_vtab(sqlite3*, const char *zSQL);
 
 /*
 ** CAPI3REF: Overload A Function For A Virtual Table
+** METHOD: sqlite3
 **
 ** ^(Virtual tables can provide alternative implementations of functions
 ** using the [xFindFunction] method of the [virtual table module].  
 ** But global versions of those functions
 ** must exist in order to be overloaded.)^
 **
 ** ^(This API makes sure a global version of a function with a particular
 ** name and number of parameters exists.  If no such function exists
@@ -5911,16 +5970,18 @@ SQLITE_API int SQLITE_STDCALL sqlite3_ov
 ** ^The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces
 ** can be used to read or write small subsections of the BLOB.
 ** ^The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes.
 */
 typedef struct sqlite3_blob sqlite3_blob;
 
 /*
 ** CAPI3REF: Open A BLOB For Incremental I/O
+** METHOD: sqlite3
+** CONSTRUCTOR: sqlite3_blob
 **
 ** ^(This interfaces opens a [BLOB handle | handle] to the BLOB located
 ** in row iRow, column zColumn, table zTable in database zDb;
 ** in other words, the same BLOB that would be selected by:
 **
 ** <pre>
 **     SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow;
 ** </pre>)^
@@ -5992,16 +6053,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bl
   const char *zColumn,
   sqlite3_int64 iRow,
   int flags,
   sqlite3_blob **ppBlob
 );
 
 /*
 ** CAPI3REF: Move a BLOB Handle to a New Row
+** METHOD: sqlite3_blob
 **
 ** ^This function is used to move an existing blob handle so that it points
 ** to a different row of the same database table. ^The new row is identified
 ** by the rowid value passed as the second argument. Only the row can be
 ** changed. ^The database, table and column on which the blob handle is open
 ** remain the same. Moving an existing blob handle to a new row can be
 ** faster than closing the existing handle and opening a new one.
 **
@@ -6016,16 +6078,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bl
 ** always returns zero.
 **
 ** ^This function sets the database handle error code and message.
 */
 SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
 
 /*
 ** CAPI3REF: Close A BLOB Handle
+** DESTRUCTOR: sqlite3_blob
 **
 ** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed
 ** unconditionally.  Even if this routine returns an error code, the 
 ** handle is still closed.)^
 **
 ** ^If the blob handle being closed was opened for read-write access, and if
 ** the database is in auto-commit mode and there are no other open read-write
 ** blob handles or active write statements, the current transaction is
@@ -6038,31 +6101,33 @@ SQLITE_API SQLITE_EXPERIMENTAL int SQLIT
 ** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
 ** is passed a valid open blob handle, the values returned by the 
 ** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_blob_close(sqlite3_blob *);
 
 /*
 ** CAPI3REF: Return The Size Of An Open BLOB
+** METHOD: sqlite3_blob
 **
 ** ^Returns the size in bytes of the BLOB accessible via the 
 ** successfully opened [BLOB handle] in its only argument.  ^The
 ** incremental blob I/O routines can only read or overwriting existing
 ** blob content; they cannot change the size of a blob.
 **
 ** This routine only works on a [BLOB handle] which has been created
 ** by a prior successful call to [sqlite3_blob_open()] and which has not
 ** been closed by [sqlite3_blob_close()].  Passing any other pointer in
 ** to this routine results in undefined and probably undesirable behavior.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_blob_bytes(sqlite3_blob *);
 
 /*
 ** CAPI3REF: Read Data From A BLOB Incrementally
+** METHOD: sqlite3_blob
 **
 ** ^(This function is used to read data from an open [BLOB handle] into a
 ** caller-supplied buffer. N bytes of data are copied into buffer Z
 ** from the open BLOB, starting at offset iOffset.)^
 **
 ** ^If offset iOffset is less than N bytes from the end of the BLOB,
 ** [SQLITE_ERROR] is returned and no data is read.  ^If N or iOffset is
 ** less than zero, [SQLITE_ERROR] is returned and no data is read.
@@ -6081,16 +6146,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_bl
 ** to this routine results in undefined and probably undesirable behavior.
 **
 ** See also: [sqlite3_blob_write()].
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
 
 /*
 ** CAPI3REF: Write Data Into A BLOB Incrementally
+** METHOD: sqlite3_blob
 **
 ** ^(This function is used to write data into an open [BLOB handle] from a
 ** caller-supplied buffer. N bytes of data are copied from the buffer Z
 ** into the open BLOB, starting at offset iOffset.)^
 **
 ** ^(On success, sqlite3_blob_write() returns SQLITE_OK.
 ** Otherwise, an  [error code] or an [extended error code] is returned.)^
 ** ^Unless SQLITE_MISUSE is returned, this function sets the 
@@ -6408,27 +6474,29 @@ SQLITE_API int SQLITE_STDCALL sqlite3_mu
 #define SQLITE_MUTEX_STATIC_LRU2      7  /* NOT USED */
 #define SQLITE_MUTEX_STATIC_PMEM      7  /* sqlite3PageMalloc() */
 #define SQLITE_MUTEX_STATIC_APP1      8  /* For use by application */
 #define SQLITE_MUTEX_STATIC_APP2      9  /* For use by application */
 #define SQLITE_MUTEX_STATIC_APP3     10  /* For use by application */
 
 /*
 ** CAPI3REF: Retrieve the mutex for a database connection
+** METHOD: sqlite3
 **
 ** ^This interface returns a pointer the [sqlite3_mutex] object that 
 ** serializes access to the [database connection] given in the argument
 ** when the [threading mode] is Serialized.
 ** ^If the [threading mode] is Single-thread or Multi-thread then this
 ** routine returns a NULL pointer.
 */
 SQLITE_API sqlite3_mutex *SQLITE_STDCALL sqlite3_db_mutex(sqlite3*);
 
 /*
 ** CAPI3REF: Low-Level Control Of Database Files
+** METHOD: sqlite3
 **
 ** ^The [sqlite3_file_control()] interface makes a direct call to the
 ** xFileControl method for the [sqlite3_io_methods] object associated
 ** with a particular database identified by the second argument. ^The
 ** name of the database is "main" for the main database or "temp" for the
 ** TEMP database, or the name that appears after the AS keyword for
 ** databases that are added using the [ATTACH] SQL command.
 ** ^A NULL pointer can be used in place of "main" to refer to the
@@ -6635,16 +6703,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_st
 #define SQLITE_STATUS_MALLOC_SIZE          5
 #define SQLITE_STATUS_PARSER_STACK         6
 #define SQLITE_STATUS_PAGECACHE_SIZE       7
 #define SQLITE_STATUS_SCRATCH_SIZE         8
 #define SQLITE_STATUS_MALLOC_COUNT         9
 
 /*
 ** CAPI3REF: Database Connection Status
+** METHOD: sqlite3
 **
 ** ^This interface is used to retrieve runtime status information 
 ** about a single [database connection].  ^The first argument is the
 ** database connection object to be interrogated.  ^The second argument
 ** is an integer constant, taken from the set of
 ** [SQLITE_DBSTATUS options], that
 ** determines the parameter to interrogate.  The set of 
 ** [SQLITE_DBSTATUS options] is likely
@@ -6763,16 +6832,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_db
 #define SQLITE_DBSTATUS_CACHE_MISS           8
 #define SQLITE_DBSTATUS_CACHE_WRITE          9
 #define SQLITE_DBSTATUS_DEFERRED_FKS        10
 #define SQLITE_DBSTATUS_MAX                 10   /* Largest defined DBSTATUS */
 
 
 /*
 ** CAPI3REF: Prepared Statement Status
+** METHOD: sqlite3_stmt
 **
 ** ^(Each prepared statement maintains various
 ** [SQLITE_STMTSTATUS counters] that measure the number
 ** of times it has performed specific operations.)^  These counters can
 ** be used to monitor the performance characteristics of the prepared
 ** statements.  For example, if the number of table steps greatly exceeds
 ** the number of table searches or result rows, that would tend to indicate
 ** that the prepared statement is using a full table scan rather than
@@ -7266,16 +7336,17 @@ SQLITE_API sqlite3_backup *SQLITE_STDCAL
 );
 SQLITE_API int SQLITE_STDCALL sqlite3_backup_step(sqlite3_backup *p, int nPage);
 SQLITE_API int SQLITE_STDCALL sqlite3_backup_finish(sqlite3_backup *p);
 SQLITE_API int SQLITE_STDCALL sqlite3_backup_remaining(sqlite3_backup *p);
 SQLITE_API int SQLITE_STDCALL sqlite3_backup_pagecount(sqlite3_backup *p);
 
 /*
 ** CAPI3REF: Unlock Notification
+** METHOD: sqlite3
 **
 ** ^When running in shared-cache mode, a database operation may fail with
 ** an [SQLITE_LOCKED] error if the required locks on the shared-cache or
 ** individual tables within the shared-cache cannot be obtained. See
 ** [SQLite Shared-Cache Mode] for a description of shared-cache locking. 
 ** ^This API may be used to register a callback that SQLite will invoke 
 ** when the connection currently holding the required lock relinquishes it.
 ** ^This API is only available if the library was compiled with the
@@ -7436,16 +7507,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_st
 ** a fixed-length buffer on the stack.  If the log message is longer than
 ** a few hundred characters, it will be truncated to the length of the
 ** buffer.
 */
 SQLITE_API void SQLITE_CDECL sqlite3_log(int iErrCode, const char *zFormat, ...);
 
 /*
 ** CAPI3REF: Write-Ahead Log Commit Hook
+** METHOD: sqlite3
 **
 ** ^The [sqlite3_wal_hook()] function is used to register a callback that
 ** is invoked each time data is committed to a database in wal mode.
 **
 ** ^(The callback is invoked by SQLite after the commit has taken place and 
 ** the associated write-lock on the database released)^, so the implementation 
 ** may read, write or [checkpoint] the database as required.
 **
@@ -7475,16 +7547,17 @@ SQLITE_API void SQLITE_CDECL sqlite3_log
 SQLITE_API void *SQLITE_STDCALL sqlite3_wal_hook(
   sqlite3*, 
   int(*)(void *,sqlite3*,const char*,int),
   void*
 );
 
 /*
 ** CAPI3REF: Configure an auto-checkpoint
+** METHOD: sqlite3
 **
 ** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around
 ** [sqlite3_wal_hook()] that causes any database on [database connection] D
 ** to automatically [checkpoint]
 ** after committing a transaction if there are N or
 ** more frames in the [write-ahead log] file.  ^Passing zero or 
 ** a negative value as the nFrame parameter disables automatic
 ** checkpoints entirely.
@@ -7505,16 +7578,17 @@ SQLITE_API void *SQLITE_STDCALL sqlite3_
 ** pages.  The use of this interface
 ** is only necessary if the default setting is found to be suboptimal
 ** for a particular application.
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
 
 /*
 ** CAPI3REF: Checkpoint a database
+** METHOD: sqlite3
 **
 ** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to
 ** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^
 **
 ** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the 
 ** [write-ahead log] for database X on [database connection] D to be
 ** transferred into the database file and for the write-ahead log to
 ** be reset.  See the [checkpointing] documentation for addition
@@ -7526,16 +7600,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_wa
 ** compatibility and as a convenience for applications that need to manually
 ** start a callback but which do not need the full power (and corresponding
 ** complication) of [sqlite3_wal_checkpoint_v2()].
 */
 SQLITE_API int SQLITE_STDCALL sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
 
 /*
 ** CAPI3REF: Checkpoint a database
+** METHOD: sqlite3
 **
 ** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint
 ** operation on database X of [database connection] D in mode M.  Status
 ** information is written back into integers pointed to by L and C.)^
 ** ^(The M parameter must be a valid [checkpoint mode]:)^
 **
 ** <dl>
 ** <dt>SQLITE_CHECKPOINT_PASSIVE<dd>
@@ -7780,16 +7855,17 @@ SQLITE_API int SQLITE_STDCALL sqlite3_vt
 #define SQLITE_SCANSTAT_NVISIT   1
 #define SQLITE_SCANSTAT_EST      2
 #define SQLITE_SCANSTAT_NAME     3
 #define SQLITE_SCANSTAT_EXPLAIN  4
 #define SQLITE_SCANSTAT_SELECTID 5
 
 /*
 ** CAPI3REF: Prepared Statement Scan Status
+** METHOD: sqlite3_stmt
 **
 ** This interface returns information about the predicted and measured
 ** performance for pStmt.  Advanced applications can use this
 ** interface to compare the predicted and the measured performance and
 ** issue warnings and/or rerun [ANALYZE] if discrepancies are found.
 **
 ** Since this interface is expected to be rarely used, it is only
 ** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS]
@@ -7817,16 +7893,17 @@ SQLITE_API SQLITE_EXPERIMENTAL int SQLIT
   sqlite3_stmt *pStmt,      /* Prepared statement for which info desired */
   int idx,                  /* Index of loop to report on */
   int iScanStatusOp,        /* Information desired.  SQLITE_SCANSTAT_* */
   void *pOut                /* Result written here */
 );     
 
 /*
 ** CAPI3REF: Zero Scan-Status Counters
+** METHOD: sqlite3_stmt
 **
 ** ^Zero all [sqlite3_stmt_scanstatus()] related event counters.
 **
 ** This API is only available if the library is built with pre-processor
 ** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined.
 */
 SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
 
@@ -8428,16 +8505,42 @@ SQLITE_PRIVATE   void sqlite3Coverage(in
 # define ALWAYS(X)      ((X)?1:(assert(0),0))
 # define NEVER(X)       ((X)?(assert(0),1):0)
 #else
 # define ALWAYS(X)      (X)
 # define NEVER(X)       (X)
 #endif
 
 /*
+** Declarations used for tracing the operating system interfaces.
+*/
+#if defined(SQLITE_FORCE_OS_TRACE) || defined(SQLITE_TEST) || \
+    (defined(SQLITE_DEBUG) && SQLITE_OS_WIN)
+  extern int sqlite3OSTrace;
+# define OSTRACE(X)          if( sqlite3OSTrace ) sqlite3DebugPrintf X
+# define SQLITE_HAVE_OS_TRACE
+#else
+# define OSTRACE(X)
+# undef  SQLITE_HAVE_OS_TRACE
+#endif
+
+/*
+** Is the sqlite3ErrName() function needed in the build?  Currently,
+** it is needed by "mutex_w32.c" (when debugging), "os_win.c" (when
+** OSTRACE is enabled), and by several "test*.c" files (which are
+** compiled using SQLITE_TEST).
+*/
+#if defined(SQLITE_HAVE_OS_TRACE) || defined(SQLITE_TEST) || \
+    (defined(SQLITE_DEBUG) && SQLITE_OS_WIN)
+# define SQLITE_NEED_ERR_NAME
+#else
+# undef  SQLITE_NEED_ERR_NAME
+#endif
+
+/*
 ** Return true (non-zero) if the input is an integer that is too large
 ** to fit in 32-bits.  This macro is used inside of various testcase()
 ** macros to verify that we have tested SQLite for large-file support.
 */
 #define IS_BIG_INT(X)  (((X)&~(i64)0xffffffff)!=0)
 
 /*
 ** The macro unlikely() is a hint that surrounds a boolean
@@ -9838,43 +9941,42 @@ typedef struct VdbeOpList VdbeOpList;
 #define OP_Explain       157
 
 
 /* Properties such as "out2" or "jump" that are specified in
 ** comments following the "case" for each opcode in the vdbe.c
 ** are encoded into bitvectors as follows:
 */
 #define OPFLG_JUMP            0x0001  /* jump:  P2 holds jmp target */
-#define OPFLG_OUT2_PRERELEASE 0x0002  /* out2-prerelease: */
-#define OPFLG_IN1             0x0004  /* in1:   P1 is an input */
-#define OPFLG_IN2             0x0008  /* in2:   P2 is an input */
-#define OPFLG_IN3             0x0010  /* in3:   P3 is an input */
-#define OPFLG_OUT2            0x0020  /* out2:  P2 is an output */
-#define OPFLG_OUT3            0x0040  /* out3:  P3 is an output */
+#define OPFLG_IN1             0x0002  /* in1:   P1 is an input */
+#define OPFLG_IN2             0x0004  /* in2:   P2 is an input */
+#define OPFLG_IN3             0x0008  /* in3:   P3 is an input */
+#define OPFLG_OUT2            0x0010  /* out2:  P2 is an output */
+#define OPFLG_OUT3            0x0020  /* out3:  P3 is an output */
 #define OPFLG_INITIALIZER {\
 /*   0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,\
-/*   8 */ 0x01, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00,\
-/*  16 */ 0x01, 0x01, 0x04, 0x24, 0x01, 0x04, 0x05, 0x10,\
-/*  24 */ 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,\
-/*  32 */ 0x00, 0x00, 0x20, 0x00, 0x00, 0x04, 0x05, 0x04,\
-/*  40 */ 0x04, 0x00, 0x00, 0x01, 0x01, 0x05, 0x05, 0x00,\
-/*  48 */ 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x00, 0x00,\
-/*  56 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11,\
-/*  64 */ 0x11, 0x11, 0x08, 0x11, 0x11, 0x11, 0x11, 0x4c,\
-/*  72 */ 0x4c, 0x02, 0x02, 0x00, 0x05, 0x05, 0x15, 0x15,\
-/*  80 */ 0x15, 0x15, 0x15, 0x15, 0x00, 0x4c, 0x4c, 0x4c,\
-/*  88 */ 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x00,\
-/*  96 */ 0x24, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,\
-/* 104 */ 0x00, 0x01, 0x01, 0x01, 0x01, 0x08, 0x08, 0x00,\
-/* 112 */ 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x00, 0x00,\
-/* 120 */ 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 128 */ 0x0c, 0x45, 0x15, 0x01, 0x02, 0x02, 0x00, 0x01,\
-/* 136 */ 0x08, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x01,\
+/*   8 */ 0x01, 0x01, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,\
+/*  16 */ 0x01, 0x01, 0x02, 0x12, 0x01, 0x02, 0x03, 0x08,\
+/*  24 */ 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10,\
+/*  32 */ 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, 0x03, 0x02,\
+/*  40 */ 0x02, 0x00, 0x00, 0x01, 0x01, 0x03, 0x03, 0x00,\
+/*  48 */ 0x00, 0x00, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00,\
+/*  56 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x09,\
+/*  64 */ 0x09, 0x09, 0x04, 0x09, 0x09, 0x09, 0x09, 0x26,\
+/*  72 */ 0x26, 0x10, 0x10, 0x00, 0x03, 0x03, 0x0b, 0x0b,\
+/*  80 */ 0x0b, 0x0b, 0x0b, 0x0b, 0x00, 0x26, 0x26, 0x26,\
+/*  88 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x00,\
+/*  96 */ 0x12, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\
+/* 104 */ 0x00, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x00,\
+/* 112 */ 0x10, 0x01, 0x01, 0x01, 0x01, 0x10, 0x00, 0x00,\
+/* 120 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 128 */ 0x06, 0x23, 0x0b, 0x01, 0x10, 0x10, 0x00, 0x01,\
+/* 136 */ 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x01,\
 /* 144 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\
-/* 152 */ 0x00, 0x02, 0x02, 0x01, 0x00, 0x00,}
+/* 152 */ 0x00, 0x10, 0x10, 0x01, 0x00, 0x00,}
 
 /************** End of opcodes.h *********************************************/
 /************** Continuing where we left off in vdbe.h ***********************/
 
 /*
 ** Prototypes for the VDBE interface.  See comments on the implementation
 ** for a description of what each of these routines does.
 */
@@ -9923,16 +10025,17 @@ SQLITE_PRIVATE sqlite3_value *sqlite3Vdb
 SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe*, int);
 #ifndef SQLITE_OMIT_TRACE
 SQLITE_PRIVATE   char *sqlite3VdbeExpandSql(Vdbe*, const char*);
 #endif
 SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
 
 SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*);
 SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*);
+SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
 SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *, char *, int, char **);
 
 typedef int (*RecordCompare)(int,const void*,UnpackedRecord*);
 SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*);
 
 #ifndef SQLITE_OMIT_TRIGGER
 SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *);
 #endif
@@ -11060,16 +11163,17 @@ struct sqlite3 {
 #define SQLITE_ForeignKeys    0x00080000  /* Enforce foreign key constraints  */
 #define SQLITE_AutoIndex      0x00100000  /* Enable automatic indexes */
 #define SQLITE_PreferBuiltin  0x00200000  /* Preference to built-in funcs */
 #define SQLITE_LoadExtension  0x00400000  /* Enable load_extension */
 #define SQLITE_EnableTrigger  0x00800000  /* True to enable triggers */
 #define SQLITE_DeferFKs       0x01000000  /* Defer all FK constraints */
 #define SQLITE_QueryOnly      0x02000000  /* Disable database changes */
 #define SQLITE_VdbeEQP        0x04000000  /* Debug EXPLAIN QUERY PLAN */
+#define SQLITE_Vacuum         0x08000000  /* Currently in a VACUUM */
 
 
 /*
 ** Bits of the sqlite3.dbOptFlags field that are used by the
 ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to
 ** selectively disable various optimizations.
 */
 #define SQLITE_QueryFlattener 0x0001   /* Query flattening */
@@ -11390,60 +11494,34 @@ struct VTable {
   sqlite3_vtab *pVtab;      /* Pointer to vtab instance */
   int nRef;                 /* Number of pointers to this structure */
   u8 bConstraint;           /* True if constraints are supported */
   int iSavepoint;           /* Depth of the SAVEPOINT stack */
   VTable *pNext;            /* Next in linked list (see above) */
 };
 
 /*
-** Each SQL table is represented in memory by an instance of the
-** following structure.
-**
-** Table.zName is the name of the table.  The case of the original
-** CREATE TABLE statement is stored, but case is not significant for
-** comparisons.
-**
-** Table.nCol is the number of columns in this table.  Table.aCol is a
-** pointer to an array of Column structures, one for each column.
-**
-** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of
-** the column that is that key.   Otherwise Table.iPKey is negative.  Note
-** that the datatype of the PRIMARY KEY must be INTEGER for this field to
-** be set.  An INTEGER PRIMARY KEY is used as the rowid for each row of
-** the table.  If a table has no INTEGER PRIMARY KEY, then a random rowid
-** is generated for each row of the table.  TF_HasPrimaryKey is set if
-** the table has any PRIMARY KEY, INTEGER or otherwise.
-**
-** Table.tnum is the page number for the root BTree page of the table in the
-** database file.  If Table.iDb is the index of the database table backend
-** in sqlite.aDb[].  0 is for the main database and 1 is for the file that
-** holds temporary tables and indices.  If TF_Ephemeral is set
-** then the table is stored in a file that is automatically deleted
-** when the VDBE cursor to the table is closed.  In this case Table.tnum 
-** refers VDBE cursor number that holds the table open, not to the root
-** page number.  Transient tables are used to hold the results of a
-** sub-query that appears instead of a real table name in the FROM clause 
-** of a SELECT statement.
+** The schema for each SQL table and view is represented in memory
+** by an instance of the following structure.
 */
 struct Table {
   char *zName;         /* Name of the table or view */
   Column *aCol;        /* Information about each column */
   Index *pIndex;       /* List of SQL indexes on this table. */
   Select *pSelect;     /* NULL for tables.  Points to definition if a view. */
   FKey *pFKey;         /* Linked list of all foreign keys in this table */
   char *zColAff;       /* String defining the affinity of each column */
 #ifndef SQLITE_OMIT_CHECK
   ExprList *pCheck;    /* All CHECK constraints */
 #endif
-  LogEst nRowLogEst;   /* Estimated rows in table - from sqlite_stat1 table */
-  int tnum;            /* Root BTree node for this table (see note above) */
-  i16 iPKey;           /* If not negative, use aCol[iPKey] as the primary key */
+  int tnum;            /* Root BTree page for this table */
+  i16 iPKey;           /* If not negative, use aCol[iPKey] as the rowid */
   i16 nCol;            /* Number of columns in this table */
   u16 nRef;            /* Number of pointers to this Table */
+  LogEst nRowLogEst;   /* Estimated rows in table - from sqlite_stat1 table */
   LogEst szTabRow;     /* Estimated size of each table row in bytes */
 #ifdef SQLITE_ENABLE_COSTMULT
   LogEst costMult;     /* Cost multiplier for using this table */
 #endif
   u8 tabFlags;         /* Mask of TF_* values */
   u8 keyConf;          /* What to do in case of uniqueness conflict on iPKey */
 #ifndef SQLITE_OMIT_ALTERTABLE
   int addColOffset;    /* Offset in CREATE TABLE stmt to add a new column */
@@ -11455,23 +11533,30 @@ struct Table {
 #endif
   Trigger *pTrigger;   /* List of triggers stored in pSchema */
   Schema *pSchema;     /* Schema that contains this table */
   Table *pNextZombie;  /* Next on the Parse.pZombieTab list */
 };
 
 /*
 ** Allowed values for Table.tabFlags.
+**
+** TF_OOOHidden applies to virtual tables that have hidden columns that are
+** followed by non-hidden columns.  Example:  "CREATE VIRTUAL TABLE x USING
+** vtab1(a HIDDEN, b);".  Since "b" is a non-hidden column but "a" is hidden,
+** the TF_OOOHidden attribute would apply in this case.  Such tables require
+** special handling during INSERT processing.
 */
 #define TF_Readonly        0x01    /* Read-only system table */
 #define TF_Ephemeral       0x02    /* An ephemeral table */
 #define TF_HasPrimaryKey   0x04    /* Table has a primary key */
 #define TF_Autoincrement   0x08    /* Integer primary key is autoincrement */
 #define TF_Virtual         0x10    /* Is a virtual table */
 #define TF_WithoutRowid    0x20    /* No rowid used. PRIMARY KEY is the key */
+#define TF_OOOHidden       0x40    /* Out-of-Order hidden columns */
 
 
 /*
 ** Test to see whether or not a table is a virtual table.  This is
 ** done as a macro so that it will be optimized out when virtual
 ** table support is omitted from the build.
 */
 #ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -12218,17 +12303,17 @@ struct Select {
 #define SF_Distinct        0x0001  /* Output should be DISTINCT */
 #define SF_Resolved        0x0002  /* Identifiers have been resolved */
 #define SF_Aggregate       0x0004  /* Contains aggregate functions */
 #define SF_UsesEphemeral   0x0008  /* Uses the OpenEphemeral opcode */
 #define SF_Expanded        0x0010  /* sqlite3SelectExpand() called on this */
 #define SF_HasTypeInfo     0x0020  /* FROM subqueries have Table metadata */
 #define SF_Compound        0x0040  /* Part of a compound query */
 #define SF_Values          0x0080  /* Synthesized from VALUES clause */
-#define SF_AllValues       0x0100  /* All terms of compound are VALUES */
+#define SF_MultiValue      0x0100  /* Single VALUES term with multiple rows */
 #define SF_NestedFrom      0x0200  /* Part of a parenthesized FROM clause */
 #define SF_MaybeConvert    0x0400  /* Need convertCompoundSelectToSubquery() */
 #define SF_Recursive       0x0800  /* The recursive part of a recursive CTE */
 #define SF_MinMaxAgg       0x1000  /* Aggregate containing min() or max() */
 #define SF_Converted       0x2000  /* By convertCompoundSelectToSubquery() */
 
 
 /*
@@ -12602,43 +12687,43 @@ struct Trigger {
  * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or
  * "SELECT" statement. The meanings of the other members is determined by the 
  * value of "op" as follows:
  *
  * (op == TK_INSERT)
  * orconf    -> stores the ON CONFLICT algorithm
  * pSelect   -> If this is an INSERT INTO ... SELECT ... statement, then
  *              this stores a pointer to the SELECT statement. Otherwise NULL.
- * target    -> A token holding the quoted name of the table to insert into.
+ * zTarget   -> Dequoted name of the table to insert into.
  * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then
  *              this stores values to be inserted. Otherwise NULL.
  * pIdList   -> If this is an INSERT INTO ... (<column-names>) VALUES ... 
  *              statement, then this stores the column-names to be
  *              inserted into.
  *
  * (op == TK_DELETE)
- * target    -> A token holding the quoted name of the table to delete from.
+ * zTarget   -> Dequoted name of the table to delete from.
  * pWhere    -> The WHERE clause of the DELETE statement if one is specified.
  *              Otherwise NULL.
  * 
  * (op == TK_UPDATE)
- * target    -> A token holding the quoted name of the table to update rows of.
+ * zTarget   -> Dequoted name of the table to update.
  * pWhere    -> The WHERE clause of the UPDATE statement if one is specified.
  *              Otherwise NULL.
  * pExprList -> A list of the columns to update and the expressions to update
  *              them to. See sqlite3Update() documentation of "pChanges"
  *              argument.
  * 
  */
 struct TriggerStep {
   u8 op;               /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
   u8 orconf;           /* OE_Rollback etc. */
   Trigger *pTrig;      /* The trigger that this step is a part of */
-  Select *pSelect;     /* SELECT statment or RHS of INSERT INTO .. SELECT ... */
-  Token target;        /* Target table for DELETE, UPDATE, INSERT */
+  Select *pSelect;     /* SELECT statement or RHS of INSERT INTO SELECT ... */
+  char *zTarget;       /* Target table for DELETE, UPDATE, INSERT */
   Expr *pWhere;        /* The WHERE clause for DELETE or UPDATE steps */
   ExprList *pExprList; /* SET clause for UPDATE. */
   IdList *pIdList;     /* Column names for INSERT */
   TriggerStep *pNext;  /* Next in the link-list */
   TriggerStep *pLast;  /* Last element in link-list. Valid for 1st elem only */
 };
 
 /*
@@ -12661,18 +12746,17 @@ struct DbFixer {
 ** do not necessarily know how big the string will be in the end.
 */
 struct StrAccum {
   sqlite3 *db;         /* Optional database for lookaside.  Can be NULL */
   char *zBase;         /* A base allocation.  Not from malloc. */
   char *zText;         /* The string collected so far */
   int  nChar;          /* Length of the string so far */
   int  nAlloc;         /* Amount of space allocated in zText */
-  int  mxAlloc;        /* Maximum allowed string length */
-  u8   useMalloc;      /* 0: none,  1: sqlite3DbMalloc,  2: sqlite3_malloc */
+  int  mxAlloc;        /* Maximum allowed allocation.  0 for no malloc usage */
   u8   accError;       /* STRACCUM_NOMEM or STRACCUM_TOOBIG */
 };
 #define STRACCUM_NOMEM   1
 #define STRACCUM_TOOBIG  2
 
 /*
 ** A pointer to this structure is used to communicate information
 ** from sqlite3Init and OP_ParseSchema into the sqlite3InitCallback.
@@ -12979,17 +13063,17 @@ struct PrintfArguments {
 
 #define SQLITE_PRINTF_INTERNAL 0x01
 #define SQLITE_PRINTF_SQLFUNC  0x02
 SQLITE_PRIVATE void sqlite3VXPrintf(StrAccum*, u32, const char*, va_list);
 SQLITE_PRIVATE void sqlite3XPrintf(StrAccum*, u32, const char*, ...);
 SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3*,const char*, ...);
 SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3*,const char*, va_list);
 SQLITE_PRIVATE char *sqlite3MAppendf(sqlite3*,char*,const char*,...);
-#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
+#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
 SQLITE_PRIVATE   void sqlite3DebugPrintf(const char*, ...);
 #endif
 #if defined(SQLITE_TEST)
 SQLITE_PRIVATE   void *sqlite3TestTextToPtr(const char*);
 #endif
 
 #if defined(SQLITE_DEBUG)
 SQLITE_PRIVATE   TreeView *sqlite3TreeViewPush(TreeView*,u8);
@@ -13326,17 +13410,17 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(
 SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
 SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
 SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
 SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
 SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3*, const char *z, int n);
 SQLITE_PRIVATE u8 sqlite3HexToInt(int h);
 SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **);
 
-#if defined(SQLITE_TEST) 
+#if defined(SQLITE_NEED_ERR_NAME)
 SQLITE_PRIVATE const char *sqlite3ErrName(int);
 #endif
 
 SQLITE_PRIVATE const char *sqlite3ErrStr(int);
 SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse);
 SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int);
 SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
 SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
@@ -13420,17 +13504,17 @@ SQLITE_PRIVATE int sqlite3KeyInfoIsWrite
 SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, 
   void (*)(sqlite3_context*,int,sqlite3_value **),
   void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*),
   FuncDestructor *pDestructor
 );
 SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int);
 SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *);
 
-SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, char*, int, int);
+SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int);
 SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum*,const char*,int);
 SQLITE_PRIVATE void sqlite3StrAccumAppendAll(StrAccum*,const char*);
 SQLITE_PRIVATE void sqlite3AppendChar(StrAccum*,int,char);
 SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*);
 SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum*);
 SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int);
 SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
 
@@ -14037,16 +14121,19 @@ static const char * const azCompileOpt[]
   "ENABLE_ATOMIC_WRITE",
 #endif
 #if SQLITE_ENABLE_CEROD
   "ENABLE_CEROD",
 #endif
 #if SQLITE_ENABLE_COLUMN_METADATA
   "ENABLE_COLUMN_METADATA",
 #endif
+#if SQLITE_ENABLE_DBSTAT_VTAB
+  "ENABLE_DBSTAT_VTAB",
+#endif
 #if SQLITE_ENABLE_EXPENSIVE_ASSERT
   "ENABLE_EXPENSIVE_ASSERT",
 #endif
 #if SQLITE_ENABLE_FTS1
   "ENABLE_FTS1",
 #endif
 #if SQLITE_ENABLE_FTS2
   "ENABLE_FTS2",
@@ -19804,26 +19891,16 @@ SQLITE_PRIVATE sqlite3_mutex_methods con
 ** At least two bugs have slipped in because we changed the MEMORY_DEBUG
 ** macro to SQLITE_DEBUG and some older makefiles have not yet made the
 ** switch.  The following code should catch this problem at compile-time.
 */
 #ifdef MEMORY_DEBUG
 # error "The MEMORY_DEBUG macro is obsolete.  Use SQLITE_DEBUG instead."
 #endif
 
-#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG)
-# ifndef SQLITE_DEBUG_OS_TRACE
-#   define SQLITE_DEBUG_OS_TRACE 0
-# endif
-  int sqlite3OSTrace = SQLITE_DEBUG_OS_TRACE;
-# define OSTRACE(X)          if( sqlite3OSTrace ) sqlite3DebugPrintf X
-#else
-# define OSTRACE(X)
-#endif
-
 /*
 ** Macros for performance tracing.  Normally turned off.  Only works
 ** on i486 hardware.
 */
 #ifdef SQLITE_PERFORMANCE_TRACE
 
 /* 
 ** hwtime.h contains inline assembler code for implementing 
@@ -21398,16 +21475,17 @@ static char et_getdigit(LONGDOUBLE_TYPE 
   return (char)digit;
 }
 #endif /* SQLITE_OMIT_FLOATING_POINT */
 
 /*
 ** Set the StrAccum object to an error mode.
 */
 static void setStrAccumError(StrAccum *p, u8 eError){
+  assert( eError==STRACCUM_NOMEM || eError==STRACCUM_TOOBIG );
   p->accError = eError;
   p->nAlloc = 0;
 }
 
 /*
 ** Extra argument values from a PrintfArguments object
 */
 static sqlite3_int64 getIntArg(PrintfArguments *p){
@@ -21512,17 +21590,16 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
         case ' ':   flag_blanksign = 1;       break;
         case '#':   flag_alternateform = 1;   break;
         case '!':   flag_altform2 = 1;        break;
         case '0':   flag_zeropad = 1;         break;
         default:    done = 1;                 break;
       }
     }while( !done && (c=(*++fmt))!=0 );
     /* Get the field width */
-    width = 0;
     if( c=='*' ){
       if( bArgList ){
         width = (int)getIntArg(pArgList);
       }else{
         width = va_arg(ap,int);
       }
       if( width<0 ){
         flag_leftjustify = 1;
@@ -21536,17 +21613,16 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
         c = *++fmt;
       }
       testcase( wx>0x7fffffff );
       width = wx & 0x7fffffff;
     }
 
     /* Get the precision */
     if( c=='.' ){
-      precision = 0;
       c = *++fmt;
       if( c=='*' ){
         if( bArgList ){
           precision = (int)getIntArg(pArgList);
         }else{
           precision = va_arg(ap,int);
         }
         c = *++fmt;
@@ -22015,17 +22091,17 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
 static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
   char *zNew;
   assert( p->nChar+(i64)N >= p->nAlloc ); /* Only called if really needed */
   if( p->accError ){
     testcase(p->accError==STRACCUM_TOOBIG);
     testcase(p->accError==STRACCUM_NOMEM);
     return 0;
   }
-  if( !p->useMalloc ){
+  if( p->mxAlloc==0 ){
     N = p->nAlloc - p->nChar - 1;
     setStrAccumError(p, STRACCUM_TOOBIG);
     return N;
   }else{
     char *zOld = (p->zText==p->zBase ? 0 : p->zText);
     i64 szNew = p->nChar;
     szNew += N + 1;
     if( szNew+p->nChar<=p->mxAlloc ){
@@ -22035,20 +22111,20 @@ static int sqlite3StrAccumEnlarge(StrAcc
     }
     if( szNew > p->mxAlloc ){
       sqlite3StrAccumReset(p);
       setStrAccumError(p, STRACCUM_TOOBIG);
       return 0;
     }else{
       p->nAlloc = (int)szNew;
     }
-    if( p->useMalloc==1 ){
+    if( p->db ){
       zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc);
     }else{
-      zNew = sqlite3_realloc(zOld, p->nAlloc);
+      zNew = sqlite3_realloc64(zOld, p->nAlloc);
     }
     if( zNew ){
       assert( p->zText!=0 || p->nChar==0 );
       if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar);
       p->zText = zNew;
       p->nAlloc = sqlite3DbMallocSize(p->db, zNew);
     }else{
       sqlite3StrAccumReset(p);
@@ -22086,17 +22162,17 @@ static void SQLITE_NOINLINE enlargeAndAp
   }
 }
 
 /*
 ** Append N bytes of text from z to the StrAccum object.  Increase the
 ** size of the memory allocation for StrAccum if necessary.
 */
 SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){
-  assert( z!=0 );
+  assert( z!=0 || N==0 );
   assert( p->zText!=0 || p->nChar==0 || p->accError );
   assert( N>=0 );
   assert( p->accError==0 || p->nAlloc==0 );
   if( p->nChar+N >= p->nAlloc ){
     enlargeAndAppend(p,z,N);
   }else{
     assert( p->zText );
     p->nChar += N;
@@ -22115,71 +22191,72 @@ SQLITE_PRIVATE void sqlite3StrAccumAppen
 /*
 ** Finish off a string by making sure it is zero-terminated.
 ** Return a pointer to the resulting string.  Return a NULL
 ** pointer if any kind of error was encountered.
 */
 SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){
   if( p->zText ){
     p->zText[p->nChar] = 0;
-    if( p->useMalloc && p->zText==p->zBase ){
-      if( p->useMalloc==1 ){
-        p->zText = sqlite3DbMallocRaw(p->db, p->nChar+1 );
-      }else{
-        p->zText = sqlite3_malloc(p->nChar+1);
-      }
+    if( p->mxAlloc>0 && p->zText==p->zBase ){
+      p->zText = sqlite3DbMallocRaw(p->db, p->nChar+1 );
       if( p->zText ){
         memcpy(p->zText, p->zBase, p->nChar+1);
       }else{
         setStrAccumError(p, STRACCUM_NOMEM);
       }
     }
   }
   return p->zText;
 }
 
 /*
 ** Reset an StrAccum string.  Reclaim all malloced memory.
 */
 SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum *p){
   if( p->zText!=p->zBase ){
-    if( p->useMalloc==1 ){
-      sqlite3DbFree(p->db, p->zText);
-    }else{
-      sqlite3_free(p->zText);
-    }
+    sqlite3DbFree(p->db, p->zText);
   }
   p->zText = 0;
 }
 
 /*
-** Initialize a string accumulator
-*/
-SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum *p, char *zBase, int n, int mx){
+** Initialize a string accumulator.
+**
+** p:     The accumulator to be initialized.
+** db:    Pointer to a database connection.  May be NULL.  Lookaside
+**        memory is used if not NULL. db->mallocFailed is set appropriately
+**        when not NULL.
+** zBase: An initial buffer.  May be NULL in which case the initial buffer
+**        is malloced.
+** n:     Size of zBase in bytes.  If total space requirements never exceed
+**        n then no memory allocations ever occur.
+** mx:    Maximum number of bytes to accumulate.  If mx==0 then no memory
+**        allocations will ever occur.
+*/
+SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum *p, sqlite3 *db, char *zBase, int n, int mx){
   p->zText = p->zBase = zBase;
-  p->db = 0;
+  p->db = db;
   p->nChar = 0;
   p->nAlloc = n;
   p->mxAlloc = mx;
-  p->useMalloc = 1;
   p->accError = 0;
 }
 
 /*
 ** Print into memory obtained from sqliteMalloc().  Use the internal
 ** %-conversion extensions.
 */
 SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3 *db, const char *zFormat, va_list ap){
   char *z;
   char zBase[SQLITE_PRINT_BUF_SIZE];
   StrAccum acc;
   assert( db!=0 );
-  sqlite3StrAccumInit(&acc, zBase, sizeof(zBase),
+  sqlite3StrAccumInit(&acc, db, zBase, sizeof(zBase),
                       db->aLimit[SQLITE_LIMIT_LENGTH]);
-  acc.db = db;
   sqlite3VXPrintf(&acc, SQLITE_PRINTF_INTERNAL, zFormat, ap);
   z = sqlite3StrAccumFinish(&acc);
   if( acc.accError==STRACCUM_NOMEM ){
     db->mallocFailed = 1;
   }
   return z;
 }
 
@@ -22227,18 +22304,17 @@ SQLITE_API char *SQLITE_STDCALL sqlite3_
   if( zFormat==0 ){
     (void)SQLITE_MISUSE_BKPT;
     return 0;
   }
 #endif
 #ifndef SQLITE_OMIT_AUTOINIT
   if( sqlite3_initialize() ) return 0;
 #endif
-  sqlite3StrAccumInit(&acc, zBase, sizeof(zBase), SQLITE_MAX_LENGTH);
-  acc.useMalloc = 2;
+  sqlite3StrAccumInit(&acc, 0, zBase, sizeof(zBase), SQLITE_MAX_LENGTH);
   sqlite3VXPrintf(&acc, 0, zFormat, ap);
   z = sqlite3StrAccumFinish(&acc);
   return z;
 }
 
 /*
 ** Print into memory obtained from sqlite3_malloc()().  Omit the internal
 ** %-conversion extensions.
@@ -22273,18 +22349,17 @@ SQLITE_API char *SQLITE_STDCALL sqlite3_
   if( n<=0 ) return zBuf;
 #ifdef SQLITE_ENABLE_API_ARMOR
   if( zBuf==0 || zFormat==0 ) {
     (void)SQLITE_MISUSE_BKPT;
     if( zBuf ) zBuf[0] = 0;
     return zBuf;
   }
 #endif
-  sqlite3StrAccumInit(&acc, zBuf, n, 0);
-  acc.useMalloc = 0;
+  sqlite3StrAccumInit(&acc, 0, zBuf, n, 0);
   sqlite3VXPrintf(&acc, 0, zFormat, ap);
   return sqlite3StrAccumFinish(&acc);
 }
 SQLITE_API char *SQLITE_CDECL sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){
   char *z;
   va_list ap;
   va_start(ap,zFormat);
   z = sqlite3_vsnprintf(n, zBuf, zFormat, ap);
@@ -22300,18 +22375,17 @@ SQLITE_API char *SQLITE_CDECL sqlite3_sn
 ** sqlite3_log() must render into a static buffer.  It cannot dynamically
 ** allocate memory because it might be called while the memory allocator
 ** mutex is held.
 */
 static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){
   StrAccum acc;                          /* String accumulator */
   char zMsg[SQLITE_PRINT_BUF_SIZE*3];    /* Complete log message */
 
-  sqlite3StrAccumInit(&acc, zMsg, sizeof(zMsg), 0);
-  acc.useMalloc = 0;
+  sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0);
   sqlite3VXPrintf(&acc, 0, zFormat, ap);
   sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode,
                            sqlite3StrAccumFinish(&acc));
 }
 
 /*
 ** Format and write a message to the log if logging is enabled.
 */
@@ -22319,28 +22393,27 @@ SQLITE_API void SQLITE_CDECL sqlite3_log
   va_list ap;                             /* Vararg list */
   if( sqlite3GlobalConfig.xLog ){
     va_start(ap, zFormat);
     renderLogMsg(iErrCode, zFormat, ap);
     va_end(ap);
   }
 }
 
-#if defined(SQLITE_DEBUG)
+#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
 /*
 ** A version of printf() that understands %lld.  Used for debugging.
 ** The printf() built into some versions of windows does not understand %lld
 ** and segfaults if you give it a long long int.
 */
 SQLITE_PRIVATE void sqlite3DebugPrintf(const char *zFormat, ...){
   va_list ap;
   StrAccum acc;
   char zBuf[500];
-  sqlite3StrAccumInit(&acc, zBuf, sizeof(zBuf), 0);
-  acc.useMalloc = 0;
+  sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
   va_start(ap,zFormat);
   sqlite3VXPrintf(&acc, 0, zFormat, ap);
   va_end(ap);
   sqlite3StrAccumFinish(&acc);
   fprintf(stdout,"%s", zBuf);
   fflush(stdout);
 }
 #endif
@@ -22357,17 +22430,17 @@ SQLITE_PRIVATE void sqlite3DebugPrintf(c
 ** Insert calls to those routines while debugging in order to display
 ** a diagram of Expr, ExprList, and Select objects.
 **
 */
 /* Add a new subitem to the tree.  The moreToFollow flag indicates that this
 ** is not the last item in the tree. */
 SQLITE_PRIVATE TreeView *sqlite3TreeViewPush(TreeView *p, u8 moreToFollow){
   if( p==0 ){
-    p = sqlite3_malloc( sizeof(*p) );
+    p = sqlite3_malloc64( sizeof(*p) );
     if( p==0 ) return 0;
     memset(p, 0, sizeof(*p));
   }else{
     p->iLevel++;
   }
   assert( moreToFollow==0 || moreToFollow==1 );
   if( p->iLevel<sizeof(p->bLine) ) p->bLine[p->iLevel] = moreToFollow;
   return p;
@@ -22380,18 +22453,17 @@ SQLITE_PRIVATE void sqlite3TreeViewPop(T
 }
 /* Generate a single line of output for the tree, with a prefix that contains
 ** all the appropriate tree lines */
 SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){
   va_list ap;
   int i;
   StrAccum acc;
   char zBuf[500];
-  sqlite3StrAccumInit(&acc, zBuf, sizeof(zBuf), 0);
-  acc.useMalloc = 0;
+  sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
   if( p ){
     for(i=0; i<p->iLevel && i<sizeof(p->bLine)-1; i++){
       sqlite3StrAccumAppend(&acc, p->bLine[i] ? "|   " : "    ", 4);
     }
     sqlite3StrAccumAppend(&acc, p->bLine[i] ? "|-- " : "'-- ", 4);
   }
   va_start(ap, zFormat);
   sqlite3VXPrintf(&acc, 0, zFormat, ap);
@@ -24004,16 +24076,17 @@ SQLITE_PRIVATE int sqlite3GetInt32(const
     if( (u&0x80000000)==0 && sqlite3Isxdigit(zNum[i])==0 ){
       memcpy(pValue, &u, 4);
       return 1;
     }else{
       return 0;
     }
   }
 #endif
+  while( zNum[0]=='0' ) zNum++;
   for(i=0; i<11 && (c = zNum[i] - '0')>=0 && c<=9; i++){
     v = v*10 + c;
   }
 
   /* The longest decimal representation of a 32 bit integer is 10 digits:
   **
   **             1234567890
   **     2^31 -> 2147483648
@@ -25259,16 +25332,27 @@ SQLITE_PRIVATE const char *sqlite3Opcode
 #endif
 
 #if SQLITE_ENABLE_LOCKING_STYLE
 # include <sys/ioctl.h>
 # include <sys/file.h>
 # include <sys/param.h>
 #endif /* SQLITE_ENABLE_LOCKING_STYLE */
 
+#if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \
+                           (__IPHONE_OS_VERSION_MIN_REQUIRED > 2000))
+#  if (!defined(TARGET_OS_EMBEDDED) || (TARGET_OS_EMBEDDED==0)) \
+       && (!defined(TARGET_IPHONE_SIMULATOR) || (TARGET_IPHONE_SIMULATOR==0))
+#    define HAVE_GETHOSTUUID 1
+#  else
+#    warning "gethostuuid() is disabled."
+#  endif
+#endif
+
+
 #if OS_VXWORKS
 /* # include <sys/ioctl.h> */
 # include <semaphore.h>
 # include <limits.h>
 #endif /* OS_VXWORKS */
 
 #if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE
 # include <sys/mount.h>
@@ -25454,26 +25538,16 @@ static pid_t randomnessPid = 0;
 ** At least two bugs have slipped in because we changed the MEMORY_DEBUG
 ** macro to SQLITE_DEBUG and some older makefiles have not yet made the
 ** switch.  The following code should catch this problem at compile-time.
 */
 #ifdef MEMORY_DEBUG
 # error "The MEMORY_DEBUG macro is obsolete.  Use SQLITE_DEBUG instead."
 #endif
 
-#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG)
-# ifndef SQLITE_DEBUG_OS_TRACE
-#   define SQLITE_DEBUG_OS_TRACE 0
-# endif
-  int sqlite3OSTrace = SQLITE_DEBUG_OS_TRACE;
-# define OSTRACE(X)          if( sqlite3OSTrace ) sqlite3DebugPrintf X
-#else
-# define OSTRACE(X)
-#endif
-
 /*
 ** Macros for performance tracing.  Normally turned off.  Only works
 ** on i486 hardware.
 */
 #ifdef SQLITE_PERFORMANCE_TRACE
 
 /* 
 ** hwtime.h contains inline assembler code for implementing 
@@ -26006,17 +26080,17 @@ static void unixLeaveMutex(void){
 }
 #ifdef SQLITE_DEBUG
 static int unixMutexHeld(void) {
   return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
 }
 #endif
 
 
-#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG)
+#ifdef SQLITE_HAVE_OS_TRACE
 /*
 ** Helper function for printing out trace information from debugging
 ** binaries. This returns the string representation of the supplied
 ** integer lock-type.
 */
 static const char *azFileLock(int eFileLock){
   switch( eFileLock ){
     case NO_LOCK: return "NONE";
@@ -26269,17 +26343,17 @@ static int vxworksSimplifyName(char *z, 
 */
 static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){
   struct vxworksFileId *pNew;         /* search key and new file ID */
   struct vxworksFileId *pCandidate;   /* For looping over existing file IDs */
   int n;                              /* Length of zAbsoluteName string */
 
   assert( zAbsoluteName[0]=='/' );
   n = (int)strlen(zAbsoluteName);
-  pNew = sqlite3_malloc( sizeof(*pNew) + (n+1) );
+  pNew = sqlite3_malloc64( sizeof(*pNew) + (n+1) );
   if( pNew==0 ) return 0;
   pNew->zCanonicalName = (char*)&pNew[1];
   memcpy(pNew->zCanonicalName, zAbsoluteName, n+1);
   n = vxworksSimplifyName(pNew->zCanonicalName, n);
 
   /* Search for an existing entry that matching the canonical name.
   ** If found, increment the reference count and return a pointer to
   ** the existing file ID.
@@ -26673,17 +26747,17 @@ static int findInodeInfo(
 #else
   fileId.ino = statbuf.st_ino;
 #endif
   pInode = inodeList;
   while( pInode && memcmp(&fileId, &pInode->fileId, sizeof(fileId)) ){
     pInode = pInode->pNext;
   }
   if( pInode==0 ){
-    pInode = sqlite3_malloc( sizeof(*pInode) );
+    pInode = sqlite3_malloc64( sizeof(*pInode) );
     if( pInode==0 ){
       return SQLITE_NOMEM;
     }
     memset(pInode, 0, sizeof(*pInode));
     memcpy(&pInode->fileId, &fileId, sizeof(fileId));
     pInode->nRef = 1;
     pInode->pNext = inodeList;
     pInode->pPrev = 0;
@@ -29194,17 +29268,17 @@ static int unixFileControl(sqlite3_file 
       unixModeBit(pFile, UNIXFILE_PSOW, (int*)pArg);
       return SQLITE_OK;
     }
     case SQLITE_FCNTL_VFSNAME: {
       *(char**)pArg = sqlite3_mprintf("%s", pFile->pVfs->zName);
       return SQLITE_OK;
     }
     case SQLITE_FCNTL_TEMPFILENAME: {
-      char *zTFile = sqlite3_malloc( pFile->pVfs->mxPathname );
+      char *zTFile = sqlite3_malloc64( pFile->pVfs->mxPathname );
       if( zTFile ){
         unixGetTempname(pFile->pVfs->mxPathname, zTFile);
         *(char**)pArg = zTFile;
       }
       return SQLITE_OK;
     }
     case SQLITE_FCNTL_HAS_MOVED: {
       *(int*)pArg = fileHasMoved(pFile);
@@ -29635,17 +29709,17 @@ static int unixOpenSharedMemory(unixFile
   struct unixShm *p = 0;          /* The connection to be opened */
   struct unixShmNode *pShmNode;   /* The underlying mmapped file */
   int rc;                         /* Result code */
   unixInodeInfo *pInode;          /* The inode of fd */
   char *zShmFilename;             /* Name of the file used for SHM */
   int nShmFilename;               /* Size of the SHM filename in bytes */
 
   /* Allocate space for the new unixShm object. */
-  p = sqlite3_malloc( sizeof(*p) );
+  p = sqlite3_malloc64( sizeof(*p) );
   if( p==0 ) return SQLITE_NOMEM;
   memset(p, 0, sizeof(*p));
   assert( pDbFd->pShm==0 );
 
   /* Check to see if a unixShmNode object already exists. Reuse an existing
   ** one if present. Create a new one if necessary.
   */
   unixEnterMutex();
@@ -29666,17 +29740,17 @@ static int unixOpenSharedMemory(unixFile
       goto shm_open_err;
     }
 
 #ifdef SQLITE_SHM_DIRECTORY
     nShmFilename = sizeof(SQLITE_SHM_DIRECTORY) + 31;
 #else
     nShmFilename = 6 + (int)strlen(zBasePath);
 #endif
-    pShmNode = sqlite3_malloc( sizeof(*pShmNode) + nShmFilename );
+    pShmNode = sqlite3_malloc64( sizeof(*pShmNode) + nShmFilename );
     if( pShmNode==0 ){
       rc = SQLITE_NOMEM;
       goto shm_open_err;
     }
     memset(pShmNode, 0, sizeof(*pShmNode)+nShmFilename);
     zShmFilename = pShmNode->zFilename = (char*)&pShmNode[1];
 #ifdef SQLITE_SHM_DIRECTORY
     sqlite3_snprintf(nShmFilename, zShmFilename, 
@@ -29876,17 +29950,17 @@ static int unixShmMap(
             pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE, 
             MAP_SHARED, pShmNode->h, szRegion*(i64)pShmNode->nRegion
         );
         if( pMem==MAP_FAILED ){
           rc = unixLogError(SQLITE_IOERR_SHMMAP, "mmap", pShmNode->zFilename);
           goto shmpage_out;
         }
       }else{
-        pMem = sqlite3_malloc(szRegion);
+        pMem = sqlite3_malloc64(szRegion);
         if( pMem==0 ){
           rc = SQLITE_NOMEM;
           goto shmpage_out;
         }
         memset(pMem, 0, szRegion);
       }
 
       for(i=0; i<nShmPerMap; i++){
@@ -30713,17 +30787,17 @@ static int fillInUnixFile(
   }
 
 #if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__)
   else if( pLockingStyle == &afpIoMethods ){
     /* AFP locking uses the file path so it needs to be included in
     ** the afpLockingContext.
     */
     afpLockingContext *pCtx;
-    pNew->lockingContext = pCtx = sqlite3_malloc( sizeof(*pCtx) );
+    pNew->lockingContext = pCtx = sqlite3_malloc64( sizeof(*pCtx) );
     if( pCtx==0 ){
       rc = SQLITE_NOMEM;
     }else{
       /* NB: zFilename exists and remains valid until the file is closed
       ** according to requirement F11141.  So we do not need to make a
       ** copy of the filename. */
       pCtx->dbPath = zFilename;
       pCtx->reserved = 0;
@@ -30743,17 +30817,17 @@ static int fillInUnixFile(
   else if( pLockingStyle == &dotlockIoMethods ){
     /* Dotfile locking uses the file path so it needs to be included in
     ** the dotlockLockingContext 
     */
     char *zLockFile;
     int nFilename;
     assert( zFilename!=0 );
     nFilename = (int)strlen(zFilename) + 6;
-    zLockFile = (char *)sqlite3_malloc(nFilename);
+    zLockFile = (char *)sqlite3_malloc64(nFilename);
     if( zLockFile==0 ){
       rc = SQLITE_NOMEM;
     }else{
       sqlite3_snprintf(nFilename, zLockFile, "%s" DOTLOCK_SUFFIX, zFilename);
     }
     pNew->lockingContext = zLockFile;
   }
 
@@ -31120,17 +31194,17 @@ static int unixOpen(
   memset(p, 0, sizeof(unixFile));
 
   if( eType==SQLITE_OPEN_MAIN_DB ){
     UnixUnusedFd *pUnused;
     pUnused = findReusableFd(zName, flags);
     if( pUnused ){
       fd = pUnused->fd;
     }else{
-      pUnused = sqlite3_malloc(sizeof(*pUnused));
+      pUnused = sqlite3_malloc64(sizeof(*pUnused));
       if( !pUnused ){
         return SQLITE_NOMEM;
       }
     }
     p->pUnused = pUnused;
 
     /* Database filenames are double-zero terminated if they are not
     ** URIs with parameters.  Hence, they can always be passed into
@@ -31500,17 +31574,17 @@ static int unixRandomness(sqlite3_vfs *N
   ** in the random seed.
   **
   ** When testing, initializing zBuf[] to zero is all we do.  That means
   ** that we always use the same random number sequence.  This makes the
   ** tests repeatable.
   */
   memset(zBuf, 0, nBuf);
   randomnessPid = osGetpid(0);  
-#if !defined(SQLITE_TEST)
+#if !defined(SQLITE_TEST) && !defined(SQLITE_OMIT_RANDOMNESS)
   {
     int fd, got;
     fd = robust_open("/dev/urandom", O_RDONLY, 0);
     if( fd<0 ){
       time_t t;
       time(&t);
       memcpy(zBuf, &t, sizeof(t));
       memcpy(&zBuf[sizeof(t)], &randomnessPid, sizeof(randomnessPid));
@@ -31912,17 +31986,17 @@ static int proxyCreateUnixFile(
   ** the parent directories and then try again.
   ** 3. if that fails, try to open the file read-only
   ** otherwise return BUSY (if lock file) or CANTOPEN for the conch file
   */
   pUnused = findReusableFd(path, openFlags);
   if( pUnused ){
     fd = pUnused->fd;
   }else{
-    pUnused = sqlite3_malloc(sizeof(*pUnused));
+    pUnused = sqlite3_malloc64(sizeof(*pUnused));
     if( !pUnused ){
       return SQLITE_NOMEM;
     }
   }
   if( fd<0 ){
     fd = robust_open(path, openFlags, 0);
     terrno = errno;
     if( fd<0 && errno==ENOENT && islockfile ){
@@ -31945,17 +32019,17 @@ static int proxyCreateUnixFile(
         return SQLITE_PERM;
       case EIO: 
         return SQLITE_IOERR_LOCK; /* even though it is the conch */
       default:
         return SQLITE_CANTOPEN_BKPT;
     }
   }
   
-  pNew = (unixFile *)sqlite3_malloc(sizeof(*pNew));
+  pNew = (unixFile *)sqlite3_malloc64(sizeof(*pNew));
   if( pNew==NULL ){
     rc = SQLITE_NOMEM;
     goto end_create_proxy;
   }
   memset(pNew, 0, sizeof(unixFile));
   pNew->openFlags = openFlags;
   memset(&dummyVfs, 0, sizeof(dummyVfs));
   dummyVfs.pAppData = (void*)&autolockIoFinder;
@@ -31978,27 +32052,28 @@ end_create_proxy:
 
 #ifdef SQLITE_TEST
 /* simulate multiple hosts by creating unique hostid file paths */
 SQLITE_API int sqlite3_hostid_num = 0;
 #endif
 
 #define PROXY_HOSTIDLEN    16  /* conch file host id length */
 
+#ifdef HAVE_GETHOSTUUID
 /* Not always defined in the headers as it ought to be */
 extern int gethostuuid(uuid_t id, const struct timespec *wait);
+#endif
 
 /* get the host ID via gethostuuid(), pHostID must point to PROXY_HOSTIDLEN 
 ** bytes of writable memory.
 */
 static int proxyGetHostID(unsigned char *pHostID, int *pError){
   assert(PROXY_HOSTIDLEN == sizeof(uuid_t));
   memset(pHostID, 0, PROXY_HOSTIDLEN);
-# if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \
-                            (__IPHONE_OS_VERSION_MIN_REQUIRED > 2000))
+#ifdef HAVE_GETHOSTUUID
   {
     struct timespec timeout = {1, 0}; /* 1 sec timeout */
     if( gethostuuid(pHostID, &timeout) ){
       int err = errno;
       if( pError ){
         *pError = err;
       }
       return SQLITE_IOERR;
@@ -32406,33 +32481,33 @@ static int proxyReleaseConch(unixFile *p
   pCtx->conchHeld = 0;
   OSTRACE(("RELEASECONCH  %d %s\n", conchFile->h,
            (rc==SQLITE_OK ? "ok" : "failed")));
   return rc;
 }
 
 /*
 ** Given the name of a database file, compute the name of its conch file.
-** Store the conch filename in memory obtained from sqlite3_malloc().
+** Store the conch filename in memory obtained from sqlite3_malloc64().
 ** Make *pConchPath point to the new name.  Return SQLITE_OK on success
 ** or SQLITE_NOMEM if unable to obtain memory.
 **
 ** The caller is responsible for ensuring that the allocated memory
 ** space is eventually freed.
 **
 ** *pConchPath is set to NULL if a memory allocation error occurs.
 */
 static int proxyCreateConchPathname(char *dbPath, char **pConchPath){
   int i;                        /* Loop counter */
   int len = (int)strlen(dbPath); /* Length of database filename - dbPath */
   char *conchPath;              /* buffer in which to construct conch name */
 
   /* Allocate space for the conch filename and initialize the name to
   ** the name of the original database file. */  
-  *pConchPath = conchPath = (char *)sqlite3_malloc(len + 8);
+  *pConchPath = conchPath = (char *)sqlite3_malloc64(len + 8);
   if( conchPath==0 ){
     return SQLITE_NOMEM;
   }
   memcpy(conchPath, dbPath, len+1);
   
   /* now insert a "." before the last / character */
   for( i=(len-1); i>=0; i-- ){
     if( conchPath[i]=='/' ){
@@ -32538,17 +32613,17 @@ static int proxyTransformUnixFile(unixFi
     lockPath=NULL;
   }else{
     lockPath=(char *)path;
   }
   
   OSTRACE(("TRANSPROXY  %d for %s pid=%d\n", pFile->h,
            (lockPath ? lockPath : ":auto:"), osGetpid(0)));
 
-  pCtx = sqlite3_malloc( sizeof(*pCtx) );
+  pCtx = sqlite3_malloc64( sizeof(*pCtx) );
   if( pCtx==0 ){
     return SQLITE_NOMEM;
   }
   memset(pCtx, 0, sizeof(*pCtx));
 
   rc = proxyCreateConchPathname(dbPath, &pCtx->conchFilePath);
   if( rc==SQLITE_OK ){
     rc = proxyCreateUnixFile(pCtx->conchFilePath, &pCtx->conchFile, 0);
@@ -32982,26 +33057,16 @@ SQLITE_API int SQLITE_STDCALL sqlite3_os
 ** At least two bugs have slipped in because we changed the MEMORY_DEBUG
 ** macro to SQLITE_DEBUG and some older makefiles have not yet made the
 ** switch.  The following code should catch this problem at compile-time.
 */
 #ifdef MEMORY_DEBUG
 # error "The MEMORY_DEBUG macro is obsolete.  Use SQLITE_DEBUG instead."
 #endif
 
-#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG)
-# ifndef SQLITE_DEBUG_OS_TRACE
-#   define SQLITE_DEBUG_OS_TRACE 0
-# endif
-  int sqlite3OSTrace = SQLITE_DEBUG_OS_TRACE;
-# define OSTRACE(X)          if( sqlite3OSTrace ) sqlite3DebugPrintf X
-#else
-# define OSTRACE(X)
-#endif
-
 /*
 ** Macros for performance tracing.  Normally turned off.  Only works
 ** on i486 hardware.
 */
 #ifdef SQLITE_PERFORMANCE_TRACE
 
 /* 
 ** hwtime.h contains inline assembler code for implementing 
@@ -35895,17 +35960,17 @@ SQLITE_API int sqlite3_fullsync_count = 
 static int winSync(sqlite3_file *id, int flags){
 #ifndef SQLITE_NO_SYNC
   /*
   ** Used only when SQLITE_NO_SYNC is not defined.
    */
   BOOL rc;
 #endif
 #if !defined(NDEBUG) || !defined(SQLITE_NO_SYNC) || \
-    (defined(SQLITE_TEST) && defined(SQLITE_DEBUG))
+    defined(SQLITE_HAVE_OS_TRACE)
   /*
   ** Used when SQLITE_NO_SYNC is not defined and by the assert() and/or
   ** OSTRACE() macros.
    */
   winFile *pFile = (winFile*)id;
 #else
   UNUSED_PARAMETER(id);
 #endif
@@ -36572,17 +36637,17 @@ struct winShmNode {
     HANDLE hMap;             /* File handle from CreateFileMapping */
     void *pMap;
   } *aRegion;
   DWORD lastErrno;           /* The Windows errno from the last I/O error */
 
   int nRef;                  /* Number of winShm objects pointing to this */
   winShm *pFirst;            /* All winShm objects pointing to this */
   winShmNode *pNext;         /* Next in list of all winShmNode objects */
-#ifdef SQLITE_DEBUG
+#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
   u8 nextShmId;              /* Next available winShm.id value */
 #endif
 };
 
 /*
 ** A global array of all winShmNode objects.
 **
 ** The winShmMutexHeld() must be true while reading or writing this list.
@@ -36603,17 +36668,17 @@ static winShmNode *winShmNodeList = 0;
 ** while accessing any read/write fields.
 */
 struct winShm {
   winShmNode *pShmNode;      /* The underlying winShmNode object */
   winShm *pNext;             /* Next winShm with the same winShmNode */
   u8 hasMutex;               /* True if holding the winShmNode mutex */
   u16 sharedMask;            /* Mask of shared locks held */
   u16 exclMask;              /* Mask of exclusive locks held */
-#ifdef SQLITE_DEBUG
+#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
   u8 id;                     /* Id of this connection with its winShmNode */
 #endif
 };
 
 /*
 ** Constants used for locking
 */
 #define WIN_SHM_BASE   ((22+SQLITE_SHM_NLOCK)*4)        /* first lock byte */
@@ -36794,17 +36859,17 @@ static int winOpenSharedMemory(winFile *
       winShmSystemLock(pShmNode, _SHM_UNLCK, WIN_SHM_DMS, 1);
       rc = winShmSystemLock(pShmNode, _SHM_RDLCK, WIN_SHM_DMS, 1);
     }
     if( rc ) goto shm_open_err;
   }
 
   /* Make the new connection a child of the winShmNode */
   p->pShmNode = pShmNode;
-#ifdef SQLITE_DEBUG
+#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
   p->id = pShmNode->nextShmId++;
 #endif
   pShmNode->nRef++;
   pDbFd->pShm = p;
   winShmLeaveMutex();
 
   /* The reference count on pShmNode has already been incremented under
   ** the cover of the winShmEnterMutex() mutex and the pointer from the
@@ -37063,17 +37128,17 @@ static int winShmMap(
       if( rc!=SQLITE_OK ){
         rc = winLogError(SQLITE_IOERR_SHMSIZE, osGetLastError(),
                          "winShmMap2", pDbFd->zPath);
         goto shmpage_out;
       }
     }
 
     /* Map the requested memory region into this processes address space. */
-    apNew = (struct ShmRegion *)sqlite3_realloc(
+    apNew = (struct ShmRegion *)sqlite3_realloc64(
         pShmNode->aRegion, (iRegion+1)*sizeof(apNew[0])
     );
     if( !apNew ){
       rc = SQLITE_IOERR_NOMEM;
       goto shmpage_out;
     }
     pShmNode->aRegion = apNew;
 
@@ -38510,17 +38575,17 @@ static void winDlClose(sqlite3_vfs *pVfs
 
 
 /*
 ** Write up to nBuf bytes of randomness into zBuf.
 */
 static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
   int n = 0;
   UNUSED_PARAMETER(pVfs);
-#if defined(SQLITE_TEST)
+#if defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS)
   n = nBuf;
   memset(zBuf, 0, nBuf);
 #else
   if( sizeof(SYSTEMTIME)<=nBuf-n ){
     SYSTEMTIME x;
     osGetSystemTime(&x);
     memcpy(&zBuf[n], &x, sizeof(x));
     n += sizeof(x);
@@ -38544,33 +38609,33 @@ static int winRandomness(sqlite3_vfs *pV
   }
 #endif
   if( sizeof(LARGE_INTEGER)<=nBuf-n ){
     LARGE_INTEGER i;
     osQueryPerformanceCounter(&i);
     memcpy(&zBuf[n], &i, sizeof(i));
     n += sizeof(i);
   }
-#endif
 #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID
   if( sizeof(UUID)<=nBuf-n ){
     UUID id;
     memset(&id, 0, sizeof(UUID));
     osUuidCreate(&id);
     memcpy(zBuf, &id, sizeof(UUID));
     n += sizeof(UUID);
   }
   if( sizeof(UUID)<=nBuf-n ){
     UUID id;
     memset(&id, 0, sizeof(UUID));
     osUuidCreateSequential(&id);
     memcpy(zBuf, &id, sizeof(UUID));
     n += sizeof(UUID);
   }
 #endif
+#endif /* defined(SQLITE_TEST) || defined(SQLITE_ZERO_PRNG_SEED) */
   return n;
 }
 
 
 /*
 ** Sleep for a little while.  Return the amount of time slept.
 */
 static int winSleep(sqlite3_vfs *pVfs, int microsec){
@@ -39115,17 +39180,17 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinT
   int rc = -1;
   int i, nx, pc, op;
   void *pTmpSpace;
 
   /* Allocate the Bitvec to be tested and a linear array of
   ** bits to act as the reference */
   pBitvec = sqlite3BitvecCreate( sz );
   pV = sqlite3MallocZero( (sz+7)/8 + 1 );
-  pTmpSpace = sqlite3_malloc(BITVEC_SZ);
+  pTmpSpace = sqlite3_malloc64(BITVEC_SZ);
   if( pBitvec==0 || pV==0 || pTmpSpace==0  ) goto bitvec_end;
 
   /* NULL pBitvec tests */
   sqlite3BitvecSet(0, 1);
   sqlite3BitvecClear(0, 1, pTmpSpace);
 
   /* Run the program */
   pc = 0;
@@ -44604,36 +44669,33 @@ static int pagerRollbackWal(Pager *pPage
 static int pagerWalFrames(
   Pager *pPager,                  /* Pager object */
   PgHdr *pList,                   /* List of frames to log */
   Pgno nTruncate,                 /* Database size after this commit */
   int isCommit                    /* True if this is a commit */
 ){
   int rc;                         /* Return code */
   int nList;                      /* Number of pages in pList */
-#if defined(SQLITE_DEBUG) || defined(SQLITE_CHECK_PAGES)
   PgHdr *p;                       /* For looping over pages */
-#endif
 
   assert( pPager->pWal );
   assert( pList );
 #ifdef SQLITE_DEBUG
   /* Verify that the page list is in accending order */
   for(p=pList; p && p->pDirty; p=p->pDirty){
     assert( p->pgno < p->pDirty->pgno );
   }
 #endif
 
   assert( pList->pDirty==0 || isCommit );
   if( isCommit ){
     /* If a WAL transaction is being committed, there is no point in writing
     ** any pages with page numbers greater than nTruncate into the WAL file.
     ** They will never be read by any client. So remove them from the pDirty
     ** list here. */
-    PgHdr *p;
     PgHdr **ppNext = &pList;
     nList = 0;
     for(p=pList; (*ppNext = p)!=0; p=p->pDirty){
       if( p->pgno<=nTruncate ){
         ppNext = &p->pDirty;
         nList++;
       }
     }
@@ -44643,17 +44705,16 @@ static int pagerWalFrames(
   }
   pPager->aStat[PAGER_STAT_WRITE] += nList;
 
   if( pList->pgno==1 ) pager_write_changecounter(pList);
   rc = sqlite3WalFrames(pPager->pWal, 
       pPager->pageSize, pList, nTruncate, isCommit, pPager->walSyncFlags
   );
   if( rc==SQLITE_OK && pPager->pBackup ){
-    PgHdr *p;
     for(p=pList; p; p=p->pDirty){
       sqlite3BackupUpdate(pPager->pBackup, p->pgno, (u8 *)p->pData);
     }
   }
 
 #ifdef SQLITE_CHECK_PAGES
   pList = sqlite3PcacheDirtyList(pPager->pPCache);
   for(p=pList; p; p=p->pDirty){
@@ -48574,16 +48635,18 @@ SQLITE_PRIVATE int sqlite3PagerSetJourna
         }
         if( rc==SQLITE_OK && state==PAGER_READER ){
           pagerUnlockDb(pPager, SHARED_LOCK);
         }else if( state==PAGER_OPEN ){
           pager_unlock(pPager);
         }
         assert( state==pPager->eState );
       }
+    }else if( eMode==PAGER_JOURNALMODE_OFF ){
+      sqlite3OsClose(pPager->jfd);
     }
   }
 
   /* Return the new journal mode */
   return (int)pPager->journalMode;
 }
 
 /*
@@ -49356,17 +49419,17 @@ struct WalIterator {
 */
 static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
   int rc = SQLITE_OK;
 
   /* Enlarge the pWal->apWiData[] array if required */
   if( pWal->nWiData<=iPage ){
     int nByte = sizeof(u32*)*(iPage+1);
     volatile u32 **apNew;
-    apNew = (volatile u32 **)sqlite3_realloc((void *)pWal->apWiData, nByte);
+    apNew = (volatile u32 **)sqlite3_realloc64((void *)pWal->apWiData, nByte);
     if( !apNew ){
       *ppPage = 0;
       return SQLITE_NOMEM;
     }
     memset((void*)&apNew[pWal->nWiData], 0,
            sizeof(u32*)*(iPage+1-pWal->nWiData));
     pWal->apWiData = apNew;
     pWal->nWiData = iPage+1;
@@ -49981,17 +50044,17 @@ static int walIndexRecover(Wal *pWal){
     version = sqlite3Get4byte(&aBuf[4]);
     if( version!=WAL_MAX_VERSION ){
       rc = SQLITE_CANTOPEN_BKPT;
       goto finished;
     }
 
     /* Malloc a buffer to read frames into. */
     szFrame = szPage + WAL_FRAME_HDRSIZE;
-    aFrame = (u8 *)sqlite3_malloc(szFrame);
+    aFrame = (u8 *)sqlite3_malloc64(szFrame);
     if( !aFrame ){
       rc = SQLITE_NOMEM;
       goto recovery_error;
     }
     aData = &aFrame[WAL_FRAME_HDRSIZE];
 
     /* Read all frames from the log file. */
     iFrame = 0;
@@ -50374,27 +50437,27 @@ static int walIteratorInit(Wal *pWal, Wa
   assert( pWal->ckptLock && pWal->hdr.mxFrame>0 );
   iLast = pWal->hdr.mxFrame;
 
   /* Allocate space for the WalIterator object. */
   nSegment = walFramePage(iLast) + 1;
   nByte = sizeof(WalIterator) 
         + (nSegment-1)*sizeof(struct WalSegment)
         + iLast*sizeof(ht_slot);
-  p = (WalIterator *)sqlite3_malloc(nByte);
+  p = (WalIterator *)sqlite3_malloc64(nByte);
   if( !p ){
     return SQLITE_NOMEM;
   }
   memset(p, 0, nByte);
   p->nSegment = nSegment;
 
   /* Allocate temporary space used by the merge-sort routine. This block
   ** of memory will be freed before this function returns.
   */
-  aTmp = (ht_slot *)sqlite3_malloc(
+  aTmp = (ht_slot *)sqlite3_malloc64(
       sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast)
   );
   if( !aTmp ){
     rc = SQLITE_NOMEM;
   }
 
   for(i=0; rc==SQLITE_OK && i<nSegment; i++){
     volatile ht_slot *aHash;
@@ -50564,16 +50627,24 @@ static int walCheckpoint(
     /* Compute in mxSafeFrame the index of the last frame of the WAL that is
     ** safe to write into the database.  Frames beyond mxSafeFrame might
     ** overwrite database pages that are in use by active readers and thus
     ** cannot be backfilled from the WAL.
     */
     mxSafeFrame = pWal->hdr.mxFrame;
     mxPage = pWal->hdr.nPage;
     for(i=1; i<WAL_NREADER; i++){
+      /* Thread-sanitizer reports that the following is an unsafe read,
+      ** as some other thread may be in the process of updating the value
+      ** of the aReadMark[] slot. The assumption here is that if that is
+      ** happening, the other client may only be increasing the value,
+      ** not decreasing it. So assuming either that either the "old" or
+      ** "new" version of the value is read, and not some arbitrary value
+      ** that would never be written by a real client, things are still 
+      ** safe.  */
       u32 y = pInfo->aReadMark[i];
       if( mxSafeFrame>y ){
         assert( y<=pWal->hdr.mxFrame );
         rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1);
         if( rc==SQLITE_OK ){
           pInfo->aReadMark[i] = (i==1 ? mxSafeFrame : READMARK_NOT_USED);
           walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
         }else if( rc==SQLITE_BUSY ){
@@ -55404,17 +55475,17 @@ SQLITE_PRIVATE int sqlite3BtreeSetPageSi
   }
   if( nReserve<0 ){
     nReserve = pBt->pageSize - pBt->usableSize;
   }
   assert( nReserve>=0 && nReserve<=255 );
   if( pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE &&
         ((pageSize-1)&pageSize)==0 ){
     assert( (pageSize & 7)==0 );
-    assert( !pBt->pPage1 && !pBt->pCursor );
+    assert( !pBt->pCursor );
     pBt->pageSize = (u32)pageSize;
     freeTempSpace(pBt);
   }
   rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve);
   pBt->usableSize = pBt->pageSize - (u16)nReserve;
   if( iFix ) pBt->btsFlags |= BTS_PAGESIZE_FIXED;
   sqlite3BtreeLeave(p);
   return rc;
@@ -57426,23 +57497,28 @@ SQLITE_PRIVATE int sqlite3BtreeData(BtCu
 ** The pointer returned by this routine looks directly into the cached
 ** page of the database.  The data might change or move the next time
 ** any btree routine is called.
 */
 static const void *fetchPayload(
   BtCursor *pCur,      /* Cursor pointing to entry to read from */
   u32 *pAmt            /* Write the number of available bytes here */
 ){
+  u32 amt;
   assert( pCur!=0 && pCur->iPage>=0 && pCur->apPage[pCur->iPage]);
   assert( pCur->eState==CURSOR_VALID );
   assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
   assert( cursorHoldsMutex(pCur) );
   assert( pCur->aiIdx[pCur->iPage]<pCur->apPage[pCur->iPage]->nCell );
   assert( pCur->info.nSize>0 );
-  *pAmt = pCur->info.nLocal;
+  assert( pCur->info.pPayload>pCur->apPage[pCur->iPage]->aData || CORRUPT_DB );
+  assert( pCur->info.pPayload<pCur->apPage[pCur->iPage]->aDataEnd ||CORRUPT_DB);
+  amt = (int)(pCur->apPage[pCur->iPage]->aDataEnd - pCur->info.pPayload);
+  if( pCur->info.nLocal<amt ) amt = pCur->info.nLocal;
+  *pAmt = amt;
   return (void*)pCur->info.pPayload;
 }
 
 
 /*
 ** For the entry that cursor pCur is point to, return as
 ** many bytes of the key or data as are available on the local
 ** b-tree page.  Write the number of available bytes into *pAmt.
@@ -59710,17 +59786,16 @@ static int balance_nonroot(
     nxDiv = 0;
   }else{
     assert( bBulk==0 || bBulk==1 );
     if( iParentIdx==0 ){                 
       nxDiv = 0;
     }else if( iParentIdx==i ){
       nxDiv = i-2+bBulk;
     }else{
-      assert( bBulk==0 );
       nxDiv = iParentIdx-1;
     }
     i = 2-bBulk;
   }
   nOld = i+1;
   if( (i+nxDiv-pParent->nOverflow)==pParent->nCell ){
     pRight = &pParent->aData[pParent->hdrOffset+8];
   }else{
@@ -61499,16 +61574,67 @@ static void checkList(
     }
 #endif
     iPage = get4byte(pOvflData);
     sqlite3PagerUnref(pOvflPage);
   }
 }
 #endif /* SQLITE_OMIT_INTEGRITY_CHECK */
 
+/*
+** An implementation of a min-heap.
+**
+** aHeap[0] is the number of elements on the heap.  aHeap[1] is the
+** root element.  The daughter nodes of aHeap[N] are aHeap[N*2]
+** and aHeap[N*2+1].
+**
+** The heap property is this:  Every node is less than or equal to both
+** of its daughter nodes.  A consequence of the heap property is that the
+** root node aHeap[1] is always the minimum value currently in the heap.
+**
+** The btreeHeapInsert() routine inserts an unsigned 32-bit number onto
+** the heap, preserving the heap property.  The btreeHeapPull() routine
+** removes the root element from the heap (the minimum value in the heap)
+** and then moves other nodes around as necessary to preserve the heap
+** property.
+**
+** This heap is used for cell overlap and coverage testing.  Each u32
+** entry represents the span of a cell or freeblock on a btree page.  
+** The upper 16 bits are the index of the first byte of a range and the
+** lower 16 bits are the index of the last byte of that range.
+*/
+static void btreeHeapInsert(u32 *aHeap, u32 x){
+  u32 j, i = ++aHeap[0];
+  aHeap[i] = x;
+  while( (j = i/2)>0 && aHeap[j]>aHeap[i] ){
+    x = aHeap[j];
+    aHeap[j] = aHeap[i];
+    aHeap[i] = x;
+    i = j;
+  }
+}
+static int btreeHeapPull(u32 *aHeap, u32 *pOut){
+  u32 j, i, x;
+  if( (x = aHeap[0])==0 ) return 0;
+  *pOut = aHeap[1];
+  aHeap[1] = aHeap[x];
+  aHeap[x] = 0xffffffff;
+  aHeap[0]--;
+  i = 1;
+  while( (j = i*2)<=aHeap[0] ){
+    if( aHeap[j]>aHeap[j+1] ) j++;
+    if( aHeap[i]<aHeap[j] ) break;
+    x = aHeap[i];
+    aHeap[i] = aHeap[j];
+    aHeap[j] = x;
+    i = j;
+  }
+  return 1;  
+}
+
 #ifndef SQLITE_OMIT_INTEGRITY_CHECK
 /*
 ** Do various sanity checks on a single page of a tree.  Return
 ** the tree depth.  Root pages return 0.  Parents of root pages
 ** return 1, and so forth.
 ** 
 ** These checks are done:
 **
@@ -61531,17 +61657,18 @@ static int checkTreePage(
 ){
   MemPage *pPage;
   int i, rc, depth, d2, pgno, cnt;
   int hdr, cellStart;
   int nCell;
   u8 *data;
   BtShared *pBt;
   int usableSize;
-  char *hit = 0;
+  u32 *heap = 0;
+  u32 x, prev = 0;
   i64 nMinKey = 0;
   i64 nMaxKey = 0;
   const char *saved_zPfx = pCheck->zPfx;
   int saved_v1 = pCheck->v1;
   int saved_v2 = pCheck->v2;
 
   /* Check that the page exists
   */
@@ -61676,90 +61803,95 @@ static int checkTreePage(
       }
     }
   }
 
   /* Check for complete coverage of the page
   */
   data = pPage->aData;
   hdr = pPage->hdrOffset;
-  hit = sqlite3PageMalloc( pBt->pageSize );
+  heap = (u32*)sqlite3PageMalloc( pBt->pageSize );
   pCheck->zPfx = 0;
-  if( hit==0 ){
+  if( heap==0 ){
     pCheck->mallocFailed = 1;
   }else{
     int contentOffset = get2byteNotZero(&data[hdr+5]);
     assert( contentOffset<=usableSize );  /* Enforced by btreeInitPage() */
-    memset(hit+contentOffset, 0, usableSize-contentOffset);
-    memset(hit, 1, contentOffset);
+    heap[0] = 0;
+    btreeHeapInsert(heap, contentOffset-1);
     /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
     ** number of cells on the page. */
     nCell = get2byte(&data[hdr+3]);
     /* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page
     ** immediately follows the b-tree page header. */
     cellStart = hdr + 12 - 4*pPage->leaf;
     /* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte
     ** integer offsets to the cell contents. */
     for(i=0; i<nCell; i++){
       int pc = get2byte(&data[cellStart+i*2]);
       u32 size = 65536;
-      int j;
       if( pc<=usableSize-4 ){
         size = cellSizePtr(pPage, &data[pc]);
       }
       if( (int)(pc+size-1)>=usableSize ){
         pCheck->zPfx = 0;
         checkAppendMsg(pCheck,
             "Corruption detected in cell %d on page %d",i,iPage);
       }else{
-        for(j=pc+size-1; j>=pc; j--) hit[j]++;
+        btreeHeapInsert(heap, (pc<<16)|(pc+size-1));
       }
     }
     /* EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header
     ** is the offset of the first freeblock, or zero if there are no
     ** freeblocks on the page. */
     i = get2byte(&data[hdr+1]);
     while( i>0 ){
       int size, j;
       assert( i<=usableSize-4 );     /* Enforced by btreeInitPage() */
       size = get2byte(&data[i+2]);
       assert( i+size<=usableSize );  /* Enforced by btreeInitPage() */
-      for(j=i+size-1; j>=i; j--) hit[j]++;
+      btreeHeapInsert(heap, (i<<16)|(i+size-1));
       /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a
       ** big-endian integer which is the offset in the b-tree page of the next
       ** freeblock in the chain, or zero if the freeblock is the last on the
       ** chain. */
       j = get2byte(&data[i]);
       /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of
       ** increasing offset. */
       assert( j==0 || j>i+size );  /* Enforced by btreeInitPage() */
       assert( j<=usableSize-4 );   /* Enforced by btreeInitPage() */
       i = j;
     }
-    for(i=cnt=0; i<usableSize; i++){
-      if( hit[i]==0 ){
-        cnt++;
-      }else if( hit[i]>1 ){
+    cnt = 0;
+    assert( heap[0]>0 );
+    assert( (heap[1]>>16)==0 );
+    btreeHeapPull(heap,&prev);
+    while( btreeHeapPull(heap,&x) ){
+      if( (prev&0xffff)+1>(x>>16) ){
         checkAppendMsg(pCheck,
-          "Multiple uses for byte %d of page %d", i, iPage);
-        break;
-      }
-    }
+          "Multiple uses for byte %u of page %d", x>>16, iPage);
+        break;
+      }else{
+        cnt += (x>>16) - (prev&0xffff) - 1;
+        prev = x;
+      }
+    }
+    cnt += usableSize - (prev&0xffff) - 1;
     /* EVIDENCE-OF: R-43263-13491 The total number of bytes in all fragments
     ** is stored in the fifth field of the b-tree page header.
     ** EVIDENCE-OF: R-07161-27322 The one-byte integer at offset 7 gives the
     ** number of fragmented free bytes within the cell content area.
     */
-    if( cnt!=data[hdr+7] ){
+    if( heap[0]==0 && cnt!=data[hdr+7] ){
       checkAppendMsg(pCheck,
           "Fragmentation of %d bytes reported as %d on page %d",
           cnt, data[hdr+7], iPage);
     }
   }
-  sqlite3PageFree(hit);
+  sqlite3PageFree(heap);
   releasePage(pPage);
 
 end_of_check:
   pCheck->zPfx = saved_zPfx;
   pCheck->v1 = saved_v1;
   pCheck->v2 = saved_v2;
   return depth+1;
 }
@@ -61813,18 +61945,17 @@ SQLITE_PRIVATE char *sqlite3BtreeIntegri
   sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1);
   if( !sCheck.aPgRef ){
     *pnErr = 1;
     sqlite3BtreeLeave(p);
     return 0;
   }
   i = PENDING_BYTE_PAGE(pBt);
   if( i<=sCheck.nPage ) setPageReferenced(&sCheck, i);
-  sqlite3StrAccumInit(&sCheck.errMsg, zErr, sizeof(zErr), SQLITE_MAX_LENGTH);
-  sCheck.errMsg.useMalloc = 2;
+  sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH);
 
   /* Check the integrity of the freelist
   */
   sCheck.zPfx = "Main freelist: ";
   checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
             get4byte(&pBt->pPage1->aData[36]));
   sCheck.zPfx = 0;
 
@@ -63150,20 +63281,21 @@ SQLITE_PRIVATE int sqlite3VdbeMemMakeWri
   f = pMem->flags;
   if( (f&(MEM_Str|MEM_Blob)) && (pMem->szMalloc==0 || pMem->z!=pMem->zMalloc) ){
     if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){
       return SQLITE_NOMEM;
     }
     pMem->z[pMem->n] = 0;
     pMem->z[pMem->n+1] = 0;
     pMem->flags |= MEM_Term;
+  }
+  pMem->flags &= ~MEM_Ephem;
 #ifdef SQLITE_DEBUG
-    pMem->pScopyFrom = 0;
-#endif
-  }
+  pMem->pScopyFrom = 0;
+#endif
 
   return SQLITE_OK;
 }
 
 /*
 ** If the given Mem* has a zero-filled tail, turn it into an ordinary
 ** blob stored in dynamically allocated space.
 */
@@ -64597,17 +64729,17 @@ SQLITE_PRIVATE int sqlite3Stat4Column(
 */
 SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord *pRec){
   if( pRec ){
     int i;
     int nCol = pRec->pKeyInfo->nField+pRec->pKeyInfo->nXField;
     Mem *aMem = pRec->aMem;
     sqlite3 *db = aMem[0].db;
     for(i=0; i<nCol; i++){
-      if( aMem[i].szMalloc ) sqlite3DbFree(db, aMem[i].zMalloc);
+      sqlite3VdbeMemRelease(&aMem[i]);
     }
     sqlite3KeyInfoUnref(pRec->pKeyInfo);
     sqlite3DbFree(db, pRec);
   }
 }
 #endif /* ifdef SQLITE_ENABLE_STAT4 */
 
 /*
@@ -66435,22 +66567,39 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCurso
     assert( pVtabCursor->pVtab->nRef>0 );
     pVtabCursor->pVtab->nRef--;
     pModule->xClose(pVtabCursor);
   }
 #endif
 }
 
 /*
+** Close all cursors in the current frame.
+*/
+static void closeCursorsInFrame(Vdbe *p){
+  if( p->apCsr ){
+    int i;
+    for(i=0; i<p->nCursor; i++){
+      VdbeCursor *pC = p->apCsr[i];
+      if( pC ){
+        sqlite3VdbeFreeCursor(p, pC);
+        p->apCsr[i] = 0;
+      }
+    }
+  }
+}
+
+/*
 ** Copy the values stored in the VdbeFrame structure to its Vdbe. This
 ** is used, for example, when a trigger sub-program is halted to restore
 ** control to the main program.
 */
 SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){
   Vdbe *v = pFrame->v;
+  closeCursorsInFrame(v);
 #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
   v->anExec = pFrame->anExec;
 #endif
   v->aOnceFlag = pFrame->aOnceFlag;
   v->nOnceFlag = pFrame->nOnceFlag;
   v->aOp = pFrame->aOp;
   v->nOp = pFrame->nOp;
   v->aMem = pFrame->aMem;
@@ -66475,27 +66624,17 @@ static void closeAllCursors(Vdbe *p){
   if( p->pFrame ){
     VdbeFrame *pFrame;
     for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent);
     sqlite3VdbeFrameRestore(pFrame);
     p->pFrame = 0;
     p->nFrame = 0;
   }
   assert( p->nFrame==0 );
-
-  if( p->apCsr ){
-    int i;
-    for(i=0; i<p->nCursor; i++){
-      VdbeCursor *pC = p->apCsr[i];
-      if( pC ){
-        sqlite3VdbeFreeCursor(p, pC);
-        p->apCsr[i] = 0;
-      }
-    }
-  }
+  closeCursorsInFrame(p);
   if( p->aMem ){
     releaseMemArray(&p->aMem[1], p->nMem);
   }
   while( p->pDelFrame ){
     VdbeFrame *pDel = p->pDelFrame;
     p->pDelFrame = pDel->pParent;
     sqlite3VdbeFrameDelete(pDel);
   }
@@ -68230,17 +68369,17 @@ static i64 vdbeRecordDecodeInt(u32 seria
 ** fields that appear in both keys are equal, then pPKey2->default_rc is 
 ** returned.
 **
 ** If database corruption is discovered, set pPKey2->errCode to 
 ** SQLITE_CORRUPT and return 0. If an OOM error is encountered, 
 ** pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the
 ** malloc-failed flag set on database handle (pPKey2->pKeyInfo->db).
 */
-static int vdbeRecordCompareWithSkip(
+SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
   int nKey1, const void *pKey1,   /* Left key */
   UnpackedRecord *pPKey2,         /* Right key */
   int bSkip                       /* If true, skip the first field */
 ){
   u32 d1;                         /* Offset into aKey[] of next data element */
   int i;                          /* Index of next field to compare */
   u32 szHdr1;                     /* Size of record header in bytes */
   u32 idx1;                       /* Offset of first type in header */
@@ -68416,17 +68555,17 @@ static int vdbeRecordCompareWithSkip(
        || pKeyInfo->db->mallocFailed
   );
   return pPKey2->default_rc;
 }
 SQLITE_PRIVATE int sqlite3VdbeRecordCompare(
   int nKey1, const void *pKey1,   /* Left key */
   UnpackedRecord *pPKey2          /* Right key */
 ){
-  return vdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0);
+  return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0);
 }
 
 
 /*
 ** This function is an optimized version of sqlite3VdbeRecordCompare() 
 ** that (a) the first field of pPKey2 is an integer, and (b) the 
 ** size-of-header varint at the start of (pKey1/nKey1) fits in a single
 ** byte (i.e. is less than 128).
@@ -68504,17 +68643,17 @@ static int vdbeRecordCompareInt(
 
   if( v>lhs ){
     res = pPKey2->r1;
   }else if( v<lhs ){
     res = pPKey2->r2;
   }else if( pPKey2->nField>1 ){
     /* The first fields of the two keys are equal. Compare the trailing 
     ** fields.  */