blob: 28938bcb1772b56494761aa98de3de4731d282b2 [file] [log] [blame]
// Copyright 2016 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 "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/scriptable_document_parser.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/inspector/thread_debugger.h"
#include "third_party/blink/renderer/core/inspector/v8_inspector_string.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding_macros.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
namespace blink {
namespace {
std::unique_ptr<v8_inspector::V8StackTrace> CaptureStackTrace(bool full) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
ThreadDebugger* debugger = ThreadDebugger::From(isolate);
if (!debugger || !isolate->InContext())
return nullptr;
ScriptForbiddenScope::AllowUserAgentScript allow_scripting;
return debugger->GetV8Inspector()->captureStackTrace(full);
}
}
// static
std::unique_ptr<SourceLocation> SourceLocation::Capture(
const String& url,
unsigned line_number,
unsigned column_number) {
std::unique_ptr<v8_inspector::V8StackTrace> stack_trace =
CaptureStackTrace(false);
if (stack_trace && !stack_trace->isEmpty()) {
return SourceLocation::CreateFromNonEmptyV8StackTrace(
std::move(stack_trace));
}
return std::make_unique<SourceLocation>(url, line_number, column_number,
std::move(stack_trace));
}
// static
std::unique_ptr<SourceLocation> SourceLocation::Capture(
ExecutionContext* execution_context) {
std::unique_ptr<v8_inspector::V8StackTrace> stack_trace =
CaptureStackTrace(false);
if (stack_trace && !stack_trace->isEmpty()) {
return SourceLocation::CreateFromNonEmptyV8StackTrace(
std::move(stack_trace));
}
if (LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(execution_context)) {
Document* document = window->document();
unsigned line_number = 0;
if (document->GetScriptableDocumentParser() &&
!document->IsInDocumentWrite()) {
if (document->GetScriptableDocumentParser()->IsParsingAtLineNumber())
line_number =
document->GetScriptableDocumentParser()->LineNumber().OneBasedInt();
}
return std::make_unique<SourceLocation>(
document->Url().GetString(), line_number, 0, std::move(stack_trace));
}
return std::make_unique<SourceLocation>(
execution_context ? execution_context->Url().GetString() : String(), 0, 0,
std::move(stack_trace));
}
// static
std::unique_ptr<SourceLocation> SourceLocation::FromMessage(
v8::Isolate* isolate,
v8::Local<v8::Message> message,
ExecutionContext* execution_context) {
v8::Local<v8::StackTrace> stack = message->GetStackTrace();
std::unique_ptr<v8_inspector::V8StackTrace> stack_trace = nullptr;
ThreadDebugger* debugger = ThreadDebugger::From(isolate);
if (debugger)
stack_trace = debugger->GetV8Inspector()->createStackTrace(stack);
int script_id = message->GetScriptOrigin().ScriptId();
if (!stack.IsEmpty() && stack->GetFrameCount() > 0) {
int top_script_id = stack->GetFrame(isolate, 0)->GetScriptId();
if (top_script_id == script_id)
script_id = 0;
}
int line_number = 0;
int column_number = 0;
if (message->GetLineNumber(isolate->GetCurrentContext()).To(&line_number) &&
message->GetStartColumn(isolate->GetCurrentContext()).To(&column_number))
++column_number;
if ((!script_id || !line_number) && stack_trace && !stack_trace->isEmpty()) {
return SourceLocation::CreateFromNonEmptyV8StackTrace(
std::move(stack_trace));
}
String url = ToCoreStringWithUndefinedOrNullCheck(
message->GetScriptOrigin().ResourceName());
if (url.IsEmpty())
url = execution_context->Url();
return std::make_unique<SourceLocation>(url, line_number, column_number,
std::move(stack_trace), script_id);
}
// static
std::unique_ptr<SourceLocation> SourceLocation::CreateFromNonEmptyV8StackTrace(
std::unique_ptr<v8_inspector::V8StackTrace> stack_trace) {
// Retrieve the data before passing the ownership to SourceLocation.
String url = ToCoreString(stack_trace->topSourceURL());
unsigned line_number = stack_trace->topLineNumber();
unsigned column_number = stack_trace->topColumnNumber();
int script_id = stack_trace->topScriptIdAsInteger();
return base::WrapUnique(new SourceLocation(
url, line_number, column_number, std::move(stack_trace), script_id));
}
// static
std::unique_ptr<SourceLocation> SourceLocation::FromFunction(
v8::Local<v8::Function> function) {
if (!function.IsEmpty())
return std::make_unique<SourceLocation>(
ToCoreStringWithUndefinedOrNullCheck(
function->GetScriptOrigin().ResourceName()),
function->GetScriptLineNumber() + 1,
function->GetScriptColumnNumber() + 1, nullptr, function->ScriptId());
return std::make_unique<SourceLocation>(String(), 0, 0, nullptr, 0);
}
// static
std::unique_ptr<SourceLocation> SourceLocation::CaptureWithFullStackTrace() {
std::unique_ptr<v8_inspector::V8StackTrace> stack_trace =
CaptureStackTrace(true);
if (stack_trace && !stack_trace->isEmpty()) {
return SourceLocation::CreateFromNonEmptyV8StackTrace(
std::move(stack_trace));
}
return std::make_unique<SourceLocation>(String(), 0, 0, nullptr, 0);
}
SourceLocation::SourceLocation(
const String& url,
unsigned line_number,
unsigned column_number,
std::unique_ptr<v8_inspector::V8StackTrace> stack_trace,
int script_id)
: url_(url),
line_number_(line_number),
column_number_(column_number),
stack_trace_(std::move(stack_trace)),
script_id_(script_id) {}
SourceLocation::~SourceLocation() = default;
void SourceLocation::ToTracedValue(TracedValue* value, const char* name) const {
if (!stack_trace_ || stack_trace_->isEmpty())
return;
value->BeginArray(name);
value->BeginDictionary();
value->SetString("functionName",
ToCoreString(stack_trace_->topFunctionName()));
value->SetInteger("scriptId", stack_trace_->topScriptIdAsInteger());
value->SetString("url", ToCoreString(stack_trace_->topSourceURL()));
value->SetInteger("lineNumber", stack_trace_->topLineNumber());
value->SetInteger("columnNumber", stack_trace_->topColumnNumber());
value->EndDictionary();
value->EndArray();
}
void SourceLocation::WriteIntoTracedValue(perfetto::TracedValue context) const {
// TODO(altimin): Consider replacing nested dict-inside-array with just an
// array here.
auto array = std::move(context).WriteArray();
auto dict = array.AppendDictionary();
// TODO(altimin): Add TracedValue support to v8::StringView and remove
// ToCoreString calls.
dict.Add("functionName", ToCoreString(stack_trace_->topFunctionName()));
dict.Add("scriptId", stack_trace_->topScriptIdAsInteger());
dict.Add("url", ToCoreString(stack_trace_->topSourceURL()));
dict.Add("lineNumber", stack_trace_->topLineNumber());
dict.Add("columnNumber", stack_trace_->topColumnNumber());
}
std::unique_ptr<SourceLocation> SourceLocation::Clone() const {
return base::WrapUnique(new SourceLocation(
url_.IsolatedCopy(), line_number_, column_number_,
stack_trace_ ? stack_trace_->clone() : nullptr, script_id_));
}
std::unique_ptr<v8_inspector::protocol::Runtime::API::StackTrace>
SourceLocation::BuildInspectorObject() const {
return BuildInspectorObject(std::numeric_limits<int>::max());
}
std::unique_ptr<v8_inspector::protocol::Runtime::API::StackTrace>
SourceLocation::BuildInspectorObject(int max_async_depth) const {
return stack_trace_ ? stack_trace_->buildInspectorObject(max_async_depth)
: nullptr;
}
String SourceLocation::ToString() const {
if (!stack_trace_)
return String();
return ToCoreString(stack_trace_->toString());
}
} // namespace blink