Bug 1531405 - Gather telemetry on cross-origin style sheet Content-Type headers r=JuniorHsu

Differential Revision: https://phabricator.services.mozilla.com/D47906

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Valentin Gosu 2019-10-04 12:47:48 +00:00
Родитель bb14f09ee9
Коммит bbd9200052
4 изменённых файлов: 252 добавлений и 1 удалений

Просмотреть файл

@ -1767,6 +1767,8 @@ nsresult nsHttpChannel::CallOnStartRequest() {
return mStatus;
}
ReportContentTypeTelemetryForCrossOriginStylesheets();
mTracingEnabled = false;
// Ensure mListener->OnStartRequest will be invoked before exiting
@ -8074,6 +8076,88 @@ nsresult nsHttpChannel::ContinueOnStopRequestAfterAuthRetry(
return ContinueOnStopRequest(aStatus, aIsFromNet, aContentComplete);
}
class HeaderValuesVisitor final : public nsIHttpHeaderVisitor {
NS_DECL_ISUPPORTS
NS_DECL_NSIHTTPHEADERVISITOR
nsTArray<nsCString>& Get() { return mHeaderValues; }
private:
nsTArray<nsCString> mHeaderValues;
~HeaderValuesVisitor() = default;
};
NS_IMETHODIMP HeaderValuesVisitor::VisitHeader(const nsACString& aHeader,
const nsACString& aValue) {
mHeaderValues.AppendElement(aValue);
return NS_OK;
}
NS_IMPL_ISUPPORTS(HeaderValuesVisitor, nsIHttpHeaderVisitor)
void nsHttpChannel::ReportContentTypeTelemetryForCrossOriginStylesheets() {
// Only record for successful requests.
if (NS_FAILED(mStatus)) {
return;
}
// Only record for stylesheet requests
if (!mContentTypeHint.EqualsLiteral("text/css") &&
mLoadInfo->GetExternalContentPolicyType() !=
nsIContentPolicy::TYPE_STYLESHEET) {
return;
}
nsCOMPtr<nsIURI> docURI = mLoadInfo->LoadingPrincipal()->GetURI();
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
bool isPrivateWin = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
nsresult rv = ssm->CheckSameOriginURI(mURI, docURI, false, isPrivateWin);
bool isSameOrigin = NS_SUCCEEDED(rv);
// Only report cross origin stylesheet requests
if (isSameOrigin) {
return;
}
RefPtr<HeaderValuesVisitor> visitor = new HeaderValuesVisitor();
rv = GetOriginalResponseHeader(NS_LITERAL_CSTRING("Content-Type"), visitor);
auto allEmptyValues = [&visitor]() {
for (const auto& v : visitor->Get()) {
if (!v.IsEmpty()) {
return false;
}
}
return true;
};
nsAutoCString contentType;
if (mResponseHead) {
mResponseHead->ContentType(contentType);
}
typedef Telemetry::LABELS_NETWORK_CROSS_ORIGIN_STYLESHEET_CONTENT_TYPE
CTLabels;
CTLabels label;
if (rv == NS_ERROR_NOT_AVAILABLE) {
// No Content-Type header
label = CTLabels::NoHeader;
} else if (allEmptyValues()) {
// Any/all Content-Type headers are empty
label = CTLabels::EmptyHeader;
} else if (contentType.IsEmpty()) {
// failed to parse
label = CTLabels::FailedToParse;
} else if (contentType.EqualsLiteral("text/css")) {
// text/css
label = CTLabels::ParsedTextCSS;
} else {
// some other value
label = CTLabels::ParsedOther;
}
Telemetry::AccumulateCategorical(label);
}
nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
bool aContentComplete) {
LOG(

Просмотреть файл

@ -504,6 +504,8 @@ class nsHttpChannel final : public HttpBaseChannel,
rv == NS_ERROR_PORT_ACCESS_NOT_ALLOWED;
}
void ReportContentTypeTelemetryForCrossOriginStylesheets();
// Report net vs cache time telemetry
void ReportNetVSCacheTelemetry();
int64_t ComputeTelemetryBucketNumber(int64_t difftime_ms);

Просмотреть файл

@ -4,6 +4,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
var charset = {};
var hadCharset = {};
var type;
@ -21,7 +23,7 @@ function check(aType, aCharset, aHadCharset) {
reset();
}
function run_test() {
add_task(function test_parseResponseContentType() {
var netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
type = netutil.parseRequestContentType("text/html", charset, hadCharset);
@ -360,4 +362,156 @@ function run_test() {
hadCharset
);
check("text/plain", "'UTF-8'", true);
});
function checkSnapshotValue(s, name, expectedValue) {
function telemetryCategoryIndex(name) {
let labels = [
"NoHeader",
"EmptyHeader",
"FailedToParse",
"ParsedTextCSS",
"ParsedOther",
];
for (let i in labels) {
if (labels[i] == name) {
return i;
}
}
ok(false, "invalid category");
return;
}
if (!name) {
Assert.deepEqual(s.values, {}, "No probes should be recorded");
return;
}
let index = telemetryCategoryIndex(name);
equal(
s.values[index],
expectedValue,
`Checking the value of ${name} to be ${expectedValue} - recorded probes: ${JSON.stringify(
s.values
)} index: ${index}`
);
for (let i in s.values) {
if (i != index && s.values[i] != 0) {
equal(s.values[i], 0, `The record at ${i} should be 0`);
}
}
}
function makeChan(url, type = Ci.nsIContentPolicy.TYPE_STYLESHEET) {
let principal = Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI("http://example.com"),
{
privateBrowsingId: 0,
}
);
return NetUtil.newChannel({
uri: url,
loadingPrincipal: principal,
contentPolicyType: type,
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
}).QueryInterface(Ci.nsIHttpChannel);
}
add_task(async function check_channels() {
do_get_profile();
let contentType = "text/css;hi";
let content = `.identity-color-blue {
--identity-tab-color: #37adff;
--identity-icon-color: #37adff;
}`;
function handler(metadata, response) {
if (contentType !== undefined) {
response.setHeader("Content-Type", contentType);
}
response.bodyOutputStream.write(content, content.length);
}
let httpserv = new HttpServer();
httpserv.registerPathHandler("/", handler);
httpserv.start(-1);
const URL = `http://localhost:${httpserv.identity.primaryPort}/`;
let h = Services.telemetry.getHistogramById(
"NETWORK_CROSS_ORIGIN_STYLESHEET_CONTENT_TYPE"
);
h.clear();
function make_test(
inContentType,
expectedContentType,
expectedProbe,
typeHint,
contentPolicyType
) {
return new Promise(resolve => {
contentType = inContentType;
let channel = makeChan(URL, contentPolicyType);
if (typeHint) {
channel.contentType = typeHint;
}
let p = new Promise(resolve1 =>
channel.asyncOpen(new ChannelListener(resolve1))
);
p.then(() => {
equal(channel.contentType, expectedContentType);
checkSnapshotValue(h.snapshot(), expectedProbe, 1);
h.clear();
resolve();
});
});
}
// If contentTypeHint != text/css and contentPolicy is not TYPE_STYLESHEET
// no telemetry probes should be recorded.
await make_test(
undefined,
"text/plain",
undefined,
undefined,
Ci.nsIContentPolicy.TYPE_OTHER
);
await make_test(
undefined,
"text/plain",
undefined,
"text/plain",
Ci.nsIContentPolicy.TYPE_OTHER
);
// We should record a probe if the contentTypeHint is text/css
await make_test(
undefined,
"text/css",
"NoHeader",
"text/css",
Ci.nsIContentPolicy.TYPE_OTHER
);
// The rest of the tests will default to contentPolicy = TYPE_STYLESHEET
// One telemetry probe should be recorded for each.
await make_test(undefined, "text/plain", "NoHeader");
await make_test(undefined, "text/abc", "NoHeader", "text/abc");
await make_test(undefined, "text/css", "NoHeader", "text/css");
await make_test("", "text/plain", "EmptyHeader");
await make_test("", "text/abc", "EmptyHeader", "text/abc");
await make_test("", "text/css", "EmptyHeader", "text/css");
await make_test("failure", "text/plain", "FailedToParse");
await make_test("failure", "text/abc", "FailedToParse", "text/abc");
await make_test("failure", "text/css", "FailedToParse", "text/css");
await make_test("text/css", "text/css", "ParsedTextCSS");
await make_test("text/css;hi", "text/css", "ParsedTextCSS");
await make_test("text/plain", "text/plain", "ParsedOther");
await make_test("text/abc", "text/abc", "ParsedOther");
await make_test("text/abc;hi", "text/abc", "ParsedOther");
await make_test("text/abc", "text/abc", "ParsedOther", "text/css");
await new Promise(resolve => httpserv.stop(resolve));
});

Просмотреть файл

@ -2486,6 +2486,17 @@
"description": "Count of the HTTP redirection that triggered by top-level document or by subresource, keyed by the URL scheme redirected to.",
"labels": ["topLevel", "subresource"]
},
"NETWORK_CROSS_ORIGIN_STYLESHEET_CONTENT_TYPE": {
"record_in_processes": ["main"],
"products": ["firefox", "fennec", "geckoview"],
"alert_emails": ["necko@mozilla.com", "vgosu@mozilla.com"],
"bug_numbers": [1531405],
"expires_in_version": "75",
"releaseChannelCollection": "opt-out",
"kind": "categorical",
"description": "Records whether a Content-Type header was present when loading a cross origin stylesheet, and if it was parsed successfully.",
"labels": ["NoHeader", "EmptyHeader", "FailedToParse", "ParsedTextCSS", "ParsedOther"]
},
"HTTP_AUTH_DIALOG_STATS_3": {
"record_in_processes": ["main", "content"],
"products": ["firefox", "fennec", "geckoview"],