зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1804638 - Add telemetry probes to measure ORB JavaScript performance. r=necko-reviewers,smaug,sefeng,kershaw
Differential Revision: https://phabricator.services.mozilla.com/D164568
This commit is contained in:
Родитель
03c58d765c
Коммит
5a0687b02f
|
@ -23,7 +23,7 @@ mozilla::ipc::IPCResult JSValidatorChild::RecvOnDataAvailable(Shmem&& aData) {
|
|||
mozilla::fallible)) {
|
||||
// To prevent an attacker from flood the validation process,
|
||||
// we don't validate here.
|
||||
Resolve(false);
|
||||
Resolve(ValidatorResult::Failure);
|
||||
}
|
||||
DeallocShmem(aData);
|
||||
|
||||
|
@ -37,7 +37,7 @@ mozilla::ipc::IPCResult JSValidatorChild::RecvOnStopRequest(
|
|||
}
|
||||
|
||||
if (NS_FAILED(aReason)) {
|
||||
Resolve(false);
|
||||
Resolve(ValidatorResult::Failure);
|
||||
} else {
|
||||
Resolve(ShouldAllowJS());
|
||||
}
|
||||
|
@ -47,35 +47,41 @@ mozilla::ipc::IPCResult JSValidatorChild::RecvOnStopRequest(
|
|||
|
||||
void JSValidatorChild::ActorDestroy(ActorDestroyReason aReason) {
|
||||
if (mResolver) {
|
||||
Resolve(false);
|
||||
Resolve(ValidatorResult::Failure);
|
||||
}
|
||||
};
|
||||
|
||||
void JSValidatorChild::Resolve(bool aAllow) {
|
||||
void JSValidatorChild::Resolve(ValidatorResult aResult) {
|
||||
MOZ_ASSERT(mResolver);
|
||||
Maybe<Shmem> data = Nothing();
|
||||
if (aAllow) {
|
||||
if (!mSourceBytes.IsEmpty()) {
|
||||
Shmem sharedData;
|
||||
nsresult rv =
|
||||
JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes, sharedData);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
data = Some(std::move(sharedData));
|
||||
}
|
||||
if (aResult == ValidatorResult::JavaScript && !mSourceBytes.IsEmpty()) {
|
||||
Shmem sharedData;
|
||||
nsresult rv =
|
||||
JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes, sharedData);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
data = Some(std::move(sharedData));
|
||||
}
|
||||
}
|
||||
mResolver.ref()(
|
||||
Tuple<const bool&, mozilla::Maybe<Shmem>&&>(aAllow, std::move(data)));
|
||||
mResolver = Nothing();
|
||||
|
||||
mResolver.ref()(Tuple<mozilla::Maybe<Shmem>&&, const ValidatorResult&>(
|
||||
std::move(data), aResult));
|
||||
mResolver.reset();
|
||||
}
|
||||
|
||||
bool JSValidatorChild::ShouldAllowJS() const {
|
||||
JSValidatorChild::ValidatorResult JSValidatorChild::ShouldAllowJS() const {
|
||||
// mSourceBytes could be empty when
|
||||
// 1. No OnDataAvailable calls
|
||||
// 2. Failed to allocate shmem
|
||||
//
|
||||
// TODO(sefeng): THIS IS A VERY TEMPORARY SOLUTION
|
||||
return !mSourceBytes.IsEmpty()
|
||||
? !StringBeginsWith(NS_ConvertUTF8toUTF16(mSourceBytes), u"{"_ns)
|
||||
: true;
|
||||
|
||||
// The empty document parses as JavaScript, so for clarity we have a condition
|
||||
// separately for that.
|
||||
if (mSourceBytes.IsEmpty()) {
|
||||
return ValidatorResult::JavaScript;
|
||||
}
|
||||
|
||||
if (StringBeginsWith(NS_ConvertUTF8toUTF16(mSourceBytes), u"{"_ns)) {
|
||||
return ValidatorResult::JSON;
|
||||
}
|
||||
|
||||
return ValidatorResult::JavaScript;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "mozilla/ProcInfo.h"
|
||||
#include "mozilla/dom/PJSValidatorChild.h"
|
||||
#include "mozilla/dom/JSValidatorUtils.h"
|
||||
#include "mozilla/net/OpaqueResponseUtils.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
class JSValidatorChild final : public PJSValidatorChild {
|
||||
|
@ -28,8 +29,9 @@ class JSValidatorChild final : public PJSValidatorChild {
|
|||
private:
|
||||
virtual ~JSValidatorChild() = default;
|
||||
|
||||
void Resolve(bool aAllow);
|
||||
bool ShouldAllowJS() const;
|
||||
using ValidatorResult = net::OpaqueResponseBlocker::ValidatorResult;
|
||||
void Resolve(ValidatorResult aResult);
|
||||
ValidatorResult ShouldAllowJS() const;
|
||||
|
||||
nsCString mSourceBytes;
|
||||
Maybe<IsOpaqueResponseAllowedResolver> mResolver;
|
||||
|
|
|
@ -24,7 +24,7 @@ already_AddRefed<JSValidatorParent> JSValidatorParent::Create() {
|
|||
}
|
||||
|
||||
void JSValidatorParent::IsOpaqueResponseAllowed(
|
||||
const std::function<void(bool, Maybe<Shmem>)>& aCallback) {
|
||||
const std::function<void(Maybe<Shmem>, ValidatorResult)>& aCallback) {
|
||||
JSOracleParent::WithJSOracle([=, self = RefPtr{this}](const auto* aParent) {
|
||||
if (aParent) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(self->CanSend());
|
||||
|
@ -34,14 +34,16 @@ void JSValidatorParent::IsOpaqueResponseAllowed(
|
|||
const IsOpaqueResponseAllowedPromise::ResolveOrRejectValue&
|
||||
aResult) {
|
||||
if (aResult.IsResolve()) {
|
||||
const Tuple<bool, Maybe<Shmem>>& result = aResult.ResolveValue();
|
||||
aCallback(Get<0>(result), Get<1>(aResult.ResolveValue()));
|
||||
Maybe<Shmem> data;
|
||||
ValidatorResult result;
|
||||
Tie(data, result) = aResult.ResolveValue();
|
||||
aCallback(std::move(data), result);
|
||||
} else {
|
||||
aCallback(false, Nothing());
|
||||
aCallback(Nothing(), ValidatorResult::Failure);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
aCallback(false, Nothing());
|
||||
aCallback(Nothing(), ValidatorResult::Failure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ class JSValidatorParent final : public PJSValidatorParent {
|
|||
static already_AddRefed<JSValidatorParent> Create();
|
||||
|
||||
void IsOpaqueResponseAllowed(
|
||||
const std::function<void(bool, Maybe<mozilla::ipc::Shmem>)>& aCallback);
|
||||
const std::function<void(Maybe<mozilla::ipc::Shmem>, ValidatorResult)>&
|
||||
aCallback);
|
||||
|
||||
void OnDataAvailable(const nsACString& aData);
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
include protocol PJSOracle;
|
||||
|
||||
using mozilla::net::OpaqueResponseBlocker::ValidatorResult from "mozilla/net/OpaqueResponseUtils.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
|
@ -12,7 +14,7 @@ async protocol PJSValidator {
|
|||
manager PJSOracle;
|
||||
|
||||
child:
|
||||
async IsOpaqueResponseAllowed() returns(bool aAllowed, Shmem? aMem);
|
||||
async IsOpaqueResponseAllowed() returns (Shmem? aMem, ValidatorResult aResult);
|
||||
|
||||
async OnDataAvailable(Shmem aData);
|
||||
|
||||
|
|
|
@ -3099,6 +3099,11 @@ HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
|
|||
return OpaqueResponse::Allow;
|
||||
}
|
||||
|
||||
Telemetry::ScalarAdd(
|
||||
Telemetry::ScalarID::
|
||||
OPAQUE_RESPONSE_BLOCKING_CROSS_ORIGIN_OPAQUE_RESPONSE_COUNT,
|
||||
1);
|
||||
|
||||
// https://whatpr.org/fetch/1442.html#orb-algorithm
|
||||
// Step 1
|
||||
nsAutoCString contentType;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/StaticPrefs_browser.h"
|
||||
#include "mozilla/dom/JSValidatorParent.h"
|
||||
#include "ErrorList.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsHttpResponseHead.h"
|
||||
|
@ -269,6 +270,10 @@ OpaqueResponseBlocker::OnStopRequest(nsIRequest* aRequest,
|
|||
}
|
||||
|
||||
if (mState == State::Sniffing) {
|
||||
// It is the call to JSValidatorParent::OnStopRequest that will trigger the
|
||||
// JS parser.
|
||||
mStartOfJavaScriptValidation = TimeStamp::Now();
|
||||
|
||||
MOZ_ASSERT(mJSValidator);
|
||||
mPendingOnStopRequestStatus = Some(aStatusCode);
|
||||
mJSValidator->OnStopRequest(aStatusCode);
|
||||
|
@ -360,6 +365,36 @@ nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff(
|
|||
return ValidateJavaScript(httpBaseChannel, uri, loadInfo);
|
||||
}
|
||||
|
||||
static void RecordTelemetry(const TimeStamp& aStartOfValidation,
|
||||
const TimeStamp& aStartOfJavaScriptValidation,
|
||||
OpaqueResponseBlocker::ValidatorResult aResult) {
|
||||
using ValidatorResult = OpaqueResponseBlocker::ValidatorResult;
|
||||
MOZ_DIAGNOSTIC_ASSERT(aStartOfValidation);
|
||||
|
||||
auto key = [aResult]() {
|
||||
switch (aResult) {
|
||||
case ValidatorResult::JavaScript:
|
||||
return "javascript"_ns;
|
||||
case ValidatorResult::JSON:
|
||||
return "json"_ns;
|
||||
case ValidatorResult::Other:
|
||||
return "other"_ns;
|
||||
case ValidatorResult::Failure:
|
||||
return "failure"_ns;
|
||||
}
|
||||
MOZ_ASSERT_UNREACHABLE("Switch statement should be saturated");
|
||||
return "failure"_ns;
|
||||
}();
|
||||
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
Telemetry::AccumulateTimeDelta(Telemetry::ORB_RECEIVE_DATA_FOR_VALIDATION_MS,
|
||||
key, aStartOfValidation,
|
||||
aStartOfJavaScriptValidation);
|
||||
|
||||
Telemetry::AccumulateTimeDelta(Telemetry::ORB_JAVASCRIPT_VALIDATION_MS, key,
|
||||
aStartOfJavaScriptValidation, now);
|
||||
}
|
||||
|
||||
// The specification for ORB is currently being written:
|
||||
// https://whatpr.org/fetch/1442.html#orb-algorithm
|
||||
// The `opaque-response-safelist check` is implemented in:
|
||||
|
@ -387,28 +422,36 @@ nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel,
|
|||
return rv;
|
||||
}
|
||||
|
||||
Telemetry::ScalarAdd(
|
||||
Telemetry::ScalarID::OPAQUE_RESPONSE_BLOCKING_JAVASCRIPT_VALIDATION_COUNT,
|
||||
1);
|
||||
|
||||
LOGORB("Send %s to the validator", aURI->GetSpecOrDefault().get());
|
||||
// https://whatpr.org/fetch/1442.html#orb-algorithm, step 15
|
||||
mJSValidator = dom::JSValidatorParent::Create();
|
||||
mJSValidator->IsOpaqueResponseAllowed(
|
||||
[self = RefPtr{this}, channel = nsCOMPtr{aChannel}, uri = nsCOMPtr{aURI},
|
||||
loadInfo = nsCOMPtr{aLoadInfo}](bool aAllowed,
|
||||
Maybe<ipc::Shmem> aSharedData) {
|
||||
loadInfo = nsCOMPtr{aLoadInfo}, startOfValidation = TimeStamp::Now()](
|
||||
Maybe<ipc::Shmem> aSharedData, ValidatorResult aResult) {
|
||||
MOZ_LOG(gORBLog, LogLevel::Debug,
|
||||
("JSValidator resolved for %s with %s",
|
||||
uri->GetSpecOrDefault().get(),
|
||||
aSharedData.isSome() ? "true" : "false"));
|
||||
if (aAllowed) {
|
||||
bool allowed = aResult == ValidatorResult::JavaScript;
|
||||
if (allowed) {
|
||||
self->AllowResponse();
|
||||
} else {
|
||||
self->BlockResponse(channel, NS_ERROR_FAILURE);
|
||||
LogORBError(loadInfo, uri);
|
||||
}
|
||||
self->ResolveAndProcessData(channel, aAllowed, aSharedData);
|
||||
self->ResolveAndProcessData(channel, allowed, aSharedData);
|
||||
if (aSharedData.isSome()) {
|
||||
self->mJSValidator->DeallocShmem(aSharedData.ref());
|
||||
}
|
||||
|
||||
RecordTelemetry(startOfValidation, self->mStartOfJavaScriptValidation,
|
||||
aResult);
|
||||
|
||||
Unused << dom::PJSValidatorParent::Send__delete__(self->mJSValidator);
|
||||
self->mJSValidator = nullptr;
|
||||
});
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
#ifndef mozilla_net_OpaqueResponseUtils_h
|
||||
#define mozilla_net_OpaqueResponseUtils_h
|
||||
|
||||
#include "mozilla/dom/JSValidatorParent.h"
|
||||
#include "ipc/EnumSerializer.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsIContentPolicy.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsUnknownDecoder.h"
|
||||
|
@ -25,6 +26,14 @@
|
|||
class nsIContentSniffer;
|
||||
static mozilla::LazyLogModule gORBLog("ORB");
|
||||
|
||||
namespace mozilla::dom {
|
||||
class JSValidatorParent;
|
||||
}
|
||||
|
||||
namespace mozilla::ipc {
|
||||
class Shmem;
|
||||
}
|
||||
|
||||
namespace mozilla::net {
|
||||
|
||||
class HttpBaseChannel;
|
||||
|
@ -70,6 +79,14 @@ class OpaqueResponseBlocker final : public nsIStreamListener {
|
|||
|
||||
nsresult EnsureOpaqueResponseIsAllowedAfterSniff(nsIRequest* aRequest);
|
||||
|
||||
// The four possible results for validation. `JavaScript` and `JSON` are
|
||||
// self-explanatory. `JavaScript` is the only successful result, in the sense
|
||||
// that it will allow the opaque response, whereas `JSON` will block. `Other`
|
||||
// is the case where validation fails, because the response is neither
|
||||
// `JavaScript` nor `JSON`, but the framework itself works as intended.
|
||||
// `Failure` implies that something has gone wrong, such as allocation, etc.
|
||||
enum class ValidatorResult : uint32_t { JavaScript, JSON, Other, Failure };
|
||||
|
||||
private:
|
||||
virtual ~OpaqueResponseBlocker() = default;
|
||||
|
||||
|
@ -89,6 +106,8 @@ class OpaqueResponseBlocker final : public nsIStreamListener {
|
|||
State mState = State::Sniffing;
|
||||
nsresult mStatus = NS_OK;
|
||||
|
||||
TimeStamp mStartOfJavaScriptValidation;
|
||||
|
||||
RefPtr<dom::JSValidatorParent> mJSValidator;
|
||||
|
||||
Maybe<nsresult> mPendingOnStopRequestStatus{Nothing()};
|
||||
|
@ -142,4 +161,13 @@ class nsCompressedAudioVideoImageDetector : public nsUnknownDecoder {
|
|||
};
|
||||
} // namespace mozilla::net
|
||||
|
||||
namespace IPC {
|
||||
template <>
|
||||
struct ParamTraits<mozilla::net::OpaqueResponseBlocker::ValidatorResult>
|
||||
: public ContiguousEnumSerializerInclusive<
|
||||
mozilla::net::OpaqueResponseBlocker::ValidatorResult,
|
||||
mozilla::net::OpaqueResponseBlocker::ValidatorResult::JavaScript,
|
||||
mozilla::net::OpaqueResponseBlocker::ValidatorResult::Failure> {};
|
||||
} // namespace IPC
|
||||
|
||||
#endif // mozilla_net_OpaqueResponseUtils_h
|
||||
|
|
|
@ -17151,5 +17151,31 @@
|
|||
"n_buckets": 100,
|
||||
"description": "The length of time (in seconds) that a Picture-in-Picture window was open.",
|
||||
"releaseChannelCollection": "opt-out"
|
||||
},
|
||||
"ORB_JAVASCRIPT_VALIDATION_MS": {
|
||||
"record_in_processes": ["main"],
|
||||
"products": ["firefox"],
|
||||
"alert_emails": ["farre@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"keyed": true,
|
||||
"keys": ["javascript", "json", "other", "failure"],
|
||||
"high": 10000,
|
||||
"n_buckets": 50,
|
||||
"bug_numbers": [1804638],
|
||||
"description": "Time (in ms) that it takes for a ORB JavaScript validator to complete a validation, including IPC to the validator actor."
|
||||
},
|
||||
"ORB_RECEIVE_DATA_FOR_VALIDATION_MS": {
|
||||
"record_in_processes": ["main"],
|
||||
"products": ["firefox"],
|
||||
"alert_emails": ["farre@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"keyed": true,
|
||||
"keys": ["javascript", "json", "other", "failure"],
|
||||
"high": 10000,
|
||||
"n_buckets": 50,
|
||||
"bug_numbers": [1804638],
|
||||
"description": "Time (in ms) that it takes to receive data for ORB JavaScript validation, including IPC to the validator actor."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8985,6 +8985,34 @@ opaque.response.blocking:
|
|||
- 'main'
|
||||
release_channel_collection: opt-out
|
||||
|
||||
cross_origin_opaque_response_count:
|
||||
bug_numbers:
|
||||
- 1804638
|
||||
description: >
|
||||
The number of loads of cross origin opaque resources.
|
||||
expires: never
|
||||
kind: uint
|
||||
notification_emails:
|
||||
- farre@mozilla.com
|
||||
products:
|
||||
- 'firefox'
|
||||
record_in_processes:
|
||||
- 'main'
|
||||
|
||||
javascript_validation_count:
|
||||
bug_numbers:
|
||||
- 1804638
|
||||
description: >
|
||||
The number of times we run the JS validator.
|
||||
expires: never
|
||||
kind: uint
|
||||
notification_emails:
|
||||
- farre@mozilla.com
|
||||
products:
|
||||
- 'firefox'
|
||||
record_in_processes:
|
||||
- 'main'
|
||||
|
||||
places:
|
||||
sponsored_visit_no_triggering_url:
|
||||
bug_numbers:
|
||||
|
|
Загрузка…
Ссылка в новой задаче