// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "media/base/media_switches.h"
#include "net/test/embedded_test_server/embedded_test_server.h"

using std::string;
namespace content {

struct SsrcEntry {
  string GetSsrcAttributeString() const {
    std::stringstream ss;
    ss << "a=ssrc:" << id;
    std::map<string, string>::const_iterator iter;
    for (iter = properties.begin(); iter != properties.end(); ++iter) {
      ss << " " << iter->first << ":" << iter->second;
    }
    return ss.str();
  }

  string GetAsJSON() const {
    std::stringstream ss;
    ss << "{";
    std::map<string, string>::const_iterator iter;
    for (iter = properties.begin(); iter != properties.end(); ++iter) {
      if (iter != properties.begin())
        ss << ",";
      ss << "\"" << iter->first << "\":\"" << iter->second << "\"";
    }
    ss << "}";
    return ss.str();
  }

  string id;
  std::map<string, string> properties;
};

struct EventEntry {
  string type;
  string value;
};

struct StatsUnit {
  string GetString() const {
    std::stringstream ss;
    ss << "{timestamp:" << timestamp << ", values:[";
    std::map<string, string>::const_iterator iter;
    for (iter = values.begin(); iter != values.end(); ++iter) {
      ss << "'" << iter->first << "','" << iter->second << "',";
    }
    ss << "]}";
    return ss.str();
  }

  int64_t timestamp;
  std::map<string, string> values;
};

struct StatsEntry {
  string type;
  string id;
  StatsUnit stats;
};

typedef std::map<string, std::vector<string> > StatsMap;

class PeerConnectionEntry {
 public:
  PeerConnectionEntry(int pid, int lid) : pid_(pid), lid_(lid) {}

  void AddEvent(const string& type, const string& value) {
    EventEntry entry = {type, value};
    events_.push_back(entry);
  }

  string getIdString() const {
    std::stringstream ss;
    ss << pid_ << "-" << lid_;
    return ss.str();
  }

  string getLogIdString() const {
    std::stringstream ss;
    ss << pid_ << "-" << lid_ << "-update-log";
    return ss.str();
  }

  string getAllUpdateString() const {
    std::stringstream ss;
    ss << "{pid:" << pid_ << ", lid:" << lid_ << ", log:[";
    for (size_t i = 0; i < events_.size(); ++i) {
      ss << "{type:'" << events_[i].type <<
          "', value:'" << events_[i].value << "'},";
    }
    ss << "]}";
    return ss.str();
  }

  int pid_;
  int lid_;
  std::vector<EventEntry> events_;
  // This is a record of the history of stats value reported for each stats
  // report id (e.g. ssrc-1234) for each stats name (e.g. framerate).
  // It a 2-D map with each map entry is a vector of reported values.
  // It is used to verify the graph data series.
  std::map<string, StatsMap> stats_;
};

class UserMediaRequestEntry {
 public:
  UserMediaRequestEntry(int pid,
                        int rid,
                        const std::string& origin,
                        const std::string& audio_constraints,
                        const std::string& video_constraints)
      : pid(pid),
        rid(rid),
        origin(origin),
        audio_constraints(audio_constraints),
        video_constraints(video_constraints) {}

  int pid;
  int rid;
  std::string origin;
  std::string audio_constraints;
  std::string video_constraints;
};

static const int64_t FAKE_TIME_STAMP = 3600000;

#if defined(OS_WIN)
// All tests are flaky on Windows: crbug.com/277322.
#define MAYBE_WebRtcInternalsBrowserTest DISABLED_WebRtcInternalsBrowserTest
#else
#define MAYBE_WebRtcInternalsBrowserTest WebRtcInternalsBrowserTest
#endif

class MAYBE_WebRtcInternalsBrowserTest: public ContentBrowserTest {
 public:
  MAYBE_WebRtcInternalsBrowserTest() {}
  ~MAYBE_WebRtcInternalsBrowserTest() override {}

  void SetUpOnMainThread() override {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kUseFakeUIForMediaStream);
    ASSERT_TRUE(base::CommandLine::ForCurrentProcess()->HasSwitch(
        switches::kUseFakeDeviceForMediaStream));
  }

 protected:
  bool ExecuteJavascript(const string& javascript) {
    return ExecuteScript(shell(), javascript);
  }

  void ExpectTitle(const std::string& expected_title) const {
    base::string16 expected_title16(base::ASCIIToUTF16(expected_title));
    TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
    EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
  }

  // Execute the javascript of addPeerConnection.
  void ExecuteAddPeerConnectionJs(const PeerConnectionEntry& pc) {
    std::stringstream ss;
    ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ << ", " <<
           "url:'u', rtcConfiguration:'s', constraints:'c'}";
    ASSERT_TRUE(ExecuteJavascript("addPeerConnection(" + ss.str() + ");"));
  }

  // Execute the javascript of removePeerConnection.
  void ExecuteRemovePeerConnectionJs(const PeerConnectionEntry& pc) {
    std::stringstream ss;
    ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ << "}";

    ASSERT_TRUE(ExecuteJavascript("removePeerConnection(" + ss.str() + ");"));
  }

  // Execute the javascript of addGetUserMedia.
  void ExecuteAddGetUserMediaJs(const UserMediaRequestEntry& request) {
    std::stringstream ss;
    ss << "{pid:" << request.pid << ", rid:" << request.rid << ", origin:'"
       << request.origin << "', audio:'" << request.audio_constraints
       << "', video:'" << request.video_constraints << "'}";

    ASSERT_TRUE(ExecuteJavascript("addGetUserMedia(" + ss.str() + ");"));
  }

  // Execute the javascript of removeGetUserMediaForRenderer.
  void ExecuteRemoveGetUserMediaForRendererJs(int rid) {
    std::stringstream ss;
    ss << "{rid:" << rid << "}";
    ASSERT_TRUE(
        ExecuteJavascript("removeGetUserMediaForRenderer(" + ss.str() + ");"));
  }

  // Verifies that the DOM element with id |id| exists.
  void VerifyElementWithId(const string& id) {
    bool result = false;
    ASSERT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send($('" + id + "') != null);",
        &result));
    EXPECT_TRUE(result);
  }

  // Verifies that the DOM element with id |id| does not exist.
  void VerifyNoElementWithId(const string& id) {
    bool result = false;
    ASSERT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send($('" + id + "') == null);",
        &result));
    EXPECT_TRUE(result);
  }

  // Verifies the JS Array of userMediaRequests matches |requests|.
  void VerifyUserMediaRequest(
      const std::vector<UserMediaRequestEntry>& requests) {
    string json_requests;
    ASSERT_TRUE(
        ExecuteScriptAndExtractString(shell(),
                                      "window.domAutomationController.send("
                                      "    JSON.stringify(userMediaRequests));",
                                      &json_requests));
    std::unique_ptr<base::Value> value_requests =
        base::JSONReader::Read(json_requests);

    EXPECT_EQ(base::Value::Type::LIST, value_requests->type());

    base::ListValue* list_request =
        static_cast<base::ListValue*>(value_requests.get());
    EXPECT_EQ(requests.size(), list_request->GetSize());

    for (size_t i = 0; i < requests.size(); ++i) {
      base::DictionaryValue* dict = nullptr;
      ASSERT_TRUE(list_request->GetDictionary(i, &dict));
      int pid, rid;
      std::string origin, audio, video;
      ASSERT_TRUE(dict->GetInteger("pid", &pid));
      ASSERT_TRUE(dict->GetInteger("rid", &rid));
      ASSERT_TRUE(dict->GetString("origin", &origin));
      ASSERT_TRUE(dict->GetString("audio", &audio));
      ASSERT_TRUE(dict->GetString("video", &video));
      EXPECT_EQ(requests[i].pid, pid);
      EXPECT_EQ(requests[i].rid, rid);
      EXPECT_EQ(requests[i].origin, origin);
      EXPECT_EQ(requests[i].audio_constraints, audio);
      EXPECT_EQ(requests[i].video_constraints, video);
    }

    bool user_media_tab_existed = false;
    ASSERT_TRUE(
        ExecuteScriptAndExtractBool(shell(),
                                    "window.domAutomationController.send("
                                    "    $('user-media-tab-id') != null);",
                                    &user_media_tab_existed));
    EXPECT_EQ(!requests.empty(), user_media_tab_existed);

    if (user_media_tab_existed) {
      int user_media_request_count = -1;
      ASSERT_TRUE(ExecuteScriptAndExtractInt(
          shell(),
          "window.domAutomationController.send("
          "    $('user-media-tab-id').childNodes.length);",
          &user_media_request_count));
      ASSERT_EQ(requests.size(), static_cast<size_t>(user_media_request_count));
    }
  }

  // Verifies that DOM for |pc| is correctly created with the right content.
  void VerifyPeerConnectionEntry(const PeerConnectionEntry& pc) {
    VerifyElementWithId(pc.getIdString());
    if (pc.events_.size() == 0)
      return;

    string log_id = pc.getLogIdString();
    VerifyElementWithId(log_id);
    string result;
    for (size_t i = 0; i < pc.events_.size(); ++i) {
      std::stringstream ss;
      ss << "var row = $('" << log_id << "').rows[" << (i + 1) << "];"
            "var cell = row.lastChild;"
            "window.domAutomationController.send(cell.firstChild.textContent);";
      ASSERT_TRUE(ExecuteScriptAndExtractString(shell(), ss.str(), &result));
      EXPECT_EQ(pc.events_[i].type + pc.events_[i].value, result);
    }
  }

  // Executes the javascript of updatePeerConnection and verifies the result.
  void ExecuteAndVerifyUpdatePeerConnection(
      PeerConnectionEntry& pc, const string& type, const string& value) {
    pc.AddEvent(type, value);

    std::stringstream ss;
    ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ <<
         ", type:'" << type << "', value:'" << value << "'}";
    ASSERT_TRUE(ExecuteJavascript("updatePeerConnection(" + ss.str() + ")"));

    VerifyPeerConnectionEntry(pc);
  }

  // Execute addStats and verifies that the stats table has the right content.
  void ExecuteAndVerifyAddStats(
      PeerConnectionEntry& pc, const string& type, const string& id,
      StatsUnit& stats) {
    StatsEntry entry = {type, id, stats};

    // Adds each new value to the map of stats history.
    std::map<string, string>::iterator iter;
    for (iter = stats.values.begin(); iter != stats.values.end(); iter++) {
      pc.stats_[id][iter->first].push_back(iter->second);
    }
    std::stringstream ss;
    ss << "{pid:" << pc.pid_ << ", lid:" << pc.lid_ << ","
           "reports:[" << "{id:'" << id << "', type:'" << type << "', "
                           "stats:" << stats.GetString() << "}]}";

    ASSERT_TRUE(ExecuteJavascript("addStats(" + ss.str() + ")"));
    VerifyStatsTable(pc, entry);
  }


  // Verifies that the stats table has the right content.
  void VerifyStatsTable(const PeerConnectionEntry& pc,
                        const StatsEntry& report) {
    string table_id =
        pc.getIdString() + "-table-" + report.id;
    VerifyElementWithId(table_id);

    std::map<string, string>::const_iterator iter;
    for (iter = report.stats.values.begin();
         iter != report.stats.values.end(); iter++) {
      VerifyStatsTableRow(table_id, iter->first, iter->second);
    }
  }

  // Verifies that the row named as |name| of the stats table |table_id| has
  // the correct content as |name| : |value|.
  void VerifyStatsTableRow(const string& table_id,
                           const string& name,
                           const string& value) {
    VerifyElementWithId(table_id + "-" + name);

    string result;
    ASSERT_TRUE(ExecuteScriptAndExtractString(
        shell(),
        "var row = $('" + table_id + "-" + name + "');"
        "var name = row.cells[0].textContent;"
        "var value = row.cells[1].textContent;"
        "window.domAutomationController.send(name + ':' + value)",
        &result));
    EXPECT_EQ(name + ":" + value, result);
  }

  // Verifies that the graph data series consistent with pc.stats_.
  void VerifyStatsGraph(const PeerConnectionEntry& pc) {
    std::map<string, StatsMap>::const_iterator stream_iter;
    for (stream_iter = pc.stats_.begin();
         stream_iter != pc.stats_.end(); stream_iter++) {
      StatsMap::const_iterator stats_iter;
      for (stats_iter = stream_iter->second.begin();
           stats_iter != stream_iter->second.end();
           stats_iter++) {
        string graph_id = stream_iter->first + "-" + stats_iter->first;
        for (size_t i = 0; i < stats_iter->second.size(); ++i) {
          float number;
          std::stringstream stream(stats_iter->second[i]);
          stream >> number;
          if (stream.fail())
            continue;
          VerifyGraphDataPoint(
              pc.getIdString(), graph_id, i, stats_iter->second[i]);
        }
      }
    }
  }

  // Verifies that the graph data point at index |index| has value |value|.
  void VerifyGraphDataPoint(const string& pc_id, const string& graph_id,
                            int index, const string& value) {
    bool result = false;
    ASSERT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send("
           "graphViews['" + pc_id + "-" + graph_id + "'] != null)",
        &result));
    EXPECT_TRUE(result);

    std::stringstream ss;
    ss << "var dp = peerConnectionDataStore['" << pc_id << "']"
          ".getDataSeries('" << graph_id << "').dataPoints_[" << index << "];"
          "window.domAutomationController.send(dp.value.toString())";
    string actual_value;
    ASSERT_TRUE(
        ExecuteScriptAndExtractString(shell(), ss.str(), &actual_value));
    EXPECT_EQ(value, actual_value);
  }

  // Get the JSON string of the ssrc info from the page.
  string GetSsrcInfo(const string& ssrc_id) {
    string result;
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        shell(),
        "window.domAutomationController.send(JSON.stringify("
           "ssrcInfoManager.streamInfoContainer_['" + ssrc_id + "']))",
        &result));
    return result;
  }

  int GetSsrcInfoBlockCount(Shell* shell) {
    int count = 0;
    EXPECT_TRUE(ExecuteScriptAndExtractInt(
        shell,
        "window.domAutomationController.send("
        "    document.getElementsByClassName("
        "        ssrcInfoManager.SSRC_INFO_BLOCK_CLASS).length);",
        &count));
    return count;
  }

  // Verifies |dump| contains |peer_connection_number| peer connection dumps,
  // each containing |update_number| updates and |stats_number| stats tables.
  void VerifyPageDumpStructure(base::Value* dump,
                               int peer_connection_number,
                               int update_number,
                               int stats_number) {
    EXPECT_NE((base::Value*)nullptr, dump);
    EXPECT_EQ(base::Value::Type::DICTIONARY, dump->type());

    base::DictionaryValue* dict_dump =
        static_cast<base::DictionaryValue*>(dump);
    EXPECT_EQ((size_t) peer_connection_number, dict_dump->size());

    base::DictionaryValue::Iterator it(*dict_dump);
    for (; !it.IsAtEnd(); it.Advance()) {
      base::Value* value = nullptr;
      dict_dump->Get(it.key(), &value);
      EXPECT_EQ(base::Value::Type::DICTIONARY, value->type());
      base::DictionaryValue* pc_dump =
          static_cast<base::DictionaryValue*>(value);
      EXPECT_TRUE(pc_dump->HasKey("updateLog"));
      EXPECT_TRUE(pc_dump->HasKey("stats"));

      // Verifies the number of updates.
      pc_dump->Get("updateLog", &value);
      EXPECT_EQ(base::Value::Type::LIST, value->type());
      base::ListValue* list = static_cast<base::ListValue*>(value);
      EXPECT_EQ((size_t) update_number, list->GetSize());

      // Verifies the number of stats tables.
      pc_dump->Get("stats", &value);
      EXPECT_EQ(base::Value::Type::DICTIONARY, value->type());
      base::DictionaryValue* dict = static_cast<base::DictionaryValue*>(value);
      EXPECT_EQ((size_t) stats_number, dict->size());
    }
  }

  // Verifies |dump| contains the correct statsTable and statsDataSeries for
  // |pc|.
  void VerifyStatsDump(base::Value* dump,
                       const PeerConnectionEntry& pc,
                       const string& report_type,
                       const string& report_id,
                       const StatsUnit& stats) {
    EXPECT_NE((base::Value*)nullptr, dump);
    EXPECT_EQ(base::Value::Type::DICTIONARY, dump->type());

    base::DictionaryValue* dict_dump =
        static_cast<base::DictionaryValue*>(dump);
    base::Value* value = nullptr;
    dict_dump->Get(pc.getIdString(), &value);
    base::DictionaryValue* pc_dump = static_cast<base::DictionaryValue*>(value);

    // Verifies there is one data series per stats name.
    value = nullptr;
    pc_dump->Get("stats", &value);
    EXPECT_EQ(base::Value::Type::DICTIONARY, value->type());

    base::DictionaryValue* dataSeries =
        static_cast<base::DictionaryValue*>(value);
    EXPECT_EQ(stats.values.size(), dataSeries->size());
  }
};

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest,
                       AddAndRemovePeerConnection) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  // Add two PeerConnections and then remove them.
  PeerConnectionEntry pc_1(1, 0);
  ExecuteAddPeerConnectionJs(pc_1);
  VerifyPeerConnectionEntry(pc_1);

  PeerConnectionEntry pc_2(2, 1);
  ExecuteAddPeerConnectionJs(pc_2);
  VerifyPeerConnectionEntry(pc_2);

  ExecuteRemovePeerConnectionJs(pc_1);
  VerifyNoElementWithId(pc_1.getIdString());
  VerifyPeerConnectionEntry(pc_2);

  ExecuteRemovePeerConnectionJs(pc_2);
  VerifyNoElementWithId(pc_2.getIdString());
}

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest,
                       UpdateAllPeerConnections) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  PeerConnectionEntry pc_0(1, 0);
  pc_0.AddEvent("e1", "v1");
  pc_0.AddEvent("e2", "v2");
  PeerConnectionEntry pc_1(1, 1);
  pc_1.AddEvent("e3", "v3");
  pc_1.AddEvent("e4", "v4");
  string pc_array = "[" + pc_0.getAllUpdateString() + ", " +
                          pc_1.getAllUpdateString() + "]";
  EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));
  VerifyPeerConnectionEntry(pc_0);
  VerifyPeerConnectionEntry(pc_1);
}

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, UpdatePeerConnection) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  // Add one PeerConnection and send one update.
  PeerConnectionEntry pc_1(1, 0);
  ExecuteAddPeerConnectionJs(pc_1);

  ExecuteAndVerifyUpdatePeerConnection(pc_1, "e1", "v1");

  // Add another PeerConnection and send two updates.
  PeerConnectionEntry pc_2(1, 1);
  ExecuteAddPeerConnectionJs(pc_2);

  SsrcEntry ssrc1, ssrc2;
  ssrc1.id = "ssrcid1";
  ssrc1.properties["msid"] = "mymsid";
  ssrc2.id = "ssrcid2";
  ssrc2.properties["label"] = "mylabel";
  ssrc2.properties["cname"] = "mycname";

  ExecuteAndVerifyUpdatePeerConnection(pc_2, "setRemoteDescription",
      ssrc1.GetSsrcAttributeString());

  ExecuteAndVerifyUpdatePeerConnection(pc_2, "setLocalDescription",
      ssrc2.GetSsrcAttributeString());

  EXPECT_EQ(ssrc1.GetAsJSON(), GetSsrcInfo(ssrc1.id));
  EXPECT_EQ(ssrc2.GetAsJSON(), GetSsrcInfo(ssrc2.id));

  StatsUnit stats = {FAKE_TIME_STAMP};
  stats.values["ssrc"] = ssrc1.id;
  ExecuteAndVerifyAddStats(pc_2, "ssrc", "dummyId", stats);
  EXPECT_GT(GetSsrcInfoBlockCount(shell()), 0);
}

// Tests that adding random named stats updates the dataSeries and graphs.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, AddStats) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  PeerConnectionEntry pc(1, 0);
  ExecuteAddPeerConnectionJs(pc);

  const string type = "ssrc";
  const string id = "ssrc-1234";
  StatsUnit stats = {FAKE_TIME_STAMP};
  stats.values["trackId"] = "abcd";
  stats.values["bitrate"] = "2000";
  stats.values["framerate"] = "30";

  // Add new stats and verify the stats table and graphs.
  ExecuteAndVerifyAddStats(pc, type, id, stats);
  VerifyStatsGraph(pc);

  // Update existing stats and verify the stats table and graphs.
  stats.values["bitrate"] = "2001";
  stats.values["framerate"] = "31";
  ExecuteAndVerifyAddStats(pc, type, id, stats);
  VerifyStatsGraph(pc);
}

// Tests that the bandwidth estimation values are drawn on a single graph.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, BweCompoundGraph) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  PeerConnectionEntry pc(1, 0);
  ExecuteAddPeerConnectionJs(pc);

  StatsUnit stats = {FAKE_TIME_STAMP};
  stats.values["googAvailableSendBandwidth"] = "1000000";
  stats.values["googTargetEncBitrate"] = "1000";
  stats.values["googActualEncBitrate"] = "1000000";
  stats.values["googRetransmitBitrate"] = "10";
  stats.values["googTransmitBitrate"] = "1000000";
  const string stats_type = "bwe";
  const string stats_id = "videobwe";
  ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);

  string graph_id =
      pc.getIdString() + "-" + stats_id + "-bweCompound";
  bool result = false;
  // Verify that the bweCompound graph exists.
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send("
        "   graphViews['" + graph_id + "'] != null)",
        &result));
  EXPECT_TRUE(result);

  // Verify that the bweCompound graph contains multiple dataSeries.
  int count = 0;
  ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell(),
        "window.domAutomationController.send("
        "   graphViews['" + graph_id + "'].getDataSeriesCount())",
        &count));
  EXPECT_EQ((int)stats.values.size(), count);
}

// Tests that the total packet/byte count is converted to count per second,
// and the converted data is drawn.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, ConvertedGraphs) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  PeerConnectionEntry pc(1, 0);
  ExecuteAddPeerConnectionJs(pc);

  const string stats_type = "s";
  const string stats_id = "1";
  const int num_converted_stats = 4;
  const string stats_names[] =
      {"packetsSent", "bytesSent", "packetsReceived", "bytesReceived"};
  const string converted_names[] =
      {"packetsSentPerSecond", "bitsSentPerSecond",
       "packetsReceivedPerSecond", "bitsReceivedPerSecond"};
  const string first_value = "1000";
  const string second_value = "2000";
  const string converted_values[] = {"1000", "8000", "1000", "8000"};

  // Send the first data point.
  StatsUnit stats = {FAKE_TIME_STAMP};
  for (int i = 0; i < num_converted_stats; ++i)
    stats.values[stats_names[i]] = first_value;

  ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);

  // Send the second data point at 1000ms after the first data point.
  stats.timestamp += 1000;
  for (int i = 0; i < num_converted_stats; ++i)
    stats.values[stats_names[i]] = second_value;
  ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);

  // Verifies the graph data matches converted_values.
  for (int i = 0; i < num_converted_stats; ++i) {
    VerifyGraphDataPoint(pc.getIdString(), stats_id + "-" + converted_names[i],
                         1, converted_values[i]);
  }
}

// Timing out on ARM linux bot: http://crbug.com/238490
// Disabling due to failure on Linux, Mac, Win: http://crbug.com/272413
// Sanity check of the page content under a real PeerConnection call.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest,
                       DISABLED_WithRealPeerConnectionCall) {
  // Start a peerconnection call in the first window.
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
  NavigateToURL(shell(), url);
  ASSERT_TRUE(ExecuteJavascript("call({video:true});"));
  ExpectTitle("OK");

  // Open webrtc-internals in the second window.
  GURL url2("chrome://webrtc-internals");
  Shell* shell2 = CreateBrowser();
  NavigateToURL(shell2, url2);

  const int NUMBER_OF_PEER_CONNECTIONS = 2;

  // Verifies the number of peerconnections.
  int count = 0;
  ASSERT_TRUE(ExecuteScriptAndExtractInt(
      shell2,
      "window.domAutomationController.send("
      "    $('peer-connections-list').getElementsByTagName('li').length);",
      &count));
  EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);

  // Verifies the the event tables.
  ASSERT_TRUE(ExecuteScriptAndExtractInt(
      shell2,
      "window.domAutomationController.send($('peer-connections-list')"
      "    .getElementsByClassName('update-log-table').length);",
      &count));
  EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);

  ASSERT_TRUE(ExecuteScriptAndExtractInt(
      shell2,
      "window.domAutomationController.send($('peer-connections-list')"
      "    .getElementsByClassName('update-log-table')[0].rows.length);",
      &count));
  EXPECT_GT(count, 1);

  ASSERT_TRUE(ExecuteScriptAndExtractInt(
      shell2,
      "window.domAutomationController.send($('peer-connections-list')"
      "    .getElementsByClassName('update-log-table')[1].rows.length);",
      &count));
  EXPECT_GT(count, 1);

  // Wait until the stats table containers are created.
  count = 0;
  while (count != NUMBER_OF_PEER_CONNECTIONS) {
    ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell2,
        "window.domAutomationController.send("
        "    $('peer-connections-list').getElementsByClassName("
        "        'stats-table-container').length);",
        &count));
  }

  // Verifies each stats table having more than one rows.
  bool result = false;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      shell2,
      "var tableContainers = $('peer-connections-list')"
      "    .getElementsByClassName('stats-table-container');"
      "var result = true;"
      "for (var i = 0; i < tableContainers.length && result; ++i) {"
      "  var tables = tableContainers[i].getElementsByTagName('table');"
      "  for (var j = 0; j < tables.length && result; ++j) {"
      "    result = (tables[j].rows.length > 1);"
      "  }"
      "  if (!result) {"
      "    console.log(tableContainers[i].innerHTML);"
      "  }"
      "}"
      "window.domAutomationController.send(result);",
      &result));

  EXPECT_TRUE(result);

  count = GetSsrcInfoBlockCount(shell2);
  EXPECT_GT(count, 0);
}

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, CreatePageDump) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  PeerConnectionEntry pc_0(1, 0);
  pc_0.AddEvent("e1", "v1");
  pc_0.AddEvent("e2", "v2");
  PeerConnectionEntry pc_1(1, 1);
  pc_1.AddEvent("e3", "v3");
  pc_1.AddEvent("e4", "v4");
  string pc_array =
      "[" + pc_0.getAllUpdateString() + ", " + pc_1.getAllUpdateString() + "]";
  EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));

  // Verifies the peer connection data store can be created without stats.
  string dump_json;
  ASSERT_TRUE(ExecuteScriptAndExtractString(
      shell(),
      "window.domAutomationController.send("
      "    JSON.stringify(peerConnectionDataStore));",
      &dump_json));
  std::unique_ptr<base::Value> dump = base::JSONReader::Read(dump_json);
  VerifyPageDumpStructure(dump.get(),
                          2 /*peer_connection_number*/,
                          2 /*update_number*/,
                          0 /*stats_number*/);

  // Adds a stats report.
  const string type = "dummy";
  const string id = "1234";
  StatsUnit stats = { FAKE_TIME_STAMP };
  stats.values["bitrate"] = "2000";
  stats.values["framerate"] = "30";
  ExecuteAndVerifyAddStats(pc_0, type, id, stats);

  ASSERT_TRUE(ExecuteScriptAndExtractString(
      shell(),
      "window.domAutomationController.send("
      "    JSON.stringify(peerConnectionDataStore));",
      &dump_json));
  dump = base::JSONReader::Read(dump_json);
  VerifyStatsDump(dump.get(), pc_0, type, id, stats);
}

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, UpdateGetUserMedia) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  UserMediaRequestEntry request1(1, 1, "origin", "ac", "vc");
  UserMediaRequestEntry request2(2, 2, "origin2", "ac2", "vc2");
  ExecuteAddGetUserMediaJs(request1);
  ExecuteAddGetUserMediaJs(request2);

  std::vector<UserMediaRequestEntry> list;
  list.push_back(request1);
  list.push_back(request2);
  VerifyUserMediaRequest(list);

  ExecuteRemoveGetUserMediaForRendererJs(1);
  list.erase(list.begin());
  VerifyUserMediaRequest(list);

  ExecuteRemoveGetUserMediaForRendererJs(2);
  list.erase(list.begin());
  VerifyUserMediaRequest(list);
}

// Tests that the received propagation delta values are converted and drawn
// correctly.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest,
                       ReceivedPropagationDelta) {
  GURL url("chrome://webrtc-internals");
  NavigateToURL(shell(), url);

  PeerConnectionEntry pc(1, 0);
  ExecuteAddPeerConnectionJs(pc);

  StatsUnit stats = {FAKE_TIME_STAMP};
  stats.values["googReceivedPacketGroupArrivalTimeDebug"] =
      "[1000, 1100, 1200]";
  stats.values["googReceivedPacketGroupPropagationDeltaDebug"] =
      "[10, 20, 30]";
  const string stats_type = "bwe";
  const string stats_id = "videobwe";
  ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);

  string graph_id = pc.getIdString() + "-" + stats_id +
      "-googReceivedPacketGroupPropagationDeltaDebug";
  string data_series_id =
      stats_id + "-googReceivedPacketGroupPropagationDeltaDebug";
  bool result = false;
  // Verify that the graph exists.
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      shell(),
      "window.domAutomationController.send("
      "   graphViews['" + graph_id + "'] != null)",
      &result));
  EXPECT_TRUE(result);

  // Verify that the graph contains multiple data points.
  int count = 0;
  ASSERT_TRUE(ExecuteScriptAndExtractInt(
      shell(),
      "window.domAutomationController.send("
      "   graphViews['" + graph_id + "'].getDataSeriesCount())",
      &count));
  EXPECT_EQ(1, count);
  ASSERT_TRUE(ExecuteScriptAndExtractInt(
      shell(),
      "window.domAutomationController.send("
      "   peerConnectionDataStore['" + pc.getIdString() + "']" +
      "       .getDataSeries('" + data_series_id + "').getCount())",
      &count));
  EXPECT_EQ(3, count);
}

}  // namespace content
