blob: 3a439912b58977e417facdbf073432f9a6f2c7c0 [file] [log] [blame]
// Copyright 2020 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 "content/browser/webui/web_ui_main_frame_observer.h"
#include <string>
#include <utility>
#include "content/browser/webui/web_ui_impl.h"
#include "content/public/browser/navigation_handle.h"
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
#include "base/callback_helpers.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "components/crash/content/browser/error_reporting/javascript_error_report.h"
#include "components/crash/content/browser/error_reporting/js_error_report_processor.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "url/gurl.h"
#endif
namespace content {
namespace {
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
// Remove the pieces of the URL we don't want to send back with the error
// reports. In particular, do not send query or fragments as those can have
// privacy-sensitive information in them.
std::string RedactURL(const GURL& url) {
std::string redacted_url = url.GetOrigin().spec();
// Path will start with / and GetOrigin ends with /. Cut one / to avoid
// chrome://discards//graph.
if (!redacted_url.empty() && redacted_url.back() == '/') {
redacted_url.pop_back();
}
base::StrAppend(&redacted_url, {url.path_piece()});
return redacted_url;
}
#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
} // namespace
WebUIMainFrameObserver::WebUIMainFrameObserver(WebUIImpl* web_ui,
WebContents* contents)
: WebContentsObserver(contents), web_ui_(web_ui) {}
WebUIMainFrameObserver::~WebUIMainFrameObserver() = default;
void WebUIMainFrameObserver::DidFinishNavigation(
NavigationHandle* navigation_handle) {
// Only disallow JavaScript on cross-document navigations in the main frame.
if (!navigation_handle->IsInMainFrame() ||
!navigation_handle->HasCommitted() ||
navigation_handle->IsSameDocument()) {
return;
}
web_ui_->DisallowJavascriptOnAllHandlers();
}
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
void WebUIMainFrameObserver::OnDidAddMessageToConsole(
RenderFrameHost* source_frame,
blink::mojom::ConsoleMessageLevel log_level,
const std::u16string& message,
int32_t line_no,
const std::u16string& source_id,
const base::Optional<std::u16string>& untrusted_stack_trace) {
// TODO(iby) Change all VLOGs to DVLOGs once tast tests are stable.
DVLOG(3) << "OnDidAddMessageToConsole called for " << message;
if (untrusted_stack_trace) {
DVLOG(3) << "stack is " << *untrusted_stack_trace;
}
if (!error_reporting_enabled_) {
DVLOG(3) << "Message not reported, error reporting disabled for this page "
"or experiment is off";
return;
}
if (log_level != blink::mojom::ConsoleMessageLevel::kError) {
DVLOG(3) << "Message not reported, not an error";
return;
}
// Some WebUI pages have another WebUI page in an <iframe>. Both
// WebUIMainFrameObservers will get a callback when either page gets an error.
// To avoid duplicates, only report on errors from this page's frame.
if (source_frame != web_ui_->frame_host()) {
DVLOG(3) << "Message not reported, different frame";
return;
}
scoped_refptr<JsErrorReportProcessor> processor =
JsErrorReportProcessor::Get();
if (!processor) {
// This usually means we are not on an official Google build, FYI.
DVLOG(3) << "Message not reported, no processor";
return;
}
// Redact query parameters & fragment. Also the username and password.
// TODO(https://crbug.com/1121816) Improve redaction.
GURL url(source_id);
if (!url.is_valid()) {
DVLOG(3) << "Message not reported, invalid URL";
return;
}
// If this is not a chrome:// page, do not report the error. In particular,
// some WebUIs use chrome-untrusted:// to host pages with some
// not-controlled-by-Chrome content. The code must not send reports for such
// content because Chrome cannot control what information is being included in
// the reports.
if (!url.SchemeIs(kChromeUIScheme)) {
DVLOG(3) << "Message not reported, not a chrome:// URL";
return;
}
JavaScriptErrorReport report;
report.message = base::UTF16ToUTF8(message);
report.line_number = line_no;
report.url = RedactURL(url);
report.source_system = JavaScriptErrorReport::SourceSystem::kWebUIObserver;
if (untrusted_stack_trace) {
report.stack_trace = base::UTF16ToUTF8(*untrusted_stack_trace);
}
report.send_to_production_servers =
features::kWebUIJavaScriptErrorReportsSendToProductionParam.Get();
GURL page_url = source_frame->GetLastCommittedURL();
if (page_url.is_valid()) {
report.page_url = RedactURL(page_url);
}
DVLOG(3) << "Error being sent to Google";
processor->SendErrorReport(std::move(report), base::DoNothing(),
web_contents()->GetBrowserContext());
}
void WebUIMainFrameObserver::ReadyToCommitNavigation(
NavigationHandle* navigation_handle) {
DVLOG(3) << "WebUIMainFrameObserver::ReadyToCommitNavigation()";
if (!base::FeatureList::IsEnabled(
features::kSendWebUIJavaScriptErrorReports)) {
DVLOG(3) << "Experiment is off";
return;
}
if (navigation_handle->GetRenderFrameHost() != web_ui_->frame_host()) {
DVLOG(3) << "Wrong frame";
return;
}
error_reporting_enabled_ =
web_ui_->GetController()->IsJavascriptErrorReportingEnabled();
// If we are collecting error reports, make sure the main frame sends us
// stacks along with those messages. Warning: Don't call
// RenderFrameHostImpl::SetWantErrorMessageStackTrace() before the remote
// frame is created, or it will lock up the communications channel. (See
// https://crbug.com/1154866).
if (error_reporting_enabled_) {
DVLOG(3) << "Enabled";
web_ui_->frame_host()->SetWantErrorMessageStackTrace();
} else {
DVLOG(3) << "Error reporting disabled for this page";
}
}
#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
} // namespace content