blob: f4e78661ed1b9aaf88a388f1da594803e0ae0165 [file] [log] [blame]
// 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 "chrome/test/chromedriver/chrome/frame_tracker.h"
#include <utility>
#include "base/json/json_writer.h"
#include "base/values.h"
#include "chrome/test/chromedriver/chrome/browser_info.h"
#include "chrome/test/chromedriver/chrome/devtools_client.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/chrome/web_view_impl.h"
FrameTracker::FrameTracker(DevToolsClient* client,
WebView* web_view,
const BrowserInfo* browser_info)
: web_view_(web_view) {
client->AddListener(this);
}
FrameTracker::~FrameTracker() {}
Status FrameTracker::GetContextIdForFrame(
const std::string& frame_id, int* context_id) {
if (frame_to_context_map_.count(frame_id) == 0) {
return Status(kNoSuchExecutionContext,
"frame does not have execution context");
}
*context_id = frame_to_context_map_[frame_id];
return Status(kOk);
}
WebView* FrameTracker::GetTargetForFrame(const std::string& frame_id) {
// Context in the current target, return current target.
if (frame_to_context_map_.count(frame_id) != 0)
return web_view_;
// Child target of the current target, return that child target.
if (frame_to_target_map_.count(frame_id) != 0)
return frame_to_target_map_[frame_id].get();
// Frame unknown, recursively search all child targets.
for (auto it = frame_to_target_map_.begin(); it != frame_to_target_map_.end();
++it) {
FrameTracker* child = it->second->GetFrameTracker();
if (child != nullptr) {
WebView* child_result = child->GetTargetForFrame(frame_id);
if (child_result != nullptr)
return child_result;
}
}
return nullptr;
}
void FrameTracker::DeleteTargetForFrame(const std::string& frame_id) {
frame_to_target_map_.erase(frame_id);
}
Status FrameTracker::OnConnected(DevToolsClient* client) {
frame_to_context_map_.clear();
frame_to_target_map_.clear();
// Enable target events to allow tracking iframe targets creation.
base::DictionaryValue params;
params.SetBoolean("autoAttach", true);
params.SetBoolean("waitForDebuggerOnStart", false);
Status status = client->SendCommand("Target.setAutoAttach", params);
if (status.IsError())
return status;
// Enable runtime events to allow tracking execution context creation.
params.Clear();
status = client->SendCommand("Runtime.enable", params);
if (status.IsError())
return status;
return client->SendCommand("Page.enable", params);
}
Status FrameTracker::OnEvent(DevToolsClient* client,
const std::string& method,
const base::DictionaryValue& params) {
if (method == "Runtime.executionContextCreated") {
const base::DictionaryValue* context;
if (!params.GetDictionary("context", &context)) {
return Status(kUnknownError,
"Runtime.executionContextCreated missing dict 'context'");
}
int context_id;
std::string frame_id;
bool is_default = true;
if (!context->GetInteger("id", &context_id)) {
std::string json;
base::JSONWriter::Write(*context, &json);
return Status(kUnknownError, method + " has invalid 'context': " + json);
}
if (context->HasKey("auxData")) {
const base::DictionaryValue* auxData;
if (!context->GetDictionary("auxData", &auxData))
return Status(kUnknownError, method + " has invalid 'auxData' value");
if (!auxData->GetBoolean("isDefault", &is_default))
return Status(kUnknownError, method + " has invalid 'isDefault' value");
if (!auxData->GetString("frameId", &frame_id))
return Status(kUnknownError, method + " has invalid 'frameId' value");
}
if (context->HasKey("isDefault")) {
// TODO(samuong): remove this when we stop supporting Chrome 53.
if (!context->GetBoolean("isDefault", &is_default))
return Status(kUnknownError, method + " has invalid 'isDefault' value");
}
if (context->HasKey("frameId")) {
// TODO(samuong): remove this when we stop supporting Chrome 53.
if (!context->GetString("frameId", &frame_id))
return Status(kUnknownError, method + " has invalid 'frameId' value");
}
if (context->HasKey("type")) {
// Before crrev.com/381172, the optional |type| field can be used to
// determine whether we're looking at the default context.
// TODO(samuong): remove this when we stop supporting Chrome 50.
std::string type;
if (!context->GetString("type", &type))
return Status(kUnknownError, method + " has invalid 'context.type'");
is_default = type != "Extension"; // exclude content scripts
}
if (is_default && !frame_id.empty())
frame_to_context_map_[frame_id] = context_id;
} else if (method == "Runtime.executionContextDestroyed") {
int execution_context_id;
if (!params.GetInteger("executionContextId", &execution_context_id))
return Status(kUnknownError, method + " missing 'executionContextId'");
for (auto entry : frame_to_context_map_) {
if (entry.second == execution_context_id) {
frame_to_context_map_.erase(entry.first);
break;
}
}
} else if (method == "Runtime.executionContextsCleared") {
frame_to_context_map_.clear();
} else if (method == "Page.frameNavigated") {
const base::Value* unused_value;
if (!params.Get("frame.parentId", &unused_value))
frame_to_context_map_.clear();
} else if (method == "Target.attachedToTarget") {
std::string type, target_id, session_id;
if (!params.GetString("targetInfo.type", &type))
return Status(kUnknownError,
"missing target type in Target.attachedToTarget event");
if (type == "iframe") {
if (!params.GetString("targetInfo.targetId", &target_id))
return Status(kUnknownError,
"missing target ID in Target.attachedToTarget event");
if (!params.GetString("sessionId", &session_id))
return Status(kUnknownError,
"missing session ID in Target.attachedToTarget event");
if (frame_to_target_map_.count(target_id) > 0) {
// Since chrome 70 we are seeing multiple Target.attachedToTarget events
// for the same target_id. This is causing crashes because:
// - replacing the value in frame_to_target_map_ is causing the
// pre-existing one to be disposed
// - if there are in-flight requests for the disposed DevToolsClient
// then chromedriver is crashing in the ProcessNextMessage processing
// - the in-flight messages observed were DOM.getDocument requests from
// DomTracker.
// The fix is to not replace an pre-existing frame_to_target_map_ entry.
} else {
std::unique_ptr<WebViewImpl> child(
static_cast<WebViewImpl*>(web_view_)->CreateChild(session_id,
target_id));
WebViewImplHolder child_holder(child.get());
frame_to_target_map_[target_id] = std::move(child);
frame_to_target_map_[target_id]->ConnectIfNecessary();
}
}
} else if (method == "Target.detachedFromTarget") {
std::string target_id;
if (!params.GetString("targetId", &target_id))
// Some types of Target.detachedFromTarget events do not have targetId.
// We are not interested in those types of targets.
return Status(kOk);
auto target_iter = frame_to_target_map_.find(target_id);
if (target_iter == frame_to_target_map_.end())
// There are some target types that we're not keeping track of, thus not
// finding the target in frame_to_target_map_ is OK.
return Status(kOk);
WebViewImpl* target = static_cast<WebViewImpl*>(target_iter->second.get());
if (target->IsLocked())
target->SetDetached();
else
frame_to_target_map_.erase(target_id);
}
return Status(kOk);
}