Skip to content

Commit

Permalink
CORB/ORB: Report issue to DevTools for CORB/ORB errors.
Browse files Browse the repository at this point in the history
CORB (and ORB) currently use a console message to highlight CORB/ORB
errors. This adds a DevTools "issue", which has a nicer UI with UI
translation, links to explainers, and hyperlinks to the offending
network request in the "network" tab. This requires a companion CL
in the devtools frontend to do something useful, and a follow-on
to actually remove the console message.

This CL contains browser_protocol.pdl and needs to land first.
Removing the console message requires this + devtools CL to land first.

Devtools companion CL: crrev.com/c/4618976
Remove console message + cleanup: crrev.com/c/4359633

Bug: 1178928
Change-Id: I2cd5a8f54cc180943f0fecd8b2aec59ff485580d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4624106
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1164727}
  • Loading branch information
otherdaniel authored and Chromium LUCI CQ committed Jun 30, 2023
1 parent 0055602 commit 12764f4
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 21 deletions.
3 changes: 3 additions & 0 deletions content/browser/devtools/devtools_instrumentation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,9 @@ protocol::Audits::GenericIssueErrorType GenericIssueErrorTypeToProtocol(
kFormInputHasWrongButWellIntendedAutocompleteValueError:
return protocol::Audits::GenericIssueErrorTypeEnum::
FormInputHasWrongButWellIntendedAutocompleteValueError;
case blink::mojom::GenericIssueErrorType::kResponseWasBlockedByORB:
return protocol::Audits::GenericIssueErrorTypeEnum::
ResponseWasBlockedByORB;
}
}

Expand Down
49 changes: 41 additions & 8 deletions content/browser/devtools/network_service_devtools_observer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ void DispatchToAgents(DevToolsAgentHostImpl* agent_host,
(h->*method)(std::forward<Args>(args)...);
}

RenderFrameHostImpl* GetRenderFrameHostImplFrom(int frame_tree_node_id) {
auto* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
if (!ftn) {
return nullptr;
}

RenderFrameHostImpl* rfhi = ftn->current_frame_host();
return rfhi;
}

} // namespace

NetworkServiceDevToolsObserver::NetworkServiceDevToolsObserver(
Expand Down Expand Up @@ -193,14 +203,7 @@ void NetworkServiceDevToolsObserver::OnCorsError(
const GURL& url,
const network::CorsErrorStatus& cors_error_status,
bool is_warning) {
if (frame_tree_node_id_ == FrameTreeNode::kFrameTreeNodeInvalidId)
return;

auto* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
if (!ftn)
return;

RenderFrameHostImpl* rfhi = ftn->current_frame_host();
RenderFrameHostImpl* rfhi = GetRenderFrameHostImplFrom(frame_tree_node_id_);
if (!rfhi)
return;

Expand Down Expand Up @@ -256,6 +259,36 @@ void NetworkServiceDevToolsObserver::OnCorsError(
devtools_instrumentation::ReportBrowserInitiatedIssue(rfhi, issue.get());
}

void NetworkServiceDevToolsObserver::OnCorbError(
const absl::optional<std::string>& devtools_request_id,
const GURL& url) {
RenderFrameHostImpl* rfhi = GetRenderFrameHostImplFrom(frame_tree_node_id_);
if (!rfhi) {
return;
}

std::unique_ptr<protocol::Audits::AffectedRequest> affected_request =
protocol::Audits::AffectedRequest::Create()
.SetRequestId(devtools_request_id.value_or(""))
.SetUrl(url.spec())
.Build();
auto generic_details =
protocol::Audits::GenericIssueDetails::Create()
.SetErrorType(protocol::Audits::GenericIssueErrorTypeEnum::
ResponseWasBlockedByORB)
.SetRequest(std::move(affected_request))
.Build();
auto details = protocol::Audits::InspectorIssueDetails::Create()
.SetGenericIssueDetails(std::move(generic_details))
.Build();
auto issue =
protocol::Audits::InspectorIssue::Create()
.SetCode(protocol::Audits::InspectorIssueCodeEnum::GenericIssue)
.SetDetails(std::move(details))
.Build();
devtools_instrumentation::ReportBrowserInitiatedIssue(rfhi, issue.get());
}

void NetworkServiceDevToolsObserver::OnSubresourceWebBundleMetadata(
const std::string& devtools_request_id,
const std::vector<GURL>& urls) {
Expand Down
2 changes: 2 additions & 0 deletions content/browser/devtools/network_service_devtools_observer.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class NetworkServiceDevToolsObserver : public network::mojom::DevToolsObserver {
const GURL& url,
const network::CorsErrorStatus& status,
bool is_warning) override;
void OnCorbError(const absl::optional<std::string>& devtools_request_id,
const GURL& url) override;
void OnSubresourceWebBundleMetadata(const std::string& devtools_request_id,
const std::vector<GURL>& urls) override;
void OnSubresourceWebBundleMetadataError(
Expand Down
7 changes: 7 additions & 0 deletions services/network/cors/cors_url_loader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,10 @@ void CorsURLLoader::ReportCorsErrorToDevTools(const CorsErrorStatus& status,
CloneClientSecurityState(), request_.url, status, is_warning);
}

void CorsURLLoader::ReportCorbErrorToDevTools() {
devtools_observer_->OnCorbError(request_.devtools_request_id, request_.url);
}

absl::optional<URLLoaderCompletionStatus> CorsURLLoader::ConvertPreflightResult(
int net_error,
absl::optional<CorsErrorStatus> status) {
Expand Down Expand Up @@ -1000,6 +1004,9 @@ void CorsURLLoader::HandleComplete(URLLoaderCompletionStatus status) {
if (devtools_observer_ && status.cors_error_status) {
ReportCorsErrorToDevTools(*status.cors_error_status);
}
if (devtools_observer_ && status.should_report_corb_blocking) {
ReportCorbErrorToDevTools();
}

// If we detect a private network access when we were not expecting one, we
// restart the request and force a preflight request. This preflight and the
Expand Down
3 changes: 3 additions & 0 deletions services/network/cors/cors_url_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CorsURLLoader
void ReportCorsErrorToDevTools(const CorsErrorStatus& status,
bool is_warning = false);

// Reports a Corb/ORB error for `request_` to DevTools, if possible.
void ReportCorbErrorToDevTools();

// Handles OnComplete() callback.
void HandleComplete(URLLoaderCompletionStatus status);

Expand Down
3 changes: 3 additions & 0 deletions services/network/cors/preflight_controller_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,9 @@ class MockDevToolsObserver : public mojom::DevToolsObserver {
const network::CorsErrorStatus& status,
bool is_warning) override {}

void OnCorbError(const absl::optional<std::string>& devtools_request_id,
const GURL& url) override {}

void Clone(mojo::PendingReceiver<DevToolsObserver> observer) override {
receivers_.Add(this, std::move(observer));
}
Expand Down
9 changes: 9 additions & 0 deletions services/network/public/mojom/devtools_observer.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ interface DevToolsObserver {
CorsErrorStatus status,
bool is_warning);

// Called to report a CORB or ORB error. This callback is intended to
// deliver a DevTools issue.
//
// |devtools_request_id| id of the affected request.
// |url| is the URL that was blocked by CORB.
OnCorbError(
string? devtools_request_id,
url.mojom.Url url);

// Called when parsing the .wbn file has succeeded. The event
// contains the information about the web bundle contents.
OnSubresourceWebBundleMetadata(
Expand Down
6 changes: 6 additions & 0 deletions services/network/test/mock_devtools_observer.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ class MockDevToolsObserver : public mojom::DevToolsObserver {
const network::CorsErrorStatus& status,
bool is_warning) override;

MOCK_METHOD(void,
OnCorbError,
(const absl::optional<std::string>& devtools_request_id,
const GURL& url),
(override));

void Clone(mojo::PendingReceiver<DevToolsObserver> observer) override;

void WaitUntilRawResponse(size_t goal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,7 @@ experimental domain Audits
FormLabelHasNeitherForNorNestedInput
FormLabelForMatchesNonExistingIdError
FormInputHasWrongButWellIntendedAutocompleteValueError
ResponseWasBlockedByORB

# Depending on the concrete errorType, different properties are set.
type GenericIssueDetails extends object
Expand All @@ -746,6 +747,7 @@ experimental domain Audits
optional Page.FrameId frameId
optional DOM.BackendNodeId violatingNodeId
optional string violatingNodeAttribute
optional AffectedRequest request

# This issue tracks information needed to print a deprecation message.
# https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/third_party/blink/renderer/core/frame/deprecation/README.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ enum GenericIssueErrorType {
kFormLabelHasNeitherForNorNestedInput,
kFormLabelForMatchesNonExistingIdError,
kFormInputHasWrongButWellIntendedAutocompleteValueError,
kResponseWasBlockedByORB,
};

struct GenericIssueDetails {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ AuditsIssue::GenericIssueErrorTypeToProtocol(
kFormInputHasWrongButWellIntendedAutocompleteValueError:
return protocol::Audits::GenericIssueErrorTypeEnum::
FormInputHasWrongButWellIntendedAutocompleteValueError;
case mojom::blink::GenericIssueErrorType::kResponseWasBlockedByORB:
return protocol::Audits::GenericIssueErrorTypeEnum::
ResponseWasBlockedByORB;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Tests the blockedCrossSiteDocumentLoad bit available as a part of the loading finished signal.
Tests that CORB/ORB blocking gets reported as an issue to DevTools.
Blocking cross-site document at http://devtools.oopif.test:8000/inspector-protocol/network/resources/nosniff.pl: shouldReportCorbBlocking=true.
Blocking cross-site document at http://devtools.oopif.test:8000/inspector-protocol/network/resources/simple-iframe.html: shouldReportCorbBlocking=true.
Blocking, but not reporting cross-site document at http://devtools.oopif.test:8000/inspector-protocol/network/resources/204.pl: shouldReportCorbBlocking=false.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,61 @@
(async function(testRunner) {
var {page, session, dp} = await testRunner.startURL(
'../resources/test-page.html',
`Tests the blockedCrossSiteDocumentLoad bit available as a part of the loading finished signal.`);
`Tests that CORB/ORB blocking gets reported as an issue to DevTools.`);

const responses = new Map();
await dp.Network.enable();
await dp.Audits.enable();

// Returns a promise that returns true if a ResponseWasBlockedByORB issue
// was added. It expectes a "probe" issue for a fetch to `probe_url` in
// order to determine (without a race condition) at what point in time the
// issue should have been received.
// We do some sanity checks on the issue parameters while we're here.
function expectIssueAdded(issueHasBeenAdded) {
return dp.Audits.onceIssueAdded().then(issue => {
const genericIssueDetails = issue?.params?.issue?.details?.genericIssueDetails;
if (!genericIssueDetails)
throw new Error('Unexpected issue shape.');
if (genericIssueDetails.errorType !== 'ResponseWasBlockedByORB')
throw new Error('Unexpected issue errorType.');
if (!genericIssueDetails?.request?.url)
throw new Error('Unexpected issue shape.');
if (genericIssueDetails.request.url.includes('probe.test'))
return issueHasBeenAdded;
return expectIssueAdded(true);
});
}

function shouldReportCorbBlocking() {
// This returns a promise which returns whether a DevTools issue (other
// than for "probe.test") was added.
return expectIssueAdded(false);
}

// In order to recognize whether the sub-test a completed, we'll always
// follow up with a URL that definitely gets (C)ORB-blocked. We'll complete
// the test when we receive an issue for that probe url. That gives us a
// non-racy point in time where the previous url - which we want to test
// for - must have been received.
const probe_url = 'http://probe.test:8000/inspector-protocol/network/resources/nosniff.pl';
function loadImageAndProbe(url) {
return `
probe = function() { new Image().src = '${probe_url}'; }
img = new Image();
img.src = '${url}';
img.onerror = probe;
img.onload = probe;
`;
}

let blocked_urls = [
'http://devtools.oopif.test:8000/inspector-protocol/network/resources/nosniff.pl',
'http://devtools.oopif.test:8000/inspector-protocol/network/resources/simple-iframe.html',
];
for (const url of blocked_urls) {
session.evaluate(`new Image().src = '${url}';`);
const response = await dp.Network.onceLoadingFinished();
session.evaluate(loadImageAndProbe(url));
testRunner.log(
`Blocking cross-site document at ${url}: ` +
`shouldReportCorbBlocking=${response.params.shouldReportCorbBlocking}.`);
`shouldReportCorbBlocking=${await shouldReportCorbBlocking()}.`);
}

let blocked_unreported_urls = [
Expand All @@ -24,11 +64,10 @@
'http://devtools.oopif.test:8000/inspector-protocol/network/resources/content-length-0.pl',
];
for (const url of blocked_unreported_urls) {
session.evaluate(`new Image().src = '${url}';`);
const response = await dp.Network.onceLoadingFinished();
session.evaluate(loadImageAndProbe(url));
testRunner.log(
`Blocking, but not reporting cross-site document at ${url}: ` +
`shouldReportCorbBlocking=${response.params.shouldReportCorbBlocking}.`);
`shouldReportCorbBlocking=${await shouldReportCorbBlocking()}.`);
}

let allowed_urls = [
Expand All @@ -37,11 +76,10 @@
'http://devtools.oopif.test:8000/inspector-protocol/network/resources/test.css',
]
for (const url of allowed_urls) {
session.evaluate(`new Image().src = '${url}';`);
const response = await dp.Network.onceLoadingFinished();
session.evaluate(loadImageAndProbe(url));
testRunner.log(
`Allowing cross-site document at ${url}: ` +
`shouldReportCorbBlocking=${response.params.shouldReportCorbBlocking}.`);
`shouldReportCorbBlocking=${await shouldReportCorbBlocking()}.`);
}

testRunner.completeTest();
Expand Down

0 comments on commit 12764f4

Please sign in to comment.