[inspector] extend protocol for code coverage.

R=jgruber@chromium.org, kozyatinskiy@chromium.org, pfeldman@chromium.org
BUG=v8:5808

Review-Url: https://codereview.chromium.org/2700743002
Cr-Commit-Position: refs/heads/master@{#43363}
diff --git a/src/api.cc b/src/api.cc
index f29a894..f64534c 100644
--- a/src/api.cc
+++ b/src/api.cc
@@ -9541,46 +9541,50 @@
   }
 }
 
-debug::Coverage::Range::Range(i::CoverageRange* range,
-                              Local<debug::Script> script)
-    : range_(range), script_(script) {
+debug::Coverage::FunctionData::FunctionData(i::CoverageFunction* function,
+                                            Local<debug::Script> script)
+    : function_(function) {
   i::Handle<i::Script> i_script = v8::Utils::OpenHandle(*script);
   i::Script::PositionInfo start;
   i::Script::PositionInfo end;
-  i::Script::GetPositionInfo(i_script, range->start, &start,
+  i::Script::GetPositionInfo(i_script, function->start, &start,
                              i::Script::WITH_OFFSET);
-  i::Script::GetPositionInfo(i_script, range->end, &end,
+  i::Script::GetPositionInfo(i_script, function->end, &end,
                              i::Script::WITH_OFFSET);
   start_ = Location(start.line, start.column);
   end_ = Location(end.line, end.column);
 }
 
-uint32_t debug::Coverage::Range::Count() { return range_->count; }
+uint32_t debug::Coverage::FunctionData::Count() { return function_->count; }
 
-size_t debug::Coverage::Range::NestedCount() { return range_->inner.size(); }
-
-debug::Coverage::Range debug::Coverage::Range::GetNested(size_t i) {
-  return Range(&range_->inner[i], script_);
+MaybeLocal<String> debug::Coverage::FunctionData::Name() {
+  return ToApiHandle<String>(function_->name);
 }
 
-MaybeLocal<String> debug::Coverage::Range::Name() {
-  return ToApiHandle<String>(range_->name);
+Local<debug::Script> debug::Coverage::ScriptData::GetScript() {
+  return ToApiHandle<debug::Script>(script_->script);
+}
+
+size_t debug::Coverage::ScriptData::FunctionCount() {
+  return script_->functions.size();
+}
+
+debug::Coverage::FunctionData debug::Coverage::ScriptData::GetFunctionData(
+    size_t i) {
+  return FunctionData(&script_->functions.at(i), GetScript());
 }
 
 debug::Coverage::~Coverage() { delete coverage_; }
 
 size_t debug::Coverage::ScriptCount() { return coverage_->size(); }
 
-Local<debug::Script> debug::Coverage::GetScript(size_t i) {
-  return ToApiHandle<debug::Script>(coverage_->at(i).script);
+debug::Coverage::ScriptData debug::Coverage::GetScriptData(size_t i) {
+  return ScriptData(&coverage_->at(i));
 }
 
-debug::Coverage::Range debug::Coverage::GetRange(size_t i) {
-  return Range(&coverage_->at(i).toplevel, GetScript(i));
-}
-
-debug::Coverage debug::Coverage::Collect(Isolate* isolate) {
-  return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate)));
+debug::Coverage debug::Coverage::Collect(Isolate* isolate, bool reset_count) {
+  return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate),
+                                       reset_count));
 }
 
 void debug::Coverage::TogglePrecise(Isolate* isolate, bool enable) {
diff --git a/src/d8.cc b/src/d8.cc
index a37eb9f..360330b 100644
--- a/src/d8.cc
+++ b/src/d8.cc
@@ -1657,51 +1657,15 @@
       JSON::Stringify(context, dispatch_counters).ToLocalChecked());
 }
 
-namespace {
-void ReadRange(std::ofstream* s, std::vector<uint32_t>* lines,
-               debug::Coverage::Range range) {
-  // Ensure space in the array.
-  lines->resize(std::max(static_cast<size_t>(range.End().GetLineNumber() + 1),
-                         lines->size()),
-                0);
-  // Boundary lines could be shared between two functions with different
-  // invocation counts. Take the maximum.
-  lines->at(range.Start().GetLineNumber()) =
-      std::max(lines->at(range.Start().GetLineNumber()), range.Count());
-  lines->at(range.End().GetLineNumber()) =
-      std::max(lines->at(range.End().GetLineNumber()), range.Count());
-  // Invocation counts for non-boundary lines are overwritten.
-  int line_plus_one = range.Start().GetLineNumber() + 1;
-  for (int i = line_plus_one; i < range.End().GetLineNumber(); i++) {
-    lines->at(i) = range.Count();
-  }
-  // Note that we use 0-based line numbers. But LCOV uses 1-based line numbers.
-  // Recurse over inner ranges.
-  for (size_t i = 0; i < range.NestedCount(); i++) {
-    ReadRange(s, lines, range.GetNested(i));
-  }
-  // Write function stats.
-  Local<String> name;
-  std::stringstream name_stream;
-  if (range.Name().ToLocal(&name)) {
-    name_stream << ToSTLString(name);
-  } else {
-    name_stream << "<" << line_plus_one << "-";
-    name_stream << range.Start().GetColumnNumber() << ">";
-  }
-  *s << "FN:" << line_plus_one << "," << name_stream.str() << std::endl;
-  *s << "FNDA:" << range.Count() << "," << name_stream.str() << std::endl;
-}
-}  // anonymous namespace
-
 // Write coverage data in LCOV format. See man page for geninfo(1).
 void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
   if (!file) return;
   HandleScope handle_scope(isolate);
-  debug::Coverage coverage = debug::Coverage::Collect(isolate);
+  debug::Coverage coverage = debug::Coverage::Collect(isolate, false);
   std::ofstream sink(file, std::ofstream::app);
   for (size_t i = 0; i < coverage.ScriptCount(); i++) {
-    Local<debug::Script> script = coverage.GetScript(i);
+    debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
+    Local<debug::Script> script = script_data.GetScript();
     // Skip unnamed scripts.
     Local<String> name;
     if (!script->Name().ToLocal(&name)) continue;
@@ -1711,7 +1675,33 @@
     sink << "SF:";
     sink << NormalizePath(file_name, GetWorkingDirectory()) << std::endl;
     std::vector<uint32_t> lines;
-    ReadRange(&sink, &lines, coverage.GetRange(i));
+    for (size_t j = 0; j < script_data.FunctionCount(); j++) {
+      debug::Coverage::FunctionData function_data =
+          script_data.GetFunctionData(j);
+      int start_line = function_data.Start().GetLineNumber();
+      int end_line = function_data.End().GetLineNumber();
+      uint32_t count = function_data.Count();
+      // Ensure space in the array.
+      lines.resize(std::max(static_cast<size_t>(end_line + 1), lines.size()),
+                   0);
+      // Boundary lines could be shared between two functions with different
+      // invocation counts. Take the maximum.
+      lines[start_line] = std::max(lines[start_line], count);
+      lines[end_line] = std::max(lines[end_line], count);
+      // Invocation counts for non-boundary lines are overwritten.
+      for (int k = start_line + 1; k < end_line; k++) lines[k] = count;
+      // Write function stats.
+      Local<String> name;
+      std::stringstream name_stream;
+      if (function_data.Name().ToLocal(&name)) {
+        name_stream << ToSTLString(name);
+      } else {
+        name_stream << "<" << start_line + 1 << "-";
+        name_stream << function_data.Start().GetColumnNumber() << ">";
+      }
+      sink << "FN:" << start_line + 1 << "," << name_stream.str() << std::endl;
+      sink << "FNDA:" << count << "," << name_stream.str() << std::endl;
+    }
     // Write per-line coverage. LCOV uses 1-based line numbers.
     for (size_t i = 0; i < lines.size(); i++) {
       sink << "DA:" << (i + 1) << "," << lines[i] << std::endl;
diff --git a/src/debug/debug-coverage.cc b/src/debug/debug-coverage.cc
index 76a88b0..8a13b6c 100644
--- a/src/debug/debug-coverage.cc
+++ b/src/debug/debug-coverage.cc
@@ -58,12 +58,7 @@
 }
 }  // anonymous namespace
 
-CoverageScript::CoverageScript(Isolate* isolate, Handle<Script> s,
-                               int source_length)
-    : script(s),
-      toplevel(0, source_length, 1, isolate->factory()->empty_string()) {}
-
-Coverage* Coverage::Collect(Isolate* isolate) {
+Coverage* Coverage::Collect(Isolate* isolate, bool reset_count) {
   SharedToCounterMap counter_map;
 
   // Feed invocation count into the counter map.
@@ -76,6 +71,7 @@
       SharedFunctionInfo* shared = vector->shared_function_info();
       DCHECK(shared->IsSubjectToDebugging());
       uint32_t count = static_cast<uint32_t>(vector->invocation_count());
+      if (reset_count) vector->clear_invocation_count();
       counter_map.Add(shared, count);
     }
   } else {
@@ -88,6 +84,7 @@
       SharedFunctionInfo* shared = vector->shared_function_info();
       if (!shared->IsSubjectToDebugging()) continue;
       uint32_t count = static_cast<uint32_t>(vector->invocation_count());
+      if (reset_count) vector->clear_invocation_count();
       counter_map.Add(shared, count);
     }
   }
@@ -101,47 +98,39 @@
     if (script->type() != Script::TYPE_NORMAL) continue;
 
     // Create and add new script data.
-    int source_end = String::cast(script->source())->length();
     Handle<Script> script_handle(script, isolate);
-    result->emplace_back(isolate, script_handle, source_end);
+    result->emplace_back(isolate, script_handle);
+    std::vector<CoverageFunction>* functions = &result->back().functions;
 
     std::vector<SharedFunctionInfo*> sorted;
+    bool has_toplevel = false;
 
     {
-      // Collect a list of shared function infos sorted by start position.
-      // Shared function infos are usually already sorted. Except for classes.
-      // If the start position is the same, sort from outer to inner function.
+      // Sort functions by start position, from outer to inner functions.
       SharedFunctionInfo::ScriptIterator infos(script_handle);
-      while (SharedFunctionInfo* info = infos.Next()) sorted.push_back(info);
+      while (SharedFunctionInfo* info = infos.Next()) {
+        has_toplevel |= info->is_toplevel();
+        sorted.push_back(info);
+      }
       std::sort(sorted.begin(), sorted.end(), CompareSharedFunctionInfo);
     }
 
-    std::vector<CoverageRange*> stack;
-    stack.push_back(&result->back().toplevel);
+    functions->reserve(sorted.size() + (has_toplevel ? 0 : 1));
+
+    if (!has_toplevel) {
+      // Add a replacement toplevel function if it does not exist.
+      int source_end = String::cast(script->source())->length();
+      functions->emplace_back(0, source_end, 1u,
+                              isolate->factory()->empty_string());
+    }
 
     // Use sorted list to reconstruct function nesting.
     for (SharedFunctionInfo* info : sorted) {
       int start = StartPosition(info);
       int end = info->end_position();
       uint32_t count = counter_map.Get(info);
-      if (info->is_toplevel()) {
-        // Top-level function is available.
-        DCHECK_EQ(1, stack.size());
-        result->back().toplevel.start = start;
-        result->back().toplevel.end = end;
-        result->back().toplevel.count = count;
-      } else {
-        // The shared function infos are sorted by start.
-        DCHECK_LE(stack.back()->start, start);
-        // Drop the stack to the outer function.
-        while (start >= stack.back()->end) stack.pop_back();
-        CoverageRange* outer = stack.back();
-        // New nested function.
-        DCHECK_LE(end, outer->end);
-        Handle<String> name(info->DebugName(), isolate);
-        outer->inner.emplace_back(start, end, count, name);
-        stack.push_back(&outer->inner.back());
-      }
+      Handle<String> name(info->DebugName(), isolate);
+      functions->emplace_back(start, end, count, name);
     }
   }
   return result;
diff --git a/src/debug/debug-coverage.h b/src/debug/debug-coverage.h
index e54b4c3..36128bc 100644
--- a/src/debug/debug-coverage.h
+++ b/src/debug/debug-coverage.h
@@ -16,28 +16,28 @@
 // Forward declaration.
 class Isolate;
 
-struct CoverageRange {
-  CoverageRange(int s, int e, uint32_t c, Handle<String> n)
+struct CoverageFunction {
+  CoverageFunction(int s, int e, uint32_t c, Handle<String> n)
       : start(s), end(e), count(c), name(n) {}
   int start;
   int end;
   uint32_t count;
   Handle<String> name;
-  std::vector<CoverageRange> inner;
 };
 
 struct CoverageScript {
   // Initialize top-level function in case it has been garbage-collected.
-  CoverageScript(Isolate* isolate, Handle<Script> s, int source_length);
+  CoverageScript(Isolate* isolate, Handle<Script> s) : script(s) {}
   Handle<Script> script;
-  CoverageRange toplevel;
+  // Functions are sorted by start position, from outer to inner function.
+  std::vector<CoverageFunction> functions;
 };
 
 class Coverage : public std::vector<CoverageScript> {
  public:
   // Allocate a new Coverage object and populate with result.
   // The ownership is transferred to the caller.
-  static Coverage* Collect(Isolate* isolate);
+  static Coverage* Collect(Isolate* isolate, bool reset_count);
 
   // Enable precise code coverage. This disables optimization and makes sure
   // invocation count is not affected by GC.
diff --git a/src/debug/debug-interface.h b/src/debug/debug-interface.h
index ea89ce5..be8ed90 100644
--- a/src/debug/debug-interface.h
+++ b/src/debug/debug-interface.h
@@ -17,7 +17,8 @@
 namespace v8 {
 
 namespace internal {
-struct CoverageRange;
+struct CoverageFunction;
+struct CoverageScript;
 class Coverage;
 class Script;
 }
@@ -211,33 +212,45 @@
  */
 class V8_EXPORT_PRIVATE Coverage {
  public:
-  class V8_EXPORT_PRIVATE Range {
+  class ScriptData;  // Forward declaration.
+
+  class V8_EXPORT_PRIVATE FunctionData {
    public:
     // 0-based line and colum numbers.
     Location Start() { return start_; }
     Location End() { return end_; }
     uint32_t Count();
-    size_t NestedCount();
-    Range GetNested(size_t i);
     MaybeLocal<String> Name();
 
    private:
-    Range(i::CoverageRange* range, Local<debug::Script> script);
-    i::CoverageRange* range_;
+    FunctionData(i::CoverageFunction* function, Local<debug::Script> script);
+    i::CoverageFunction* function_;
     Location start_;
     Location end_;
-    Local<debug::Script> script_;
 
-    friend class debug::Coverage;
+    friend class v8::debug::Coverage::ScriptData;
   };
 
-  static Coverage Collect(Isolate* isolate);
+  class V8_EXPORT_PRIVATE ScriptData {
+   public:
+    Local<debug::Script> GetScript();
+    size_t FunctionCount();
+    FunctionData GetFunctionData(size_t i);
+
+   private:
+    explicit ScriptData(i::CoverageScript* script) : script_(script) {}
+    i::CoverageScript* script_;
+
+    friend class v8::debug::Coverage;
+  };
+
+  static Coverage Collect(Isolate* isolate, bool reset_count);
 
   static void TogglePrecise(Isolate* isolate, bool enable);
 
   size_t ScriptCount();
-  Local<debug::Script> GetScript(size_t i);
-  Range GetRange(size_t i);
+  ScriptData GetScriptData(size_t i);
+  bool IsEmpty() { return coverage_ == nullptr; }
 
   ~Coverage();
 
diff --git a/src/inspector/js_protocol.json b/src/inspector/js_protocol.json
index 8b08b0b..1aa840c 100644
--- a/src/inspector/js_protocol.json
+++ b/src/inspector/js_protocol.json
@@ -204,6 +204,38 @@
                     { "name": "parent", "$ref": "StackTrace", "optional": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." },
                     { "name": "promiseCreationFrame", "$ref": "CallFrame", "optional": true, "experimental": true, "description": "Creation frame of the Promise which produced the next synchronous trace when resolved, if available." }
                 ]
+            },
+            {   "id": "CoverageRange",
+                "type": "object",
+                "description": "Coverage data for a source range.",
+                "properties": [
+                    { "name": "startLineNumber", "type": "integer", "description": "JavaScript script line number (0-based) for the range start." },
+                    { "name": "startColumnNumber", "type": "integer", "description": "JavaScript script column number (0-based) for the range start." },
+                    { "name": "endLineNumber", "type": "integer", "description": "JavaScript script line number (0-based) for the range end." },
+                    { "name": "endColumnNumber", "type": "integer", "description": "JavaScript script column number (0-based) for the range end." },
+                    { "name": "count", "type": "integer", "description": "Collected execution count of the source range." }
+                ],
+                "experimental": "true"
+            },
+            {   "id": "FunctionCoverage",
+                "type": "object",
+                "description": "Coverage data for a JavaScript function.",
+                "properties": [
+                    { "name": "functionName", "type": "string", "description": "JavaScript function name." },
+                    { "name": "ranges", "type": "array", "items": { "$ref": "CoverageRange" }, "description": "Source ranges inside the function with coverage data." }
+                ],
+                "experimental": "true"
+            },
+            {
+                "id": "ScriptCoverage",
+                "type": "object",
+                "description": "Coverage data for a JavaScript script.",
+                "properties": [
+                    { "name": "scriptId", "$ref": "ScriptId", "description": "JavaScript script id." },
+                    { "name": "url", "type": "string", "description": "JavaScript script name or url." },
+                    { "name": "functions", "type": "array", "items": { "$ref": "FunctionCoverage" }, "description": "Functions contained in the script that has coverage data." }
+                ],
+                "experimental": "true"
             }
         ],
         "commands": [
@@ -343,6 +375,32 @@
                     { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."}
                 ],
                 "description": "Runs script with given id in a given context."
+            },
+            {
+                "name": "startPreciseCoverage",
+                "description": "Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code coverage may be incomplete. Enabling prevents running optimized code and resets execution counters.",
+                "experimental": true
+            },
+            {
+                "name": "stopPreciseCoverage",
+                "description": "Disable precise code coverage. Disabling releases unnecessary execution count records and allows executing optimized code.",
+                "experimental": true
+            },
+            {
+                "name": "takePreciseCoverage",
+                "returns": [
+                    { "name": "result", "type": "array", "items": { "$ref": "ScriptCoverage" }, "description": "Coverage data for the current isolate." }
+                ],
+                "description": "Collect coverage data for the current isolate, and resets execution counters. Precise code coverage needs to have started.",
+                "experimental": true
+            },
+            {
+                "name": "getBestEffortCoverage",
+                "returns": [
+                    { "name": "result", "type": "array", "items": { "$ref": "ScriptCoverage" }, "description": "Coverage data for the current isolate." }
+                ],
+                "description": "Collect coverage data for the current isolate. The coverage data may be incomplete due to garbage collection.",
+                "experimental": true
             }
         ],
         "events": [
diff --git a/src/inspector/v8-runtime-agent-impl.cc b/src/inspector/v8-runtime-agent-impl.cc
index b40f08e..d324d34 100644
--- a/src/inspector/v8-runtime-agent-impl.cc
+++ b/src/inspector/v8-runtime-agent-impl.cc
@@ -30,6 +30,7 @@
 
 #include "src/inspector/v8-runtime-agent-impl.h"
 
+#include "src/debug/debug-interface.h"
 #include "src/inspector/injected-script.h"
 #include "src/inspector/inspected-context.h"
 #include "src/inspector/protocol/Protocol.h"
@@ -51,6 +52,7 @@
 static const char customObjectFormatterEnabled[] =
     "customObjectFormatterEnabled";
 static const char runtimeEnabled[] = "runtimeEnabled";
+static const char preciseCoverageStarted[] = "preciseCoverageStarted";
 };
 
 using protocol::Runtime::RemoteObject;
@@ -645,6 +647,88 @@
       std::move(callback));
 }
 
+Response V8RuntimeAgentImpl::startPreciseCoverage() {
+  m_state->setBoolean(V8RuntimeAgentImplState::preciseCoverageStarted, true);
+  v8::debug::Coverage::TogglePrecise(m_inspector->isolate(), true);
+  return Response::OK();
+}
+
+Response V8RuntimeAgentImpl::stopPreciseCoverage() {
+  m_state->setBoolean(V8RuntimeAgentImplState::preciseCoverageStarted, false);
+  v8::debug::Coverage::TogglePrecise(m_inspector->isolate(), false);
+  return Response::OK();
+}
+
+namespace {
+Response takeCoverage(
+    v8::Isolate* isolate, bool reset_count,
+    std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
+        out_result) {
+  std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>> result =
+      protocol::Array<protocol::Runtime::ScriptCoverage>::create();
+  v8::HandleScope handle_scope(isolate);
+  v8::debug::Coverage coverage =
+      v8::debug::Coverage::Collect(isolate, reset_count);
+  for (size_t i = 0; i < coverage.ScriptCount(); i++) {
+    v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
+    v8::Local<v8::debug::Script> script = script_data.GetScript();
+    std::unique_ptr<protocol::Array<protocol::Runtime::FunctionCoverage>>
+        functions =
+            protocol::Array<protocol::Runtime::FunctionCoverage>::create();
+    for (size_t j = 0; j < script_data.FunctionCount(); j++) {
+      v8::debug::Coverage::FunctionData function_data =
+          script_data.GetFunctionData(j);
+      std::unique_ptr<protocol::Array<protocol::Runtime::CoverageRange>>
+          ranges = protocol::Array<protocol::Runtime::CoverageRange>::create();
+      // At this point we only have per-function coverage data, so there is
+      // only one range per function.
+      ranges->addItem(
+          protocol::Runtime::CoverageRange::create()
+              .setStartLineNumber(function_data.Start().GetLineNumber())
+              .setStartColumnNumber(function_data.Start().GetColumnNumber())
+              .setEndLineNumber(function_data.End().GetLineNumber())
+              .setEndColumnNumber(function_data.End().GetColumnNumber())
+              .setCount(function_data.Count())
+              .build());
+      functions->addItem(
+          protocol::Runtime::FunctionCoverage::create()
+              .setFunctionName(toProtocolString(
+                  function_data.Name().FromMaybe(v8::Local<v8::String>())))
+              .setRanges(std::move(ranges))
+              .build());
+    }
+    String16 url;
+    v8::Local<v8::String> name;
+    if (script->Name().ToLocal(&name) || script->SourceURL().ToLocal(&name)) {
+      url = toProtocolString(name);
+    }
+    result->addItem(protocol::Runtime::ScriptCoverage::create()
+                        .setScriptId(String16::fromInteger(script->Id()))
+                        .setUrl(url)
+                        .setFunctions(std::move(functions))
+                        .build());
+  }
+  *out_result = std::move(result);
+  return Response::OK();
+}
+}  // anonymous namespace
+
+Response V8RuntimeAgentImpl::takePreciseCoverage(
+    std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
+        out_result) {
+  if (!m_state->booleanProperty(V8RuntimeAgentImplState::preciseCoverageStarted,
+                                false)) {
+    return Response::Error("Precise coverage has not been started.");
+  }
+  return takeCoverage(m_inspector->isolate(), true, out_result);
+}
+
+Response V8RuntimeAgentImpl::getBestEffortCoverage(
+    std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
+        out_result) {
+  return takeCoverage(m_inspector->isolate(), false, out_result);
+}
+
 void V8RuntimeAgentImpl::restore() {
   if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
     return;
@@ -653,6 +737,9 @@
   if (m_state->booleanProperty(
           V8RuntimeAgentImplState::customObjectFormatterEnabled, false))
     m_session->setCustomObjectFormatterEnabled(true);
+  if (m_state->booleanProperty(V8RuntimeAgentImplState::preciseCoverageStarted,
+                               false))
+    startPreciseCoverage();
 }
 
 Response V8RuntimeAgentImpl::enable() {
@@ -680,6 +767,7 @@
   reset();
   m_inspector->client()->endEnsureAllContextsInGroup(
       m_session->contextGroupId());
+  stopPreciseCoverage();
   return Response::OK();
 }
 
diff --git a/src/inspector/v8-runtime-agent-impl.h b/src/inspector/v8-runtime-agent-impl.h
index 9caa1fb..fb592bd 100644
--- a/src/inspector/v8-runtime-agent-impl.h
+++ b/src/inspector/v8-runtime-agent-impl.h
@@ -97,6 +97,14 @@
                  Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue,
                  Maybe<bool> generatePreview, Maybe<bool> awaitPromise,
                  std::unique_ptr<RunScriptCallback>) override;
+  Response startPreciseCoverage() override;
+  Response stopPreciseCoverage() override;
+  Response takePreciseCoverage(
+      std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
+          out_result) override;
+  Response getBestEffortCoverage(
+      std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
+          out_result) override;
 
   void reset();
   void reportExecutionContextCreated(InspectedContext*);
diff --git a/src/runtime/runtime-debug.cc b/src/runtime/runtime-debug.cc
index fb2e893..3649621 100644
--- a/src/runtime/runtime-debug.cc
+++ b/src/runtime/runtime-debug.cc
@@ -1895,49 +1895,16 @@
   return Smi::FromInt(isolate->debug()->is_active());
 }
 
-
 RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) {
   UNIMPLEMENTED();
   return NULL;
 }
 
-namespace {
-Handle<JSObject> CreateRangeObject(Isolate* isolate, const CoverageRange* range,
-                                   Handle<String> inner_string,
-                                   Handle<String> start_string,
-                                   Handle<String> end_string,
-                                   Handle<String> count_string) {
-  HandleScope scope(isolate);
-  Factory* factory = isolate->factory();
-  Handle<JSObject> range_obj = factory->NewJSObjectWithNullProto();
-  JSObject::AddProperty(range_obj, start_string,
-                        factory->NewNumberFromInt(range->start), NONE);
-  JSObject::AddProperty(range_obj, end_string,
-                        factory->NewNumberFromInt(range->end), NONE);
-  JSObject::AddProperty(range_obj, count_string,
-                        factory->NewNumberFromUint(range->count), NONE);
-  JSObject::AddProperty(range_obj, factory->name_string(), range->name, NONE);
-  if (!range->inner.empty()) {
-    int size = static_cast<int>(range->inner.size());
-    Handle<FixedArray> inner_array = factory->NewFixedArray(size);
-    for (int i = 0; i < size; i++) {
-      Handle<JSObject> element =
-          CreateRangeObject(isolate, &range->inner[i], inner_string,
-                            start_string, end_string, count_string);
-      inner_array->set(i, *element);
-    }
-    Handle<JSArray> inner =
-        factory->NewJSArrayWithElements(inner_array, FAST_ELEMENTS);
-    JSObject::AddProperty(range_obj, inner_string, inner, NONE);
-  }
-  return scope.CloseAndEscape(range_obj);
-}
-}  // anonymous namespace
-
 RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
   HandleScope scope(isolate);
+  DCHECK_EQ(0, args.length());
   // Collect coverage data.
-  std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate));
+  std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate, false));
   Factory* factory = isolate->factory();
   // Turn the returned data structure into JavaScript.
   // Create an array of scripts.
@@ -1945,22 +1912,31 @@
   // Prepare property keys.
   Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts);
   Handle<String> script_string = factory->NewStringFromStaticChars("script");
-  Handle<String> toplevel_string =
-      factory->NewStringFromStaticChars("toplevel");
-  Handle<String> inner_string = factory->NewStringFromStaticChars("inner");
   Handle<String> start_string = factory->NewStringFromStaticChars("start");
   Handle<String> end_string = factory->NewStringFromStaticChars("end");
   Handle<String> count_string = factory->NewStringFromStaticChars("count");
   for (int i = 0; i < num_scripts; i++) {
-    const auto& data = coverage->at(i);
+    const auto& script_data = coverage->at(i);
     HandleScope inner_scope(isolate);
-    Handle<JSObject> script_obj = factory->NewJSObjectWithNullProto();
-    Handle<JSObject> wrapper = Script::GetWrapper(data.script);
+    int num_functions = static_cast<int>(script_data.functions.size());
+    Handle<FixedArray> functions_array = factory->NewFixedArray(num_functions);
+    for (int j = 0; j < num_functions; j++) {
+      const auto& function_data = script_data.functions[j];
+      Handle<JSObject> range_obj = factory->NewJSObjectWithNullProto();
+      JSObject::AddProperty(range_obj, start_string,
+                            factory->NewNumberFromInt(function_data.start),
+                            NONE);
+      JSObject::AddProperty(range_obj, end_string,
+                            factory->NewNumberFromInt(function_data.end), NONE);
+      JSObject::AddProperty(range_obj, count_string,
+                            factory->NewNumberFromUint(function_data.count),
+                            NONE);
+      functions_array->set(j, *range_obj);
+    }
+    Handle<JSArray> script_obj =
+        factory->NewJSArrayWithElements(functions_array, FAST_ELEMENTS);
+    Handle<JSObject> wrapper = Script::GetWrapper(script_data.script);
     JSObject::AddProperty(script_obj, script_string, wrapper, NONE);
-    Handle<JSObject> toplevel =
-        CreateRangeObject(isolate, &data.toplevel, inner_string, start_string,
-                          end_string, count_string);
-    JSObject::AddProperty(script_obj, toplevel_string, toplevel, NONE);
     scripts_array->set(i, *script_obj);
   }
   return *factory->NewJSArrayWithElements(scripts_array, FAST_ELEMENTS);
diff --git a/test/cctest/test-debug.cc b/test/cctest/test-debug.cc
index b30c6d9..79a18a0 100644
--- a/test/cctest/test-debug.cc
+++ b/test/cctest/test-debug.cc
@@ -6637,27 +6637,28 @@
       "f();\n"
       "f();");
   CompileRun(source);
-  v8::debug::Coverage coverage = v8::debug::Coverage::Collect(isolate);
+  v8::debug::Coverage coverage = v8::debug::Coverage::Collect(isolate, false);
   CHECK_EQ(1u, coverage.ScriptCount());
-  v8::Local<v8::debug::Script> script = coverage.GetScript(0);
+  v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(0);
+  v8::Local<v8::debug::Script> script = script_data.GetScript();
   CHECK(script->Source()
             .ToLocalChecked()
             ->Equals(env.local(), source)
             .FromMaybe(false));
 
-  v8::debug::Coverage::Range range = coverage.GetRange(0);
-  CHECK_EQ(0, range.Start().GetLineNumber());
-  CHECK_EQ(0, range.Start().GetColumnNumber());
-  CHECK_EQ(3, range.End().GetLineNumber());
-  CHECK_EQ(4, range.End().GetColumnNumber());
-  CHECK_EQ(1, range.Count());
-  CHECK_EQ(1u, range.NestedCount());
+  CHECK_EQ(2u, script_data.FunctionCount());
+  v8::debug::Coverage::FunctionData function_data =
+      script_data.GetFunctionData(0);
+  CHECK_EQ(0, function_data.Start().GetLineNumber());
+  CHECK_EQ(0, function_data.Start().GetColumnNumber());
+  CHECK_EQ(3, function_data.End().GetLineNumber());
+  CHECK_EQ(4, function_data.End().GetColumnNumber());
+  CHECK_EQ(1, function_data.Count());
 
-  range = range.GetNested(0);
-  CHECK_EQ(0, range.Start().GetLineNumber());
-  CHECK_EQ(0, range.Start().GetColumnNumber());
-  CHECK_EQ(1, range.End().GetLineNumber());
-  CHECK_EQ(1, range.End().GetColumnNumber());
-  CHECK_EQ(2, range.Count());
-  CHECK_EQ(0, range.NestedCount());
+  function_data = script_data.GetFunctionData(1);
+  CHECK_EQ(0, function_data.Start().GetLineNumber());
+  CHECK_EQ(0, function_data.Start().GetColumnNumber());
+  CHECK_EQ(1, function_data.End().GetLineNumber());
+  CHECK_EQ(1, function_data.End().GetColumnNumber());
+  CHECK_EQ(2, function_data.Count());
 }
diff --git a/test/inspector/runtime/coverage-expected.txt b/test/inspector/runtime/coverage-expected.txt
new file mode 100644
index 0000000..1c45409
--- /dev/null
+++ b/test/inspector/runtime/coverage-expected.txt
@@ -0,0 +1,333 @@
+Test collecting code coverage data with Runtime.collectCoverage.
+
+Running test: testPreciseCoverage
+{
+    id : <messageId>
+    result : {
+    }
+}
+{
+    id : <messageId>
+    result : {
+        result : [
+            [0] : {
+                functions : [
+                    [0] : {
+                        functionName : 
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 0
+                                endLineNumber : 9
+                                startColumnNumber : 0
+                                startLineNumber : 0
+                            }
+                        ]
+                    }
+                    [1] : {
+                        functionName : fib
+                        ranges : [
+                            [0] : {
+                                count : 15
+                                endColumnNumber : 1
+                                endLineNumber : 4
+                                startColumnNumber : 0
+                                startLineNumber : 1
+                            }
+                        ]
+                    }
+                    [2] : {
+                        functionName : iife
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 1
+                                endLineNumber : 7
+                                startColumnNumber : 1
+                                startLineNumber : 5
+                            }
+                        ]
+                    }
+                ]
+                scriptId : <scriptId>
+                url : 1
+            }
+            [1] : {
+                functions : [
+                    [0] : {
+                        functionName : 
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 11
+                                endLineNumber : 0
+                                startColumnNumber : 0
+                                startLineNumber : 0
+                            }
+                        ]
+                    }
+                ]
+                scriptId : <scriptId>
+                url : 
+            }
+        ]
+    }
+}
+{
+    id : <messageId>
+    result : {
+        result : [
+            [0] : {
+                functions : [
+                    [0] : {
+                        functionName : 
+                        ranges : [
+                            [0] : {
+                                count : 0
+                                endColumnNumber : 0
+                                endLineNumber : 9
+                                startColumnNumber : 0
+                                startLineNumber : 0
+                            }
+                        ]
+                    }
+                    [1] : {
+                        functionName : fib
+                        ranges : [
+                            [0] : {
+                                count : 0
+                                endColumnNumber : 1
+                                endLineNumber : 4
+                                startColumnNumber : 0
+                                startLineNumber : 1
+                            }
+                        ]
+                    }
+                    [2] : {
+                        functionName : iife
+                        ranges : [
+                            [0] : {
+                                count : 0
+                                endColumnNumber : 1
+                                endLineNumber : 7
+                                startColumnNumber : 1
+                                startLineNumber : 5
+                            }
+                        ]
+                    }
+                ]
+                scriptId : <scriptId>
+                url : 1
+            }
+            [1] : {
+                functions : [
+                    [0] : {
+                        functionName : 
+                        ranges : [
+                            [0] : {
+                                count : 0
+                                endColumnNumber : 11
+                                endLineNumber : 0
+                                startColumnNumber : 0
+                                startLineNumber : 0
+                            }
+                        ]
+                    }
+                ]
+                scriptId : <scriptId>
+                url : 
+            }
+        ]
+    }
+}
+
+Running test: testPreciseCoverageFail
+{
+    id : <messageId>
+    result : {
+        result : {
+            description : 8
+            type : number
+            value : 8
+        }
+    }
+}
+{
+    error : {
+        code : -32000
+        message : Precise coverage has not been started.
+    }
+    id : <messageId>
+}
+
+Running test: testBestEffortCoverage
+{
+    id : <messageId>
+    result : {
+        result : {
+            description : 8
+            type : number
+            value : 8
+        }
+    }
+}
+{
+    id : <messageId>
+    result : {
+        result : [
+        ]
+    }
+}
+{
+    id : <messageId>
+    result : {
+        result : [
+        ]
+    }
+}
+
+Running test: testBestEffortCoveragePrecise
+{
+    id : <messageId>
+    result : {
+        result : {
+            description : 8
+            type : number
+            value : 8
+        }
+    }
+}
+{
+    id : <messageId>
+    result : {
+        result : [
+            [0] : {
+                functions : [
+                    [0] : {
+                        functionName : 
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 0
+                                endLineNumber : 9
+                                startColumnNumber : 0
+                                startLineNumber : 0
+                            }
+                        ]
+                    }
+                    [1] : {
+                        functionName : fib
+                        ranges : [
+                            [0] : {
+                                count : 15
+                                endColumnNumber : 1
+                                endLineNumber : 4
+                                startColumnNumber : 0
+                                startLineNumber : 1
+                            }
+                        ]
+                    }
+                    [2] : {
+                        functionName : iife
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 1
+                                endLineNumber : 7
+                                startColumnNumber : 1
+                                startLineNumber : 5
+                            }
+                        ]
+                    }
+                ]
+                scriptId : <scriptId>
+                url : 4
+            }
+            [1] : {
+                functions : [
+                    [0] : {
+                        functionName : 
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 11
+                                endLineNumber : 0
+                                startColumnNumber : 0
+                                startLineNumber : 0
+                            }
+                        ]
+                    }
+                ]
+                scriptId : <scriptId>
+                url : 
+            }
+        ]
+    }
+}
+{
+    id : <messageId>
+    result : {
+        result : [
+            [0] : {
+                functions : [
+                    [0] : {
+                        functionName : 
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 0
+                                endLineNumber : 9
+                                startColumnNumber : 0
+                                startLineNumber : 0
+                            }
+                        ]
+                    }
+                    [1] : {
+                        functionName : fib
+                        ranges : [
+                            [0] : {
+                                count : 15
+                                endColumnNumber : 1
+                                endLineNumber : 4
+                                startColumnNumber : 0
+                                startLineNumber : 1
+                            }
+                        ]
+                    }
+                    [2] : {
+                        functionName : iife
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 1
+                                endLineNumber : 7
+                                startColumnNumber : 1
+                                startLineNumber : 5
+                            }
+                        ]
+                    }
+                ]
+                scriptId : <scriptId>
+                url : 4
+            }
+            [1] : {
+                functions : [
+                    [0] : {
+                        functionName : 
+                        ranges : [
+                            [0] : {
+                                count : 1
+                                endColumnNumber : 11
+                                endLineNumber : 0
+                                startColumnNumber : 0
+                                startLineNumber : 0
+                            }
+                        ]
+                    }
+                ]
+                scriptId : <scriptId>
+                url : 
+            }
+        ]
+    }
+}
diff --git a/test/inspector/runtime/coverage.js b/test/inspector/runtime/coverage.js
new file mode 100644
index 0000000..dff7eca
--- /dev/null
+++ b/test/inspector/runtime/coverage.js
@@ -0,0 +1,89 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var source =
+`
+function fib(x) {
+  if (x < 2) return 1;
+  return fib(x-1) + fib(x-2);
+}
+(function iife() {
+  return 1;
+})();
+fib(5);
+`;
+
+print("Test collecting code coverage data with Runtime.collectCoverage.");
+
+function ClearAndGC() {
+  return Protocol.Runtime.evaluate({ expression: "fib = null;" })
+           .then(() => Protocol.HeapProfiler.enable())
+           .then(() => Protocol.HeapProfiler.collectGarbage())
+           .then(() => Protocol.HeapProfiler.disable());
+}
+
+InspectorTest.runTestSuite([
+  function testPreciseCoverage(next)
+  {
+    Protocol.Runtime.enable()
+      .then(Protocol.Runtime.startPreciseCoverage)
+      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "1", persistScript: true }))
+      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
+      .then(ClearAndGC)
+      .then(InspectorTest.logMessage)
+      .then(Protocol.Runtime.takePreciseCoverage)
+      .then(InspectorTest.logMessage)
+      .then(Protocol.Runtime.takePreciseCoverage)
+      .then(InspectorTest.logMessage)
+      .then(ClearAndGC)
+      .then(Protocol.Runtime.stopPreciseCoverage)
+      .then(Protocol.Runtime.disable)
+      .then(next);
+  },
+  function testPreciseCoverageFail(next)
+  {
+    Protocol.Runtime.enable()
+      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "2", persistScript: true }))
+      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
+      .then(InspectorTest.logMessage)
+      .then(ClearAndGC)
+      .then(Protocol.Runtime.takePreciseCoverage)
+      .then(InspectorTest.logMessage)
+      .then(ClearAndGC)
+      .then(Protocol.Runtime.disable)
+      .then(next);
+  },
+  function testBestEffortCoverage(next)
+  {
+    Protocol.Runtime.enable()
+      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "3", persistScript: true }))
+      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
+      .then(InspectorTest.logMessage)
+      .then(ClearAndGC)
+      .then(Protocol.Runtime.getBestEffortCoverage)
+      .then(InspectorTest.logMessage)
+      .then(Protocol.Runtime.getBestEffortCoverage)
+      .then(InspectorTest.logMessage)
+      .then(ClearAndGC)
+      .then(Protocol.Runtime.disable)
+      .then(next);
+  },
+  function testBestEffortCoveragePrecise(next)
+  {
+    Protocol.Runtime.enable()
+      .then(Protocol.Runtime.startPreciseCoverage)
+      .then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "4", persistScript: true }))
+      .then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
+      .then(InspectorTest.logMessage)
+      .then(ClearAndGC)
+      .then(Protocol.Runtime.getBestEffortCoverage)
+      .then(InspectorTest.logMessage)
+      .then(Protocol.Runtime.getBestEffortCoverage)
+      .then(InspectorTest.logMessage)
+      .then(ClearAndGC)
+      .then(Protocol.Runtime.stopPreciseCoverage)
+      .then(Protocol.Runtime.disable)
+      .then(next);
+  },
+]);
diff --git a/test/mjsunit/code-coverage-ad-hoc.js b/test/mjsunit/code-coverage-ad-hoc.js
index 5dd2da2..9289c21 100644
--- a/test/mjsunit/code-coverage-ad-hoc.js
+++ b/test/mjsunit/code-coverage-ad-hoc.js
@@ -8,31 +8,18 @@
 
 function GetCoverage(source) {
   for (var script of %DebugCollectCoverage()) {
-    if (script.script.source == source) return script.toplevel;
+    if (script.script.source == source) return script;
   }
   return undefined;
 }
 
-function ApplyCoverageToSource(source, range) {
-  var content = "";
-  var cursor = range.start;
-  if (range.inner) for (var inner of range.inner) {
-    content += source.substring(cursor, inner.start);
-    content += ApplyCoverageToSource(source, inner);
-    cursor = inner.end;
-  }
-  content += source.substring(cursor, range.end);
-  return `[${content}](${range.name}:${range.count})`;
-}
-
 function TestCoverage(name, source, expectation) {
   source = source.trim();
-  expectation = expectation.trim();
   eval(source);
   var coverage = GetCoverage(source);
-  var result = ApplyCoverageToSource(source, coverage);
+  var result = JSON.stringify(coverage);
   print(result);
-  assertEquals(expectation, result, name + " failed");
+  assertEquals(JSON.stringify(expectation), result, name + " failed");
 }
 
 TestCoverage(
@@ -42,11 +29,8 @@
 f();
 f();
 `,
-`
-[[function f() {}](f:2)
-f();
-f();](:1)
-`
+[{"start":0,"end":25,"count":1},
+ {"start":0,"end":15,"count":2}]
 );
 
 TestCoverage(
@@ -56,11 +40,8 @@
 f();
 f();
 `,
-`
-[var f = [() => 1](f:2);
-f();
-f();](:1)
-`
+[{"start":0,"end":26,"count":1},
+ {"start":8,"end":15,"count":2}]
 );
 
 TestCoverage(
@@ -74,16 +55,9 @@
 f();
 f();
 `,
-`
-[[function f() {
-  [function g() {}](g:4)
-  g();
-  g();
-}](f:2)
-f();
-f();](:1)
-
-`
+[{"start":0,"end":58,"count":1},
+ {"start":0,"end":48,"count":2},
+ {"start":17,"end":32,"count":4}]
 );
 
 TestCoverage(
@@ -95,11 +69,6 @@
 }
 fib(5);
 `,
-`
-[[function fib(x) {
-  if (x < 2) return 1;
-  return fib(x-1) + fib(x-2);
-}](fib:15)
-fib(5);](:1)
-`
+[{"start":0,"end":80,"count":1},
+ {"start":0,"end":72,"count":15}]
 );
diff --git a/test/mjsunit/code-coverage-precise.js b/test/mjsunit/code-coverage-precise.js
index a25542d..03015f9 100644
--- a/test/mjsunit/code-coverage-precise.js
+++ b/test/mjsunit/code-coverage-precise.js
@@ -8,39 +8,21 @@
 
 function GetCoverage(source) {
   for (var script of %DebugCollectCoverage()) {
-    if (script.script.source == source) return script.toplevel;
+    if (script.script.source == source) return script;
   }
   return undefined;
 }
 
-function ApplyCoverageToSource(source, range) {
-  var content = "";
-  var cursor = range.start;
-  if (range.inner) for (var inner of range.inner) {
-    content += source.substring(cursor, inner.start);
-    content += ApplyCoverageToSource(source, inner);
-    cursor = inner.end;
-  }
-  content += source.substring(cursor, range.end);
-  return `[${content}](${range.name}:${range.count})`;
-}
-
 function TestCoverage(name, source, expectation) {
   source = source.trim();
   eval(source);
-  %CollectGarbage("remove dead objects");
+  %CollectGarbage("collect dead objects");
   var coverage = GetCoverage(source);
-  if (expectation === undefined) {
-    assertEquals(undefined, coverage);
-  } else {
-    expectation = expectation.trim();
-    var result = ApplyCoverageToSource(source, coverage);
-    print(result);
-    assertEquals(expectation, result, name + " failed");
-  }
+  var result = JSON.stringify(coverage);
+  print(result);
+  assertEquals(JSON.stringify(expectation), result, name + " failed");
 }
 
-
 // Without precise coverage enabled, we lose coverage data to the GC.
 TestCoverage(
 "call an IIFE",
@@ -69,9 +51,7 @@
 `
 (function f() {})();
 `,
-`
-[([function f() {}](f:1))();](:1)
-`
+[{"start":0,"end":20,"count":1},{"start":1,"end":16,"count":1}]
 );
 
 TestCoverage(
@@ -82,12 +62,7 @@
   i += f();
 }
 `,
-`
-[for (var i = 0; i < 10; i++) {
-  let f = [() => 1](f:5);
-  i += f();
-}](:1)
-`
+[{"start":0,"end":63,"count":1},{"start":41,"end":48,"count":5}]
 );
 
 %DebugTogglePreciseCoverage(false);