browser/devtools/profiler/cleopatra/js/parserWorker.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/devtools/profiler/cleopatra/js/parserWorker.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1664 @@
     1.4 +/* -*- Mode: js2; indent-tabs-mode: nil; js2-basic-offset: 2; -*- */
     1.5 +
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +"use strict";
    1.11 +
    1.12 +importScripts("ProgressReporter.js");
    1.13 +
    1.14 +var gProfiles = [];
    1.15 +
    1.16 +var partialTaskData = {};
    1.17 +
    1.18 +var gNextProfileID = 0;
    1.19 +
    1.20 +var gLogLines = [];
    1.21 +
    1.22 +var gDebugLog = false;
    1.23 +var gDebugTrace = false;
    1.24 +// Use for verbose tracing, otherwise use log
    1.25 +function PROFILDERTRACE(msg) {
    1.26 +  if (gDebugTrace)
    1.27 +    PROFILERLOG(msg);
    1.28 +}
    1.29 +function PROFILERLOG(msg) {
    1.30 +  if (gDebugLog) {
    1.31 +    msg = "Cleo: " + msg;
    1.32 +    //if (window.dump)
    1.33 +    //  window.dump(msg + "\n");
    1.34 +  }
    1.35 +}
    1.36 +function PROFILERERROR(msg) {
    1.37 +  msg = "Cleo: " + msg;
    1.38 +  //if (window.dump)
    1.39 +  //  window.dump(msg + "\n");
    1.40 +}
    1.41 +
    1.42 +// http://stackoverflow.com/a/2548133
    1.43 +function endsWith(str, suffix) {
    1.44 +      return str.indexOf(suffix, this.length - suffix.length) !== -1;
    1.45 +};
    1.46 +
    1.47 +// https://bugzilla.mozilla.org/show_bug.cgi?id=728780
    1.48 +if (!String.prototype.startsWith) {
    1.49 +  String.prototype.startsWith =
    1.50 +    function(s) { return this.lastIndexOf(s, 0) === 0; }
    1.51 +}
    1.52 +
    1.53 +// functions for which lr is unconditionally valid.  These are
    1.54 +// largely going to be atomics and other similar functions
    1.55 +// that don't touch lr.  This is currently populated with
    1.56 +// some functions from bionic, largely via manual inspection
    1.57 +// of the assembly in e.g.
    1.58 +// http://androidxref.com/source/xref/bionic/libc/arch-arm/syscalls/
    1.59 +var sARMFunctionsWithValidLR = [
    1.60 +  "__atomic_dec",
    1.61 +  "__atomic_inc",
    1.62 +  "__atomic_cmpxchg",
    1.63 +  "__atomic_swap",
    1.64 +  "__atomic_dec",
    1.65 +  "__atomic_inc",
    1.66 +  "__atomic_cmpxchg",
    1.67 +  "__atomic_swap",
    1.68 +  "__futex_syscall3",
    1.69 +  "__futex_wait",
    1.70 +  "__futex_wake",
    1.71 +  "__futex_syscall3",
    1.72 +  "__futex_wait",
    1.73 +  "__futex_wake",
    1.74 +  "__futex_syscall4",
    1.75 +  "__ioctl",
    1.76 +  "__brk",
    1.77 +  "__wait4",
    1.78 +  "epoll_wait",
    1.79 +  "fsync",
    1.80 +  "futex",
    1.81 +  "nanosleep",
    1.82 +  "pause",
    1.83 +  "sched_yield",
    1.84 +  "syscall"
    1.85 +];
    1.86 +
    1.87 +function log() {
    1.88 +  var z = [];
    1.89 +  for (var i = 0; i < arguments.length; ++i)
    1.90 +    z.push(arguments[i]);
    1.91 +  gLogLines.push(z.join(" "));
    1.92 +}
    1.93 +
    1.94 +self.onmessage = function (msg) {
    1.95 +  try {
    1.96 +    var requestID = msg.data.requestID;
    1.97 +    var task = msg.data.task;
    1.98 +    var taskData = msg.data.taskData;
    1.99 +    if (!taskData &&
   1.100 +        (["chunkedStart", "chunkedChunk", "chunkedEnd"].indexOf(task) == -1)) {
   1.101 +      taskData = partialTaskData[requestID];
   1.102 +      delete partialTaskData[requestID];
   1.103 +    }
   1.104 +    PROFILERLOG("Start task: " + task);
   1.105 +
   1.106 +    gLogLines = [];
   1.107 +
   1.108 +    switch (task) {
   1.109 +      case "initWorker":
   1.110 +        gDebugLog = taskData.debugLog;
   1.111 +        gDebugTrace = taskData.debugTrace;
   1.112 +        PROFILERLOG("Init logging in parserWorker");
   1.113 +        return;
   1.114 +      case "chunkedStart":
   1.115 +        partialTaskData[requestID] = null;
   1.116 +        break;
   1.117 +      case "chunkedChunk":
   1.118 +        if (partialTaskData[requestID] === null)
   1.119 +          partialTaskData[requestID] = msg.data.chunk;
   1.120 +        else
   1.121 +          partialTaskData[requestID] = partialTaskData[requestID].concat(msg.data.chunk);
   1.122 +        break;
   1.123 +      case "chunkedEnd":
   1.124 +        break;
   1.125 +      case "parseRawProfile":
   1.126 +        parseRawProfile(requestID, msg.data.params, taskData);
   1.127 +        break;
   1.128 +      case "updateFilters":
   1.129 +        updateFilters(requestID, taskData.profileID, taskData.filters);
   1.130 +        break;
   1.131 +      case "updateViewOptions":
   1.132 +        updateViewOptions(requestID, taskData.profileID, taskData.options);
   1.133 +        break;
   1.134 +      case "getSerializedProfile":
   1.135 +        getSerializedProfile(requestID, taskData.profileID, taskData.complete);
   1.136 +        break;
   1.137 +      case "calculateHistogramData":
   1.138 +        calculateHistogramData(requestID, taskData.profileID);
   1.139 +        break;
   1.140 +      case "calculateDiagnosticItems":
   1.141 +        calculateDiagnosticItems(requestID, taskData.profileID, taskData.meta);
   1.142 +        break;
   1.143 +      default:
   1.144 +        sendError(requestID, "Unknown task " + task);
   1.145 +        break;
   1.146 +    }
   1.147 +    PROFILERLOG("Complete task: " + task);
   1.148 +  } catch (e) {
   1.149 +    PROFILERERROR("Exception: " + e + " (" + e.fileName + ":" + e.lineNumber + ")");
   1.150 +    sendError(requestID, "Exception: " + e + " (" + e.fileName + ":" + e.lineNumber + ")");
   1.151 +  }
   1.152 +}
   1.153 +
   1.154 +function sendError(requestID, error) {
   1.155 +  // support sendError(msg)
   1.156 +  if (error == null) {
   1.157 +    error = requestID;
   1.158 +    requestID = null;
   1.159 +  }
   1.160 +
   1.161 +  self.postMessage({
   1.162 +    requestID: requestID,
   1.163 +    type: "error",
   1.164 +    error: error,
   1.165 +    log: gLogLines
   1.166 +  });
   1.167 +}
   1.168 +
   1.169 +function sendProgress(requestID, progress) {
   1.170 +  self.postMessage({
   1.171 +    requestID: requestID,
   1.172 +    type: "progress",
   1.173 +    progress: progress
   1.174 +  });
   1.175 +}
   1.176 +
   1.177 +function sendFinished(requestID, result) {
   1.178 +  self.postMessage({
   1.179 +    requestID: requestID,
   1.180 +    type: "finished",
   1.181 +    result: result,
   1.182 +    log: gLogLines
   1.183 +  });
   1.184 +}
   1.185 +
   1.186 +function bucketsBySplittingArray(array, maxCostPerBucket, costOfElementCallback) {
   1.187 +  var buckets = [];
   1.188 +  var currentBucket = [];
   1.189 +  var currentBucketCost = 0;
   1.190 +  for (var i = 0; i < array.length; i++) {
   1.191 +    var element = array[i];
   1.192 +    var costOfCurrentElement = costOfElementCallback ? costOfElementCallback(element) : 1;
   1.193 +    if (currentBucketCost + costOfCurrentElement > maxCostPerBucket) {
   1.194 +      buckets.push(currentBucket);
   1.195 +      currentBucket = [];
   1.196 +      currentBucketCost = 0;
   1.197 +    }
   1.198 +    currentBucket.push(element);
   1.199 +    currentBucketCost += costOfCurrentElement;
   1.200 +  }
   1.201 +  buckets.push(currentBucket);
   1.202 +  return buckets;
   1.203 +}
   1.204 +
   1.205 +function sendFinishedInChunks(requestID, result, maxChunkCost, costOfElementCallback) {
   1.206 +  if (result.length === undefined || result.slice === undefined)
   1.207 +    throw new Error("Can't slice result into chunks");
   1.208 +  self.postMessage({
   1.209 +    requestID: requestID,
   1.210 +    type: "finishedStart"
   1.211 +  });
   1.212 +  var chunks = bucketsBySplittingArray(result, maxChunkCost, costOfElementCallback);
   1.213 +  for (var i = 0; i < chunks.length; i++) {
   1.214 +    self.postMessage({
   1.215 +      requestID: requestID,
   1.216 +      type: "finishedChunk",
   1.217 +      chunk: chunks[i]
   1.218 +    });
   1.219 +  }
   1.220 +  self.postMessage({
   1.221 +    requestID: requestID,
   1.222 +    type: "finishedEnd",
   1.223 +    log: gLogLines
   1.224 +  });
   1.225 +}
   1.226 +
   1.227 +function makeSample(frames, extraInfo) {
   1.228 +  return {
   1.229 +    frames: frames,
   1.230 +    extraInfo: extraInfo
   1.231 +  };
   1.232 +}
   1.233 +
   1.234 +function cloneSample(sample) {
   1.235 +  return makeSample(sample.frames.slice(0), sample.extraInfo);
   1.236 +}
   1.237 +
   1.238 +function parseRawProfile(requestID, params, rawProfile) {
   1.239 +  var progressReporter = new ProgressReporter();
   1.240 +  progressReporter.addListener(function (r) {
   1.241 +    sendProgress(requestID, r.getProgress());
   1.242 +  });
   1.243 +  progressReporter.begin("Parsing...");
   1.244 +
   1.245 +  var symbolicationTable = {};
   1.246 +  var symbols = [];
   1.247 +  var symbolIndices = {};
   1.248 +  var resources = {};
   1.249 +  var functions = [];
   1.250 +  var functionIndices = {};
   1.251 +  var samples = [];
   1.252 +  var meta = {};
   1.253 +  var armIncludePCIndex = {};
   1.254 +
   1.255 +  if (rawProfile == null) {
   1.256 +    throw "rawProfile is null";
   1.257 +  }
   1.258 +
   1.259 +  if (typeof rawProfile == "string" && rawProfile[0] == "{") {
   1.260 +    // rawProfile is a JSON string.
   1.261 +    rawProfile = JSON.parse(rawProfile);
   1.262 +    if (rawProfile === null) {
   1.263 +      throw "rawProfile couldn't not successfully be parsed using JSON.parse. Make sure that the profile is a valid JSON encoding.";
   1.264 +    }
   1.265 +  }
   1.266 +
   1.267 +  if (rawProfile.profileJSON && !rawProfile.profileJSON.meta && rawProfile.meta) {
   1.268 +    rawProfile.profileJSON.meta = rawProfile.meta;
   1.269 +  }
   1.270 +
   1.271 +  if (typeof rawProfile == "object") {
   1.272 +    switch (rawProfile.format || null) {
   1.273 +      case "profileStringWithSymbolicationTable,1":
   1.274 +        symbolicationTable = rawProfile.symbolicationTable;
   1.275 +        parseProfileString(rawProfile.profileString);
   1.276 +        break;
   1.277 +      case "profileJSONWithSymbolicationTable,1":
   1.278 +        symbolicationTable = rawProfile.symbolicationTable;
   1.279 +        parseProfileJSON(rawProfile.profileJSON);
   1.280 +        break;
   1.281 +      default:
   1.282 +        parseProfileJSON(rawProfile);
   1.283 +    }
   1.284 +  } else {
   1.285 +    parseProfileString(rawProfile);
   1.286 +  }
   1.287 +
   1.288 +  if (params.profileId) {
   1.289 +    meta.profileId = params.profileId;
   1.290 +  }
   1.291 +
   1.292 +  function cleanFunctionName(functionName) {
   1.293 +    var ignoredPrefix = "non-virtual thunk to ";
   1.294 +    if (functionName.startsWith(ignoredPrefix))
   1.295 +      return functionName.substr(ignoredPrefix.length);
   1.296 +    return functionName;
   1.297 +  }
   1.298 +
   1.299 +  function resourceNameForAddon(addon) {
   1.300 +    if (!addon)
   1.301 +      return "";
   1.302 +
   1.303 +    var iconHTML = "";
   1.304 +    if (addon.iconURL)
   1.305 +      iconHTML = "<img src=\"" + addon.iconURL + "\" style='width:12px; height:12px;'> "
   1.306 +    return iconHTML + " " + (/@jetpack$/.exec(addon.id) ? "Jetpack: " : "") + addon.name;
   1.307 +  }
   1.308 +
   1.309 +  function addonWithID(addonID) {
   1.310 +    return firstMatch(meta.addons, function addonHasID(addon) {
   1.311 +      return addon.id.toLowerCase() == addonID.toLowerCase();
   1.312 +    })
   1.313 +  }
   1.314 +
   1.315 +  function resourceNameForAddonWithID(addonID) {
   1.316 +    return resourceNameForAddon(addonWithID(addonID));
   1.317 +  }
   1.318 +
   1.319 +  function findAddonForChromeURIHost(host) {
   1.320 +    return firstMatch(meta.addons, function addonUsesChromeURIHost(addon) {
   1.321 +      return addon.chromeURIHosts && addon.chromeURIHosts.indexOf(host) != -1;
   1.322 +    });
   1.323 +  }
   1.324 +
   1.325 +  function ensureResource(name, resourceDescription) {
   1.326 +    if (!(name in resources)) {
   1.327 +      resources[name] = resourceDescription;
   1.328 +    }
   1.329 +    return name;
   1.330 +  }
   1.331 +
   1.332 +  function resourceNameFromLibrary(library) {
   1.333 +    return ensureResource("lib_" + library, {
   1.334 +      type: "library",
   1.335 +      name: library
   1.336 +    });
   1.337 +  }
   1.338 +
   1.339 +  function getAddonForScriptURI(url, host) {
   1.340 +    if (!meta || !meta.addons)
   1.341 +      return null;
   1.342 +
   1.343 +    if (url.startsWith("resource:") && endsWith(host, "-at-jetpack")) {
   1.344 +      // Assume this is a jetpack url
   1.345 +      var jetpackID = host.substring(0, host.length - 11) + "@jetpack";
   1.346 +      return addonWithID(jetpackID);
   1.347 +    }
   1.348 +
   1.349 +    if (url.startsWith("file:///") && url.indexOf("/extensions/") != -1) {
   1.350 +      var unpackedAddonNameMatch = /\/extensions\/(.*?)\//.exec(url);
   1.351 +      if (unpackedAddonNameMatch)
   1.352 +        return addonWithID(decodeURIComponent(unpackedAddonNameMatch[1]));
   1.353 +      return null;
   1.354 +    }
   1.355 +
   1.356 +    if (url.startsWith("jar:file:///") && url.indexOf("/extensions/") != -1) {
   1.357 +      var packedAddonNameMatch = /\/extensions\/(.*?).xpi/.exec(url);
   1.358 +      if (packedAddonNameMatch)
   1.359 +        return addonWithID(decodeURIComponent(packedAddonNameMatch[1]));
   1.360 +      return null;
   1.361 +    }
   1.362 +
   1.363 +    if (url.startsWith("chrome://")) {
   1.364 +      var chromeURIMatch = /chrome\:\/\/(.*?)\//.exec(url);
   1.365 +      if (chromeURIMatch)
   1.366 +        return findAddonForChromeURIHost(chromeURIMatch[1]);
   1.367 +      return null;
   1.368 +    }
   1.369 +
   1.370 +    return null;
   1.371 +  }
   1.372 +
   1.373 +  function resourceNameFromURI(url) {
   1.374 +    if (!url)
   1.375 +      return ensureResource("unknown", {type: "unknown", name: "<unknown>"});
   1.376 +
   1.377 +    var match = /^(.*):\/\/(.*?)\//.exec(url);
   1.378 +
   1.379 +    if (!match) {
   1.380 +      // Can this happen? If so, we should change the regular expression above.
   1.381 +      return ensureResource("url_" + url, {type: "url", name: url});
   1.382 +    }
   1.383 +
   1.384 +    var urlRoot = match[0];
   1.385 +    var protocol = match[1];
   1.386 +    var host = match[2];
   1.387 +
   1.388 +    var addon = getAddonForScriptURI(url, host);
   1.389 +    if (addon) {
   1.390 +      return ensureResource("addon_" + addon.id, {
   1.391 +        type: "addon",
   1.392 +        name: addon.name,
   1.393 +        addonID: addon.id,
   1.394 +        icon: addon.iconURL
   1.395 +      });
   1.396 +    }
   1.397 +
   1.398 +    if (protocol.startsWith("http")) {
   1.399 +      return ensureResource("webhost_" + host, {
   1.400 +        type: "webhost",
   1.401 +        name: host,
   1.402 +        icon: urlRoot + "favicon.ico"
   1.403 +      });
   1.404 +    }
   1.405 +
   1.406 +    if (protocol.startsWith("file")) {
   1.407 +      return ensureResource("file_" + host, {
   1.408 +        type: "file",
   1.409 +        name: host
   1.410 +      });
   1.411 +    }
   1.412 +
   1.413 +    return ensureResource("otherhost_" + host, {
   1.414 +      type: "otherhost",
   1.415 +      name: host
   1.416 +    });
   1.417 +  }
   1.418 +
   1.419 +  function parseScriptFile(url) {
   1.420 +     var match = /([^\/]*)$/.exec(url);
   1.421 +     if (match && match[1])
   1.422 +       return match[1];
   1.423 +
   1.424 +     return url;
   1.425 +  }
   1.426 +
   1.427 +  // JS File information sometimes comes with multiple URIs which are chained
   1.428 +  // with " -> ". We only want the last URI in this list.
   1.429 +  function getRealScriptURI(url) {
   1.430 +    if (url) {
   1.431 +      var urls = url.split(" -> ");
   1.432 +      return urls[urls.length - 1];
   1.433 +    }
   1.434 +    return url;
   1.435 +  }
   1.436 +
   1.437 +  function getFunctionInfo(fullName) {
   1.438 +
   1.439 +    function getCPPFunctionInfo(fullName) {
   1.440 +      var match =
   1.441 +        /^(.*) \(in ([^\)]*)\) (\+ [0-9]+)$/.exec(fullName) ||
   1.442 +        /^(.*) \(in ([^\)]*)\) (\(.*:.*\))$/.exec(fullName) ||
   1.443 +        /^(.*) \(in ([^\)]*)\)$/.exec(fullName);
   1.444 +
   1.445 +      if (!match)
   1.446 +        return null;
   1.447 +
   1.448 +      return {
   1.449 +        functionName: cleanFunctionName(match[1]),
   1.450 +        libraryName: resourceNameFromLibrary(match[2]),
   1.451 +        lineInformation: match[3] || "",
   1.452 +        isRoot: false,
   1.453 +        isJSFrame: false
   1.454 +      };
   1.455 +    }
   1.456 +
   1.457 +    function getJSFunctionInfo(fullName) {
   1.458 +      var jsMatch =
   1.459 +        /^(.*) \((.*):([0-9]+)\)$/.exec(fullName) ||
   1.460 +        /^()(.*):([0-9]+)$/.exec(fullName);
   1.461 +
   1.462 +      if (!jsMatch)
   1.463 +        return null;
   1.464 +
   1.465 +      var functionName = jsMatch[1] || "<Anonymous>";
   1.466 +      var scriptURI = getRealScriptURI(jsMatch[2]);
   1.467 +      var lineNumber = jsMatch[3];
   1.468 +      var scriptFile = parseScriptFile(scriptURI);
   1.469 +      var resourceName = resourceNameFromURI(scriptURI);
   1.470 +
   1.471 +      return {
   1.472 +        functionName: functionName + "() @ " + scriptFile + ":" + lineNumber,
   1.473 +        libraryName: resourceName,
   1.474 +        lineInformation: "",
   1.475 +        isRoot: false,
   1.476 +        isJSFrame: true,
   1.477 +        scriptLocation: {
   1.478 +          scriptURI: scriptURI,
   1.479 +          lineInformation: lineNumber
   1.480 +        }
   1.481 +      };
   1.482 +    }
   1.483 +
   1.484 +    function getFallbackFunctionInfo(fullName) {
   1.485 +      return {
   1.486 +        functionName: cleanFunctionName(fullName),
   1.487 +        libraryName: "",
   1.488 +        lineInformation: "",
   1.489 +        isRoot: fullName == "(root)",
   1.490 +        isJSFrame: false
   1.491 +      };
   1.492 +    }
   1.493 +
   1.494 +    return getCPPFunctionInfo(fullName) ||
   1.495 +           getJSFunctionInfo(fullName) ||
   1.496 +           getFallbackFunctionInfo(fullName);
   1.497 +  }
   1.498 +
   1.499 +  function indexForFunction(symbol, info) {
   1.500 +    var resolve = info.functionName + "__" + info.libraryName;
   1.501 +    if (resolve in functionIndices)
   1.502 +      return functionIndices[resolve];
   1.503 +    var newIndex = functions.length;
   1.504 +    info.symbol = symbol;
   1.505 +    functions[newIndex] = info;
   1.506 +    functionIndices[resolve] = newIndex;
   1.507 +    return newIndex;
   1.508 +  }
   1.509 +
   1.510 +  function parseSymbol(symbol) {
   1.511 +    var info = getFunctionInfo(symbol);
   1.512 +    //dump("Parse symbol: " + symbol + "\n");
   1.513 +    return {
   1.514 +      symbolName: symbol,
   1.515 +      functionName: info.functionName,
   1.516 +      functionIndex: indexForFunction(symbol, info),
   1.517 +      lineInformation: info.lineInformation,
   1.518 +      isRoot: info.isRoot,
   1.519 +      isJSFrame: info.isJSFrame,
   1.520 +      scriptLocation: info.scriptLocation
   1.521 +    };
   1.522 +  }
   1.523 +
   1.524 +  function translatedSymbol(symbol) {
   1.525 +    return symbolicationTable[symbol] || symbol;
   1.526 +  }
   1.527 +
   1.528 +  function indexForSymbol(symbol) {
   1.529 +    if (symbol in symbolIndices)
   1.530 +      return symbolIndices[symbol];
   1.531 +    var newIndex = symbols.length;
   1.532 +    symbols[newIndex] = parseSymbol(translatedSymbol(symbol));
   1.533 +    symbolIndices[symbol] = newIndex;
   1.534 +    return newIndex;
   1.535 +  }
   1.536 +
   1.537 +  function clearRegExpLastMatch() {
   1.538 +    /./.exec(" ");
   1.539 +  }
   1.540 +
   1.541 +  function shouldIncludeARMLRForPC(pcIndex) {
   1.542 +    if (pcIndex in armIncludePCIndex)
   1.543 +      return armIncludePCIndex[pcIndex];
   1.544 +
   1.545 +    var pcName = symbols[pcIndex].functionName;
   1.546 +    var include = sARMFunctionsWithValidLR.indexOf(pcName) != -1;
   1.547 +    armIncludePCIndex[pcIndex] = include;
   1.548 +    return include;
   1.549 +  }
   1.550 +
   1.551 +  function parseProfileString(data) {
   1.552 +    var extraInfo = {};
   1.553 +    var lines = data.split("\n");
   1.554 +    var sample = null;
   1.555 +    for (var i = 0; i < lines.length; ++i) {
   1.556 +      var line = lines[i];
   1.557 +      if (line.length < 2 || line[1] != '-') {
   1.558 +        // invalid line, ignore it
   1.559 +        continue;
   1.560 +      }
   1.561 +      var info = line.substring(2);
   1.562 +      switch (line[0]) {
   1.563 +      //case 'l':
   1.564 +      //  // leaf name
   1.565 +      //  if ("leafName" in extraInfo) {
   1.566 +      //    extraInfo.leafName += ":" + info;
   1.567 +      //  } else {
   1.568 +      //    extraInfo.leafName = info;
   1.569 +      //  }
   1.570 +      //  break;
   1.571 +      case 'm':
   1.572 +        // marker
   1.573 +        if (!("marker" in extraInfo)) {
   1.574 +          extraInfo.marker = [];
   1.575 +        }
   1.576 +        extraInfo.marker.push(info);
   1.577 +        break;
   1.578 +      case 's':
   1.579 +        // sample
   1.580 +        var sampleName = info;
   1.581 +        sample = makeSample([indexForSymbol(sampleName)], extraInfo);
   1.582 +        samples.push(sample);
   1.583 +        extraInfo = {}; // reset the extra info for future rounds
   1.584 +        break;
   1.585 +      case 'c':
   1.586 +      case 'l':
   1.587 +        // continue sample
   1.588 +        if (sample) { // ignore the case where we see a 'c' before an 's'
   1.589 +          sample.frames.push(indexForSymbol(info));
   1.590 +        }
   1.591 +        break;
   1.592 +      case 'L':
   1.593 +        // continue sample; this is an ARM LR record.  Stick it before the
   1.594 +        // PC if it's one of the functions where we know LR is good.
   1.595 +        if (sample && sample.frames.length > 1) {
   1.596 +          var pcIndex = sample.frames[sample.frames.length - 1];
   1.597 +          if (shouldIncludeARMLRForPC(pcIndex)) {
   1.598 +            sample.frames.splice(-1, 0, indexForSymbol(info));
   1.599 +          }
   1.600 +        }
   1.601 +        break;
   1.602 +      case 't':
   1.603 +        // time
   1.604 +        if (sample) {
   1.605 +          sample.extraInfo["time"] = parseFloat(info);
   1.606 +        }
   1.607 +        break;
   1.608 +      case 'r':
   1.609 +        // responsiveness
   1.610 +        if (sample) {
   1.611 +          sample.extraInfo["responsiveness"] = parseFloat(info);
   1.612 +        }
   1.613 +        break;
   1.614 +      }
   1.615 +      progressReporter.setProgress((i + 1) / lines.length);
   1.616 +    }
   1.617 +  }
   1.618 +
   1.619 +  function parseProfileJSON(profile) {
   1.620 +    // Thread 0 will always be the main thread of interest
   1.621 +    // TODO support all the thread in the profile
   1.622 +    var profileSamples = null;
   1.623 +    meta = profile.meta || {};
   1.624 +    if (params.appendVideoCapture) {
   1.625 +      meta.videoCapture = {
   1.626 +        src: params.appendVideoCapture,
   1.627 +      };
   1.628 +    }
   1.629 +    // Support older format that aren't thread aware
   1.630 +    if (profile.threads != null) {
   1.631 +      profileSamples = profile.threads[0].samples;
   1.632 +    } else {
   1.633 +      profileSamples = profile;
   1.634 +    }
   1.635 +    var rootSymbol = null;
   1.636 +    var insertCommonRoot = false;
   1.637 +    var frameStart = {};
   1.638 +    meta.frameStart = frameStart;
   1.639 +    for (var j = 0; j < profileSamples.length; j++) {
   1.640 +      var sample = profileSamples[j];
   1.641 +      var indicedFrames = [];
   1.642 +      if (!sample) {
   1.643 +        // This sample was filtered before saving
   1.644 +        samples.push(null);
   1.645 +        progressReporter.setProgress((j + 1) / profileSamples.length);
   1.646 +        continue;
   1.647 +      }
   1.648 +      for (var k = 0; sample.frames && k < sample.frames.length; k++) {
   1.649 +        var frame = sample.frames[k];
   1.650 +        var pcIndex;
   1.651 +        if (frame.location !== undefined) {
   1.652 +          pcIndex = indexForSymbol(frame.location);
   1.653 +        } else {
   1.654 +          pcIndex = indexForSymbol(frame);
   1.655 +        }
   1.656 +
   1.657 +        if (frame.lr !== undefined && shouldIncludeARMLRForPC(pcIndex)) {
   1.658 +          indicedFrames.push(indexForSymbol(frame.lr));
   1.659 +        }
   1.660 +
   1.661 +        indicedFrames.push(pcIndex);
   1.662 +      }
   1.663 +      if (indicedFrames.length >= 1) {
   1.664 +        if (rootSymbol && rootSymbol != indicedFrames[0]) {
   1.665 +          insertCommonRoot = true;
   1.666 +        }
   1.667 +        rootSymbol = rootSymbol || indicedFrames[0];
   1.668 +      }
   1.669 +      if (sample.extraInfo == null) {
   1.670 +        sample.extraInfo = {};
   1.671 +      }
   1.672 +      if (sample.responsiveness) {
   1.673 +        sample.extraInfo["responsiveness"] = sample.responsiveness;
   1.674 +      }
   1.675 +      if (sample.marker) {
   1.676 +        sample.extraInfo["marker"] = sample.marker;
   1.677 +      }
   1.678 +      if (sample.time) {
   1.679 +        sample.extraInfo["time"] = sample.time;
   1.680 +      }
   1.681 +      if (sample.frameNumber) {
   1.682 +        sample.extraInfo["frameNumber"] = sample.frameNumber;
   1.683 +        //dump("Got frame number: " + sample.frameNumber + "\n");
   1.684 +        frameStart[sample.frameNumber] = samples.length;
   1.685 +      }
   1.686 +      samples.push(makeSample(indicedFrames, sample.extraInfo));
   1.687 +      progressReporter.setProgress((j + 1) / profileSamples.length);
   1.688 +    }
   1.689 +    if (insertCommonRoot) {
   1.690 +      var rootIndex = indexForSymbol("(root)");
   1.691 +      for (var i = 0; i < samples.length; i++) {
   1.692 +        var sample = samples[i];
   1.693 +        if (!sample) continue;
   1.694 +        // If length == 0 then the sample was filtered when saving the profile
   1.695 +        if (sample.frames.length >= 1 && sample.frames[0] != rootIndex)
   1.696 +          sample.frames.unshift(rootIndex)
   1.697 +      }
   1.698 +    }
   1.699 +  }
   1.700 +
   1.701 +  progressReporter.finish();
   1.702 +  // Don't increment the profile ID now because (1) it's buggy
   1.703 +  // and (2) for now there's no point in storing each profile
   1.704 +  // here if we're storing them in the local storage.
   1.705 +  //var profileID = gNextProfileID++;
   1.706 +  var profileID = gNextProfileID;
   1.707 +  gProfiles[profileID] = JSON.parse(JSON.stringify({
   1.708 +    meta: meta,
   1.709 +    symbols: symbols,
   1.710 +    functions: functions,
   1.711 +    resources: resources,
   1.712 +    allSamples: samples
   1.713 +  }));
   1.714 +  clearRegExpLastMatch();
   1.715 +  sendFinished(requestID, {
   1.716 +    meta: meta,
   1.717 +    numSamples: samples.length,
   1.718 +    profileID: profileID,
   1.719 +    symbols: symbols,
   1.720 +    functions: functions,
   1.721 +    resources: resources
   1.722 +  });
   1.723 +}
   1.724 +
   1.725 +function getSerializedProfile(requestID, profileID, complete) {
   1.726 +  var profile = gProfiles[profileID];
   1.727 +  var symbolicationTable = {};
   1.728 +  if (complete || !profile.filterSettings.mergeFunctions) {
   1.729 +    for (var symbolIndex in profile.symbols) {
   1.730 +      symbolicationTable[symbolIndex] = profile.symbols[symbolIndex].symbolName;
   1.731 +    }
   1.732 +  } else {
   1.733 +    for (var functionIndex in profile.functions) {
   1.734 +      var f = profile.functions[functionIndex];
   1.735 +      symbolicationTable[functionIndex] = f.symbol;
   1.736 +    }
   1.737 +  }
   1.738 +  var serializedProfile = JSON.stringify({
   1.739 +    format: "profileJSONWithSymbolicationTable,1",
   1.740 +    meta: profile.meta,
   1.741 +    profileJSON: complete ? profile.allSamples : profile.filteredSamples,
   1.742 +    symbolicationTable: symbolicationTable
   1.743 +  });
   1.744 +  sendFinished(requestID, serializedProfile);
   1.745 +}
   1.746 +
   1.747 +function TreeNode(name, parent, startCount) {
   1.748 +  this.name = name;
   1.749 +  this.children = [];
   1.750 +  this.counter = startCount;
   1.751 +  this.parent = parent;
   1.752 +}
   1.753 +TreeNode.prototype.getDepth = function TreeNode__getDepth() {
   1.754 +  if (this.parent)
   1.755 +    return this.parent.getDepth() + 1;
   1.756 +  return 0;
   1.757 +};
   1.758 +TreeNode.prototype.findChild = function TreeNode_findChild(name) {
   1.759 +  for (var i = 0; i < this.children.length; i++) {
   1.760 +    var child = this.children[i];
   1.761 +    if (child.name == name)
   1.762 +      return child;
   1.763 +  }
   1.764 +  return null;
   1.765 +}
   1.766 +// path is an array of strings which is matched to our nodes' names.
   1.767 +// Try to walk path in our own tree and return the last matching node. The
   1.768 +// length of the match can be calculated by the caller by comparing the
   1.769 +// returned node's depth with the depth of the path's start node.
   1.770 +TreeNode.prototype.followPath = function TreeNode_followPath(path) {
   1.771 +  if (path.length == 0)
   1.772 +    return this;
   1.773 +
   1.774 +  var matchingChild = this.findChild(path[0]);
   1.775 +  if (!matchingChild)
   1.776 +    return this;
   1.777 +
   1.778 +  return matchingChild.followPath(path.slice(1));
   1.779 +};
   1.780 +TreeNode.prototype.incrementCountersInParentChain = function TreeNode_incrementCountersInParentChain() {
   1.781 +  this.counter++;
   1.782 +  if (this.parent)
   1.783 +    this.parent.incrementCountersInParentChain();
   1.784 +};
   1.785 +
   1.786 +function convertToCallTree(samples, isReverse) {
   1.787 +  function areSamplesMultiroot(samples) {
   1.788 +    var previousRoot;
   1.789 +    for (var i = 0; i < samples.length; ++i) {
   1.790 +      if (!previousRoot) {
   1.791 +        previousRoot = samples[i].frames[0];
   1.792 +        continue;
   1.793 +      }
   1.794 +      if (previousRoot != samples[i].frames[0]) {
   1.795 +        return true;
   1.796 +      }
   1.797 +    }
   1.798 +    return false;
   1.799 +  }
   1.800 +  samples = samples.filter(function noNullSamples(sample) {
   1.801 +    return sample != null;
   1.802 +  });
   1.803 +  if (samples.length == 0)
   1.804 +    return new TreeNode("(empty)", null, 0);
   1.805 +  var firstRoot = null;
   1.806 +  for (var i = 0; i < samples.length; ++i) {
   1.807 +    firstRoot = samples[i].frames[0];
   1.808 +    break;
   1.809 +  }
   1.810 +  if (firstRoot == null) {
   1.811 +    return new TreeNode("(all filtered)", null, 0);
   1.812 +  }
   1.813 +  var multiRoot = areSamplesMultiroot(samples);
   1.814 +  var treeRoot = new TreeNode((isReverse || multiRoot) ? "(total)" : firstRoot, null, 0);
   1.815 +  for (var i = 0; i < samples.length; ++i) {
   1.816 +    var sample = samples[i];
   1.817 +    var callstack = sample.frames.slice(0);
   1.818 +    callstack.shift();
   1.819 +    if (isReverse)
   1.820 +      callstack.reverse();
   1.821 +    var deepestExistingNode = treeRoot.followPath(callstack);
   1.822 +    var remainingCallstack = callstack.slice(deepestExistingNode.getDepth());
   1.823 +    deepestExistingNode.incrementCountersInParentChain();
   1.824 +    var node = deepestExistingNode;
   1.825 +    for (var j = 0; j < remainingCallstack.length; ++j) {
   1.826 +      var frame = remainingCallstack[j];
   1.827 +      var child = new TreeNode(frame, node, 1);
   1.828 +      node.children.push(child);
   1.829 +      node = child;
   1.830 +    }
   1.831 +  }
   1.832 +  return treeRoot;
   1.833 +}
   1.834 +
   1.835 +function filterByJank(samples, filterThreshold) {
   1.836 +  return samples.map(function nullNonJank(sample) {
   1.837 +    if (!sample ||
   1.838 +        !("responsiveness" in sample.extraInfo) ||
   1.839 +        sample.extraInfo["responsiveness"] < filterThreshold)
   1.840 +      return null;
   1.841 +    return sample;
   1.842 +  });
   1.843 +}
   1.844 +
   1.845 +function filterBySymbol(samples, symbolOrFunctionIndex) {
   1.846 +  return samples.map(function filterSample(origSample) {
   1.847 +    if (!origSample)
   1.848 +      return null;
   1.849 +    var sample = cloneSample(origSample);
   1.850 +    for (var i = 0; i < sample.frames.length; i++) {
   1.851 +      if (symbolOrFunctionIndex == sample.frames[i]) {
   1.852 +        sample.frames = sample.frames.slice(i);
   1.853 +        return sample;
   1.854 +      }
   1.855 +    }
   1.856 +    return null; // no frame matched; filter out complete sample
   1.857 +  });
   1.858 +}
   1.859 +
   1.860 +function filterByCallstackPrefix(samples, symbols, functions, callstack, appliesToJS, useFunctions) {
   1.861 +  var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
   1.862 +      return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
   1.863 +    } : function isJSSymbolOrRoot(symbolIndex) {
   1.864 +      return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
   1.865 +    };
   1.866 +  return samples.map(function filterSample(sample) {
   1.867 +    if (!sample)
   1.868 +      return null;
   1.869 +    if (sample.frames.length < callstack.length)
   1.870 +      return null;
   1.871 +    for (var i = 0, j = 0; j < callstack.length; i++) {
   1.872 +      if (i >= sample.frames.length)
   1.873 +        return null;
   1.874 +      if (appliesToJS && !isJSFrameOrRoot(sample.frames[i]))
   1.875 +        continue;
   1.876 +      if (sample.frames[i] != callstack[j])
   1.877 +        return null;
   1.878 +      j++;
   1.879 +    }
   1.880 +    return makeSample(sample.frames.slice(i - 1), sample.extraInfo);
   1.881 +  });
   1.882 +}
   1.883 +
   1.884 +function filterByCallstackPostfix(samples, symbols, functions, callstack, appliesToJS, useFunctions) {
   1.885 +  var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
   1.886 +      return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
   1.887 +    } : function isJSSymbolOrRoot(symbolIndex) {
   1.888 +      return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
   1.889 +    };
   1.890 +  return samples.map(function filterSample(sample) {
   1.891 +    if (!sample)
   1.892 +      return null;
   1.893 +    if (sample.frames.length < callstack.length)
   1.894 +      return null;
   1.895 +    for (var i = 0, j = 0; j < callstack.length; i++) {
   1.896 +      if (i >= sample.frames.length)
   1.897 +        return null;
   1.898 +      if (appliesToJS && !isJSFrameOrRoot(sample.frames[sample.frames.length - i - 1]))
   1.899 +        continue;
   1.900 +      if (sample.frames[sample.frames.length - i - 1] != callstack[j])
   1.901 +        return null;
   1.902 +      j++;
   1.903 +    }
   1.904 +    var newFrames = sample.frames.slice(0, sample.frames.length - i + 1);
   1.905 +    return makeSample(newFrames, sample.extraInfo);
   1.906 +  });
   1.907 +}
   1.908 +
   1.909 +function chargeNonJSToCallers(samples, symbols, functions, useFunctions) {
   1.910 +  var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
   1.911 +      return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
   1.912 +    } : function isJSSymbolOrRoot(symbolIndex) {
   1.913 +      return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
   1.914 +    };
   1.915 +  samples = samples.slice(0);
   1.916 +  for (var i = 0; i < samples.length; ++i) {
   1.917 +    var sample = samples[i];
   1.918 +    if (!sample)
   1.919 +      continue;
   1.920 +    var newFrames = sample.frames.filter(isJSFrameOrRoot);
   1.921 +    if (!newFrames.length) {
   1.922 +      samples[i] = null;
   1.923 +    } else {
   1.924 +      samples[i].frames = newFrames;
   1.925 +    }
   1.926 +  }
   1.927 +  return samples;
   1.928 +}
   1.929 +
   1.930 +function filterByName(samples, symbols, functions, filterName, useFunctions) {
   1.931 +  function getSymbolOrFunctionName(index, useFunctions) {
   1.932 +    if (useFunctions) {
   1.933 +      if (!(index in functions))
   1.934 +        return "";
   1.935 +      return functions[index].functionName;
   1.936 +    }
   1.937 +    if (!(index in symbols))
   1.938 +      return "";
   1.939 +    return symbols[index].symbolName;
   1.940 +  }
   1.941 +  function getLibraryName(index, useFunctions) {
   1.942 +    if (useFunctions) {
   1.943 +      if (!(index in functions))
   1.944 +        return "";
   1.945 +      return functions[index].libraryName;
   1.946 +    }
   1.947 +    if (!(index in symbols))
   1.948 +      return "";
   1.949 +    return symbols[index].libraryName;
   1.950 +  }
   1.951 +  samples = samples.slice(0);
   1.952 +  filterName = filterName.toLowerCase();
   1.953 +  calltrace_it: for (var i = 0; i < samples.length; ++i) {
   1.954 +    var sample = samples[i];
   1.955 +    if (!sample)
   1.956 +      continue;
   1.957 +    var callstack = sample.frames;
   1.958 +    for (var j = 0; j < callstack.length; ++j) { 
   1.959 +      var symbolOrFunctionName = getSymbolOrFunctionName(callstack[j], useFunctions);
   1.960 +      var libraryName = getLibraryName(callstack[j], useFunctions);
   1.961 +      if (symbolOrFunctionName.toLowerCase().indexOf(filterName) != -1 || 
   1.962 +          libraryName.toLowerCase().indexOf(filterName) != -1) {
   1.963 +        continue calltrace_it;
   1.964 +      }
   1.965 +    }
   1.966 +    samples[i] = null;
   1.967 +  }
   1.968 +  return samples;
   1.969 +}
   1.970 +
   1.971 +function discardLineLevelInformation(samples, symbols, functions) {
   1.972 +  var data = samples;
   1.973 +  var filteredData = [];
   1.974 +  for (var i = 0; i < data.length; i++) {
   1.975 +    if (!data[i]) {
   1.976 +      filteredData.push(null);
   1.977 +      continue;
   1.978 +    }
   1.979 +    filteredData.push(cloneSample(data[i]));
   1.980 +    var frames = filteredData[i].frames;
   1.981 +    for (var j = 0; j < frames.length; j++) {
   1.982 +      if (!(frames[j] in symbols))
   1.983 +        continue;
   1.984 +      frames[j] = symbols[frames[j]].functionIndex;
   1.985 +    }
   1.986 +  }
   1.987 +  return filteredData;
   1.988 +}
   1.989 +
   1.990 +function mergeUnbranchedCallPaths(root) {
   1.991 +  var mergedNames = [root.name];
   1.992 +  var node = root;
   1.993 +  while (node.children.length == 1 && node.counter == node.children[0].counter) {
   1.994 +    node = node.children[0];
   1.995 +    mergedNames.push(node.name);
   1.996 +  }
   1.997 +  if (node != root) {
   1.998 +    // Merge path from root to node into root.
   1.999 +    root.children = node.children;
  1.1000 +    root.mergedNames = mergedNames;
  1.1001 +    //root.name = clipText(root.name, 50) + " to " + this._clipText(node.name, 50);
  1.1002 +  }
  1.1003 +  for (var i = 0; i < root.children.length; i++) {
  1.1004 +    mergeUnbranchedCallPaths(root.children[i]);
  1.1005 +  }
  1.1006 +}
  1.1007 +
  1.1008 +function FocusedFrameSampleFilter(focusedSymbol) {
  1.1009 +  this._focusedSymbol = focusedSymbol;
  1.1010 +}
  1.1011 +FocusedFrameSampleFilter.prototype = {
  1.1012 +  filter: function FocusedFrameSampleFilter_filter(samples, symbols, functions, useFunctions) {
  1.1013 +    return filterBySymbol(samples, this._focusedSymbol);
  1.1014 +  }
  1.1015 +};
  1.1016 +
  1.1017 +function FocusedCallstackPrefixSampleFilter(focusedCallstack, appliesToJS) {
  1.1018 +  this._focusedCallstackPrefix = focusedCallstack;
  1.1019 +  this._appliesToJS = appliesToJS;
  1.1020 +}
  1.1021 +FocusedCallstackPrefixSampleFilter.prototype = {
  1.1022 +  filter: function FocusedCallstackPrefixSampleFilter_filter(samples, symbols, functions, useFunctions) {
  1.1023 +    return filterByCallstackPrefix(samples, symbols, functions, this._focusedCallstackPrefix, this._appliesToJS, useFunctions);
  1.1024 +  }
  1.1025 +};
  1.1026 +
  1.1027 +function FocusedCallstackPostfixSampleFilter(focusedCallstack, appliesToJS) {
  1.1028 +  this._focusedCallstackPostfix = focusedCallstack;
  1.1029 +  this._appliesToJS = appliesToJS;
  1.1030 +}
  1.1031 +FocusedCallstackPostfixSampleFilter.prototype = {
  1.1032 +  filter: function FocusedCallstackPostfixSampleFilter_filter(samples, symbols, functions, useFunctions) {
  1.1033 +    return filterByCallstackPostfix(samples, symbols, functions, this._focusedCallstackPostfix, this._appliesToJS, useFunctions);
  1.1034 +  }
  1.1035 +};
  1.1036 +
  1.1037 +function RangeSampleFilter(start, end) {
  1.1038 +  this._start = start;
  1.1039 +  this._end = end;
  1.1040 +}
  1.1041 +RangeSampleFilter.prototype = {
  1.1042 +  filter: function RangeSampleFilter_filter(samples, symbols, functions) {
  1.1043 +    return samples.slice(this._start, this._end);
  1.1044 +  }
  1.1045 +}
  1.1046 +
  1.1047 +function unserializeSampleFilters(filters) {
  1.1048 +  return filters.map(function (filter) {
  1.1049 +    switch (filter.type) {
  1.1050 +      case "FocusedFrameSampleFilter":
  1.1051 +        return new FocusedFrameSampleFilter(filter.focusedSymbol);
  1.1052 +      case "FocusedCallstackPrefixSampleFilter":
  1.1053 +        return new FocusedCallstackPrefixSampleFilter(filter.focusedCallstack, filter.appliesToJS);
  1.1054 +      case "FocusedCallstackPostfixSampleFilter":
  1.1055 +        return new FocusedCallstackPostfixSampleFilter(filter.focusedCallstack, filter.appliesToJS);
  1.1056 +      case "RangeSampleFilter":
  1.1057 +        return new RangeSampleFilter(filter.start, filter.end);
  1.1058 +      case "PluginView":
  1.1059 +        return null;
  1.1060 +      default:
  1.1061 +        throw new Error("Unknown filter");
  1.1062 +    }
  1.1063 +  })
  1.1064 +}
  1.1065 +
  1.1066 +var gJankThreshold = 50 /* ms */;
  1.1067 +
  1.1068 +function updateFilters(requestID, profileID, filters) {
  1.1069 +  var profile = gProfiles[profileID];
  1.1070 +  var samples = profile.allSamples;
  1.1071 +  var symbols = profile.symbols;
  1.1072 +  var functions = profile.functions;
  1.1073 +
  1.1074 +  if (filters.mergeFunctions) {
  1.1075 +    samples = discardLineLevelInformation(samples, symbols, functions);
  1.1076 +  }
  1.1077 +  if (filters.nameFilter) {
  1.1078 +    try {
  1.1079 +      samples = filterByName(samples, symbols, functions, filters.nameFilter, filters.mergeFunctions);
  1.1080 +    } catch (e) {
  1.1081 +      dump("Could not filer by name: " + e + "\n");
  1.1082 +    }
  1.1083 +  }
  1.1084 +  samples = unserializeSampleFilters(filters.sampleFilters).reduce(function (filteredSamples, currentFilter) {
  1.1085 +    if (currentFilter===null) return filteredSamples;
  1.1086 +    return currentFilter.filter(filteredSamples, symbols, functions, filters.mergeFunctions);
  1.1087 +  }, samples);
  1.1088 +  if (filters.jankOnly) {
  1.1089 +    samples = filterByJank(samples, gJankThreshold);
  1.1090 +  }
  1.1091 +  if (filters.javascriptOnly) {
  1.1092 +    samples = chargeNonJSToCallers(samples, symbols, functions, filters.mergeFunctions);
  1.1093 +  }
  1.1094 +
  1.1095 +  gProfiles[profileID].filterSettings = filters;
  1.1096 +  gProfiles[profileID].filteredSamples = samples;
  1.1097 +  sendFinishedInChunks(requestID, samples, 40000,
  1.1098 +                       function (sample) { return sample ? sample.frames.length : 1; });
  1.1099 +}
  1.1100 +
  1.1101 +function updateViewOptions(requestID, profileID, options) {
  1.1102 +  var profile = gProfiles[profileID];
  1.1103 +  var samples = profile.filteredSamples;
  1.1104 +  var symbols = profile.symbols;
  1.1105 +  var functions = profile.functions;
  1.1106 +
  1.1107 +  var treeData = convertToCallTree(samples, options.invertCallstack);
  1.1108 +  if (options.mergeUnbranched)
  1.1109 +    mergeUnbranchedCallPaths(treeData);
  1.1110 +  sendFinished(requestID, treeData);
  1.1111 +}
  1.1112 +
  1.1113 +// The responsiveness threshold (in ms) after which the sample shuold become
  1.1114 +// completely red in the histogram.
  1.1115 +var kDelayUntilWorstResponsiveness = 1000;
  1.1116 +
  1.1117 +function calculateHistogramData(requestID, profileID) {
  1.1118 +
  1.1119 +  function getStepColor(step) {
  1.1120 +    if (step.extraInfo && "responsiveness" in step.extraInfo) {
  1.1121 +      var res = step.extraInfo.responsiveness;
  1.1122 +      var redComponent = Math.round(255 * Math.min(1, res / kDelayUntilWorstResponsiveness));
  1.1123 +      return "rgb(" + redComponent + ",0,0)";
  1.1124 +    }
  1.1125 +
  1.1126 +    return "rgb(0,0,0)";
  1.1127 +  }
  1.1128 +
  1.1129 +  var profile = gProfiles[profileID];
  1.1130 +  var data = profile.filteredSamples;
  1.1131 +  var histogramData = [];
  1.1132 +  var maxHeight = 0;
  1.1133 +  for (var i = 0; i < data.length; ++i) {
  1.1134 +    if (!data[i])
  1.1135 +      continue;
  1.1136 +    var value = data[i].frames.length;
  1.1137 +    if (maxHeight < value)
  1.1138 +      maxHeight = value;
  1.1139 +  }
  1.1140 +  maxHeight += 1;
  1.1141 +  var nextX = 0;
  1.1142 +  // The number of data items per histogramData rects.
  1.1143 +  // Except when seperated by a marker.
  1.1144 +  // This is used to cut down the number of rects, since
  1.1145 +  // there's no point in having more rects then pixels
  1.1146 +  var samplesPerStep = Math.max(1, Math.floor(data.length / 2000));
  1.1147 +  var frameStart = {};
  1.1148 +  for (var i = 0; i < data.length; i++) {
  1.1149 +    var step = data[i];
  1.1150 +    if (!step) {
  1.1151 +      // Add a gap for the sample that was filtered out.
  1.1152 +      nextX += 1 / samplesPerStep;
  1.1153 +      continue;
  1.1154 +    }
  1.1155 +    nextX = Math.ceil(nextX);
  1.1156 +    var value = step.frames.length / maxHeight;
  1.1157 +    var frames = step.frames;
  1.1158 +    var currHistogramData = histogramData[histogramData.length-1];
  1.1159 +    if (step.extraInfo && "marker" in step.extraInfo) {
  1.1160 +      // A new marker boundary has been discovered.
  1.1161 +      histogramData.push({
  1.1162 +        frames: "marker",
  1.1163 +        x: nextX,
  1.1164 +        width: 2,
  1.1165 +        value: 1,
  1.1166 +        marker: step.extraInfo.marker,
  1.1167 +        color: "fuchsia"
  1.1168 +      });
  1.1169 +      nextX += 2;
  1.1170 +      histogramData.push({
  1.1171 +        frames: [step.frames],
  1.1172 +        x: nextX,
  1.1173 +        width: 1,
  1.1174 +        value: value,
  1.1175 +        color: getStepColor(step),
  1.1176 +      });
  1.1177 +      nextX += 1;
  1.1178 +    } else if (currHistogramData != null &&
  1.1179 +      currHistogramData.frames.length < samplesPerStep &&
  1.1180 +      !(step.extraInfo && "frameNumber" in step.extraInfo)) {
  1.1181 +      currHistogramData.frames.push(step.frames);
  1.1182 +      // When merging data items take the average:
  1.1183 +      currHistogramData.value =
  1.1184 +        (currHistogramData.value * (currHistogramData.frames.length - 1) + value) /
  1.1185 +        currHistogramData.frames.length;
  1.1186 +      // Merge the colors? For now we keep the first color set.
  1.1187 +    } else {
  1.1188 +      // A new name boundary has been discovered.
  1.1189 +      currHistogramData = {
  1.1190 +        frames: [step.frames],
  1.1191 +        x: nextX,
  1.1192 +        width: 1,
  1.1193 +        value: value,
  1.1194 +        color: getStepColor(step),
  1.1195 +      };
  1.1196 +      if (step.extraInfo && "frameNumber" in step.extraInfo) {
  1.1197 +        currHistogramData.frameNumber = step.extraInfo.frameNumber;
  1.1198 +        frameStart[step.extraInfo.frameNumber] = histogramData.length;
  1.1199 +      }
  1.1200 +      histogramData.push(currHistogramData);
  1.1201 +      nextX += 1;
  1.1202 +    }
  1.1203 +  }
  1.1204 +  sendFinished(requestID, { histogramData: histogramData, frameStart: frameStart, widthSum: Math.ceil(nextX) });
  1.1205 +}
  1.1206 +
  1.1207 +var diagnosticList = [
  1.1208 +  // *************** Known bugs first (highest priority)
  1.1209 +  {
  1.1210 +    image: "io.png",
  1.1211 +    title: "Main Thread IO - Bug 765135 - TISCreateInputSourceList",
  1.1212 +    check: function(frames, symbols, meta) {
  1.1213 +
  1.1214 +      if (!stepContains('TISCreateInputSourceList', frames, symbols))
  1.1215 +        return false;
  1.1216 +
  1.1217 +      return stepContains('__getdirentries64', frames, symbols) 
  1.1218 +          || stepContains('__read', frames, symbols) 
  1.1219 +          || stepContains('__open', frames, symbols) 
  1.1220 +          || stepContains('stat$INODE64', frames, symbols)
  1.1221 +          ;
  1.1222 +    },
  1.1223 +  },
  1.1224 +
  1.1225 +  {
  1.1226 +    image: "js.png",
  1.1227 +    title: "Bug 772916 - Gradients are slow on mobile",
  1.1228 +    bugNumber: "772916",
  1.1229 +    check: function(frames, symbols, meta) {
  1.1230 +
  1.1231 +      return stepContains('PaintGradient', frames, symbols)
  1.1232 +          && stepContains('ClientTiledLayerBuffer::PaintThebesSingleBufferDraw', frames, symbols)
  1.1233 +          ;
  1.1234 +    },
  1.1235 +  },
  1.1236 +  {
  1.1237 +    image: "cache.png",
  1.1238 +    title: "Bug 717761 - Main thread can be blocked by IO on the cache thread",
  1.1239 +    bugNumber: "717761",
  1.1240 +    check: function(frames, symbols, meta) {
  1.1241 +
  1.1242 +      return stepContains('nsCacheEntryDescriptor::GetStoragePolicy', frames, symbols)
  1.1243 +          ;
  1.1244 +    },
  1.1245 +  },
  1.1246 +  {
  1.1247 +    image: "js.png",
  1.1248 +    title: "Web Content Shutdown Notification",
  1.1249 +    check: function(frames, symbols, meta) {
  1.1250 +
  1.1251 +      return stepContains('nsAppStartup::Quit', frames, symbols)
  1.1252 +          && stepContains('nsDocShell::FirePageHideNotification', frames, symbols)
  1.1253 +          ;
  1.1254 +    },
  1.1255 +  },
  1.1256 +  {
  1.1257 +    image: "js.png",
  1.1258 +    title: "Bug 789193 - AMI_startup() takes 200ms on startup",
  1.1259 +    bugNumber: "789193",
  1.1260 +    check: function(frames, symbols, meta) {
  1.1261 +
  1.1262 +      return stepContains('AMI_startup()', frames, symbols)
  1.1263 +          ;
  1.1264 +    },
  1.1265 +  },
  1.1266 +  {
  1.1267 +    image: "js.png",
  1.1268 +    title: "Bug 818296 - [Shutdown] js::NukeCrossCompartmentWrappers takes up 300ms on shutdown",
  1.1269 +    bugNumber: "818296",
  1.1270 +    check: function(frames, symbols, meta) {
  1.1271 +      return stepContains('js::NukeCrossCompartmentWrappers', frames, symbols)
  1.1272 +          && (stepContains('WindowDestroyedEvent', frames, symbols) || stepContains('DoShutdown', frames, symbols))
  1.1273 +          ;
  1.1274 +    },
  1.1275 +  },
  1.1276 +  {
  1.1277 +    image: "js.png",
  1.1278 +    title: "Bug 818274 - [Shutdown] Telemetry takes ~10ms on shutdown",
  1.1279 +    bugNumber: "818274",
  1.1280 +    check: function(frames, symbols, meta) {
  1.1281 +      return stepContains('TelemetryPing.js', frames, symbols)
  1.1282 +          ;
  1.1283 +    },
  1.1284 +  },
  1.1285 +  {
  1.1286 +    image: "plugin.png",
  1.1287 +    title: "Bug 818265 - [Shutdown] Plug-in shutdown takes ~90ms on shutdown",
  1.1288 +    bugNumber: "818265",
  1.1289 +    check: function(frames, symbols, meta) {
  1.1290 +      return stepContains('PluginInstanceParent::Destroy', frames, symbols)
  1.1291 +          ;
  1.1292 +    },
  1.1293 +  },
  1.1294 +  {
  1.1295 +    image: "snapshot.png",
  1.1296 +    title: "Bug 720575 - Make thumbnailing faster and/or asynchronous",
  1.1297 +    bugNumber: "720575",
  1.1298 +    check: function(frames, symbols, meta) {
  1.1299 +      return stepContains('Thumbnails_capture()', frames, symbols)
  1.1300 +          ;
  1.1301 +    },
  1.1302 +  },
  1.1303 +
  1.1304 +  {
  1.1305 +    image: "js.png",
  1.1306 +    title: "Bug 789185 - LoginManagerStorage_mozStorage.init() takes 300ms on startup ",
  1.1307 +    bugNumber: "789185",
  1.1308 +    check: function(frames, symbols, meta) {
  1.1309 +
  1.1310 +      return stepContains('LoginManagerStorage_mozStorage.prototype.init()', frames, symbols)
  1.1311 +          ;
  1.1312 +    },
  1.1313 +  },
  1.1314 +
  1.1315 +  {
  1.1316 +    image: "js.png",
  1.1317 +    title: "JS - Bug 767070 - Text selection performance is bad on android",
  1.1318 +    bugNumber: "767070",
  1.1319 +    check: function(frames, symbols, meta) {
  1.1320 +
  1.1321 +      if (!stepContains('FlushPendingNotifications', frames, symbols))
  1.1322 +        return false;
  1.1323 +
  1.1324 +      return stepContains('sh_', frames, symbols)
  1.1325 +          && stepContains('browser.js', frames, symbols)
  1.1326 +          ;
  1.1327 +    },
  1.1328 +  },
  1.1329 +
  1.1330 +  {
  1.1331 +    image: "js.png",
  1.1332 +    title: "JS - Bug 765930 - Reader Mode: Optimize readability check",
  1.1333 +    bugNumber: "765930",
  1.1334 +    check: function(frames, symbols, meta) {
  1.1335 +
  1.1336 +      return stepContains('Readability.js', frames, symbols)
  1.1337 +          ;
  1.1338 +    },
  1.1339 +  },
  1.1340 +
  1.1341 +  // **************** General issues
  1.1342 +  {
  1.1343 +    image: "js.png",
  1.1344 +    title: "JS is triggering a sync reflow",
  1.1345 +    check: function(frames, symbols, meta) {
  1.1346 +      return symbolSequence(['js::RunScript','layout::DoReflow'], frames, symbols) ||
  1.1347 +             symbolSequence(['js::RunScript','layout::Flush'], frames, symbols)
  1.1348 +          ;
  1.1349 +    },
  1.1350 +  },
  1.1351 +
  1.1352 +  {
  1.1353 +    image: "gc.png",
  1.1354 +    title: "Garbage Collection Slice",
  1.1355 +    canMergeWithGC: false,
  1.1356 +    check: function(frames, symbols, meta, step) {
  1.1357 +      var slice = findGCSlice(frames, symbols, meta, step);
  1.1358 +
  1.1359 +      if (slice) {
  1.1360 +        var gcEvent = findGCEvent(frames, symbols, meta, step);
  1.1361 +        //dump("found event matching diagnostic\n");
  1.1362 +        //dump(JSON.stringify(gcEvent) + "\n");
  1.1363 +        return true;
  1.1364 +      }
  1.1365 +      return false;
  1.1366 +    },
  1.1367 +    details: function(frames, symbols, meta, step) {
  1.1368 +      var slice = findGCSlice(frames, symbols, meta, step);
  1.1369 +      if (slice) {
  1.1370 +        return "" +
  1.1371 +          "Reason: " + slice.reason + "\n" +
  1.1372 +          "Slice: " + slice.slice + "\n" +
  1.1373 +          "Pause: " + slice.pause + " ms";
  1.1374 +      }
  1.1375 +      return null;
  1.1376 +    },
  1.1377 +    onclickDetails: function(frames, symbols, meta, step) {
  1.1378 +      var gcEvent = findGCEvent(frames, symbols, meta, step);
  1.1379 +      if (gcEvent) {
  1.1380 +        return JSON.stringify(gcEvent);
  1.1381 +      } else {
  1.1382 +        return null;
  1.1383 +      }
  1.1384 +    },
  1.1385 +  },
  1.1386 +  {
  1.1387 +    image: "cc.png",
  1.1388 +    title: "Cycle Collect",
  1.1389 +    check: function(frames, symbols, meta, step) {
  1.1390 +      var ccEvent = findCCEvent(frames, symbols, meta, step);
  1.1391 +
  1.1392 +      if (ccEvent) {
  1.1393 +        return true;
  1.1394 +      }
  1.1395 +      return false;
  1.1396 +    },
  1.1397 +    details: function(frames, symbols, meta, step) {
  1.1398 +      var ccEvent = findCCEvent(frames, symbols, meta, step);
  1.1399 +      if (ccEvent) {
  1.1400 +        return "" +
  1.1401 +          "Duration: " + ccEvent.duration + " ms\n" +
  1.1402 +          "Suspected: " + ccEvent.suspected;
  1.1403 +      }
  1.1404 +      return null;
  1.1405 +    },
  1.1406 +    onclickDetails: function(frames, symbols, meta, step) {
  1.1407 +      var ccEvent = findCCEvent(frames, symbols, meta, step);
  1.1408 +      if (ccEvent) {
  1.1409 +        return JSON.stringify(ccEvent);
  1.1410 +      } else {
  1.1411 +        return null;
  1.1412 +      }
  1.1413 +    },
  1.1414 +  },
  1.1415 +  {
  1.1416 +    image: "gc.png",
  1.1417 +    title: "Garbage Collection",
  1.1418 +    canMergeWithGC: false,
  1.1419 +    check: function(frames, symbols, meta) {
  1.1420 +      return stepContainsRegEx(/.*Collect.*Runtime.*Invocation.*/, frames, symbols)
  1.1421 +          || stepContains('GarbageCollectNow', frames, symbols) // Label
  1.1422 +          || stepContains('JS_GC(', frames, symbols) // Label
  1.1423 +          || stepContains('CycleCollect__', frames, symbols) // Label
  1.1424 +          ;
  1.1425 +    },
  1.1426 +  },
  1.1427 +  {
  1.1428 +    image: "cc.png",
  1.1429 +    title: "Cycle Collect",
  1.1430 +    check: function(frames, symbols, meta) {
  1.1431 +      return stepContains('nsCycleCollector::Collect', frames, symbols)
  1.1432 +          || stepContains('CycleCollect__', frames, symbols) // Label
  1.1433 +          || stepContains('nsCycleCollectorRunner::Collect', frames, symbols) // Label
  1.1434 +          ;
  1.1435 +    },
  1.1436 +  },
  1.1437 +  {
  1.1438 +    image: "plugin.png",
  1.1439 +    title: "Sync Plugin Constructor",
  1.1440 +    check: function(frames, symbols, meta) {
  1.1441 +      return stepContains('CallPPluginInstanceConstructor', frames, symbols) 
  1.1442 +          || stepContains('CallPCrashReporterConstructor', frames, symbols) 
  1.1443 +          || stepContains('PPluginModuleParent::CallNP_Initialize', frames, symbols)
  1.1444 +          || stepContains('GeckoChildProcessHost::SyncLaunch', frames, symbols)
  1.1445 +          ;
  1.1446 +    },
  1.1447 +  },
  1.1448 +  {
  1.1449 +    image: "text.png",
  1.1450 +    title: "Font Loading",
  1.1451 +    check: function(frames, symbols, meta) {
  1.1452 +      return stepContains('gfxFontGroup::BuildFontList', frames, symbols);
  1.1453 +    },
  1.1454 +  },
  1.1455 +  {
  1.1456 +    image: "io.png",
  1.1457 +    title: "Main Thread IO!",
  1.1458 +    check: function(frames, symbols, meta) {
  1.1459 +      return stepContains('__getdirentries64', frames, symbols) 
  1.1460 +          || stepContains('__open', frames, symbols) 
  1.1461 +          || stepContains('NtFlushBuffersFile', frames, symbols) 
  1.1462 +          || stepContains('storage:::Statement::ExecuteStep', frames, symbols) 
  1.1463 +          || stepContains('__unlink', frames, symbols) 
  1.1464 +          || stepContains('fsync', frames, symbols) 
  1.1465 +          || stepContains('stat$INODE64', frames, symbols)
  1.1466 +          ;
  1.1467 +    },
  1.1468 +  },
  1.1469 +];
  1.1470 +
  1.1471 +function hasJSFrame(frames, symbols) {
  1.1472 +  for (var i = 0; i < frames.length; i++) {
  1.1473 +    if (symbols[frames[i]].isJSFrame === true) {
  1.1474 +      return true;
  1.1475 +    }
  1.1476 +  }
  1.1477 +  return false;
  1.1478 +}
  1.1479 +function findCCEvent(frames, symbols, meta, step) {
  1.1480 +  if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats)
  1.1481 +    return null;
  1.1482 +
  1.1483 +  var time = step.extraInfo.time;
  1.1484 +
  1.1485 +  for (var i = 0; i < meta.gcStats.ccEvents.length; i++) {
  1.1486 +    var ccEvent = meta.gcStats.ccEvents[i];
  1.1487 +    if (ccEvent.start_timestamp <= time && ccEvent.end_timestamp >= time) {
  1.1488 +      //dump("JSON: " + js_beautify(JSON.stringify(ccEvent)) + "\n");
  1.1489 +      return ccEvent;
  1.1490 +    }
  1.1491 +  }
  1.1492 +
  1.1493 +  return null;
  1.1494 +}
  1.1495 +function findGCEvent(frames, symbols, meta, step) {
  1.1496 +  if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats)
  1.1497 +    return null;
  1.1498 +
  1.1499 +  var time = step.extraInfo.time;
  1.1500 +
  1.1501 +  for (var i = 0; i < meta.gcStats.gcEvents.length; i++) {
  1.1502 +    var gcEvent = meta.gcStats.gcEvents[i];
  1.1503 +    if (!gcEvent.slices)
  1.1504 +      continue;
  1.1505 +    for (var j = 0; j < gcEvent.slices.length; j++) {
  1.1506 +      var slice = gcEvent.slices[j];
  1.1507 +      if (slice.start_timestamp <= time && slice.end_timestamp >= time) {
  1.1508 +        return gcEvent;
  1.1509 +      }
  1.1510 +    }
  1.1511 +  }
  1.1512 +
  1.1513 +  return null;
  1.1514 +}
  1.1515 +function findGCSlice(frames, symbols, meta, step) {
  1.1516 +  if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats)
  1.1517 +    return null;
  1.1518 +
  1.1519 +  var time = step.extraInfo.time;
  1.1520 +
  1.1521 +  for (var i = 0; i < meta.gcStats.gcEvents.length; i++) {
  1.1522 +    var gcEvent = meta.gcStats.gcEvents[i];
  1.1523 +    if (!gcEvent.slices)
  1.1524 +      continue;
  1.1525 +    for (var j = 0; j < gcEvent.slices.length; j++) {
  1.1526 +      var slice = gcEvent.slices[j];
  1.1527 +      if (slice.start_timestamp <= time && slice.end_timestamp >= time) {
  1.1528 +        return slice;
  1.1529 +      }
  1.1530 +    }
  1.1531 +  }
  1.1532 +
  1.1533 +  return null;
  1.1534 +}
  1.1535 +function stepContains(substring, frames, symbols) {
  1.1536 +  for (var i = 0; frames && i < frames.length; i++) {
  1.1537 +    if (!(frames[i] in symbols))
  1.1538 +      continue;
  1.1539 +    var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
  1.1540 +    if (frameSym.indexOf(substring) != -1) {
  1.1541 +      return true;
  1.1542 +    }
  1.1543 +  }
  1.1544 +  return false;
  1.1545 +}
  1.1546 +function stepContainsRegEx(regex, frames, symbols) {
  1.1547 +  for (var i = 0; frames && i < frames.length; i++) {
  1.1548 +    if (!(frames[i] in symbols))
  1.1549 +      continue;
  1.1550 +    var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
  1.1551 +    if (regex.exec(frameSym)) {
  1.1552 +      return true;
  1.1553 +    }
  1.1554 +  }
  1.1555 +  return false;
  1.1556 +}
  1.1557 +function symbolSequence(symbolsOrder, frames, symbols) {
  1.1558 +  var symbolIndex = 0;
  1.1559 +  for (var i = 0; frames && i < frames.length; i++) {
  1.1560 +    if (!(frames[i] in symbols))
  1.1561 +      continue;
  1.1562 +    var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
  1.1563 +    var substring = symbolsOrder[symbolIndex];
  1.1564 +    if (frameSym.indexOf(substring) != -1) {
  1.1565 +      symbolIndex++;
  1.1566 +      if (symbolIndex == symbolsOrder.length) {
  1.1567 +        return true;
  1.1568 +      }
  1.1569 +    }
  1.1570 +  }
  1.1571 +  return false;
  1.1572 +}
  1.1573 +function firstMatch(array, matchFunction) {
  1.1574 +  for (var i = 0; i < array.length; i++) {
  1.1575 +    if (matchFunction(array[i]))
  1.1576 +      return array[i];
  1.1577 +  }
  1.1578 +  return undefined;
  1.1579 +}
  1.1580 +
  1.1581 +function calculateDiagnosticItems(requestID, profileID, meta) {
  1.1582 +  /*
  1.1583 +  if (!histogramData || histogramData.length < 1) {
  1.1584 +    sendFinished(requestID, []);
  1.1585 +    return;
  1.1586 +  }*/
  1.1587 +
  1.1588 +  var profile = gProfiles[profileID];
  1.1589 +  //var symbols = profile.symbols;
  1.1590 +  var symbols = profile.functions;
  1.1591 +  var data = profile.filteredSamples;
  1.1592 +
  1.1593 +  var lastStep = data[data.length-1];
  1.1594 +  var widthSum = data.length;
  1.1595 +  var pendingDiagnosticInfo = null;
  1.1596 +
  1.1597 +  var diagnosticItems = [];
  1.1598 +
  1.1599 +  function finishPendingDiagnostic(endX) {
  1.1600 +    if (!pendingDiagnosticInfo)
  1.1601 +      return;
  1.1602 +
  1.1603 +    var diagnostic = pendingDiagnosticInfo.diagnostic;
  1.1604 +    var currDiagnostic = {
  1.1605 +      x: pendingDiagnosticInfo.x / widthSum,
  1.1606 +      width: (endX - pendingDiagnosticInfo.x) / widthSum,
  1.1607 +      imageFile: pendingDiagnosticInfo.diagnostic.image,
  1.1608 +      title: pendingDiagnosticInfo.diagnostic.title,
  1.1609 +      details: pendingDiagnosticInfo.details,
  1.1610 +      onclickDetails: pendingDiagnosticInfo.onclickDetails
  1.1611 +    };
  1.1612 +
  1.1613 +    if (!currDiagnostic.onclickDetails && diagnostic.bugNumber) {
  1.1614 +      currDiagnostic.onclickDetails = "bug " + diagnostic.bugNumber;
  1.1615 +    }
  1.1616 +
  1.1617 +    diagnosticItems.push(currDiagnostic);
  1.1618 +
  1.1619 +    pendingDiagnosticInfo = null;
  1.1620 +  }
  1.1621 +
  1.1622 +/*
  1.1623 +  dump("meta: " + meta.gcStats + "\n");
  1.1624 +  if (meta && meta.gcStats) {
  1.1625 +    dump("GC Stats: " + JSON.stringify(meta.gcStats) + "\n");
  1.1626 +  }
  1.1627 +*/
  1.1628 +
  1.1629 +  data.forEach(function diagnoseStep(step, x) {
  1.1630 +    if (step) {
  1.1631 +      var frames = step.frames;
  1.1632 +
  1.1633 +      var diagnostic = firstMatch(diagnosticList, function (diagnostic) {
  1.1634 +        return diagnostic.check(frames, symbols, meta, step);
  1.1635 +      });
  1.1636 +    }
  1.1637 +
  1.1638 +    if (!diagnostic) {
  1.1639 +      finishPendingDiagnostic(x);
  1.1640 +      return;
  1.1641 +    }
  1.1642 +
  1.1643 +    var details = diagnostic.details ? diagnostic.details(frames, symbols, meta, step) : null;
  1.1644 +
  1.1645 +    if (pendingDiagnosticInfo) {
  1.1646 +      // We're already inside a diagnostic range.
  1.1647 +      if (diagnostic == pendingDiagnosticInfo.diagnostic && pendingDiagnosticInfo.details == details) {
  1.1648 +        // We're still inside the same diagnostic.
  1.1649 +        return;
  1.1650 +      }
  1.1651 +
  1.1652 +      // We have left the old diagnostic and found a new one. Finish the old one.
  1.1653 +      finishPendingDiagnostic(x);
  1.1654 +    }
  1.1655 +
  1.1656 +    pendingDiagnosticInfo = {
  1.1657 +      diagnostic: diagnostic,
  1.1658 +      x: x,
  1.1659 +      details: details,
  1.1660 +      onclickDetails: diagnostic.onclickDetails ? diagnostic.onclickDetails(frames, symbols, meta, step) : null
  1.1661 +    };
  1.1662 +  });
  1.1663 +  if (pendingDiagnosticInfo)
  1.1664 +    finishPendingDiagnostic(data.length);
  1.1665 +
  1.1666 +  sendFinished(requestID, diagnosticItems);
  1.1667 +}

mercurial