text: // Copyright (c) 2012 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 "chrome/test/chromedriver/chrome/dom_tracker.h"
  
  #include <stddef.h>
  
  #include <utility>
  
  #include "base/json/json_writer.h"
  #include "base/values.h"
  #include "chrome/test/chromedriver/chrome/devtools_client.h"
  #include "chrome/test/chromedriver/chrome/status.h"
  
  DomTracker::DomTracker(DevToolsClient* client) {
    client->AddListener(this);
  }
  
  DomTracker::~DomTracker() {}
  
  Status DomTracker::GetFrameIdForNode(
      int node_id, std::string* frame_id) {
    if (node_to_frame_map_.count(node_id) == 0)
      return Status(kNoSuchFrame, "element is not a frame");
    *frame_id = node_to_frame_map_[node_id];
    return Status(kOk);
  }
  
  Status DomTracker::OnConnected(DevToolsClient* client) {
    node_to_frame_map_.clear();
    // Fetch the root document node so that Inspector will push DOM node
    // information to the client.
    base::DictionaryValue params;
    return client->SendCommand("DOM.getDocument", params);
  }
  
  Status DomTracker::OnEvent(DevToolsClient* client,
                             const std::string& method,
                             const base::DictionaryValue& params) {
    if (method == "DOM.setChildNodes") {
      const base::Value* nodes;
      if (!params.Get("nodes", &nodes))
        return Status(kUnknownError, "DOM.setChildNodes missing 'nodes'");
  
      if (!ProcessNodeList(nodes)) {
        std::string json;
        base::JSONWriter::Write(*nodes, &json);
        return Status(kUnknownError,
                      "DOM.setChildNodes has invalid 'nodes': " + json);
      }
    } else if (method == "DOM.childNodeInserted") {
      const base::Value* node;
      if (!params.Get("node", &node))
        return Status(kUnknownError, "DOM.childNodeInserted missing 'node'");
  
      if (!ProcessNode(node)) {
        std::string json;
        base::JSONWriter::Write(*node, &json);
        return Status(kUnknownError,
                      "DOM.childNodeInserted has invalid 'node': " + json);
      }
    } else if (method == "DOM.documentUpdated") {
      node_to_frame_map_.clear();
      // Calling DOM.getDocument is necessary to receive future DOM update events.
      client->SendCommandAndIgnoreResponse("DOM.getDocument", {});
    }
    return Status(kOk);
  }
  
  bool DomTracker::ProcessNodeList(const base::Value* nodes) {
    const base::ListValue* nodes_list;
    if (!nodes->GetAsList(&nodes_list))
      return false;
    for (size_t i = 0; i < nodes_list->GetSize(); ++i) {
      const base::Value* node;
      if (!nodes_list->Get(i, &node))
        return false;
      if (!ProcessNode(node))
        return false;
    }
    return true;
  }
  
  bool DomTracker::ProcessNode(const base::Value* node) {
    const base::DictionaryValue* dict;
    if (!node->GetAsDictionary(&dict))
      return false;
    int node_id;
    if (!dict->GetInteger("nodeId", &node_id))
      return false;
    std::string frame_id;
    if (dict->GetString("frameId", &frame_id))
      node_to_frame_map_.insert(std::make_pair(node_id, frame_id));
  
    const base::Value* children;
    if (dict->Get("children", &children))
      return ProcessNodeList(children);
    return true;
  }
  