OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "platform/scheduler/renderer/renderer_scheduler_impl.h" | 5 #include "platform/scheduler/renderer/renderer_scheduler_impl.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/debug/stack_trace.h" | 8 #include "base/debug/stack_trace.h" |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/memory/ptr_util.h" | 10 #include "base/memory/ptr_util.h" |
11 #include "base/metrics/histogram_macros.h" | 11 #include "base/metrics/histogram_macros.h" |
12 #include "base/trace_event/trace_event.h" | 12 #include "base/trace_event/trace_event.h" |
13 #include "base/trace_event/trace_event_argument.h" | 13 #include "base/trace_event/trace_event_argument.h" |
14 #include "cc/output/begin_frame_args.h" | 14 #include "cc/output/begin_frame_args.h" |
| 15 #include "platform/scheduler/base/real_time_domain.h" |
15 #include "platform/scheduler/base/task_queue_impl.h" | 16 #include "platform/scheduler/base/task_queue_impl.h" |
16 #include "platform/scheduler/base/task_queue_selector.h" | 17 #include "platform/scheduler/base/task_queue_selector.h" |
17 #include "platform/scheduler/base/virtual_time_domain.h" | 18 #include "platform/scheduler/base/virtual_time_domain.h" |
18 #include "platform/scheduler/child/scheduler_tqm_delegate.h" | 19 #include "platform/scheduler/child/scheduler_tqm_delegate.h" |
19 #include "platform/scheduler/renderer/auto_advancing_virtual_time_domain.h" | 20 #include "platform/scheduler/renderer/auto_advancing_virtual_time_domain.h" |
20 #include "platform/scheduler/renderer/task_queue_throttler.h" | 21 #include "platform/scheduler/renderer/task_queue_throttler.h" |
21 #include "platform/scheduler/renderer/web_view_scheduler_impl.h" | 22 #include "platform/scheduler/renderer/web_view_scheduler_impl.h" |
22 #include "platform/scheduler/renderer/webthread_impl_for_renderer_scheduler.h" | 23 #include "platform/scheduler/renderer/webthread_impl_for_renderer_scheduler.h" |
23 | 24 |
24 namespace blink { | 25 namespace blink { |
25 namespace scheduler { | 26 namespace scheduler { |
26 namespace { | 27 namespace { |
27 // The run time of loading tasks is strongly bimodal. The vast majority are | 28 // The run time of loading tasks is strongly bimodal. The vast majority are |
28 // very cheap, but there are usually a handful of very expensive tasks (e.g ~1 | 29 // very cheap, but there are usually a handful of very expensive tasks (e.g ~1 |
29 // second on a mobile device) so we take a very pessimistic view when estimating | 30 // second on a mobile device) so we take a very pessimistic view when estimating |
30 // the cost of loading tasks. | 31 // the cost of loading tasks. |
31 const int kLoadingTaskEstimationSampleCount = 1000; | 32 const int kLoadingTaskEstimationSampleCount = 1000; |
32 const double kLoadingTaskEstimationPercentile = 99; | 33 const double kLoadingTaskEstimationPercentile = 99; |
33 const int kTimerTaskEstimationSampleCount = 1000; | 34 const int kTimerTaskEstimationSampleCount = 1000; |
34 const double kTimerTaskEstimationPercentile = 99; | 35 const double kTimerTaskEstimationPercentile = 99; |
35 const int kShortIdlePeriodDurationSampleCount = 10; | 36 const int kShortIdlePeriodDurationSampleCount = 10; |
36 const double kShortIdlePeriodDurationPercentile = 50; | 37 const double kShortIdlePeriodDurationPercentile = 50; |
37 // Amount of idle time left in a frame (as a ratio of the vsync interval) above | 38 // Amount of idle time left in a frame (as a ratio of the vsync interval) above |
38 // which main thread compositing can be considered fast. | 39 // which main thread compositing can be considered fast. |
39 const double kFastCompositingIdleTimeThreshold = .2; | 40 const double kFastCompositingIdleTimeThreshold = .2; |
| 41 // We do not throttle anything while audio is played and shortly after that. |
| 42 const int kThrottlingDelayAfterAudioIsPlayedInSeconds = 5; |
40 | 43 |
41 void ReportForegroundRendererTaskLoad(base::TimeTicks time, double load) { | 44 void ReportForegroundRendererTaskLoad(base::TimeTicks time, double load) { |
42 int load_percentage = static_cast<int>(load * 100); | 45 int load_percentage = static_cast<int>(load * 100); |
43 UMA_HISTOGRAM_PERCENTAGE("RendererScheduler.ForegroundRendererMainThreadLoad", | 46 UMA_HISTOGRAM_PERCENTAGE("RendererScheduler.ForegroundRendererMainThreadLoad", |
44 load_percentage); | 47 load_percentage); |
45 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), | 48 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
46 "RendererScheduler.ForegroundRendererLoad", load_percentage); | 49 "RendererScheduler.ForegroundRendererLoad", load_percentage); |
47 } | 50 } |
48 | 51 |
49 void ReportBackgroundRendererTaskLoad(base::TimeTicks time, double load) { | 52 void ReportBackgroundRendererTaskLoad(base::TimeTicks time, double load) { |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
151 time_source, | 154 time_source, |
152 kShortIdlePeriodDurationSampleCount, | 155 kShortIdlePeriodDurationSampleCount, |
153 kShortIdlePeriodDurationPercentile), | 156 kShortIdlePeriodDurationPercentile), |
154 background_main_thread_load_tracker( | 157 background_main_thread_load_tracker( |
155 now, | 158 now, |
156 base::Bind(&ReportBackgroundRendererTaskLoad)), | 159 base::Bind(&ReportBackgroundRendererTaskLoad)), |
157 foreground_main_thread_load_tracker( | 160 foreground_main_thread_load_tracker( |
158 now, | 161 now, |
159 base::Bind(&ReportForegroundRendererTaskLoad)), | 162 base::Bind(&ReportForegroundRendererTaskLoad)), |
160 current_use_case(UseCase::NONE), | 163 current_use_case(UseCase::NONE), |
| 164 throttling_delay_after_audio_is_played(base::TimeDelta::FromSeconds( |
| 165 kThrottlingDelayAfterAudioIsPlayedInSeconds)), |
161 timer_queue_suspend_count(0), | 166 timer_queue_suspend_count(0), |
162 navigation_task_expected_count(0), | 167 navigation_task_expected_count(0), |
163 expensive_task_policy(ExpensiveTaskPolicy::RUN), | 168 expensive_task_policy(ExpensiveTaskPolicy::RUN), |
164 renderer_hidden(false), | 169 renderer_hidden(false), |
165 renderer_backgrounded(false), | 170 renderer_backgrounded(false), |
166 renderer_suspended(false), | 171 renderer_suspended(false), |
167 timer_queue_suspension_when_backgrounded_enabled(false), | 172 timer_queue_suspension_when_backgrounded_enabled(false), |
168 timer_queue_suspended_when_backgrounded(false), | 173 timer_queue_suspended_when_backgrounded(false), |
169 was_shutdown(false), | 174 was_shutdown(false), |
170 loading_tasks_seem_expensive(false), | 175 loading_tasks_seem_expensive(false), |
171 timer_tasks_seem_expensive(false), | 176 timer_tasks_seem_expensive(false), |
172 touchstart_expected_soon(false), | 177 touchstart_expected_soon(false), |
173 have_seen_a_begin_main_frame(false), | 178 have_seen_a_begin_main_frame(false), |
174 have_reported_blocking_intervention_in_current_policy(false), | 179 have_reported_blocking_intervention_in_current_policy(false), |
175 have_reported_blocking_intervention_since_navigation(false), | 180 have_reported_blocking_intervention_since_navigation(false), |
176 has_visible_render_widget_with_touch_handler(false), | 181 has_visible_render_widget_with_touch_handler(false), |
177 begin_frame_not_expected_soon(false), | 182 begin_frame_not_expected_soon(false), |
178 in_idle_period_for_testing(false), | 183 in_idle_period_for_testing(false), |
179 use_virtual_time(false), | 184 use_virtual_time(false), |
| 185 is_audio_playing(false), |
180 rail_mode_observer(nullptr) {} | 186 rail_mode_observer(nullptr) {} |
181 | 187 |
182 RendererSchedulerImpl::MainThreadOnly::~MainThreadOnly() {} | 188 RendererSchedulerImpl::MainThreadOnly::~MainThreadOnly() {} |
183 | 189 |
184 RendererSchedulerImpl::AnyThread::AnyThread() | 190 RendererSchedulerImpl::AnyThread::AnyThread() |
185 : awaiting_touch_start_response(false), | 191 : awaiting_touch_start_response(false), |
186 in_idle_period(false), | 192 in_idle_period(false), |
187 begin_main_frame_on_critical_path(false), | 193 begin_main_frame_on_critical_path(false), |
188 last_gesture_was_compositor_driven(false), | 194 last_gesture_was_compositor_driven(false), |
189 default_gesture_prevented(true), | 195 default_gesture_prevented(true), |
(...skipping 279 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
469 MainThreadOnly().renderer_suspended = false; | 475 MainThreadOnly().renderer_suspended = false; |
470 | 476 |
471 base::TimeTicks now = tick_clock()->NowTicks(); | 477 base::TimeTicks now = tick_clock()->NowTicks(); |
472 MainThreadOnly().foreground_main_thread_load_tracker.Resume(now); | 478 MainThreadOnly().foreground_main_thread_load_tracker.Resume(now); |
473 MainThreadOnly().background_main_thread_load_tracker.Pause(now); | 479 MainThreadOnly().background_main_thread_load_tracker.Pause(now); |
474 | 480 |
475 suspend_timers_when_backgrounded_closure_.Cancel(); | 481 suspend_timers_when_backgrounded_closure_.Cancel(); |
476 ResumeTimerQueueWhenForegrounded(); | 482 ResumeTimerQueueWhenForegrounded(); |
477 } | 483 } |
478 | 484 |
| 485 void RendererSchedulerImpl::OnAudioStateChanged() { |
| 486 bool is_audio_playing = false; |
| 487 for (WebViewSchedulerImpl* web_view_scheduler : |
| 488 MainThreadOnly().web_view_schedulers) { |
| 489 is_audio_playing = is_audio_playing || web_view_scheduler->IsAudioPlaying(); |
| 490 } |
| 491 |
| 492 if (is_audio_playing == MainThreadOnly().is_audio_playing) |
| 493 return; |
| 494 |
| 495 MainThreadOnly().last_audio_state_change = |
| 496 helper_.scheduler_tqm_delegate()->NowTicks(); |
| 497 MainThreadOnly().is_audio_playing = is_audio_playing; |
| 498 |
| 499 UpdatePolicy(); |
| 500 } |
| 501 |
479 void RendererSchedulerImpl::SuspendRenderer() { | 502 void RendererSchedulerImpl::SuspendRenderer() { |
480 helper_.CheckOnValidThread(); | 503 helper_.CheckOnValidThread(); |
481 DCHECK(MainThreadOnly().renderer_backgrounded); | 504 DCHECK(MainThreadOnly().renderer_backgrounded); |
482 if (helper_.IsShutdown()) | 505 if (helper_.IsShutdown()) |
483 return; | 506 return; |
484 suspend_timers_when_backgrounded_closure_.Cancel(); | 507 suspend_timers_when_backgrounded_closure_.Cancel(); |
485 // TODO(hajimehoshi): We might need to suspend not only timer queue but also | 508 // TODO(hajimehoshi): We might need to suspend not only timer queue but also |
486 // e.g. loading tasks or postMessage. | 509 // e.g. loading tasks or postMessage. |
487 MainThreadOnly().renderer_suspended = true; | 510 MainThreadOnly().renderer_suspended = true; |
488 SuspendTimerQueueWhenBackgrounded(); | 511 SuspendTimerQueueWhenBackgrounded(); |
(...skipping 310 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
799 // The |new_policy_duration| is the minimum of |expected_use_case_duration| | 822 // The |new_policy_duration| is the minimum of |expected_use_case_duration| |
800 // and |touchstart_expected_flag_valid_for_duration| unless one is zero in | 823 // and |touchstart_expected_flag_valid_for_duration| unless one is zero in |
801 // which case we choose the other. | 824 // which case we choose the other. |
802 base::TimeDelta new_policy_duration = expected_use_case_duration; | 825 base::TimeDelta new_policy_duration = expected_use_case_duration; |
803 if (new_policy_duration.is_zero() || | 826 if (new_policy_duration.is_zero() || |
804 (touchstart_expected_flag_valid_for_duration > base::TimeDelta() && | 827 (touchstart_expected_flag_valid_for_duration > base::TimeDelta() && |
805 new_policy_duration > touchstart_expected_flag_valid_for_duration)) { | 828 new_policy_duration > touchstart_expected_flag_valid_for_duration)) { |
806 new_policy_duration = touchstart_expected_flag_valid_for_duration; | 829 new_policy_duration = touchstart_expected_flag_valid_for_duration; |
807 } | 830 } |
808 | 831 |
| 832 // Do not throttle while audio is playing or for a short period after that |
| 833 // to make sure that pages playing short audio clips powered by timers |
| 834 // work. |
| 835 if (MainThreadOnly().last_audio_state_change && |
| 836 !MainThreadOnly().is_audio_playing) { |
| 837 base::TimeTicks audio_will_expire = |
| 838 MainThreadOnly().last_audio_state_change.value() + |
| 839 MainThreadOnly().throttling_delay_after_audio_is_played; |
| 840 |
| 841 base::TimeDelta audio_will_expire_after = audio_will_expire - now; |
| 842 |
| 843 if (audio_will_expire_after > base::TimeDelta()) { |
| 844 if (new_policy_duration.is_zero()) { |
| 845 new_policy_duration = audio_will_expire_after; |
| 846 } else { |
| 847 new_policy_duration = |
| 848 std::min(new_policy_duration, audio_will_expire_after); |
| 849 } |
| 850 } |
| 851 } |
| 852 |
809 if (new_policy_duration > base::TimeDelta()) { | 853 if (new_policy_duration > base::TimeDelta()) { |
810 MainThreadOnly().current_policy_expiration_time = now + new_policy_duration; | 854 MainThreadOnly().current_policy_expiration_time = now + new_policy_duration; |
811 delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration, | 855 delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration, |
812 now); | 856 now); |
813 } else { | 857 } else { |
814 MainThreadOnly().current_policy_expiration_time = base::TimeTicks(); | 858 MainThreadOnly().current_policy_expiration_time = base::TimeTicks(); |
815 } | 859 } |
816 | 860 |
817 // Avoid prioritizing main thread compositing (e.g., rAF) if it is extremely | 861 // Avoid prioritizing main thread compositing (e.g., rAF) if it is extremely |
818 // slow, because that can cause starvation in other task sources. | 862 // slow, because that can cause starvation in other task sources. |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
954 } | 998 } |
955 | 999 |
956 if (MainThreadOnly().use_virtual_time) { | 1000 if (MainThreadOnly().use_virtual_time) { |
957 new_policy.compositor_queue_policy.time_domain_type = | 1001 new_policy.compositor_queue_policy.time_domain_type = |
958 TimeDomainType::VIRTUAL; | 1002 TimeDomainType::VIRTUAL; |
959 new_policy.default_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; | 1003 new_policy.default_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; |
960 new_policy.loading_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; | 1004 new_policy.loading_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; |
961 new_policy.timer_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; | 1005 new_policy.timer_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; |
962 } | 1006 } |
963 | 1007 |
| 1008 new_policy.should_disable_throttling = |
| 1009 ShouldDisableThrottlingBecauseOfAudio(now) || |
| 1010 MainThreadOnly().use_virtual_time; |
| 1011 |
964 // Tracing is done before the early out check, because it's quite possible we | 1012 // Tracing is done before the early out check, because it's quite possible we |
965 // will otherwise miss this information in traces. | 1013 // will otherwise miss this information in traces. |
966 CreateTraceEventObjectSnapshotLocked(); | 1014 CreateTraceEventObjectSnapshotLocked(); |
967 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "use_case", | 1015 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "use_case", |
968 use_case); | 1016 use_case); |
969 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "rail_mode", | 1017 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "rail_mode", |
970 new_policy.rail_mode); | 1018 new_policy.rail_mode); |
971 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), | 1019 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
972 "touchstart_expected_soon", | 1020 "touchstart_expected_soon", |
973 MainThreadOnly().touchstart_expected_soon); | 1021 MainThreadOnly().touchstart_expected_soon); |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1009 // are mostly dispatched on the default queue) need to be preserved. | 1057 // are mostly dispatched on the default queue) need to be preserved. |
1010 ApplyTaskQueuePolicy(helper_.DefaultTaskRunner().get(), | 1058 ApplyTaskQueuePolicy(helper_.DefaultTaskRunner().get(), |
1011 MainThreadOnly().current_policy.default_queue_policy, | 1059 MainThreadOnly().current_policy.default_queue_policy, |
1012 new_policy.default_queue_policy); | 1060 new_policy.default_queue_policy); |
1013 if (MainThreadOnly().rail_mode_observer && | 1061 if (MainThreadOnly().rail_mode_observer && |
1014 new_policy.rail_mode != MainThreadOnly().current_policy.rail_mode) { | 1062 new_policy.rail_mode != MainThreadOnly().current_policy.rail_mode) { |
1015 MainThreadOnly().rail_mode_observer->OnRAILModeChanged( | 1063 MainThreadOnly().rail_mode_observer->OnRAILModeChanged( |
1016 new_policy.rail_mode); | 1064 new_policy.rail_mode); |
1017 } | 1065 } |
1018 | 1066 |
| 1067 if (new_policy.should_disable_throttling != |
| 1068 MainThreadOnly().current_policy.should_disable_throttling) { |
| 1069 if (new_policy.should_disable_throttling) { |
| 1070 task_queue_throttler()->DisableThrottling(); |
| 1071 } else { |
| 1072 task_queue_throttler()->EnableThrottling(); |
| 1073 } |
| 1074 } |
| 1075 |
1019 DCHECK(compositor_task_runner_->IsQueueEnabled()); | 1076 DCHECK(compositor_task_runner_->IsQueueEnabled()); |
1020 MainThreadOnly().current_policy = new_policy; | 1077 MainThreadOnly().current_policy = new_policy; |
1021 } | 1078 } |
1022 | 1079 |
1023 void RendererSchedulerImpl::ApplyTaskQueuePolicy( | 1080 void RendererSchedulerImpl::ApplyTaskQueuePolicy( |
1024 TaskQueue* task_queue, | 1081 TaskQueue* task_queue, |
1025 const TaskQueuePolicy& old_task_queue_policy, | 1082 const TaskQueuePolicy& old_task_queue_policy, |
1026 const TaskQueuePolicy& new_task_queue_policy) const { | 1083 const TaskQueuePolicy& new_task_queue_policy) const { |
1027 if (old_task_queue_policy.is_enabled != new_task_queue_policy.is_enabled) { | 1084 if (old_task_queue_policy.is_enabled != new_task_queue_policy.is_enabled) { |
1028 task_queue_throttler_->SetQueueEnabled(task_queue, | 1085 task_queue_throttler_->SetQueueEnabled(task_queue, |
(...skipping 502 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1531 } | 1588 } |
1532 | 1589 |
1533 void RendererSchedulerImpl::EnableVirtualTime() { | 1590 void RendererSchedulerImpl::EnableVirtualTime() { |
1534 MainThreadOnly().use_virtual_time = true; | 1591 MainThreadOnly().use_virtual_time = true; |
1535 | 1592 |
1536 // The |unthrottled_task_runners_| are not actively managed by UpdatePolicy(). | 1593 // The |unthrottled_task_runners_| are not actively managed by UpdatePolicy(). |
1537 AutoAdvancingVirtualTimeDomain* time_domain = GetVirtualTimeDomain(); | 1594 AutoAdvancingVirtualTimeDomain* time_domain = GetVirtualTimeDomain(); |
1538 for (const scoped_refptr<TaskQueue>& task_queue : unthrottled_task_runners_) | 1595 for (const scoped_refptr<TaskQueue>& task_queue : unthrottled_task_runners_) |
1539 task_queue->SetTimeDomain(time_domain); | 1596 task_queue->SetTimeDomain(time_domain); |
1540 | 1597 |
1541 task_queue_throttler_->EnableVirtualTime(); | 1598 ForceUpdatePolicy(); |
| 1599 } |
1542 | 1600 |
1543 ForceUpdatePolicy(); | 1601 bool RendererSchedulerImpl::ShouldDisableThrottlingBecauseOfAudio( |
| 1602 base::TimeTicks now) { |
| 1603 if (!MainThreadOnly().last_audio_state_change) |
| 1604 return false; |
| 1605 |
| 1606 if (MainThreadOnly().is_audio_playing) |
| 1607 return true; |
| 1608 |
| 1609 return MainThreadOnly().last_audio_state_change.value() + |
| 1610 MainThreadOnly().throttling_delay_after_audio_is_played > |
| 1611 now; |
| 1612 } |
| 1613 |
| 1614 TimeDomain* RendererSchedulerImpl::GetActiveTimeDomain() { |
| 1615 if (MainThreadOnly().use_virtual_time) { |
| 1616 return GetVirtualTimeDomain(); |
| 1617 } else { |
| 1618 return real_time_domain(); |
| 1619 } |
1544 } | 1620 } |
1545 | 1621 |
1546 // static | 1622 // static |
1547 const char* RendererSchedulerImpl::UseCaseToString(UseCase use_case) { | 1623 const char* RendererSchedulerImpl::UseCaseToString(UseCase use_case) { |
1548 switch (use_case) { | 1624 switch (use_case) { |
1549 case UseCase::NONE: | 1625 case UseCase::NONE: |
1550 return "none"; | 1626 return "none"; |
1551 case UseCase::COMPOSITOR_GESTURE: | 1627 case UseCase::COMPOSITOR_GESTURE: |
1552 return "compositor_gesture"; | 1628 return "compositor_gesture"; |
1553 case UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING: | 1629 case UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING: |
(...skipping 24 matching lines...) Expand all Loading... |
1578 case v8::PERFORMANCE_LOAD: | 1654 case v8::PERFORMANCE_LOAD: |
1579 return "load"; | 1655 return "load"; |
1580 default: | 1656 default: |
1581 NOTREACHED(); | 1657 NOTREACHED(); |
1582 return nullptr; | 1658 return nullptr; |
1583 } | 1659 } |
1584 } | 1660 } |
1585 | 1661 |
1586 } // namespace scheduler | 1662 } // namespace scheduler |
1587 } // namespace blink | 1663 } // namespace blink |
OLD | NEW |