1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/scheduler/dom_task.h"
6
7 #include <utility>
8
9 #include "base/logging.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
12 #include "third_party/blink/renderer/bindings/core/v8/v8_function.h"
13 #include "third_party/blink/renderer/core/dom/dom_exception.h"
14 #include "third_party/blink/renderer/core/probe/core_probes.h"
15 #include "third_party/blink/renderer/modules/scheduler/dom_scheduler.h"
16 #include "third_party/blink/renderer/modules/scheduler/dom_task_signal.h"
17 #include "third_party/blink/renderer/platform/bindings/script_state.h"
18
19 namespace blink {
20
21 #define QUEUEING_TIME_PER_PRIORITY_METRIC_NAME \
22 "DOMScheduler.QueueingDurationPerPriority"
23
24 #define PRIORITY_CHANGED_HISTOGRAM_NAME \
25 "DOMSchedler.TaskSignalPriorityWasChanged"
26
27 // Same as UMA_HISTOGRAM_TIMES but for a broader view of this metric we end
28 // at 1 minute instead of 10 seconds.
29 #define QUEUEING_TIME_HISTOGRAM(name, sample) \
30 UMA_HISTOGRAM_CUSTOM_TIMES(QUEUEING_TIME_PER_PRIORITY_METRIC_NAME name, \
31 sample, base::TimeDelta::FromMilliseconds(1), \
32 base::TimeDelta::FromMinutes(1), 50)
33
DOMTask(DOMScheduler * scheduler,ScriptPromiseResolver * resolver,V8Function * callback,const HeapVector<ScriptValue> & args,DOMTaskSignal * signal,base::TimeDelta delay)34 DOMTask::DOMTask(DOMScheduler* scheduler,
35 ScriptPromiseResolver* resolver,
36 V8Function* callback,
37 const HeapVector<ScriptValue>& args,
38 DOMTaskSignal* signal,
39 base::TimeDelta delay)
40 : scheduler_(scheduler),
41 callback_(callback),
42 arguments_(args),
43 resolver_(resolver),
44 signal_(signal),
45 // TODO(kdillon): Expose queuing time from base::sequence_manager so we
46 // don't have to recalculate it here.
47 queue_time_(delay.is_zero() ? base::TimeTicks::Now()
48 : base::TimeTicks()) {
49 DCHECK(signal_);
50 DCHECK(signal_->GetTaskRunner());
51 DCHECK(callback_);
52 signal_->AddAlgorithm(WTF::Bind(&DOMTask::Abort, WrapWeakPersistent(this)));
53
54 task_handle_ = PostDelayedCancellableTask(
55 *signal_->GetTaskRunner(), FROM_HERE,
56 WTF::Bind(&DOMTask::Invoke, WrapPersistent(this)), delay);
57
58 ScriptState* script_state =
59 callback_->CallbackRelevantScriptStateOrReportError("DOMTask", "Create");
60 DCHECK(script_state && script_state->ContextIsValid());
61 probe::AsyncTaskScheduled(ExecutionContext::From(script_state), "postTask",
62 &async_task_id_);
63 }
64
Trace(Visitor * visitor)65 void DOMTask::Trace(Visitor* visitor) {
66 visitor->Trace(scheduler_);
67 visitor->Trace(callback_);
68 visitor->Trace(arguments_);
69 visitor->Trace(resolver_);
70 visitor->Trace(signal_);
71 }
72
Invoke()73 void DOMTask::Invoke() {
74 DCHECK(callback_);
75
76 ScriptState* script_state =
77 callback_->CallbackRelevantScriptStateOrReportError("DOMTask", "Invoke");
78 if (!script_state || !script_state->ContextIsValid())
79 return;
80
81 RecordTaskStartMetrics();
82 InvokeInternal(script_state);
83 callback_.Release();
84 }
85
InvokeInternal(ScriptState * script_state)86 void DOMTask::InvokeInternal(ScriptState* script_state) {
87 v8::Isolate* isolate = script_state->GetIsolate();
88 ScriptState::Scope scope(script_state);
89 v8::TryCatch try_catch(isolate);
90 try_catch.SetVerbose(true);
91
92 ExecutionContext* context = ExecutionContext::From(script_state);
93 DCHECK(context);
94 probe::AsyncTask async_task(context, &async_task_id_);
95 probe::UserCallback probe(context, "postTask", AtomicString(), true);
96
97 v8::Local<v8::Context> v8_context = script_state->GetContext();
98 v8_context->SetContinuationPreservedEmbedderData(
99 ToV8(signal_.Get(), v8_context->Global(), isolate));
100 ScriptValue result;
101 if (callback_->Invoke(nullptr, arguments_).To(&result))
102 resolver_->Resolve(result.V8Value());
103 else if (try_catch.HasCaught())
104 resolver_->Reject(try_catch.Exception());
105 v8_context->SetContinuationPreservedEmbedderData(v8::Local<v8::Object>());
106 }
107
Abort()108 void DOMTask::Abort() {
109 // If the task has already finished running, the promise is either resolved or
110 // rejected, in which case abort will no longer have any effect.
111 if (!callback_)
112 return;
113
114 task_handle_.Cancel();
115 resolver_->Reject(
116 MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError));
117
118 ScriptState* script_state =
119 callback_->CallbackRelevantScriptStateOrReportError("DOMTask", "Abort");
120 DCHECK(script_state && script_state->ContextIsValid());
121 probe::AsyncTaskCanceled(ExecutionContext::From(script_state),
122 &async_task_id_);
123 }
124
RecordTaskStartMetrics()125 void DOMTask::RecordTaskStartMetrics() {
126 UMA_HISTOGRAM_ENUMERATION(PRIORITY_CHANGED_HISTOGRAM_NAME,
127 signal_->GetPriorityChangeStatus());
128
129 if (queue_time_ > base::TimeTicks()) {
130 base::TimeDelta queue_duration = base::TimeTicks::Now() - queue_time_;
131 DCHECK_GT(queue_duration, base::TimeDelta());
132 if (signal_->GetPriorityChangeStatus() ==
133 DOMTaskSignal::PriorityChangeStatus::kNoPriorityChange) {
134 WebSchedulingPriority priority =
135 WebSchedulingPriorityFromString(signal_->priority());
136 switch (priority) {
137 case WebSchedulingPriority::kUserBlockingPriority:
138 QUEUEING_TIME_HISTOGRAM(".UserBlocking", queue_duration);
139 break;
140 case WebSchedulingPriority::kUserVisiblePriority:
141 QUEUEING_TIME_HISTOGRAM(".UserVisable", queue_duration);
142 break;
143 case WebSchedulingPriority::kBackgroundPriority:
144 QUEUEING_TIME_HISTOGRAM(".Background", queue_duration);
145 break;
146 }
147 }
148 }
149 }
150
151 } // namespace blink
152