<commit_before>{%- block doctype -%}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
{% endblock %}
<!-- Copied and modified from:
http://src.chromium.org/viewvc/chrome/trunk/tools/build/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html?revision=89637&content-type=text/plain
-->
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    {% block head %}
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    {% if metatags %}
      {{ metatags }}
    {% endif %}
    <title>{{ pageTitle|e }}</title>
    <link rel="stylesheet" href="{{ stylesheet }}" type="text/css" />
    <link rel="alternate" type="application/rss+xml" title="RSS" href="{{ path_to_root }}rss">
    <script type="text/javascript">
    "use strict";

    // Revision of the buildbot code on the build master.
    var javascript_revision = "{{ master_revision }}";

    // Ports used to access the buildbot webstatus pages.
    var internalPort = "{{ internal_port }}";
    var externalPort = "{{ external_port }}";

    // JSON data URL.
    var tree_status_appspot_url = "{{ tree_status_baseurl }}";

    // Per-builder status messages.
    var builder_statuses = {};

    // How often, in milliseconds, to refresh the page.
    var refreshInterval = {{ refresh or default_refresh }} * 1000;
    var refreshTimer = null;

    // Number of times a page reload has been cancelled due to activity on the page.
    var cancelledReloads = 0;

    // Maximum number of cancelled reloads before forcing a reload.
    var maxCancelledReloads = 10;

    // Variables passed in the URL.
    var urlVars = {};

    // Categories of builders, and whether each category should be displayed.
    var allCategories = {
      {% for r in all_categories %}
      "{{ r }}": true,
      {% endfor %}
    };

    // Subcategories of builders, and whether each category should be displayed.
    var allSubcategories = {
      {% for subcategory in all_subcategories %}
      "{{ subcategory }}": true,
      {% endfor %}
    };

    // Subcategories listed by which category they fall under.
    var subcategoriesByCategory = {
      {% for category in subcategories_by_category %}
      "{{ category }}": [
        {% for subcategory in subcategories_by_category[category] %}
          "{{ subcategory }}",
        {% endfor %}
      ],
      {% endfor %}
    }

    function addLinks(text) {
      // Linkify arbitrary URLs.
      var new_text = "";
      if (text && text.indexOf("<a href=") == -1) {
        new_text = text.replace(
            /(https?:\/\/.+?)(\s|\t|<|\n|\r|$)/g, '<a href="$1" target="_blank">$1</a>$2');
      }

      // Linkify instances of "skia:123".
      var re = new RegExp("skia:[0-9]+");
      var found_bugs = re.exec(new_text);
      if (found_bugs) {
        for (var i = 0; i < found_bugs.length; ++i) {
          var found_bug = found_bugs[i];
          var bug_number = found_bug.split(":")[1];
          var bug_link = '<a href="https://code.google.com/p/skia/issues/detail?id=' + bug_number + '" target="_blank">' + found_bug + '</a>';
          new_text = new_text.replace(found_bug, bug_link);
        }
      }
      return new_text;
    }

    // Switch to a new host/port.
    function setHostAndPort(host, port) {
      var current_host = window.location.host.split(":");
      if (!host) {
        host = current_host[0];
      }
      if (!port) {
        port = current_host[1];
      }
      window.location.host = host + ":" + port;
    }

    // Parse the variables in the URL and return a key/value dictionary.
    function parseArgs()
    {
      var args = {};
      var dict = window.location.search.slice(1).split('&');
      for(var i = 0; i < dict.length; i++) {
        var split = dict[i].split('=');
        var key = split[0];
        var value = split[1];
        if (args[key]) {
          if (Object.prototype.toString.call(args[key]) == "[object Array]") {
            args[key].push(value);
          } else {
            args[key] = [args[key], value];
          }
        } else if (key != "") {
          args[key] = value;
        }
      }
      return args;
    }

    // If the currently running revision of this page differs from that of the
    // build master, reload the whole page instead of just the JSON data.
    function doReload() {
      // Get the master's code revision.
      loadJSONP("/json/master_revision",
                gotMasterRevision,
                null);
    }

    // Callback function for doReload.
    function gotMasterRevision(data) {
      if (data["master_checkedout_revision"] != javascript_revision) {
        // Force a reload of the page if the master has a new revision.
        location.reload(true);
      } else {
        loadAllData();
      }
    }

    // Reload the page, if the user isn't actively interacting with it.
    function reloadPage() {
      var reloadIsAllowed = true;

      // Don't allow reload if a build box is open.
      var buildBox = document.getElementById("divBox");
      if (buildBox && buildBox.style.display != "none") {
        console.log("divBox is displaying (" + buildBox.style.display + ").");
        reloadIsAllowed = false;
      }

      if (reloadIsAllowed) {
        doReload();
      } else if (cancelledReloads >= maxCancelledReloads) {
        console.log("Forcing a reload because the number of " +
                    "previously-cancelled reloads exceeded " +
                    maxCancelledReloads + ".");
        doReload();
      } else {
        console.log("Not reloading page because the user is interacting with it.");
        cancelledReloads++;
        refreshTimer = setTimeout(reloadPage, refreshInterval);
      }
    }

    // Return the name of a non-anonymous function.
    function getFunctionName(func) {
      var name = func.toString();
      name = name.substr("function ".length);
      name = name.substr(0, name.indexOf("("));
      return name;
    }

    // Use JSONP to retrieve data from the given URL. There are a few possible
    // outcomes:
    //   1. The server returns valid JSONP. In this case, the provided callback
    //      will be called with the data as the parameter.
    //   2. The server returns an error code (eg. if the requested URL isn't
    //      valid). In this case, the provided errback will be called.
    //   3. The server returns a success code but the response does not contain
    //      valid JSONP. In this case, the callback cannot be called, since that
    //      call is part of the JSONP response. Additionally, the errback will
    //      not fire, since there is no way to dynamically insert a try/catch
    //      around the response content, and the script tag's onError handler
    //      only fires if the script could not be obtained from the server.
    //
    // Note: callback must NOT be an anonymous function.
    //
    function loadJSONP(url, callback, errback) {
      var script = document.createElement("script");
      var src = appendParamToURL(url, "callback", getFunctionName(callback));
      script.setAttribute("src", src);
      script.onError = errback
      document.getElementsByTagName("head")[0].appendChild(script);
    }

    // Use an XmlHttpRequest to retrieve data from the given URL.
    function loadXML(url, callback) {
      var req = new XMLHttpRequest();
      req.open("GET", url, true);
      req.onreadystatechange = function() {
        if (req.readyState == 4) { callback(req.responseText); }
      };
      req.send();
    }

    // Set the tree status.
    function setTreeStatus(data) {
      var tree_status_div = document.getElementById("tree_status_div");
      if (tree_status_div) {
        tree_status_div.className = "status-message " + data["general_state"];
      }
      var tree_status_message = document.getElementById("tree_status_message");
      if (tree_status_message) {
        tree_status_message.innerHTML = data["message"];
      }
    }

    // Set the current sheriff.
    function setSheriff(data) {
      var sheriff_div = document.getElementById("sheriff_div");
      if (sheriff_div) {
        sheriff_div.innerHTML = "Sheriff: " + data["username"];
      }
    }

    // Set builder statuses.
    function setBuilderStatuses(data) {
      // Set the global builder status variable.
      builder_statuses = data;

      // Mark builders who have comments.
      for (var builder_name in data) {
        // Builder column headers.
        var comment_indicator = document.getElementById("commentIndicator_" + builder_name);
        if (comment_indicator) {
          comment_indicator.className += " BuilderHasComment";
        }

        // Summary boxes for all masters.
        var summary_box = document.getElementById("summaryBox_" + builder_name);
        if (summary_box) {
          summary_box.className += " BuilderHasComment";
        }
      }

      // Optionally display the builder status message on the page.
      var display_comment_elems = document.getElementsByClassName("DisplayBuilderComment");
      for (var i = 0; i < display_comment_elems.length; ++i) {
        var display_comment = display_comment_elems[i];
        display_comment.innerHTML = "";
        var builder_name = display_comment.id.slice("displayBuilderComment_".length);
        buildComment(builder_name, display_comment);
        if (builder_statuses[builder_name]) {
          display_comment.className += " BuilderHasComment";
        }
      }

      // Rebuild the console table, if applicable.
      if (typeof buildConsoleTable == "function") {
        buildConsoleTable();
      }
    }

    // Function to call on page load. Reloads the page with appropriate options
    // if they are not already set.
    function init() {
      var host = window.location.host;
      var checkedRadio;
      if (host.indexOf(internalPort, host.length - internalPort.length) !== -1) {
        checkedRadio = document.getElementById("radio_internal");
      } else if (host.indexOf(externalPort, host.length - externalPort.length) !== -1) {
        checkedRadio = document.getElementById("radio_external");
      }
      if (checkedRadio) {
        checkedRadio.checked = true;
      }

      urlVars = parseArgs();
      if (urlVars["hideCategories"]) {
        var categoryList = urlVars["hideCategories"].split(",");
        for (var i = 0; i < categoryList.length; ++i) {
          var name = categoryList[i];
          allCategories[name] = false;
          var chkbox = document.getElementById("chkbox_category_" + name);
          if (chkbox) {
            chkbox.checked = false;
          }
        }
      }
      if (urlVars["hideSubcategories"]) {
        var subcategoryList = urlVars["hideSubcategories"].split(",");
        for (var i = 0; i < subcategoryList.length; ++i) {
          var name = subcategoryList[i];
          allSubcategories[name] = false;
          var chkbox = document.getElementById("chkbox_subcategory_" + name);
          if (chkbox) {
            chkbox.checked = false;
          }
        }
      }
      loadAllData();
    }

    // Display the most recent build for each builder on the given master.
    function setLatestBuildsForMaster(master, master_builder_url, builders) {
      var master_data_row = document.getElementById("results_summary_" + master);
      if (!master_data_row) {
        return;
      }
      // Clear any old results, up to the first cell which is the label.
      while (master_data_row.cells.length > 1) {
        master_data_row.deleteCell(-1);
      }
      // Add the new results.
      for (var builder_index = 0; builder_index < builders.length; ++builder_index) {
        var cell = master_data_row.insertCell(-1);
        cell.className = "mini-box";
        cell.style.padding = "0px";
        cell.style.verticalAlign = "bottom";
        var builder_name = builders[builder_index].name;
        cell.id = "summaryBox_" + builder_name
        var outcome = builders[builder_index].outcome;
        if (!outcome) {
          outcome = "offline";
        }
        var link = document.createElement("a");
        link.href = master_builder_url + builder_name;
        link.title = builder_name;
        link.target = "_blank";
        link.innerHTML = " ";
        cell.style.width = "10px";
        link.style.width = "100%";
        link.style.height = "100%";
        link.className = outcome;
        link.style.margin = "0px";
        link.style.padding = "0px";

        // Decorate the result summary box if there's a comment for the builder.
        if (builder_statuses[builder_name]) {
          cell.className += " BuilderHasComment";
        }

        cell.appendChild(link);
      }
    }

    // Callback functions for individual masters.
    {% for master in masters %}
    {% if not (not is_internal_view and master['name'] == 'PrivateSkia') %}
    function setLatestBuildsFor{{ master['name'] }}(data) {
      setLatestBuildsForMaster("{{ master['name'] }}",
                               "http://{{ master['host'] }}:{{ master['internal_port'] if is_internal_view else master['external_port'] }}/waterfall?show=",
                               data['builders']);
    }
    {% endif %}
    {% endfor %}

    // Callback function for AutoRoll bot status.
    function gotARBStatus(data) {
      var arb_status_div = document.getElementById("arb_status_div");
      if (arb_status_div) {
        arb_status_div.innerHTML = "AutoRoll Status: " + addLinks(data);
      }
    }

    // Load JSON data from various places.
    function loadAllData() {
      // Tree status.
      loadJSONP(tree_status_appspot_url + "/banner-status?format=json",
                setTreeStatus,
                function() {
                  setTreeStatus({
                    "general_state": "",
                    "message": "Failed to load tree status.",
                  });
                });

      // Current sheriff.
      loadJSONP(tree_status_appspot_url + "/current-sheriff",
                setSheriff,
                function() { setSheriff({"username": "unknown"}); });

      // Builder statuses.
      loadJSONP(tree_status_appspot_url + "/builder-status/get_builder_statuses",
                setBuilderStatuses);

      // Last build result for each builder on each master.
      var master_json_url = "";
      {% for master in masters %}
      {% if not (not is_internal_view and master['name'] == 'PrivateSkia') %}
      master_json_url = "http://{{ master['host'] }}:{{ master['internal_port'] if is_internal_view else master['external_port'] }}/json/builder_statuses";
      loadJSONP(master_json_url, setLatestBuildsFor{{ master['name'] }});
      {% endif %}
      {% endfor %}

      // AutoRoll bot status.
      loadXML("http://chromium-skia-gm.storage.googleapis.com/arb_status.html", gotARBStatus);

      // Set the reload timer.
      refreshTimer = setTimeout(reloadPage, refreshInterval);

      // Load the console data, if applicable.
      if (typeof loadConsoleData == "function") {
        loadConsoleData();
      }
    }

    // Get the comment text for a given builder.
    function buildComment(builder_name, container) {
      var file_bug_link = document.createElement("a");
      file_bug_link.href = "http://code.google.com/p/skia/issues/entry?labels=BreakingTheBuildbots,Type-Defect,Priority-Medium&summary=" + builder_name;
      file_bug_link.target = "_blank";
      file_bug_link.appendChild(document.createTextNode("file bug"));

      var edit_status_url = tree_status_appspot_url + "/builder-status/?selected_builder_name=" + builder_name;
      var edit_status_link = document.createElement("a");
      edit_status_link.href = edit_status_url;
      edit_status_link.target = "_blank";

      var builder_para = document.createElement("p");
      builder_para.style.fontWeight = "bold";
      builder_para.style.lineHeight = "105%";
      builder_para.appendChild(document.createTextNode(builder_name));
      container.appendChild(builder_para);

      var comment_para = document.createElement("p");

      var comment = null;
      if (builder_statuses[builder_name]) {
        comment = builder_statuses[builder_name]["message"];
      }
      if (comment) {
        comment_para.appendChild(document.createTextNode(comment));
        comment_para.innerHTML = addLinks(comment_para.innerHTML);
        edit_status_link.appendChild(document.createTextNode("edit"));
      } else {
        edit_status_link.appendChild(document.createTextNode("add comment"));
      }
      container.appendChild(comment_para);

      var links_para = document.createElement("p");
      links_para.appendChild(document.createTextNode("("));
      links_para.appendChild(edit_status_link);
      links_para.appendChild(document.createTextNode(" or "));
      links_para.appendChild(file_bug_link);
      links_para.appendChild(document.createTextNode(")"));
      container.appendChild(links_para);
    }

    // Display a builder tooltip.
    function showBuilderTooltip(builder_name, event) {
      var tooltipbox = document.getElementById('builderTooltip');
      if (tooltipbox) {
        var cursorPosTop = (window.event ? window.event.clientY : event.pageY)
        var cursorPosLeft = (window.event ? window.event.clientX : event.pageX)

        cursorPosTop  = cursorPosTop  + document.body.scrollTop + 5;
        cursorPosLeft = cursorPosLeft  + document.body.scrollLeft;

        tooltipbox.innerHTML = "";
        buildComment(builder_name, tooltipbox);
        tooltipbox.style.top = parseInt(cursorPosTop) + 'px';
        tooltipbox.style.left = parseInt(cursorPosLeft) + 'px';
        tooltipbox.style.display = "block";
      }
    }

    // Hide the builder tooltip.
    function hideBuilderTooltip(event) {
      var tooltipbox = document.getElementById('builderTooltip');
      if (tooltipbox) {
        // Only hide the tooltip if the mouse is outside the tooltip area.
        var cursorPosTop = (window.event ? window.event.clientY : event.pageY)
        var cursorPosLeft = (window.event ? window.event.clientX : event.pageX)
        cursorPosTop  = cursorPosTop  + document.body.scrollTop;
        cursorPosLeft = cursorPosLeft  + document.body.scrollLeft;

        var top = parseInt(tooltipbox.style.top.replace("px", ""));
        var left = parseInt(tooltipbox.style.left.replace("px", ""));
        var bottom = top + parseInt(tooltipbox.offsetHeight);
        var right = left + parseInt(tooltipbox.offsetWidth);

        if (cursorPosTop < top - 5 || cursorPosTop > bottom ||
            cursorPosLeft < left || cursorPosLeft > right) {
          tooltipbox.style.display = "none";
        }
      }
    }

    // Helper function used by refreshCategories which loops through an array of
    // elements and shows or hides them as indicated.
    function showOrHideElements(elems, shouldDisplay) {
      for (var i = 0; i < elems.length; ++i) {
        elems[i].style.display = shouldDisplay ? "table-cell" : "none";
      }
    }

    // Update the display based on the selected set of categories and
    // subcategories.
    function refreshCategories() {
      for (var category in allCategories) {
        // Show or hide each subcategory.
        var colSpan = 0;
        for (var i = 0; i < subcategoriesByCategory[category].length; ++i) {
          var subcategory = subcategoriesByCategory[category][i];
          var className = "category_" + category + "_subcategory_" + subcategory;
          var elemsToChange = document.getElementsByClassName(className);
          // Only display a subcategory if its category is also displaying.
          var shouldDisplay = allCategories[category] && allSubcategories[subcategory]
          showOrHideElements(elemsToChange, shouldDisplay);
          if (shouldDisplay) {
            colSpan++;
          }
        }

        // Show or hide the category label.
        var elemsToChange = document.getElementsByClassName("category_" + category);
        var shouldDisplay = allCategories[category] && colSpan > 0;
        showOrHideElements(elemsToChange, shouldDisplay);

        // Fix the colSpan of the category header.
        var header = document.getElementById("category_header_" + category);
        if (header) {
          header.colSpan = colSpan;
        }
      }
    }

    // Adds the key/value parameter pair to the given URL.
    function appendParamToURL(url, key, value) {
      if (url.indexOf("?") == "-1") {
        url += "?";
      } else {
        url += "&";
      }
      url += key + "=" + value;
      return url;
    }

    // Build a URL using the previous URL parameters and the category and
    // subcategory lists.
    function buildURL() {
      var newLocation = location.protocol + "//" + location.host + location.pathname;
      var firstVar = true;
      for (var urlVar in urlVars) {
        if (urlVar != "hideCategories" && urlVar != "hideSubcategories") {
          if (Object.prototype.toString.call(urlVars[urlVar]) == "[object Array]") {
            for (var i = 0; i < urlVars[urlVar].length; ++i) {
              newLocation = appendParamToURL(newLocation, urlVar, urlVars[urlVar][i]);
            }
          } else {
            newLocation = appendParamToURL(newLocation, urlVar, urlVars[urlVar]);
          }
        }
      }

      // Add hidden categories to the URL.
      var hiddenCategories = ""
      for (var category in allCategories) {
        if (!allCategories[category]) {
          if (hiddenCategories == "") {
            hiddenCategories = category;
          } else {
            hiddenCategories += "," + category;
          }
        }
      }
      if (hiddenCategories != "") {
        newLocation = appendParamToURL(newLocation, "hideCategories", hiddenCategories);
      }

      // Add hidden subcategories to the URL.
      var hiddenSubcategories = ""
      for (var subcategory in allSubcategories) {
        if (!allSubcategories[subcategory]) {
          if (hiddenSubcategories == "") {
            hiddenSubcategories = subcategory;
          } else {
            hiddenSubcategories += "," + subcategory;
          }
        }
      }
      if (hiddenSubcategories != "") {
        newLocation = appendParamToURL(newLocation, "hideSubcategories", hiddenSubcategories);
      }

      return newLocation;
    }

    // Callback function for when a category or subcategory checkbox is clicked.
    function checkboxClicked(id) {
      var chkbox = document.getElementById(id);
      var toChange = id.substring("chkbox_".length).split("_");
      if (toChange[0] == "category") {
        allCategories[toChange[1]] = chkbox.checked;
      } else if (toChange[0] == "subcategory") {
        allSubcategories[toChange[1]] = chkbox.checked;
      }
      window.history.replaceState("", "Checked box", buildURL());

      // Rebuild the console table, if applicable, or fall back on attempting to
      // hide the correct categories and subcategories.
      if (typeof buildConsoleTable == "function") {
        buildConsoleTable();
      } else {
        refreshCategories();
      }
    }

    // Callback function to adjust the reload time.
    function setReload(textBox) {
      var regex = /^\d+$/;
      if (regex.test(textBox.value)) {
        refreshInterval = textBox.value * 1000;
        urlVars["reload"] = textBox.value;
        window.history.replaceState("", "Changed refresh", buildURL());
        clearTimeout(refreshTimer);
        refreshTimer = setTimeout(reloadPage, refreshInterval);
      }
    }
    </script>
    {% endblock %}
  </head>
  <body class="interface">
    {% block header -%}
    <div id="skia_header" style="top:5px;">
      <a href="{{ tree_status_baseurl }}" target="_blank" style="text-decoration:inherit;">
        <div id="tree_status_div" width="100%" height="50" class="status-message">
          <span id="tree_status_message"></span>
          <div id="sheriff_div" class="sheriff-message"></div>
          <div id="arb_status_div" class="arb-status"></div>
        </div>
      </a>
      <div id="container" style="float: left; width: 100%;">
        <div style="float:left; width:25%;">
        <form name="unused" action="javascript:void(0);">
          <input type="radio" name="internal_external" value="external" id="radio_external" onClick="setHostAndPort(null, '{{ external_port }}');">External View<br>
          <input type="radio" name="internal_external" value="internal" id="radio_internal" onClick="setHostAndPort(null, '{{ internal_port }}');">Internal View
          <br/><nobr>Refresh (seconds): <input type="text" name="reload_time" value="{{ refresh or default_refresh }}" onChange="setReload(this)" style="width: 50px;" maxlength="4" /></nobr>
          <div>
            {% if master_launch_datetime %}
            <br/>
            master launched at {{ master_launch_datetime.strftime('%Y-%m-%d %H:%M:%S UTC') }}

            <br/>
            <span title="master_running_revision: git hash of code at which the buildbot master was launched">
              <u>master_running_revision</u>
            </span> is
            <a href="https://skia.googlesource.com/buildbot.git/+/{{ master_running_revision }}">
              {{ master_running_revision|truncate(8, True, "") }}
            </a>

            <br/>
            <span title="master_revision: git hash of code currently checked out on master">
              <u>master_revision</u>
            </span> is
            <a href="https://skia.googlesource.com/buildbot.git/+/{{ master_revision }}">
              {{ master_revision|truncate(8, True, "") }}
            </a>
            {% endif %}
          </div>
        </form>
        </div>

        <div id="links" style="float:right; width:25%;">
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://sites.google.com/site/skiadocs/developer-documentation/the-skia-buildbots/1-overview" target="_blank">Full Skia documentation</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="{{ tree_status_baseurl }}" target="_blank">Skia Tree Status Manager</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://code.google.com/p/skia/issues/list?can=2&q=label%3ABreakingTheBuildbots" target="_blank">Current build-breaking bugs</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="http://go/skpdash" target="_blank">Bench Dashboard Internal</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="bug_graphs.html" target="_blank">Open Bugs Graph</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://storage.cloud.google.com/chromium-skia-gm/static_analyzers/clang_static_analyzer/index.html" target="_blank">Clang Static Analyzer</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="buildbots_self_analysis.html" target="_blank">Buildbot Self-Dashboard</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="buildslave_idle.html" target="_blank">Buildslave Idle Time Analysis</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://skia-tree-status.appspot.com/sheriff" target="_blank">Sheriff Schedule</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://skia-tree-status.appspot.com/skia-telemetry/lua_script" target="_blank">Run Lua Scripts on SKP Repo</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://skia-tree-status.appspot.com/skia-telemetry/skia_try" target="_blank">Run Patches on SKP Repo</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="http://skia-tree-status.appspot.com/redirect/rebaseline-server/static/view.html#/view.html?resultsToLoad=/results/failures" target="_blank">Recent GM Failures</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="http://chromium-skia-gm.commondatastorage.googleapis.com/depsroll.html" target="_blank">Current DEPS roll attempt</a><br/></li></ul></div>
        </div>
  
        <div style="text-align:center; width: 50%; margin-left: 25%; margin-right: 25%">
          <div id="heading" style="font-size:3.5em; text-align:center;">Skia Buildbots ({{ active_master_name }})</div>
          <div>
          <p>
          Skia Build Masters:
          <table>
            {% for master in masters %}
            {% if not (not is_internal_view and master['name'] == 'PrivateSkia') %}
              <tr id="results_summary_{{ master['name'] }}">
                <td style="text-align: right"><a href="http://{{ master['host'] }}:{{ master['internal_port'] if is_internal_view else master['external_port'] }}/console">{{ master['name'] }}</a></td>
              </tr>
            {% endif %}
            {% endfor %}
           </table>
          </div>
          </p>
        </div>
      </div>

      <div class="header">
          <a href="{{ path_to_root or '.' }}">Home</a> -
          <a href="{{ path_to_root }}waterfall">Waterfall</a>
          <a href="{{ path_to_root }}console">Console</a>
          <a href="{{ path_to_root }}builders">Builders</a>
          <a href="{{ path_to_root }}buildslaves">Buildslaves</a> -
          <a href="{{ path_to_root }}failures">Currently Failing</a> -
          <a href="{{ path_to_root }}trybots">Trybot Waterfall</a> -
          <a href="{{ path_to_root }}json/help">JSON API</a> -
          <a href="{{ path_to_root }}about">About</a>
      </div>
    </div>
    {% endblock %}

    {%- block barecontent -%}

    <div class="content">
      {%- block content -%}
      {%- endblock -%}
    </div>
    {%- endblock -%}

    {%- block footer -%}
    <div class="footer" style="clear:both">
      <hr/>
      <a href="http://buildbot.net/">BuildBot</a> ({{version}})
      {% if title -%}
        working for the&nbsp;
        {%- if title_url -%}
          <a href="{{ title_url }}">{{ title }}</a>
        {%- else -%}
          {{ title }}
        {%- endif -%}
        &nbsp;project.
      {%- endif -%}
      <br/>
      Page built: <b>{{ time }}</b> ({{ tz }})
    </div>
    {% endblock -%}
    <div id="builderTooltip" class="BuilderTooltip RoundedRect" style="display: none;" onmouseout="hideBuilderTooltip(event)"></div>
    <script language="JavaScript">
      init();
    </script>
  </body>
</html>
<commit_msg>Fix addLinks for multiple "skia:123" matches<commit_after>{%- block doctype -%}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
{% endblock %}
<!-- Copied and modified from:
http://src.chromium.org/viewvc/chrome/trunk/tools/build/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html?revision=89637&content-type=text/plain
-->
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    {% block head %}
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    {% if metatags %}
      {{ metatags }}
    {% endif %}
    <title>{{ pageTitle|e }}</title>
    <link rel="stylesheet" href="{{ stylesheet }}" type="text/css" />
    <link rel="alternate" type="application/rss+xml" title="RSS" href="{{ path_to_root }}rss">
    <script type="text/javascript">
    "use strict";

    // Revision of the buildbot code on the build master.
    var javascript_revision = "{{ master_revision }}";

    // Ports used to access the buildbot webstatus pages.
    var internalPort = "{{ internal_port }}";
    var externalPort = "{{ external_port }}";

    // JSON data URL.
    var tree_status_appspot_url = "{{ tree_status_baseurl }}";

    // Per-builder status messages.
    var builder_statuses = {};

    // How often, in milliseconds, to refresh the page.
    var refreshInterval = {{ refresh or default_refresh }} * 1000;
    var refreshTimer = null;

    // Number of times a page reload has been cancelled due to activity on the page.
    var cancelledReloads = 0;

    // Maximum number of cancelled reloads before forcing a reload.
    var maxCancelledReloads = 10;

    // Variables passed in the URL.
    var urlVars = {};

    // Categories of builders, and whether each category should be displayed.
    var allCategories = {
      {% for r in all_categories %}
      "{{ r }}": true,
      {% endfor %}
    };

    // Subcategories of builders, and whether each category should be displayed.
    var allSubcategories = {
      {% for subcategory in all_subcategories %}
      "{{ subcategory }}": true,
      {% endfor %}
    };

    // Subcategories listed by which category they fall under.
    var subcategoriesByCategory = {
      {% for category in subcategories_by_category %}
      "{{ category }}": [
        {% for subcategory in subcategories_by_category[category] %}
          "{{ subcategory }}",
        {% endfor %}
      ],
      {% endfor %}
    }

    function addLinks(text) {
      // Linkify arbitrary URLs.
      var new_text = "";
      if (text && text.indexOf("<a href=") == -1) {
        new_text = text.replace(
            /(https?:\/\/.+?)(\s|\t|<|\n|\r|$)/g, '<a href="$1" target="_blank">$1</a>$2');
      }

      // Linkify instances of "skia:123".
      var re = new RegExp("skia:[0-9]+");
      var found_bugs = new_text.match(/skia:[0-9]+/g);
      if (found_bugs) {
        for (var i = 0; i < found_bugs.length; ++i) {
          var found_bug = found_bugs[i];
          var bug_number = found_bug.split(":")[1];
          var bug_link = '<a href="https://code.google.com/p/skia/issues/detail?id=' + bug_number + '" target="_blank">' + found_bug + '</a>';
          new_text = new_text.replace(found_bug, bug_link);
        }
      }
      return new_text;
    }

    // Switch to a new host/port.
    function setHostAndPort(host, port) {
      var current_host = window.location.host.split(":");
      if (!host) {
        host = current_host[0];
      }
      if (!port) {
        port = current_host[1];
      }
      window.location.host = host + ":" + port;
    }

    // Parse the variables in the URL and return a key/value dictionary.
    function parseArgs()
    {
      var args = {};
      var dict = window.location.search.slice(1).split('&');
      for(var i = 0; i < dict.length; i++) {
        var split = dict[i].split('=');
        var key = split[0];
        var value = split[1];
        if (args[key]) {
          if (Object.prototype.toString.call(args[key]) == "[object Array]") {
            args[key].push(value);
          } else {
            args[key] = [args[key], value];
          }
        } else if (key != "") {
          args[key] = value;
        }
      }
      return args;
    }

    // If the currently running revision of this page differs from that of the
    // build master, reload the whole page instead of just the JSON data.
    function doReload() {
      // Get the master's code revision.
      loadJSONP("/json/master_revision",
                gotMasterRevision,
                null);
    }

    // Callback function for doReload.
    function gotMasterRevision(data) {
      if (data["master_checkedout_revision"] != javascript_revision) {
        // Force a reload of the page if the master has a new revision.
        location.reload(true);
      } else {
        loadAllData();
      }
    }

    // Reload the page, if the user isn't actively interacting with it.
    function reloadPage() {
      var reloadIsAllowed = true;

      // Don't allow reload if a build box is open.
      var buildBox = document.getElementById("divBox");
      if (buildBox && buildBox.style.display != "none") {
        console.log("divBox is displaying (" + buildBox.style.display + ").");
        reloadIsAllowed = false;
      }

      if (reloadIsAllowed) {
        doReload();
      } else if (cancelledReloads >= maxCancelledReloads) {
        console.log("Forcing a reload because the number of " +
                    "previously-cancelled reloads exceeded " +
                    maxCancelledReloads + ".");
        doReload();
      } else {
        console.log("Not reloading page because the user is interacting with it.");
        cancelledReloads++;
        refreshTimer = setTimeout(reloadPage, refreshInterval);
      }
    }

    // Return the name of a non-anonymous function.
    function getFunctionName(func) {
      var name = func.toString();
      name = name.substr("function ".length);
      name = name.substr(0, name.indexOf("("));
      return name;
    }

    // Use JSONP to retrieve data from the given URL. There are a few possible
    // outcomes:
    //   1. The server returns valid JSONP. In this case, the provided callback
    //      will be called with the data as the parameter.
    //   2. The server returns an error code (eg. if the requested URL isn't
    //      valid). In this case, the provided errback will be called.
    //   3. The server returns a success code but the response does not contain
    //      valid JSONP. In this case, the callback cannot be called, since that
    //      call is part of the JSONP response. Additionally, the errback will
    //      not fire, since there is no way to dynamically insert a try/catch
    //      around the response content, and the script tag's onError handler
    //      only fires if the script could not be obtained from the server.
    //
    // Note: callback must NOT be an anonymous function.
    //
    function loadJSONP(url, callback, errback) {
      var script = document.createElement("script");
      var src = appendParamToURL(url, "callback", getFunctionName(callback));
      script.setAttribute("src", src);
      script.onError = errback
      document.getElementsByTagName("head")[0].appendChild(script);
    }

    // Use an XmlHttpRequest to retrieve data from the given URL.
    function loadXML(url, callback) {
      var req = new XMLHttpRequest();
      req.open("GET", url, true);
      req.onreadystatechange = function() {
        if (req.readyState == 4) { callback(req.responseText); }
      };
      req.send();
    }

    // Set the tree status.
    function setTreeStatus(data) {
      var tree_status_div = document.getElementById("tree_status_div");
      if (tree_status_div) {
        tree_status_div.className = "status-message " + data["general_state"];
      }
      var tree_status_message = document.getElementById("tree_status_message");
      if (tree_status_message) {
        tree_status_message.innerHTML = data["message"];
      }
    }

    // Set the current sheriff.
    function setSheriff(data) {
      var sheriff_div = document.getElementById("sheriff_div");
      if (sheriff_div) {
        sheriff_div.innerHTML = "Sheriff: " + data["username"];
      }
    }

    // Set builder statuses.
    function setBuilderStatuses(data) {
      // Set the global builder status variable.
      builder_statuses = data;

      // Mark builders who have comments.
      for (var builder_name in data) {
        // Builder column headers.
        var comment_indicator = document.getElementById("commentIndicator_" + builder_name);
        if (comment_indicator) {
          comment_indicator.className += " BuilderHasComment";
        }

        // Summary boxes for all masters.
        var summary_box = document.getElementById("summaryBox_" + builder_name);
        if (summary_box) {
          summary_box.className += " BuilderHasComment";
        }
      }

      // Optionally display the builder status message on the page.
      var display_comment_elems = document.getElementsByClassName("DisplayBuilderComment");
      for (var i = 0; i < display_comment_elems.length; ++i) {
        var display_comment = display_comment_elems[i];
        display_comment.innerHTML = "";
        var builder_name = display_comment.id.slice("displayBuilderComment_".length);
        buildComment(builder_name, display_comment);
        if (builder_statuses[builder_name]) {
          display_comment.className += " BuilderHasComment";
        }
      }

      // Rebuild the console table, if applicable.
      if (typeof buildConsoleTable == "function") {
        buildConsoleTable();
      }
    }

    // Function to call on page load. Reloads the page with appropriate options
    // if they are not already set.
    function init() {
      var host = window.location.host;
      var checkedRadio;
      if (host.indexOf(internalPort, host.length - internalPort.length) !== -1) {
        checkedRadio = document.getElementById("radio_internal");
      } else if (host.indexOf(externalPort, host.length - externalPort.length) !== -1) {
        checkedRadio = document.getElementById("radio_external");
      }
      if (checkedRadio) {
        checkedRadio.checked = true;
      }

      urlVars = parseArgs();
      if (urlVars["hideCategories"]) {
        var categoryList = urlVars["hideCategories"].split(",");
        for (var i = 0; i < categoryList.length; ++i) {
          var name = categoryList[i];
          allCategories[name] = false;
          var chkbox = document.getElementById("chkbox_category_" + name);
          if (chkbox) {
            chkbox.checked = false;
          }
        }
      }
      if (urlVars["hideSubcategories"]) {
        var subcategoryList = urlVars["hideSubcategories"].split(",");
        for (var i = 0; i < subcategoryList.length; ++i) {
          var name = subcategoryList[i];
          allSubcategories[name] = false;
          var chkbox = document.getElementById("chkbox_subcategory_" + name);
          if (chkbox) {
            chkbox.checked = false;
          }
        }
      }
      loadAllData();
    }

    // Display the most recent build for each builder on the given master.
    function setLatestBuildsForMaster(master, master_builder_url, builders) {
      var master_data_row = document.getElementById("results_summary_" + master);
      if (!master_data_row) {
        return;
      }
      // Clear any old results, up to the first cell which is the label.
      while (master_data_row.cells.length > 1) {
        master_data_row.deleteCell(-1);
      }
      // Add the new results.
      for (var builder_index = 0; builder_index < builders.length; ++builder_index) {
        var cell = master_data_row.insertCell(-1);
        cell.className = "mini-box";
        cell.style.padding = "0px";
        cell.style.verticalAlign = "bottom";
        var builder_name = builders[builder_index].name;
        cell.id = "summaryBox_" + builder_name
        var outcome = builders[builder_index].outcome;
        if (!outcome) {
          outcome = "offline";
        }
        var link = document.createElement("a");
        link.href = master_builder_url + builder_name;
        link.title = builder_name;
        link.target = "_blank";
        link.innerHTML = " ";
        cell.style.width = "10px";
        link.style.width = "100%";
        link.style.height = "100%";
        link.className = outcome;
        link.style.margin = "0px";
        link.style.padding = "0px";

        // Decorate the result summary box if there's a comment for the builder.
        if (builder_statuses[builder_name]) {
          cell.className += " BuilderHasComment";
        }

        cell.appendChild(link);
      }
    }

    // Callback functions for individual masters.
    {% for master in masters %}
    {% if not (not is_internal_view and master['name'] == 'PrivateSkia') %}
    function setLatestBuildsFor{{ master['name'] }}(data) {
      setLatestBuildsForMaster("{{ master['name'] }}",
                               "http://{{ master['host'] }}:{{ master['internal_port'] if is_internal_view else master['external_port'] }}/waterfall?show=",
                               data['builders']);
    }
    {% endif %}
    {% endfor %}

    // Callback function for AutoRoll bot status.
    function gotARBStatus(data) {
      var arb_status_div = document.getElementById("arb_status_div");
      if (arb_status_div) {
        arb_status_div.innerHTML = "AutoRoll Status: " + addLinks(data);
      }
    }

    // Load JSON data from various places.
    function loadAllData() {
      // Tree status.
      loadJSONP(tree_status_appspot_url + "/banner-status?format=json",
                setTreeStatus,
                function() {
                  setTreeStatus({
                    "general_state": "",
                    "message": "Failed to load tree status.",
                  });
                });

      // Current sheriff.
      loadJSONP(tree_status_appspot_url + "/current-sheriff",
                setSheriff,
                function() { setSheriff({"username": "unknown"}); });

      // Builder statuses.
      loadJSONP(tree_status_appspot_url + "/builder-status/get_builder_statuses",
                setBuilderStatuses);

      // Last build result for each builder on each master.
      var master_json_url = "";
      {% for master in masters %}
      {% if not (not is_internal_view and master['name'] == 'PrivateSkia') %}
      master_json_url = "http://{{ master['host'] }}:{{ master['internal_port'] if is_internal_view else master['external_port'] }}/json/builder_statuses";
      loadJSONP(master_json_url, setLatestBuildsFor{{ master['name'] }});
      {% endif %}
      {% endfor %}

      // AutoRoll bot status.
      loadXML("http://chromium-skia-gm.storage.googleapis.com/arb_status.html", gotARBStatus);

      // Set the reload timer.
      refreshTimer = setTimeout(reloadPage, refreshInterval);

      // Load the console data, if applicable.
      if (typeof loadConsoleData == "function") {
        loadConsoleData();
      }
    }

    // Get the comment text for a given builder.
    function buildComment(builder_name, container) {
      var file_bug_link = document.createElement("a");
      file_bug_link.href = "http://code.google.com/p/skia/issues/entry?labels=BreakingTheBuildbots,Type-Defect,Priority-Medium&summary=" + builder_name;
      file_bug_link.target = "_blank";
      file_bug_link.appendChild(document.createTextNode("file bug"));

      var edit_status_url = tree_status_appspot_url + "/builder-status/?selected_builder_name=" + builder_name;
      var edit_status_link = document.createElement("a");
      edit_status_link.href = edit_status_url;
      edit_status_link.target = "_blank";

      var builder_para = document.createElement("p");
      builder_para.style.fontWeight = "bold";
      builder_para.style.lineHeight = "105%";
      builder_para.appendChild(document.createTextNode(builder_name));
      container.appendChild(builder_para);

      var comment_para = document.createElement("p");

      var comment = null;
      if (builder_statuses[builder_name]) {
        comment = builder_statuses[builder_name]["message"];
      }
      if (comment) {
        comment_para.appendChild(document.createTextNode(comment));
        comment_para.innerHTML = addLinks(comment_para.innerHTML);
        edit_status_link.appendChild(document.createTextNode("edit"));
      } else {
        edit_status_link.appendChild(document.createTextNode("add comment"));
      }
      container.appendChild(comment_para);

      var links_para = document.createElement("p");
      links_para.appendChild(document.createTextNode("("));
      links_para.appendChild(edit_status_link);
      links_para.appendChild(document.createTextNode(" or "));
      links_para.appendChild(file_bug_link);
      links_para.appendChild(document.createTextNode(")"));
      container.appendChild(links_para);
    }

    // Display a builder tooltip.
    function showBuilderTooltip(builder_name, event) {
      var tooltipbox = document.getElementById('builderTooltip');
      if (tooltipbox) {
        var cursorPosTop = (window.event ? window.event.clientY : event.pageY)
        var cursorPosLeft = (window.event ? window.event.clientX : event.pageX)

        cursorPosTop  = cursorPosTop  + document.body.scrollTop + 5;
        cursorPosLeft = cursorPosLeft  + document.body.scrollLeft;

        tooltipbox.innerHTML = "";
        buildComment(builder_name, tooltipbox);
        tooltipbox.style.top = parseInt(cursorPosTop) + 'px';
        tooltipbox.style.left = parseInt(cursorPosLeft) + 'px';
        tooltipbox.style.display = "block";
      }
    }

    // Hide the builder tooltip.
    function hideBuilderTooltip(event) {
      var tooltipbox = document.getElementById('builderTooltip');
      if (tooltipbox) {
        // Only hide the tooltip if the mouse is outside the tooltip area.
        var cursorPosTop = (window.event ? window.event.clientY : event.pageY)
        var cursorPosLeft = (window.event ? window.event.clientX : event.pageX)
        cursorPosTop  = cursorPosTop  + document.body.scrollTop;
        cursorPosLeft = cursorPosLeft  + document.body.scrollLeft;

        var top = parseInt(tooltipbox.style.top.replace("px", ""));
        var left = parseInt(tooltipbox.style.left.replace("px", ""));
        var bottom = top + parseInt(tooltipbox.offsetHeight);
        var right = left + parseInt(tooltipbox.offsetWidth);

        if (cursorPosTop < top - 5 || cursorPosTop > bottom ||
            cursorPosLeft < left || cursorPosLeft > right) {
          tooltipbox.style.display = "none";
        }
      }
    }

    // Helper function used by refreshCategories which loops through an array of
    // elements and shows or hides them as indicated.
    function showOrHideElements(elems, shouldDisplay) {
      for (var i = 0; i < elems.length; ++i) {
        elems[i].style.display = shouldDisplay ? "table-cell" : "none";
      }
    }

    // Update the display based on the selected set of categories and
    // subcategories.
    function refreshCategories() {
      for (var category in allCategories) {
        // Show or hide each subcategory.
        var colSpan = 0;
        for (var i = 0; i < subcategoriesByCategory[category].length; ++i) {
          var subcategory = subcategoriesByCategory[category][i];
          var className = "category_" + category + "_subcategory_" + subcategory;
          var elemsToChange = document.getElementsByClassName(className);
          // Only display a subcategory if its category is also displaying.
          var shouldDisplay = allCategories[category] && allSubcategories[subcategory]
          showOrHideElements(elemsToChange, shouldDisplay);
          if (shouldDisplay) {
            colSpan++;
          }
        }

        // Show or hide the category label.
        var elemsToChange = document.getElementsByClassName("category_" + category);
        var shouldDisplay = allCategories[category] && colSpan > 0;
        showOrHideElements(elemsToChange, shouldDisplay);

        // Fix the colSpan of the category header.
        var header = document.getElementById("category_header_" + category);
        if (header) {
          header.colSpan = colSpan;
        }
      }
    }

    // Adds the key/value parameter pair to the given URL.
    function appendParamToURL(url, key, value) {
      if (url.indexOf("?") == "-1") {
        url += "?";
      } else {
        url += "&";
      }
      url += key + "=" + value;
      return url;
    }

    // Build a URL using the previous URL parameters and the category and
    // subcategory lists.
    function buildURL() {
      var newLocation = location.protocol + "//" + location.host + location.pathname;
      var firstVar = true;
      for (var urlVar in urlVars) {
        if (urlVar != "hideCategories" && urlVar != "hideSubcategories") {
          if (Object.prototype.toString.call(urlVars[urlVar]) == "[object Array]") {
            for (var i = 0; i < urlVars[urlVar].length; ++i) {
              newLocation = appendParamToURL(newLocation, urlVar, urlVars[urlVar][i]);
            }
          } else {
            newLocation = appendParamToURL(newLocation, urlVar, urlVars[urlVar]);
          }
        }
      }

      // Add hidden categories to the URL.
      var hiddenCategories = ""
      for (var category in allCategories) {
        if (!allCategories[category]) {
          if (hiddenCategories == "") {
            hiddenCategories = category;
          } else {
            hiddenCategories += "," + category;
          }
        }
      }
      if (hiddenCategories != "") {
        newLocation = appendParamToURL(newLocation, "hideCategories", hiddenCategories);
      }

      // Add hidden subcategories to the URL.
      var hiddenSubcategories = ""
      for (var subcategory in allSubcategories) {
        if (!allSubcategories[subcategory]) {
          if (hiddenSubcategories == "") {
            hiddenSubcategories = subcategory;
          } else {
            hiddenSubcategories += "," + subcategory;
          }
        }
      }
      if (hiddenSubcategories != "") {
        newLocation = appendParamToURL(newLocation, "hideSubcategories", hiddenSubcategories);
      }

      return newLocation;
    }

    // Callback function for when a category or subcategory checkbox is clicked.
    function checkboxClicked(id) {
      var chkbox = document.getElementById(id);
      var toChange = id.substring("chkbox_".length).split("_");
      if (toChange[0] == "category") {
        allCategories[toChange[1]] = chkbox.checked;
      } else if (toChange[0] == "subcategory") {
        allSubcategories[toChange[1]] = chkbox.checked;
      }
      window.history.replaceState("", "Checked box", buildURL());

      // Rebuild the console table, if applicable, or fall back on attempting to
      // hide the correct categories and subcategories.
      if (typeof buildConsoleTable == "function") {
        buildConsoleTable();
      } else {
        refreshCategories();
      }
    }

    // Callback function to adjust the reload time.
    function setReload(textBox) {
      var regex = /^\d+$/;
      if (regex.test(textBox.value)) {
        refreshInterval = textBox.value * 1000;
        urlVars["reload"] = textBox.value;
        window.history.replaceState("", "Changed refresh", buildURL());
        clearTimeout(refreshTimer);
        refreshTimer = setTimeout(reloadPage, refreshInterval);
      }
    }
    </script>
    {% endblock %}
  </head>
  <body class="interface">
    {% block header -%}
    <div id="skia_header" style="top:5px;">
      <a href="{{ tree_status_baseurl }}" target="_blank" style="text-decoration:inherit;">
        <div id="tree_status_div" width="100%" height="50" class="status-message">
          <span id="tree_status_message"></span>
          <div id="sheriff_div" class="sheriff-message"></div>
          <div id="arb_status_div" class="arb-status"></div>
        </div>
      </a>
      <div id="container" style="float: left; width: 100%;">
        <div style="float:left; width:25%;">
        <form name="unused" action="javascript:void(0);">
          <input type="radio" name="internal_external" value="external" id="radio_external" onClick="setHostAndPort(null, '{{ external_port }}');">External View<br>
          <input type="radio" name="internal_external" value="internal" id="radio_internal" onClick="setHostAndPort(null, '{{ internal_port }}');">Internal View
          <br/><nobr>Refresh (seconds): <input type="text" name="reload_time" value="{{ refresh or default_refresh }}" onChange="setReload(this)" style="width: 50px;" maxlength="4" /></nobr>
          <div>
            {% if master_launch_datetime %}
            <br/>
            master launched at {{ master_launch_datetime.strftime('%Y-%m-%d %H:%M:%S UTC') }}

            <br/>
            <span title="master_running_revision: git hash of code at which the buildbot master was launched">
              <u>master_running_revision</u>
            </span> is
            <a href="https://skia.googlesource.com/buildbot.git/+/{{ master_running_revision }}">
              {{ master_running_revision|truncate(8, True, "") }}
            </a>

            <br/>
            <span title="master_revision: git hash of code currently checked out on master">
              <u>master_revision</u>
            </span> is
            <a href="https://skia.googlesource.com/buildbot.git/+/{{ master_revision }}">
              {{ master_revision|truncate(8, True, "") }}
            </a>
            {% endif %}
          </div>
        </form>
        </div>

        <div id="links" style="float:right; width:25%;">
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://sites.google.com/site/skiadocs/developer-documentation/the-skia-buildbots/1-overview" target="_blank">Full Skia documentation</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="{{ tree_status_baseurl }}" target="_blank">Skia Tree Status Manager</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://code.google.com/p/skia/issues/list?can=2&q=label%3ABreakingTheBuildbots" target="_blank">Current build-breaking bugs</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="http://go/skpdash" target="_blank">Bench Dashboard Internal</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="bug_graphs.html" target="_blank">Open Bugs Graph</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://storage.cloud.google.com/chromium-skia-gm/static_analyzers/clang_static_analyzer/index.html" target="_blank">Clang Static Analyzer</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="buildbots_self_analysis.html" target="_blank">Buildbot Self-Dashboard</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="buildslave_idle.html" target="_blank">Buildslave Idle Time Analysis</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://skia-tree-status.appspot.com/sheriff" target="_blank">Sheriff Schedule</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://skia-tree-status.appspot.com/skia-telemetry/lua_script" target="_blank">Run Lua Scripts on SKP Repo</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="https://skia-tree-status.appspot.com/skia-telemetry/skia_try" target="_blank">Run Patches on SKP Repo</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="http://skia-tree-status.appspot.com/redirect/rebaseline-server/static/view.html#/view.html?resultsToLoad=/results/failures" target="_blank">Recent GM Failures</a><br/></li></ul></div>
          <div style="float:right; width: 195px;"><ul style="margin: 0px 0px 0px 25px; padding:0;"><li><a href="http://chromium-skia-gm.commondatastorage.googleapis.com/depsroll.html" target="_blank">Current DEPS roll attempt</a><br/></li></ul></div>
        </div>
  
        <div style="text-align:center; width: 50%; margin-left: 25%; margin-right: 25%">
          <div id="heading" style="font-size:3.5em; text-align:center;">Skia Buildbots ({{ active_master_name }})</div>
          <div>
          <p>
          Skia Build Masters:
          <table>
            {% for master in masters %}
            {% if not (not is_internal_view and master['name'] == 'PrivateSkia') %}
              <tr id="results_summary_{{ master['name'] }}">
                <td style="text-align: right"><a href="http://{{ master['host'] }}:{{ master['internal_port'] if is_internal_view else master['external_port'] }}/console">{{ master['name'] }}</a></td>
              </tr>
            {% endif %}
            {% endfor %}
           </table>
          </div>
          </p>
        </div>
      </div>

      <div class="header">
          <a href="{{ path_to_root or '.' }}">Home</a> -
          <a href="{{ path_to_root }}waterfall">Waterfall</a>
          <a href="{{ path_to_root }}console">Console</a>
          <a href="{{ path_to_root }}builders">Builders</a>
          <a href="{{ path_to_root }}buildslaves">Buildslaves</a> -
          <a href="{{ path_to_root }}failures">Currently Failing</a> -
          <a href="{{ path_to_root }}trybots">Trybot Waterfall</a> -
          <a href="{{ path_to_root }}json/help">JSON API</a> -
          <a href="{{ path_to_root }}about">About</a>
      </div>
    </div>
    {% endblock %}

    {%- block barecontent -%}

    <div class="content">
      {%- block content -%}
      {%- endblock -%}
    </div>
    {%- endblock -%}

    {%- block footer -%}
    <div class="footer" style="clear:both">
      <hr/>
      <a href="http://buildbot.net/">BuildBot</a> ({{version}})
      {% if title -%}
        working for the&nbsp;
        {%- if title_url -%}
          <a href="{{ title_url }}">{{ title }}</a>
        {%- else -%}
          {{ title }}
        {%- endif -%}
        &nbsp;project.
      {%- endif -%}
      <br/>
      Page built: <b>{{ time }}</b> ({{ tz }})
    </div>
    {% endblock -%}
    <div id="builderTooltip" class="BuilderTooltip RoundedRect" style="display: none;" onmouseout="hideBuilderTooltip(event)"></div>
    <script language="JavaScript">
      init();
    </script>
  </body>
</html>

{'fraction_non_alphanumeric': 0.1400538668718738, 'fraction_numerical': 0.0076471719892266254, 'mean_word_length': 2.57358922944715, 'pattern_counts': {'":': 18, '<': 493, '<?xml version=': 0, '>': 477, 'https://': 18, 'lorem ipsum': 0, 'www.': 4, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1334598', 'n_tokens_mistral': 18949, 'n_tokens_neox': 17999, 'n_words': 4317}