MiniProfiler/rack-mini-profiler

View on GitHub
lib/html/includes.js

Summary

Maintainability
F
3 wks
Test Coverage
"use strict";

var _MiniProfiler = (function() {
  var _arguments = arguments;
  var options,
    container,
    controls,
    fetchedIds = (window.MiniProfiler && window.MiniProfiler.fetchedIds) || [],
    fetchingIds =
      (window.MiniProfiler && window.MiniProfiler.fetchingIds) || [],
    // so we never pull down a profiler twice
    ajaxStartTime,
    totalsControl,
    reqs = 0,
    expandedResults = false,
    totalTime = 0,
    totalSqlCount = 0;

  var hasLocalStorage = function hasLocalStorage(keyPrefix) {
    try {
      // attempt to save to localStorage as Safari private windows will throw an error
      localStorage[keyPrefix + "-test"] = "1";
      localStorage.removeItem(keyPrefix + "-test");
      return "localStorage" in window && window["localStorage"] !== null;
    } catch (e) {
      return false;
    }
  };

  var getVersionedKey = function getVersionedKey(keyPrefix) {
    return keyPrefix + "-" + options.version;
  };

  // polyfills as helper functions to avoid conflicts
  // needed for IE11
  // remove and replace with Element.closest when we drop IE11 support
  // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

  var elementMatches =
    Element.prototype.msMatchesSelector ||
    Element.prototype.webkitMatchesSelector;

  var elementClosest = function elementClosest(el, s) {
    if (typeof el.closest === "function") {
      return el.closest(s);
    }

    do {
      if (typeof el.matches === "function") {
        if (el.matches(s)) return el;
      } else {
        if (elementMatches.call(el, s)) return el;
      }

      el = el.parentElement || el.parentNode;
    } while (el !== null && el.nodeType === 1);

    return null;
  };

  var save = function save(keyPrefix, value) {
    if (!hasLocalStorage(keyPrefix)) {
      return;
    } // clear old keys with this prefix, if any

    for (var i = 0; i < localStorage.length; i++) {
      if ((localStorage.key(i) || "").indexOf(keyPrefix) > -1) {
        localStorage.removeItem(localStorage.key(i));
      }
    } // save under this version

    localStorage[getVersionedKey(keyPrefix)] = value;
  };

  var load = function load(keyPrefix) {
    if (!hasLocalStorage(keyPrefix)) {
      return null;
    }

    return localStorage[getVersionedKey(keyPrefix)];
  };

  var getClientPerformance = function getClientPerformance() {
    return window.performance === null ? null : window.performance;
  };

  var fetchResults = function fetchResults(ids) {
    var clientPerformance, clientProbes, i, j, p, id, idx;

    for (i = 0; i < ids.length; i++) {
      id = ids[i];
      clientPerformance = null;
      clientProbes = null;

      if (window.mPt) {
        clientProbes = mPt.results();

        for (j = 0; j < clientProbes.length; j++) {
          clientProbes[j].d = clientProbes[j].d.getTime();
        }

        mPt.flush();
      }

      if (id == options.currentId) {
        clientPerformance = getClientPerformance();

        if (clientPerformance !== null) {
          // ie is buggy strip out functions
          var copy = {
            navigation: {},
            timing: clientPerformance.timing.toJSON()
          };

          if (clientPerformance.navigation) {
            copy.navigation.redirectCount =
              clientPerformance.navigation.redirectCount;
          }

          clientPerformance = copy;
        }
      } else if (
        ajaxStartTime !== null &&
        clientProbes &&
        clientProbes.length > 0
      ) {
        clientPerformance = {
          timing: {
            navigationStart: ajaxStartTime.getTime()
          }
        };
        ajaxStartTime = null;
      }

      if (fetchedIds.indexOf(id) < 0 && fetchingIds.indexOf(id) < 0) {
        idx = fetchingIds.push(id) - 1;

        (function() {
          var request = new XMLHttpRequest();
          var url = options.path + "results";
          var params = {
            id: id,
            clientPerformance: clientPerformance,
            clientProbes: clientProbes,
            popup: 1
          };
          var queryParam = toQueryString(params);
          request.open("POST", url, true);

          request.onload = function() {
            if (request.status >= 200 && request.status < 400) {
              var json = JSON.parse(request.responseText);
              fetchedIds.push(id);

              if (json != "hidden" && MiniProfiler.templates) {
                buttonShow(json);
              }
            }

            fetchingIds.splice(idx, 1);
          };

          request.setRequestHeader("Accept", "application/json");
          request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
          request.setRequestHeader(
            "Content-Type",
            "application/x-www-form-urlencoded"
          );
          request.send(queryParam);
        })();
      }
    }
  };

  var toQueryString = function toQueryString(data, parentKey) {
    var result = [];
    for (var key in data) {
      var val = data[key];
      var newKey = !parentKey ? key : parentKey + "[" + key + "]";
      if (
        typeof val === "object" &&
        !Array.isArray(val) &&
        val !== null &&
        val !== undefined
      ) {
        result[result.length] = toQueryString(val, newKey);
      } else {
        if (Array.isArray(val)) {
          val.forEach(function(v) {
            result[result.length] =
              encodeURIComponent(newKey + "[]") + "=" + encodeURIComponent(v);
          });
        } else if (val === null || val === undefined) {
          result[result.length] = encodeURIComponent(newKey) + "=";
        } else {
          result[result.length] =
            encodeURIComponent(newKey) +
            "=" +
            encodeURIComponent(val.toString());
        }
      }
    }
    return result
      .filter(function(element) {
        return element && element.length > 0;
      })
      .join("&");
  };

  var renderTemplate = function renderTemplate(json) {
    var textDom = MiniProfiler.templates.profilerTemplate(json);
    var tempElement = document.createElement("DIV");
    tempElement.innerHTML = textDom;
    return tempElement.children[0];
  };

  var buttonShow = function buttonShow(json) {
    var result = renderTemplate(json);
    totalTime += parseFloat(json.duration_milliseconds, 10);
    totalSqlCount += parseInt(json.sql_count);
    reqs++;

    if (!controls && options.collapseResults && !expandedResults) {
      if (!totalsControl) {
        toArray(container.querySelectorAll(".profiler-result")).forEach(
          function(el) {
            return (el.style.display = "none");
          }
        );
        totalsControl = document.createElement("div");
        totalsControl.setAttribute("class", "profiler-result");
        totalsControl.innerHTML =
          "<div class='profiler-button profiler-totals'></div>";
        container.appendChild(totalsControl);
        totalsControl.addEventListener("click", function() {
          toArray(
            totalsControl.parentNode.querySelectorAll(".profiler-result")
          ).forEach(function(el) {
            return (el.style.display = "block");
          });
          totalsControl.style.display = "none";
          expandedResults = true;
        });
        toArray(totalsControl.querySelectorAll(".profiler-button")).forEach(
          function(el) {
            return (el.style.display = "block");
          }
        );
      }

      var reqsHtml =
        reqs > 1 ? "<span class='profiler-reqs'>" + reqs + "</span>" : "";
      var sqlHtml =
        options.showTotalSqlCount && totalSqlCount > 0
          ? " / <span class='profiler-number'>" +
            totalSqlCount +
            "</span> <span class='profiler-unit'>sql</span>"
          : "";
      totalsControl.querySelector(".profiler-button").innerHTML =
        "<span class='profiler-number'>" +
        totalTime.toFixed(1) +
        "</span> <span class='profiler-unit'>ms</span>" +
        sqlHtml +
        reqsHtml;
      result.style.display = "none";
    }

    if (controls) result.insertBefore(controls);
    else container.appendChild(result);
    var button = result.querySelector(".profiler-button"),
      popup = result.querySelector(".profiler-popup"); // button will appear in corner with the total profiling duration - click to show details

    button.addEventListener("click", function() {
      buttonClick(button, popup);
    }); // small duration steps and the column with aggregate durations are hidden by default; allow toggling

    toggleHidden(popup); // lightbox in the queries

    toArray(popup.querySelectorAll(".profiler-queries-show")).forEach(function(
      el
    ) {
      el.addEventListener("click", function() {
        queriesShow(this, result);
      });
    }); // limit count

    if (
      container.querySelectorAll(".profiler-result").length >
      options.maxTracesToShow
    ) {
      var elem = container.querySelector(".profiler-result");

      if (elem) {
        elem.parentElement.removeChild(elem);
      }
    }

    button.style.display = "block";
  };

  var toggleHidden = function toggleHidden(popup) {
    var trivial = popup.querySelector(".profiler-toggle-trivial");
    var childrenTime = popup.querySelector(
      ".profiler-toggle-duration-with-children"
    );
    var trivialGaps = popup.parentNode.querySelector(
      ".profiler-toggle-trivial-gaps"
    );

    var toggleIt = function toggleIt(node) {
      var link = node,
        klass =
          "profiler-" +
          link.getAttribute("class").substr("profiler-toggle-".length),
        isHidden = link.textContent.indexOf("show") > -1;
      var elements = toArray(popup.parentNode.querySelectorAll("." + klass));

      if (isHidden) {
        elements.forEach(function(el) {
          return (el.style.display = "table-row");
        });
      } else {
        elements.forEach(function(el) {
          return (el.style.display = "none");
        });
      }

      var text = link.textContent;
      link.textContent = text.replace(
        isHidden ? "show" : "hide",
        isHidden ? "hide" : "show"
      );
      popupPreventHorizontalScroll(popup);
    };

    [childrenTime, trivial, trivialGaps].forEach(function(el) {
      if (el) {
        el.addEventListener("click", function() {
          toggleIt(this);
        });
      }
    }); // if option is set or all our timings are trivial, go ahead and show them

    if (
      trivial &&
      (options.showTrivial || trivial.getAttribute("show-on-load"))
    ) {
      toggleIt(trivial);
    } // if option is set, go ahead and show time with children

    if (childrenTime && options.showChildrenTime) {
      toggleIt(childrenTime);
    }
  };

  var toArray = function toArray(list) {
    var arr = [];

    if (!list) {
      return arr;
    }

    for (var i = 0; i < list.length; i++) {
      arr.push(list[i]);
    }

    return arr;
  };

  var buttonClick = function buttonClick(button, popup) {
    // we're toggling this button/popup
    if (popup.offsetWidth > 0 || popup.offsetHeight > 0) {
      // if visible
      popupHide(button, popup);
    } else {
      var visiblePopups = toArray(
        container.querySelectorAll(".profiler-popup")
      ).filter(function(el) {
        return el.offsetWidth > 0 || el.offsetHeight > 0;
      }); // theirButtons = visiblePopups.siblings(".profiler-button");

      var theirButtons = [];
      visiblePopups.forEach(function(el) {
        theirButtons.push(el.parentNode.querySelector(".profiler-button"));
      }); // hide any other popups

      popupHide(theirButtons, visiblePopups); // before showing the one we clicked

      popupShow(button, popup);
    }
  };

  var popupShow = function popupShow(button, popup) {
    button.classList.add("profiler-button-active");
    popupSetDimensions(button, popup);
    popup.style.display = "block";
    popupPreventHorizontalScroll(popup);
  };

  var popupSetDimensions = function popupSetDimensions(button, popup) {
    var px = button.offsetTop - 1,
      // position next to the button we clicked
      windowHeight = window.innerHeight,
      maxHeight = windowHeight - px - 40, // make sure the popup doesn't extend below the fold
      pxVertical = options.renderVerticalPosition === 'bottom' ? button.offsetParent.clientHeight - 21 - px : px;

    popup.style[options.renderVerticalPosition] = "".concat(pxVertical, "px");
    popup.style.maxHeight = "".concat(maxHeight, "px");
    popup.style[options.renderHorizontalPosition] = "".concat(
      button.offsetWidth - 3,
      "px"
    ); // move left or right, based on config
  };

  var popupPreventHorizontalScroll = function popupPreventHorizontalScroll(
    popup
  ) {
    var childrenHeight = 0;
    toArray(popup.children).forEach(function(el) {
      childrenHeight += el.offsetHeight;
    });
    popup.style.paddingRight = "".concat(
      childrenHeight > popup.offsetHeight ? 40 : 10,
      "px"
    );
  };

  var popupHide = function popupHide(button, popup) {
    if (button) {
      if (Array.isArray(button)) {
        button.forEach(function(el) {
          return el.classList.remove("profiler-button-active");
        });
      } else {
        button.classList.remove("profiler-button-active");
      }
    }

    if (popup) {
      if (Array.isArray(popup)) {
        popup.forEach(function(el) {
          return (el.style.display = "none");
        });
      } else {
        popup.style.display = "none";
      }
    }
  };

  var queriesShow = function queriesShow(link, result) {
    result = result;
    var px = 30,
      win = window,
      width = win.innerWidth - 2 * px,
      height = win.innerHeight - 2 * px,
      queries = result.querySelector(".profiler-queries"); // opaque background

    var background = document.createElement("div");
    background.classList.add("profiler-queries-bg");
    document.body.appendChild(background);
    background.style.height = "".concat(window.innerHeight, "px");
    background.style.display = "block"; // center the queries and ensure long content is scrolled

    queries.style[options.renderVerticalPosition] = "".concat(px, "px");
    queries.style.maxHeight = "".concat(height, "px");
    queries.style.width = "".concat(width, "px");
    queries.style[options.renderHorizontalPosition] = "".concat(px, "px");
    queries.querySelector("table").style.width = "".concat(width, "px"); // have to show everything before we can get a position for the first query

    queries.style.display = "block";
    queriesScrollIntoView(link, queries, queries); // syntax highlighting

    prettyPrint();
  };

  var queriesScrollIntoView = function queriesScrollIntoView(
    link,
    queries,
    whatToScroll
  ) {
    var id = elementClosest(link, "tr").getAttribute("data-timing-id"),
      cells = toArray(
        queries.querySelectorAll('tr[data-timing-id="' + id + '"]')
      ); // ensure they're in view

    whatToScroll.scrollTop =
      (whatToScroll.scrollTop || 0) + cells[0].offsetTop - 100; // highlight and then fade back to original bg color; do it ourselves to prevent any conflicts w/ jquery.UI or other implementations of Resig's color plugin

    cells.forEach(function(el) {
      el.classList.add("higlight-animate");
    });
    setTimeout(function() {
      cells.forEach(function(el) {
        return el.classList.remove("higlight-animate");
      });
    }, 3000);
  };

  var onTurboBeforeVisit = function onTurboBeforeVisit(e) {
    if(!e.defaultPrevented) {
      window.MiniProfilerContainer = document.querySelector('body > .profiler-results')
      window.MiniProfiler.pageTransition()
    }
  }

  var onTurboLoad = function onTurboLoad(e) {
    if(window.MiniProfilerContainer) {
      document.body.appendChild(window.MiniProfilerContainer)
    }
  }

  var onClickEvents = function onClickEvents(e) {
    // this happens on every keystroke, and :visible is crazy expensive in IE <9
    // and in this case, the display:none check is sufficient.
    var popup = toArray(document.querySelectorAll(".profiler-popup")).filter(
      function(el) {
        return el.style.display === "block";
      }
    );

    if (!popup.length) {
      return;
    }

    popup = popup[0];
    var button = popup.parentNode.querySelector(".profiler-button"),
      queries = elementClosest(popup, ".profiler-result").querySelector(
        ".profiler-queries"
      ),
      bg = document.querySelector(".profiler-queries-bg"),
      isEscPress = e.type == "keyup" && e.which == 27,
      hidePopup = false,
      hideQueries = false;

    if (bg && bg.style.display === "block") {
      hideQueries =
        isEscPress ||
        (e.type == "click" &&
          !(queries !== e.target && queries.contains(e.target)) &&
          !(popup !== e.target && popup.contains(e.target)));
    } else if (popup.style.display === "block") {
      hidePopup =
        isEscPress ||
        (e.type == "click" &&
          !(popup !== e.target && popup.contains(e.target)) &&
          !(button !== e.target && button.contains(e.target)) &&
          button != e.target);
    }

    if (hideQueries) {
      bg.parentElement.removeChild(bg);
      queries.style.display = "none";
    }

    if (hidePopup) {
      popupHide(button, popup);
    }
  };

  var keydownEvent = function keydownEvent() {
    var results = document.querySelector(".profiler-results");

    if (results.style.display === "none") {
      results.style.display = "block";
    } else {
      results.style.display = "none";
    }

    sessionStorage["rack-mini-profiler-start-hidden"] =
      results.style.display === "none";
  };

  var toggleShortcutEvent = function toggleShortcutEvent(e) {
    // simplified version of https://github.com/jeresig/jquery.hotkeys/blob/master/jquery.hotkeys.js
    var shortcut = options.toggleShortcut.toLowerCase();
    var modifier = "";
    ["alt", "ctrl", "shift"].forEach(function(k) {
      if (e[k + "Key"]) {
        modifier += "".concat(k, "+");
      }
    });
    var specialKeys = {
      8: "backspace",
      9: "tab",
      10: "return",
      13: "return",
      16: "shift",
      17: "ctrl",
      18: "alt",
      27: "esc",
      32: "space",
      59: ";",
      61: "=",
      96: "0",
      97: "1",
      98: "2",
      99: "3",
      100: "4",
      101: "5",
      102: "6",
      103: "7",
      104: "8",
      105: "9",
      106: "*",
      107: "+",
      109: "-",
      110: ".",
      173: "-",
      186: ";",
      187: "="
    };
    var shiftNums = {
      "`": "~",
      "1": "!",
      "2": "@",
      "3": "#",
      "4": "$",
      "5": "%",
      "6": "^",
      "7": "&",
      "8": "*",
      "9": "(",
      "0": ")",
      "-": "_",
      "=": "+",
      ";": ": ",
      "'": '"',
      ",": "<",
      ".": ">",
      "/": "?",
      "\\": "|"
    };
    var character = String.fromCharCode(e.which).toLowerCase();
    var special = specialKeys[e.which];
    var keys = [];

    if (special) {
      keys.push(special);
    } else {
      keys.push(character);
      keys.push(shiftNums[character]);
    }

    for (var i = 0; i < keys.length; i++) {
      if (modifier + keys[i] === shortcut) {
        keydownEvent();
        break;
      }
    }
  };

  var turbolinksSkipResultsFetch = function turbolinksSkipResultsFetch(event) {
    event.data.xhr.__miniProfilerSkipResultsFetch = true;
  };

  var bindDocumentEvents = function bindDocumentEvents() {
    document.addEventListener("click", onClickEvents);
    document.addEventListener("keyup", onClickEvents);
    document.addEventListener("keyup", toggleShortcutEvent);

    if (typeof Turbolinks !== "undefined" && Turbolinks.supported) {
      document.addEventListener("page:change", unbindDocumentEvents);
      document.addEventListener("turbolinks:load", unbindDocumentEvents);
      document.addEventListener(
        "turbolinks:request-start",
        turbolinksSkipResultsFetch
      );
    }

    if (options.hotwireTurboDriveSupport) {
      document.addEventListener("turbo:before-visit", onTurboBeforeVisit)
      document.addEventListener("turbo:load", onTurboLoad)
    }
  };

  var unbindDocumentEvents = function unbindDocumentEvents() {
    document.removeEventListener("click", onClickEvents);
    document.removeEventListener("keyup", onClickEvents);
    document.removeEventListener("keyup", toggleShortcutEvent);
    document.removeEventListener("page:change", unbindDocumentEvents);
    document.removeEventListener("turbolinks:load", unbindDocumentEvents);
    document.removeEventListener(
      "turbolinks:request-start",
      turbolinksSkipResultsFetch
    );
    document.removeEventListener("turbo:before-visit", onTurboBeforeVisit);
    document.removeEventListener("turbo:load", onTurboLoad);
  };

  var initFullView = function initFullView() {
    // profiler will be defined in the full page's head
    container[0].appendChild(renderTemplate(profiler));
    var popup = document.querySelector(".profiler-popup");
    toggleHidden(popup);
    prettyPrint(); // since queries are already shown, just highlight and scroll when clicking a "1 sql" link

    toArray(popup.querySelectorAll(".profiler-queries-show")).forEach(function(
      el
    ) {
      el.addEventListener("click", function() {
        queriesScrollIntoView(
          this,
          document.querySelector(".profiler-queries"),
          document.body
        );
      });
    });
  };

  var initSnapshots = function initSnapshots(dataElement) {
    var data = JSON.parse(dataElement.textContent);
    var temp = document.createElement("DIV");
    if (data.page === "overview") {
      temp.innerHTML = MiniProfiler.templates.snapshotsGroupsList(data);
    } else if (data.group_name) {
      var allCustomFieldsNames = [];
      data.list.forEach(function(snapshot) {
        Object.keys(snapshot.custom_fields).forEach(function(k) {
          if (
            allCustomFieldsNames.indexOf(k) === -1 &&
            options.hiddenCustomFields.indexOf(k.toLowerCase()) === -1
          ) {
            allCustomFieldsNames.push(k);
          }
        });
      });
      allCustomFieldsNames.sort();
      temp.innerHTML = MiniProfiler.templates.snapshotsList({
        data: data,
        allCustomFieldsNames: allCustomFieldsNames
      });
    }
    Array.from(temp.children).forEach(function(child) {
      document.body.appendChild(child);
    });
  };

  var initControls = function initControls(container) {
    if (options.showControls) {
      var _controls = document.createElement("div");

      _controls.classList.add("profiler-controls");

      _controls.innerHTML =
        '<span class="profiler-min-max">m</span><span class="profiler-clear">c</span>';
      container.appendChild(_controls);
      document
        .querySelector(".profiler-controls .profiler-min-max")
        .addEventListener("click", function() {
          return toggleClass(container, "profiler-min");
        });
      container.addEventListener("mouseenter", function() {
        if (this.classList.contains("profiler-min")) {
          this.querySelector(".profiler-min-max").style.display = "block";
        }
      });
      container.addEventListener("mouseleave", function() {
        if (this.classList.contains("profiler-min")) {
          this.querySelector(".profiler-min-max").style.display = "none";
        }
      });
      document
        .querySelector(".profiler-controls .profiler-clear")
        .addEventListener("click", function() {
          toArray(container.querySelectorAll(".profiler-result")).forEach(
            function(el) {
              return el.parentElement.removeChild(el);
            }
          );
        });
    } else {
      container.classList.add("profiler-no-controls");
    }
  };

  var toggleClass = function toggleClass(el, className) {
    if (el.classList) {
      el.classList.toggle(className);
    } else {
      var classes = el.className.split(" ");
      var existingIndex = classes.indexOf(className);
      if (existingIndex >= 0) classes.splice(existingIndex, 1);
      else classes.push(className);
      el.className = classes.join(" ");
    }
  };

  var initPopupView = function initPopupView() {
    if (options.authorized) {
      // all fetched profilings will go in here
      container = document.createElement("div");
      container.className = "profiler-results";
      document.querySelector(options.htmlContainer).appendChild(container); // MiniProfiler.RenderIncludes() sets which corner to render in - default is upper left

      container.classList.add("profiler-" + options.renderHorizontalPosition);
      container.classList.add("profiler-" + options.renderVerticalPosition); //initialize the controls

      initControls(container);
      fetchResults(options.ids);

      if (options.startHidden) container.style.display = "none";
    } else {
      fetchResults(options.ids);
    }

    if (!window.MiniProfiler || !window.MiniProfiler.patchesApplied) {
      var send = XMLHttpRequest.prototype.send;

      XMLHttpRequest.prototype.send = function(data) {
        ajaxStartTime = new Date();
        this.addEventListener("load", function() {
          // responseURL isn't available in IE11
          if (
            this.responseURL &&
            this.responseURL.indexOf(window.location.origin) !== 0
          ) {
            return;
          }
          if (this.__miniProfilerSkipResultsFetch) {
            return;
          }
          // getAllResponseHeaders isn't available in Edge.
          var allHeaders = this.getAllResponseHeaders
            ? this.getAllResponseHeaders()
            : null;
          if (
            allHeaders &&
            allHeaders.toLowerCase().indexOf("x-miniprofiler-ids") === -1
          ) {
            return;
          }
          // should be a string of comma-separated ids
          var stringIds = this.getResponseHeader("X-MiniProfiler-Ids");

          if (stringIds) {
            var ids = stringIds.split(",");
            fetchResults(ids);
          }
        });
        send.call(this, data);
      }; // fetch results after ASP Ajax calls

      if (
        typeof Sys != "undefined" &&
        typeof Sys.WebForms != "undefined" &&
        typeof Sys.WebForms.PageRequestManager != "undefined"
      ) {
        // Get the instance of PageRequestManager.
        var PageRequestManager = Sys.WebForms.PageRequestManager.getInstance();
        PageRequestManager.add_endRequest(function(sender, args) {
          if (args) {
            var response = args.get_response();

            if (
              response.get_responseAvailable() &&
              response._xmlHttpRequest !== null
            ) {
              var stringIds = args
                .get_response()
                .getResponseHeader("X-MiniProfiler-Ids");

              if (stringIds) {
                var ids = stringIds.split(",");
                fetchResults(ids);
              }
            }
          }
        });
      } // more Asp.Net callbacks

      if (typeof WebForm_ExecuteCallback == "function") {
        WebForm_ExecuteCallback = (function(callbackObject) {
          // Store original function
          var original = WebForm_ExecuteCallback;
          return function(callbackObject) {
            original(callbackObject);
            var stringIds = callbackObject.xmlRequest.getResponseHeader(
              "X-MiniProfiler-Ids"
            );

            if (stringIds) {
              var ids = stringIds.split(",");
              fetchResults(ids);
            }
          };
        })();
      } // also fetch results after ExtJS requests, in case it is being used

      if (
        typeof Ext != "undefined" &&
        typeof Ext.Ajax != "undefined" &&
        typeof Ext.Ajax.on != "undefined"
      ) {
        // Ext.Ajax is a singleton, so we just have to attach to its 'requestcomplete' event
        Ext.Ajax.on("requestcomplete", function(e, xhr, settings) {
          //iframed file uploads don't have headers
          if (!xhr || !xhr.getResponseHeader) {
            return;
          }

          var stringIds = xhr.getResponseHeader("X-MiniProfiler-Ids");

          if (stringIds) {
            var ids = stringIds.split(",");
            fetchResults(ids);
          }
        });
      }

      if (typeof MooTools != "undefined" && typeof Request != "undefined") {
        Request.prototype.addEvents({
          onComplete: function onComplete() {
            var stringIds = this.xhr.getResponseHeader("X-MiniProfiler-Ids");

            if (stringIds) {
              var ids = stringIds.split(",");
              fetchResults(ids);
            }
          }
        });
      } // add support for AngularJS, which use the basic XMLHttpRequest object.

      if (window.angular && typeof XMLHttpRequest != "undefined") {
        var _send = XMLHttpRequest.prototype.send;

        XMLHttpRequest.prototype.send = function sendReplacement(data) {
          if (this.onreadystatechange) {
            if (
              typeof this.miniprofiler == "undefined" ||
              typeof this.miniprofiler.prev_onreadystatechange == "undefined"
            ) {
              this.miniprofiler = {
                prev_onreadystatechange: this.onreadystatechange
              };

              this.onreadystatechange = function onReadyStateChangeReplacement() {
                if (this.readyState == 4) {
                  var stringIds = this.getResponseHeader("X-MiniProfiler-Ids");

                  if (stringIds) {
                    var ids = stringIds.split(",");
                    fetchResults(ids);
                  }
                }

                if (this.miniprofiler.prev_onreadystatechange !== null)
                  return this.miniprofiler.prev_onreadystatechange.apply(
                    this,
                    arguments
                  );
              };
            }
          }

          return _send.apply(this, arguments);
        };
      } // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

      if (typeof window.fetch === "function") {
        var __originalFetch = window.fetch;

        window.fetch = function(input, init) {
          var originalFetchRun = __originalFetch(input, init);

          originalFetchRun.then(function(response) {
            try {
              // look for x-mini-profile-ids
              var entries = response.headers.entries();
              var _iteratorNormalCompletion = true;
              var _didIteratorError = false;
              var _iteratorError = undefined;

              try {
                for (
                  var _iterator = entries[Symbol.iterator](), _step;
                  !(_iteratorNormalCompletion = (_step = _iterator.next())
                    .done);
                  _iteratorNormalCompletion = true
                ) {
                  var pair = _step.value;

                  if (
                    pair[0] &&
                    pair[0].toLowerCase() == "x-miniprofiler-ids"
                  ) {
                    var ids = pair[1].split(",");
                    fetchResults(ids);
                  }
                }
              } catch (err) {
                _didIteratorError = true;
                _iteratorError = err;
              } finally {
                try {
                  if (!_iteratorNormalCompletion && _iterator.return != null) {
                    _iterator.return();
                  }
                } finally {
                  if (_didIteratorError) {
                    throw _iteratorError;
                  }
                }
              }
            } catch (e) {
              console.error(e);
            }
          });
          return originalFetchRun;
        };
      }
      window.MiniProfiler.patchesApplied = true;
    }

    bindDocumentEvents();
  };

  return {
    fetchedIds: fetchedIds,
    fetchingIds: fetchingIds,
    init: function init() {
      var script = document.getElementById("mini-profiler");
      if (!script || !script.getAttribute) return;

      this.options = options = (function() {
        var version = script.getAttribute("data-version");
        var path = script.getAttribute("data-path");
        var currentId = script.getAttribute("data-current-id");
        var ids = script.getAttribute("data-ids");
        if (ids) ids = ids.split(",");
        var horizontal_position = script.getAttribute(
          "data-horizontal-position"
        );
        var vertical_position = script.getAttribute("data-vertical-position");
        var toggleShortcut = script.getAttribute("data-toggle-shortcut");

        if (script.getAttribute("data-max-traces")) {
          var maxTraces = parseInt(script.getAttribute("data-max-traces"), 10);
        }

        var collapseResults =
          script.getAttribute("data-collapse-results") === "true";
        var trivial = script.getAttribute("data-trivial") === "true";
        var children = script.getAttribute("data-children") === "true";
        var controls = script.getAttribute("data-controls") === "true";
        var totalSqlCount =
          script.getAttribute("data-total-sql-count") === "true";
        var authorized = script.getAttribute("data-authorized") === "true";
        var startHidden =
          script.getAttribute("data-start-hidden") === "true" ||
          sessionStorage["rack-mini-profiler-start-hidden"] === "true";
        var htmlContainer = script.getAttribute("data-html-container");
        var cssUrl = script.getAttribute("data-css-url");
        var hiddenCustomFields = script
          .getAttribute("data-hidden-custom-fields")
          .toLowerCase()
          .split(",");
        var hotwireTurboDriveSupport = script
          .getAttribute('data-turbo-permanent') === "true";
        return {
          ids: ids,
          path: path,
          version: version,
          renderHorizontalPosition: horizontal_position,
          renderVerticalPosition: vertical_position,
          showTrivial: trivial,
          showChildrenTime: children,
          maxTracesToShow: maxTraces,
          showControls: controls,
          showTotalSqlCount: totalSqlCount,
          currentId: currentId,
          authorized: authorized,
          toggleShortcut: toggleShortcut,
          startHidden: startHidden,
          collapseResults: collapseResults,
          htmlContainer: htmlContainer,
          cssUrl: cssUrl,
          hiddenCustomFields: hiddenCustomFields,
          hotwireTurboDriveSupport: hotwireTurboDriveSupport
        };
      })();

      var doInit = function doInit() {
        var snapshotsElement = document.getElementById("snapshots-data");
        if (snapshotsElement != null) {
          initSnapshots(snapshotsElement);
          return;
        }

        // when rendering a shared, full page, this div will exist
        container = document.querySelectorAll(".profiler-result-full");

        if (container.length) {
          if (window.location.href.indexOf("&trivial=1") > 0) {
            options.showTrivial = true;
          }

          initFullView();
        } else {
          initPopupView();
        }
      }; // this preserves debugging

      var load = function load(s, f) {
        var sc = document.createElement("script");
        sc.async = "async";
        sc.type = "text/javascript";
        sc.src = s;
        var done = false;

        sc.onload = sc.onreadystatechange = function(_, abort) {
          if (!sc.readyState || /loaded|complete/.test(sc.readyState)) {
            if (!abort && !done) {
              done = true;
              f();
            }
          }
        };

        document.getElementsByTagName("head")[0].appendChild(sc);
      };

      var wait = 0;
      var finish = false;

      var deferInit = function deferInit() {
        if (finish) return;

        if (
          window.performance &&
          window.performance.timing &&
          window.performance.timing.loadEventEnd === 0 &&
          wait < 10000
        ) {
          setTimeout(deferInit, 100);
          wait += 100;
        } else {
          finish = true;
          init();
        }
      };

      var init = function init() {
        if (options.authorized) {
          var url = options.cssUrl;

          if (document.createStyleSheet) {
            document.createStyleSheet(url);
          } else {
            var head = document.querySelector("head");
            var link = document.createElement("link");
            link.rel = "stylesheet";
            link.type = "text/css";
            link.href = url;
            head.appendChild(link);
          }

          if (!MiniProfiler.loadedVendor) {
            load(options.path + "vendor.js?v=" + options.version, doInit);
          } else {
            doInit();
          }
        } else {
          doInit();
        }
      };

      deferInit();
    },
    cleanUp: function cleanUp() {
      unbindDocumentEvents();
    },
    pageTransition: function pageTransition() {
      if (totalsControl) {
        if (totalsControl.parentElement) {
          totalsControl.parentElement.removeChild(totalsControl);
        }
        totalsControl = null;
      }

      reqs = 0;
      totalTime = 0;
      expandedResults = false;
      toArray(
        document.querySelectorAll(".profiler-results .profiler-result")
      ).forEach(function(el) {
        return el.parentElement.removeChild(el);
      });
    },
    getClientTimingByName: function getClientTimingByName(clientTiming, name) {
      for (var i = 0; i < clientTiming.timings.length; i++) {
        if (clientTiming.timings[i].name == name) {
          return clientTiming.timings[i];
        }
      }

      return {
        Name: name,
        Duration: "",
        Start: ""
      };
    },
    renderDate: function renderDate(jsonDate) {
      // JavaScriptSerializer sends dates as /Date(1308024322065)/
      if (jsonDate) {
        return typeof jsonDate === "string"
          ? new Date(
              parseInt(jsonDate.replace("/Date(", "").replace(")/", ""), 10)
            ).toUTCString()
          : jsonDate;
      }
    },
    renderIndent: function renderIndent(depth) {
      var result = "";

      for (var i = 0; i < depth; i++) {
        result += "&nbsp;";
      }

      return result;
    },
    renderExecuteType: function renderExecuteType(typeId) {
      // see StackExchange.Profiling.ExecuteType enum
      switch (typeId) {
        case 0:
          return "None";

        case 1:
          return "NonQuery";

        case 2:
          return "Scalar";

        case 3:
          return "Reader";
      }
    },
    shareUrl: function shareUrl(id) {
      return options.path + "results?id=" + id;
    },
    flamegraphUrl: function flamegrapgUrl(id) {
      return options.path + "flamegraph?id=" + id;
    },
    moreUrl: function moreUrl(requestName) {
      var linkSuffix = requestName.indexOf("?") > 0 ? "&pp=help" : "?pp=help";
      return requestName + linkSuffix;
    },
    getClientTimings: function getClientTimings(clientTimings) {
      var list = [];
      var t;
      if (!clientTimings.timings) return [];

      for (var i = 0; i < clientTimings.timings.length; i++) {
        t = clientTimings.timings[i];
        var trivial = t.Name != "Dom Complete" && t.Name != "Response";
        trivial = t.Duration < 2 ? trivial : false;
        list.push({
          isTrivial: trivial,
          name: t.Name,
          duration: t.Duration,
          start: t.Start
        });
      } // Use the Paint Timing API for render performance.

      if (window.performance && window.performance.getEntriesByName) {
        var firstPaint = window.performance.getEntriesByName("first-paint");

        if (firstPaint !== undefined && firstPaint.length >= 1) {
          list.push({
            isTrivial: false,
            name: "First Paint Time",
            duration: firstPaint[0].duration,
            start: firstPaint[0].startTime
          });
        }
      }

      list.sort(function(a, b) {
        return a.start - b.start;
      });
      return list;
    },
    getSqlTimings: function getSqlTimings(root) {
      var result = [],
        addToResults = function addToResults(timing) {
          if (timing.sql_timings) {
            for (var i = 0, sqlTiming; i < timing.sql_timings.length; i++) {
              sqlTiming = timing.sql_timings[i]; // HACK: add info about the parent Timing to each SqlTiming so UI can render

              sqlTiming.parent_timing_name = timing.name;

              if (sqlTiming.duration_milliseconds > 50) {
                sqlTiming.row_class = "slow";
              }

              if (sqlTiming.duration_milliseconds > 200) {
                sqlTiming.row_class = "very-slow";
              }

              if (sqlTiming.duration_milliseconds > 400) {
                sqlTiming.row_class = "very-very-slow";
              }

              result.push(sqlTiming);
            }
          }

          if (timing.children) {
            for (var i = 0; i < timing.children.length; i++) {
              addToResults(timing.children[i]);
            }
          }
        }; // start adding at the root and recurse down

      addToResults(root);

      var removeDuration = function removeDuration(list, duration) {
        var newList = [];

        for (var i = 0; i < list.length; i++) {
          var item = list[i];

          if (duration.start > item.start) {
            if (duration.start > item.finish) {
              newList.push(item);
              continue;
            }

            newList.push({
              start: item.start,
              finish: duration.start
            });
          }

          if (duration.finish < item.finish) {
            if (duration.finish < item.start) {
              newList.push(item);
              continue;
            }

            newList.push({
              start: duration.finish,
              finish: item.finish
            });
          }
        }

        return newList;
      };

      var processTimes = function processTimes(elem, parent) {
        var duration = {
          start: elem.start_milliseconds,
          finish: elem.start_milliseconds + elem.duration_milliseconds
        };
        elem.richTiming = [duration];

        if (parent !== null) {
          elem.parent = parent;
          elem.parent.richTiming = removeDuration(
            elem.parent.richTiming,
            duration
          );
        }

        if (elem.children) {
          for (var i = 0; i < elem.children.length; i++) {
            processTimes(elem.children[i], elem);
          }
        }
      };

      processTimes(root, null); // sort results by time

      result.sort(function(a, b) {
        return a.start_milliseconds - b.start_milliseconds;
      });

      var determineOverlap = function determineOverlap(gap, node) {
        var overlap = 0;

        for (var i = 0; i < node.richTiming.length; i++) {
          var current = node.richTiming[i];

          if (current.start > gap.finish) {
            break;
          }

          if (current.finish < gap.start) {
            continue;
          }

          overlap +=
            Math.min(gap.finish, current.finish) -
            Math.max(gap.start, current.start);
        }

        return overlap;
      };

      var determineGap = function determineGap(gap, node, match) {
        var overlap = determineOverlap(gap, node);

        if (match === null || overlap > match.duration) {
          match = {
            name: node.name,
            duration: overlap
          };
        } else if (match.name == node.name) {
          match.duration += overlap;
        }

        if (node.children) {
          for (var i = 0; i < node.children.length; i++) {
            match = determineGap(gap, node.children[i], match);
          }
        }

        return match;
      };

      var time = 0;
      var prev = null;
      result.forEach(function(r) {
        r.prevGap = {
          duration: (r.start_milliseconds - time).toFixed(2),
          start: time,
          finish: r.start_milliseconds
        };
        r.prevGap.topReason = determineGap(r.prevGap, root, null);
        time = r.start_milliseconds + r.duration_milliseconds;
        prev = r;
      });

      if (result.length > 0) {
        var me = result[result.length - 1];
        me.nextGap = {
          duration: (root.duration_milliseconds - time).toFixed(2),
          start: time,
          finish: root.duration_milliseconds
        };
        me.nextGap.topReason = determineGap(me.nextGap, root, null);
      }

      return result;
    },
    getSqlTimingsCount: function getSqlTimingsCount(root) {
      var result = 0,
        countSql = function countSql(timing) {
          if (timing.sql_timings) {
            result += timing.sql_timings.length;
          }

          if (timing.children) {
            for (var i = 0; i < timing.children.length; i++) {
              countSql(timing.children[i]);
            }
          }
        };

      countSql(root);
      return result;
    },
    fetchResultsExposed: function fetchResultsExposed(ids) {
      return fetchResults(ids);
    },
    formatParameters: function formatParameters(parameters) {
      if (parameters != null) {
        return parameters
          .map(function(item, index) {
            return "[" + item[0] + ", " + item[1] + "]";
          })
          .join(", ");
      } else {
        return "";
      }
    },
    formatDuration: function formatDuration(duration) {
      return (duration || 0).toFixed(1);
    },
    showTotalSqlCount: function showTotalSqlCount() {
      return options.showTotalSqlCount;
    },
    timestampToRelative: function timestampToRelative(timestamp) {
      var now = Math.round(new Date().getTime() / 1000);
      timestamp = Math.round(timestamp / 1000);
      var diff = now - timestamp;
      if (diff < 60) {
        return "< 1 minute";
      }
      var buildDisplayTime = function buildDisplayTime(num, unit) {
        var res = num + " " + unit;
        if (num !== 1) {
          res += "s";
        }
        return res;
      };
      diff = Math.round(diff / 60);
      if (diff <= 60) {
        return buildDisplayTime(diff, "minute");
      }
      diff = Math.round(diff / 60);
      if (diff <= 24) {
        return buildDisplayTime(diff, "hour");
      }
      diff = Math.round(diff / 24);
      return buildDisplayTime(diff, "day");
    }
  };
})();

if (window.MiniProfiler) {
  _MiniProfiler.patchesApplied = window.MiniProfiler.patchesApplied;
}
window.MiniProfiler = _MiniProfiler;
_MiniProfiler.init();