| 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" | |
| 16 #include "platform/scheduler/base/task_queue_impl.h" | 15 #include "platform/scheduler/base/task_queue_impl.h" |
| 17 #include "platform/scheduler/base/task_queue_selector.h" | 16 #include "platform/scheduler/base/task_queue_selector.h" |
| 18 #include "platform/scheduler/base/virtual_time_domain.h" | 17 #include "platform/scheduler/base/virtual_time_domain.h" |
| 19 #include "platform/scheduler/child/scheduler_tqm_delegate.h" | 18 #include "platform/scheduler/child/scheduler_tqm_delegate.h" |
| 20 #include "platform/scheduler/renderer/auto_advancing_virtual_time_domain.h" | 19 #include "platform/scheduler/renderer/auto_advancing_virtual_time_domain.h" |
| 21 #include "platform/scheduler/renderer/task_queue_throttler.h" | 20 #include "platform/scheduler/renderer/task_queue_throttler.h" |
| 22 #include "platform/scheduler/renderer/web_view_scheduler_impl.h" | 21 #include "platform/scheduler/renderer/web_view_scheduler_impl.h" |
| 23 #include "platform/scheduler/renderer/webthread_impl_for_renderer_scheduler.h" | 22 #include "platform/scheduler/renderer/webthread_impl_for_renderer_scheduler.h" |
| 24 | 23 |
| 25 namespace blink { | 24 namespace blink { |
| 26 namespace scheduler { | 25 namespace scheduler { |
| 27 namespace { | 26 namespace { |
| 28 // The run time of loading tasks is strongly bimodal. The vast majority are | 27 // The run time of loading tasks is strongly bimodal. The vast majority are |
| 29 // very cheap, but there are usually a handful of very expensive tasks (e.g ~1 | 28 // very cheap, but there are usually a handful of very expensive tasks (e.g ~1 |
| 30 // second on a mobile device) so we take a very pessimistic view when estimating | 29 // second on a mobile device) so we take a very pessimistic view when estimating |
| 31 // the cost of loading tasks. | 30 // the cost of loading tasks. |
| 32 const int kLoadingTaskEstimationSampleCount = 1000; | 31 const int kLoadingTaskEstimationSampleCount = 1000; |
| 33 const double kLoadingTaskEstimationPercentile = 99; | 32 const double kLoadingTaskEstimationPercentile = 99; |
| 34 const int kTimerTaskEstimationSampleCount = 1000; | 33 const int kTimerTaskEstimationSampleCount = 1000; |
| 35 const double kTimerTaskEstimationPercentile = 99; | 34 const double kTimerTaskEstimationPercentile = 99; |
| 36 const int kShortIdlePeriodDurationSampleCount = 10; | 35 const int kShortIdlePeriodDurationSampleCount = 10; |
| 37 const double kShortIdlePeriodDurationPercentile = 50; | 36 const double kShortIdlePeriodDurationPercentile = 50; |
| 38 // Amount of idle time left in a frame (as a ratio of the vsync interval) above | 37 // Amount of idle time left in a frame (as a ratio of the vsync interval) above |
| 39 // which main thread compositing can be considered fast. | 38 // which main thread compositing can be considered fast. |
| 40 const double kFastCompositingIdleTimeThreshold = .2; | 39 const double kFastCompositingIdleTimeThreshold = .2; |
| 41 constexpr base::TimeDelta kThreadLoadTrackerReportingInterval = | 40 constexpr base::TimeDelta kThreadLoadTrackerReportingInterval = |
| 42 base::TimeDelta::FromMinutes(1); | 41 base::TimeDelta::FromMinutes(1); |
| 43 constexpr base::TimeDelta kThreadLoadTrackerWaitingPeriodBeforeReporting = | 42 constexpr base::TimeDelta kThreadLoadTrackerWaitingPeriodBeforeReporting = |
| 44 base::TimeDelta::FromMinutes(2); | 43 base::TimeDelta::FromMinutes(2); |
| 45 // We do not throttle anything while audio is played and shortly after that. | |
| 46 constexpr base::TimeDelta kThrottlingDelayAfterAudioIsPlayed = | |
| 47 base::TimeDelta::FromSeconds(5); | |
| 48 | 44 |
| 49 void ReportForegroundRendererTaskLoad(base::TimeTicks time, double load) { | 45 void ReportForegroundRendererTaskLoad(base::TimeTicks time, double load) { |
| 50 int load_percentage = static_cast<int>(load * 100); | 46 int load_percentage = static_cast<int>(load * 100); |
| 51 UMA_HISTOGRAM_PERCENTAGE("RendererScheduler.ForegroundRendererMainThreadLoad", | 47 UMA_HISTOGRAM_PERCENTAGE("RendererScheduler.ForegroundRendererMainThreadLoad", |
| 52 load_percentage); | 48 load_percentage); |
| 53 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), | 49 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
| 54 "RendererScheduler.ForegroundRendererLoad", load_percentage); | 50 "RendererScheduler.ForegroundRendererLoad", load_percentage); |
| 55 } | 51 } |
| 56 | 52 |
| 57 void ReportBackgroundRendererTaskLoad(base::TimeTicks time, double load) { | 53 void ReportBackgroundRendererTaskLoad(base::TimeTicks time, double load) { |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 186 loading_tasks_seem_expensive(false), | 182 loading_tasks_seem_expensive(false), |
| 187 timer_tasks_seem_expensive(false), | 183 timer_tasks_seem_expensive(false), |
| 188 touchstart_expected_soon(false), | 184 touchstart_expected_soon(false), |
| 189 have_seen_a_begin_main_frame(false), | 185 have_seen_a_begin_main_frame(false), |
| 190 have_reported_blocking_intervention_in_current_policy(false), | 186 have_reported_blocking_intervention_in_current_policy(false), |
| 191 have_reported_blocking_intervention_since_navigation(false), | 187 have_reported_blocking_intervention_since_navigation(false), |
| 192 has_visible_render_widget_with_touch_handler(false), | 188 has_visible_render_widget_with_touch_handler(false), |
| 193 begin_frame_not_expected_soon(false), | 189 begin_frame_not_expected_soon(false), |
| 194 in_idle_period_for_testing(false), | 190 in_idle_period_for_testing(false), |
| 195 use_virtual_time(false), | 191 use_virtual_time(false), |
| 196 is_audio_playing(false), | |
| 197 rail_mode_observer(nullptr) {} | 192 rail_mode_observer(nullptr) {} |
| 198 | 193 |
| 199 RendererSchedulerImpl::MainThreadOnly::~MainThreadOnly() {} | 194 RendererSchedulerImpl::MainThreadOnly::~MainThreadOnly() {} |
| 200 | 195 |
| 201 RendererSchedulerImpl::AnyThread::AnyThread() | 196 RendererSchedulerImpl::AnyThread::AnyThread() |
| 202 : awaiting_touch_start_response(false), | 197 : awaiting_touch_start_response(false), |
| 203 in_idle_period(false), | 198 in_idle_period(false), |
| 204 begin_main_frame_on_critical_path(false), | 199 begin_main_frame_on_critical_path(false), |
| 205 last_gesture_was_compositor_driven(false), | 200 last_gesture_was_compositor_driven(false), |
| 206 default_gesture_prevented(true), | 201 default_gesture_prevented(true), |
| (...skipping 283 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 490 MainThreadOnly().renderer_suspended = false; | 485 MainThreadOnly().renderer_suspended = false; |
| 491 | 486 |
| 492 base::TimeTicks now = tick_clock()->NowTicks(); | 487 base::TimeTicks now = tick_clock()->NowTicks(); |
| 493 MainThreadOnly().foreground_main_thread_load_tracker.Resume(now); | 488 MainThreadOnly().foreground_main_thread_load_tracker.Resume(now); |
| 494 MainThreadOnly().background_main_thread_load_tracker.Pause(now); | 489 MainThreadOnly().background_main_thread_load_tracker.Pause(now); |
| 495 | 490 |
| 496 suspend_timers_when_backgrounded_closure_.Cancel(); | 491 suspend_timers_when_backgrounded_closure_.Cancel(); |
| 497 ResumeTimerQueueWhenForegroundedOrResumed(); | 492 ResumeTimerQueueWhenForegroundedOrResumed(); |
| 498 } | 493 } |
| 499 | 494 |
| 500 void RendererSchedulerImpl::OnAudioStateChanged() { | |
| 501 bool is_audio_playing = false; | |
| 502 for (WebViewSchedulerImpl* web_view_scheduler : | |
| 503 MainThreadOnly().web_view_schedulers) { | |
| 504 is_audio_playing = is_audio_playing || web_view_scheduler->IsAudioPlaying(); | |
| 505 } | |
| 506 | |
| 507 if (is_audio_playing == MainThreadOnly().is_audio_playing) | |
| 508 return; | |
| 509 | |
| 510 MainThreadOnly().last_audio_state_change = | |
| 511 helper_.scheduler_tqm_delegate()->NowTicks(); | |
| 512 MainThreadOnly().is_audio_playing = is_audio_playing; | |
| 513 | |
| 514 UpdatePolicy(); | |
| 515 } | |
| 516 | |
| 517 void RendererSchedulerImpl::SuspendRenderer() { | 495 void RendererSchedulerImpl::SuspendRenderer() { |
| 518 helper_.CheckOnValidThread(); | 496 helper_.CheckOnValidThread(); |
| 519 DCHECK(MainThreadOnly().renderer_backgrounded); | 497 DCHECK(MainThreadOnly().renderer_backgrounded); |
| 520 if (helper_.IsShutdown()) | 498 if (helper_.IsShutdown()) |
| 521 return; | 499 return; |
| 522 suspend_timers_when_backgrounded_closure_.Cancel(); | 500 suspend_timers_when_backgrounded_closure_.Cancel(); |
| 523 | 501 |
| 524 UMA_HISTOGRAM_COUNTS("PurgeAndSuspend.PendingTaskCount", | 502 UMA_HISTOGRAM_COUNTS("PurgeAndSuspend.PendingTaskCount", |
| 525 helper_.GetNumberOfPendingTasks()); | 503 helper_.GetNumberOfPendingTasks()); |
| 526 | 504 |
| (...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 851 // The |new_policy_duration| is the minimum of |expected_use_case_duration| | 829 // The |new_policy_duration| is the minimum of |expected_use_case_duration| |
| 852 // and |touchstart_expected_flag_valid_for_duration| unless one is zero in | 830 // and |touchstart_expected_flag_valid_for_duration| unless one is zero in |
| 853 // which case we choose the other. | 831 // which case we choose the other. |
| 854 base::TimeDelta new_policy_duration = expected_use_case_duration; | 832 base::TimeDelta new_policy_duration = expected_use_case_duration; |
| 855 if (new_policy_duration.is_zero() || | 833 if (new_policy_duration.is_zero() || |
| 856 (touchstart_expected_flag_valid_for_duration > base::TimeDelta() && | 834 (touchstart_expected_flag_valid_for_duration > base::TimeDelta() && |
| 857 new_policy_duration > touchstart_expected_flag_valid_for_duration)) { | 835 new_policy_duration > touchstart_expected_flag_valid_for_duration)) { |
| 858 new_policy_duration = touchstart_expected_flag_valid_for_duration; | 836 new_policy_duration = touchstart_expected_flag_valid_for_duration; |
| 859 } | 837 } |
| 860 | 838 |
| 861 // Do not throttle while audio is playing or for a short period after that | |
| 862 // to make sure that pages playing short audio clips powered by timers | |
| 863 // work. | |
| 864 if (MainThreadOnly().last_audio_state_change && | |
| 865 !MainThreadOnly().is_audio_playing) { | |
| 866 base::TimeTicks audio_will_expire = | |
| 867 MainThreadOnly().last_audio_state_change.value() + | |
| 868 kThrottlingDelayAfterAudioIsPlayed; | |
| 869 | |
| 870 base::TimeDelta audio_will_expire_after = audio_will_expire - now; | |
| 871 | |
| 872 if (audio_will_expire_after > base::TimeDelta()) { | |
| 873 if (new_policy_duration.is_zero()) { | |
| 874 new_policy_duration = audio_will_expire_after; | |
| 875 } else { | |
| 876 new_policy_duration = | |
| 877 std::min(new_policy_duration, audio_will_expire_after); | |
| 878 } | |
| 879 } | |
| 880 } | |
| 881 | |
| 882 if (new_policy_duration > base::TimeDelta()) { | 839 if (new_policy_duration > base::TimeDelta()) { |
| 883 MainThreadOnly().current_policy_expiration_time = now + new_policy_duration; | 840 MainThreadOnly().current_policy_expiration_time = now + new_policy_duration; |
| 884 delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration, | 841 delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration, |
| 885 now); | 842 now); |
| 886 } else { | 843 } else { |
| 887 MainThreadOnly().current_policy_expiration_time = base::TimeTicks(); | 844 MainThreadOnly().current_policy_expiration_time = base::TimeTicks(); |
| 888 } | 845 } |
| 889 | 846 |
| 890 // Avoid prioritizing main thread compositing (e.g., rAF) if it is extremely | 847 // Avoid prioritizing main thread compositing (e.g., rAF) if it is extremely |
| 891 // slow, because that can cause starvation in other task sources. | 848 // slow, because that can cause starvation in other task sources. |
| (...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1027 } | 984 } |
| 1028 | 985 |
| 1029 if (MainThreadOnly().use_virtual_time) { | 986 if (MainThreadOnly().use_virtual_time) { |
| 1030 new_policy.compositor_queue_policy.time_domain_type = | 987 new_policy.compositor_queue_policy.time_domain_type = |
| 1031 TimeDomainType::VIRTUAL; | 988 TimeDomainType::VIRTUAL; |
| 1032 new_policy.default_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; | 989 new_policy.default_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; |
| 1033 new_policy.loading_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; | 990 new_policy.loading_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; |
| 1034 new_policy.timer_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; | 991 new_policy.timer_queue_policy.time_domain_type = TimeDomainType::VIRTUAL; |
| 1035 } | 992 } |
| 1036 | 993 |
| 1037 new_policy.should_disable_throttling = | |
| 1038 ShouldDisableThrottlingBecauseOfAudio(now) || | |
| 1039 MainThreadOnly().use_virtual_time; | |
| 1040 | |
| 1041 // Tracing is done before the early out check, because it's quite possible we | 994 // Tracing is done before the early out check, because it's quite possible we |
| 1042 // will otherwise miss this information in traces. | 995 // will otherwise miss this information in traces. |
| 1043 CreateTraceEventObjectSnapshotLocked(); | 996 CreateTraceEventObjectSnapshotLocked(); |
| 1044 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "use_case", | 997 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "use_case", |
| 1045 use_case); | 998 use_case); |
| 1046 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "rail_mode", | 999 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "rail_mode", |
| 1047 new_policy.rail_mode); | 1000 new_policy.rail_mode); |
| 1048 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), | 1001 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
| 1049 "touchstart_expected_soon", | 1002 "touchstart_expected_soon", |
| 1050 MainThreadOnly().touchstart_expected_soon); | 1003 MainThreadOnly().touchstart_expected_soon); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1086 // are mostly dispatched on the default queue) need to be preserved. | 1039 // are mostly dispatched on the default queue) need to be preserved. |
| 1087 ApplyTaskQueuePolicy(helper_.DefaultTaskRunner().get(), | 1040 ApplyTaskQueuePolicy(helper_.DefaultTaskRunner().get(), |
| 1088 MainThreadOnly().current_policy.default_queue_policy, | 1041 MainThreadOnly().current_policy.default_queue_policy, |
| 1089 new_policy.default_queue_policy); | 1042 new_policy.default_queue_policy); |
| 1090 if (MainThreadOnly().rail_mode_observer && | 1043 if (MainThreadOnly().rail_mode_observer && |
| 1091 new_policy.rail_mode != MainThreadOnly().current_policy.rail_mode) { | 1044 new_policy.rail_mode != MainThreadOnly().current_policy.rail_mode) { |
| 1092 MainThreadOnly().rail_mode_observer->OnRAILModeChanged( | 1045 MainThreadOnly().rail_mode_observer->OnRAILModeChanged( |
| 1093 new_policy.rail_mode); | 1046 new_policy.rail_mode); |
| 1094 } | 1047 } |
| 1095 | 1048 |
| 1096 if (new_policy.should_disable_throttling != | |
| 1097 MainThreadOnly().current_policy.should_disable_throttling) { | |
| 1098 if (new_policy.should_disable_throttling) { | |
| 1099 task_queue_throttler()->DisableThrottling(); | |
| 1100 } else { | |
| 1101 task_queue_throttler()->EnableThrottling(); | |
| 1102 } | |
| 1103 } | |
| 1104 | |
| 1105 DCHECK(compositor_task_runner_->IsQueueEnabled()); | 1049 DCHECK(compositor_task_runner_->IsQueueEnabled()); |
| 1106 MainThreadOnly().current_policy = new_policy; | 1050 MainThreadOnly().current_policy = new_policy; |
| 1107 } | 1051 } |
| 1108 | 1052 |
| 1109 void RendererSchedulerImpl::ApplyTaskQueuePolicy( | 1053 void RendererSchedulerImpl::ApplyTaskQueuePolicy( |
| 1110 TaskQueue* task_queue, | 1054 TaskQueue* task_queue, |
| 1111 const TaskQueuePolicy& old_task_queue_policy, | 1055 const TaskQueuePolicy& old_task_queue_policy, |
| 1112 const TaskQueuePolicy& new_task_queue_policy) const { | 1056 const TaskQueuePolicy& new_task_queue_policy) const { |
| 1113 if (old_task_queue_policy.is_enabled != new_task_queue_policy.is_enabled) { | 1057 if (old_task_queue_policy.is_enabled != new_task_queue_policy.is_enabled) { |
| 1114 task_queue_throttler_->SetQueueEnabled(task_queue, | 1058 task_queue_throttler_->SetQueueEnabled(task_queue, |
| (...skipping 509 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1624 } | 1568 } |
| 1625 | 1569 |
| 1626 void RendererSchedulerImpl::EnableVirtualTime() { | 1570 void RendererSchedulerImpl::EnableVirtualTime() { |
| 1627 MainThreadOnly().use_virtual_time = true; | 1571 MainThreadOnly().use_virtual_time = true; |
| 1628 | 1572 |
| 1629 // The |unthrottled_task_runners_| are not actively managed by UpdatePolicy(). | 1573 // The |unthrottled_task_runners_| are not actively managed by UpdatePolicy(). |
| 1630 AutoAdvancingVirtualTimeDomain* time_domain = GetVirtualTimeDomain(); | 1574 AutoAdvancingVirtualTimeDomain* time_domain = GetVirtualTimeDomain(); |
| 1631 for (const scoped_refptr<TaskQueue>& task_queue : unthrottled_task_runners_) | 1575 for (const scoped_refptr<TaskQueue>& task_queue : unthrottled_task_runners_) |
| 1632 task_queue->SetTimeDomain(time_domain); | 1576 task_queue->SetTimeDomain(time_domain); |
| 1633 | 1577 |
| 1578 task_queue_throttler_->EnableVirtualTime(); |
| 1579 |
| 1634 ForceUpdatePolicy(); | 1580 ForceUpdatePolicy(); |
| 1635 } | 1581 } |
| 1636 | 1582 |
| 1637 bool RendererSchedulerImpl::ShouldDisableThrottlingBecauseOfAudio( | |
| 1638 base::TimeTicks now) { | |
| 1639 if (!MainThreadOnly().last_audio_state_change) | |
| 1640 return false; | |
| 1641 | |
| 1642 if (MainThreadOnly().is_audio_playing) | |
| 1643 return true; | |
| 1644 | |
| 1645 return MainThreadOnly().last_audio_state_change.value() + | |
| 1646 kThrottlingDelayAfterAudioIsPlayed > | |
| 1647 now; | |
| 1648 } | |
| 1649 | |
| 1650 TimeDomain* RendererSchedulerImpl::GetActiveTimeDomain() { | |
| 1651 if (MainThreadOnly().use_virtual_time) { | |
| 1652 return GetVirtualTimeDomain(); | |
| 1653 } else { | |
| 1654 return real_time_domain(); | |
| 1655 } | |
| 1656 } | |
| 1657 | |
| 1658 // static | 1583 // static |
| 1659 const char* RendererSchedulerImpl::UseCaseToString(UseCase use_case) { | 1584 const char* RendererSchedulerImpl::UseCaseToString(UseCase use_case) { |
| 1660 switch (use_case) { | 1585 switch (use_case) { |
| 1661 case UseCase::NONE: | 1586 case UseCase::NONE: |
| 1662 return "none"; | 1587 return "none"; |
| 1663 case UseCase::COMPOSITOR_GESTURE: | 1588 case UseCase::COMPOSITOR_GESTURE: |
| 1664 return "compositor_gesture"; | 1589 return "compositor_gesture"; |
| 1665 case UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING: | 1590 case UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING: |
| 1666 return "main_thread_custom_input_handling"; | 1591 return "main_thread_custom_input_handling"; |
| 1667 case UseCase::SYNCHRONIZED_GESTURE: | 1592 case UseCase::SYNCHRONIZED_GESTURE: |
| (...skipping 22 matching lines...) Expand all Loading... |
| 1690 case v8::PERFORMANCE_LOAD: | 1615 case v8::PERFORMANCE_LOAD: |
| 1691 return "load"; | 1616 return "load"; |
| 1692 default: | 1617 default: |
| 1693 NOTREACHED(); | 1618 NOTREACHED(); |
| 1694 return nullptr; | 1619 return nullptr; |
| 1695 } | 1620 } |
| 1696 } | 1621 } |
| 1697 | 1622 |
| 1698 } // namespace scheduler | 1623 } // namespace scheduler |
| 1699 } // namespace blink | 1624 } // namespace blink |
| OLD | NEW |