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