Commit ba6e1195 authored by Nicolas Pena's avatar Nicolas Pena Committed by Commit Bot

Allow WebFrameScheduler to know if its frame is main

This CL adds methods to allow the MainThreadTaskQueue to determine
whether its associated frame is main or not. This will be used to split
EQT by frame type.

Bug: chromium:747504
Change-Id: I478f2c1f20ce93c6de0837fdde90a495bc4409d7
Reviewed-on: https://chromium-review.googlesource.com/656110
Commit-Queue: Nicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarSami Kyöstilä <skyostil@chromium.org>
Reviewed-by: default avatarNate Chapin <japhet@chromium.org>
Reviewed-by: default avatarAlexander Timin <altimin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#506858}
parent d4e1b8bc
......@@ -1658,6 +1658,7 @@ jumbo_source_set("unit_tests") {
"paint/ng/ng_text_fragment_painter_test.cc",
"resize_observer/ResizeObserverTest.cpp",
"scheduler/ActiveConnectionThrottlingTest.cpp",
"scheduler/FrameSchedulerTest.cpp",
"scheduler/FrameThrottlingTest.cpp",
"scheduler/ThrottlingTest.cpp",
"scheduler/VirtualTimeTest.cpp",
......
......@@ -23,3 +23,11 @@ include_rules = [
"!core/frame/WebLocalFrameImpl.h",
"!core/frame/WebRemoteFrameImpl.h",
]
specific_include_rules = {
# Allow tests to use Web(Local|Remote)FrameBase.h.
".*Test\.cpp" : [
"+core/frame/WebLocalFrameImpl.h",
"+core/frame/WebRemoteFrameImpl.h",
]
}
......@@ -746,7 +746,9 @@ inline LocalFrame::LocalFrame(LocalFrameClient* client,
InterfaceRegistry* interface_registry)
: Frame(client, page, owner, LocalWindowProxyManager::Create(*this)),
frame_scheduler_(page.GetChromeClient().CreateFrameScheduler(
client->GetFrameBlameContext())),
client->GetFrameBlameContext(),
IsMainFrame() ? WebFrameScheduler::FrameType::kMainFrame
: WebFrameScheduler::FrameType::kSubframe)),
loader_(this),
navigation_scheduler_(NavigationScheduler::Create(this)),
script_controller_(ScriptController::Create(
......
......@@ -100,6 +100,9 @@ class EmptyFrameScheduler : public WebFrameScheduler {
void SetPaused(bool) override {}
void SetCrossOrigin(bool) override {}
bool IsCrossOrigin() const override { return false; }
WebFrameScheduler::FrameType GetFrameType() const override {
return WebFrameScheduler::FrameType::kSubframe;
}
WebViewScheduler* GetWebViewScheduler() override { return nullptr; }
void DidStartLoading(unsigned long identifier) override {}
void DidStopLoading(unsigned long identifier) override {}
......@@ -151,7 +154,8 @@ String EmptyChromeClient::AcceptLanguages() {
}
std::unique_ptr<WebFrameScheduler> EmptyChromeClient::CreateFrameScheduler(
BlameContext*) {
BlameContext* blame_context,
WebFrameScheduler::FrameType frame_type) {
return WTF::MakeUnique<EmptyFrameScheduler>();
}
......
......@@ -225,7 +225,8 @@ class CORE_EXPORT EmptyChromeClient : public ChromeClient {
void InstallSupplements(LocalFrame&) override {}
std::unique_ptr<WebFrameScheduler> CreateFrameScheduler(
BlameContext*) override;
BlameContext*,
WebFrameScheduler::FrameType) override;
};
class CORE_EXPORT EmptyLocalFrameClient : public LocalFrameClient {
......
......@@ -36,6 +36,7 @@
#include "core/style/ComputedStyleConstants.h"
#include "platform/Cursor.h"
#include "platform/PlatformChromeClient.h"
#include "platform/WebFrameScheduler.h"
#include "platform/graphics/TouchAction.h"
#include "platform/heap/Handle.h"
#include "platform/scroll/ScrollTypes.h"
......@@ -77,7 +78,6 @@ class PagePopup;
class PagePopupClient;
class PopupOpeningObserver;
class WebDragData;
class WebFrameScheduler;
class WebImage;
class WebLayer;
class WebLayerTreeView;
......@@ -345,7 +345,8 @@ class CORE_EXPORT ChromeClient : public PlatformChromeClient {
virtual void DidObserveNonGetFetchFromScript() const {}
virtual std::unique_ptr<WebFrameScheduler> CreateFrameScheduler(
BlameContext*) = 0;
BlameContext*,
WebFrameScheduler::FrameType) = 0;
// Returns the time of the beginning of the last beginFrame, in seconds, if
// any, and 0.0 otherwise.
......
......@@ -1098,8 +1098,10 @@ void ChromeClientImpl::DidObserveNonGetFetchFromScript() const {
}
std::unique_ptr<WebFrameScheduler> ChromeClientImpl::CreateFrameScheduler(
BlameContext* blame_context) {
return web_view_->Scheduler()->CreateFrameScheduler(blame_context);
BlameContext* blame_context,
WebFrameScheduler::FrameType frame_type) {
return web_view_->Scheduler()->CreateFrameScheduler(blame_context,
frame_type);
}
double ChromeClientImpl::LastFrameTimeMonotonic() const {
......
......@@ -222,7 +222,8 @@ class CORE_EXPORT ChromeClientImpl final : public ChromeClient {
void DidObserveNonGetFetchFromScript() const override;
std::unique_ptr<WebFrameScheduler> CreateFrameScheduler(
BlameContext*) override;
BlameContext*,
WebFrameScheduler::FrameType) override;
double LastFrameTimeMonotonic() const override;
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code if governed by a BSD-style license that can be
// found in LICENSE file.
#include "core/frame/WebLocalFrameImpl.h"
#include "core/testing/sim/SimRequest.h"
#include "core/testing/sim/SimTest.h"
#include "platform/WebFrameScheduler.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::ElementsAre;
namespace blink {
class WebFrameSchedulerFrameTypeTest : public SimTest {};
TEST_F(WebFrameSchedulerFrameTypeTest, GetFrameType) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(
"<!DOCTYPE HTML>"
"<body>"
"<iframe src=\"about:blank\"></iframe>"
"</body>");
EXPECT_EQ(WebFrameScheduler::FrameType::kMainFrame,
MainFrame().GetFrame()->FrameScheduler()->GetFrameType());
Frame* child = MainFrame().GetFrame()->Tree().FirstChild();
ASSERT_TRUE(child->IsLocalFrame());
EXPECT_EQ(WebFrameScheduler::FrameType::kSubframe,
ToLocalFrame(child)->FrameScheduler()->GetFrameType());
}
} // namespace blink
......@@ -27,6 +27,12 @@ class WebFrameScheduler {
kNotThrottled,
};
// Represents the type of frame: main (top-level) vs not.
enum class FrameType {
kMainFrame,
kSubframe,
};
class ActiveConnectionHandle {
public:
ActiveConnectionHandle() {}
......@@ -71,6 +77,10 @@ class WebFrameScheduler {
virtual void SetCrossOrigin(bool) = 0;
virtual bool IsCrossOrigin() const = 0;
// Returns the frame type, which currently determines whether this frame is
// the top level frame, i.e. a main frame.
virtual FrameType GetFrameType() const = 0;
// The tasks runners below are listed in increasing QoS order.
// - throttleable task queue. Designed for custom user-provided javascript
// tasks. Lowest guarantees. Can be paused, blocked during user gesture,
......
......@@ -3929,7 +3929,8 @@ TEST_F(RendererSchedulerImplTest, Tracing) {
scheduler_->AddWebViewScheduler(web_view_scheduler1.get());
std::unique_ptr<WebFrameSchedulerImpl> web_frame_scheduler =
web_view_scheduler1->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler1->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
std::unique_ptr<WebViewSchedulerImpl> web_view_scheduler2 = base::WrapUnique(
new WebViewSchedulerImpl(nullptr, nullptr, scheduler_.get(), false));
......
......@@ -36,7 +36,8 @@ WebFrameSchedulerImpl::ActiveConnectionHandleImpl::
WebFrameSchedulerImpl::WebFrameSchedulerImpl(
RendererSchedulerImpl* renderer_scheduler,
WebViewSchedulerImpl* parent_web_view_scheduler,
base::trace_event::BlameContext* blame_context)
base::trace_event::BlameContext* blame_context,
WebFrameScheduler::FrameType frame_type)
: renderer_scheduler_(renderer_scheduler),
parent_web_view_scheduler_(parent_web_view_scheduler),
blame_context_(blame_context),
......@@ -44,6 +45,7 @@ WebFrameSchedulerImpl::WebFrameSchedulerImpl(
page_visible_(true),
frame_paused_(false),
cross_origin_(false),
frame_type_(frame_type),
active_connection_count_(0),
weak_factory_(this) {}
......@@ -148,6 +150,10 @@ bool WebFrameSchedulerImpl::IsCrossOrigin() const {
return cross_origin_;
}
WebFrameScheduler::FrameType WebFrameSchedulerImpl::GetFrameType() const {
return frame_type_;
}
RefPtr<blink::WebTaskRunner> WebFrameSchedulerImpl::LoadingTaskRunner() {
DCHECK(parent_web_view_scheduler_);
if (!loading_web_task_runner_) {
......@@ -327,6 +333,10 @@ void WebFrameSchedulerImpl::AsValueInto(
state->SetBoolean("frame_visible", frame_visible_);
state->SetBoolean("page_visible", page_visible_);
state->SetBoolean("cross_origin", cross_origin_);
state->SetString("frame_type",
frame_type_ == WebFrameScheduler::FrameType::kMainFrame
? "MainFrame"
: "Subframe");
if (loading_task_queue_) {
state->SetString("loading_task_queue",
trace_helper::PointerToString(loading_task_queue_.get()));
......
......@@ -36,7 +36,8 @@ class WebFrameSchedulerImpl : public WebFrameScheduler {
public:
WebFrameSchedulerImpl(RendererSchedulerImpl* renderer_scheduler,
WebViewSchedulerImpl* parent_web_view_scheduler,
base::trace_event::BlameContext* blame_context);
base::trace_event::BlameContext* blame_context,
WebFrameScheduler::FrameType frame_type);
~WebFrameSchedulerImpl() override;
......@@ -50,6 +51,7 @@ class WebFrameSchedulerImpl : public WebFrameScheduler {
void SetPaused(bool frame_paused) override;
void SetCrossOrigin(bool cross_origin) override;
bool IsCrossOrigin() const override;
WebFrameScheduler::FrameType GetFrameType() const override;
RefPtr<WebTaskRunner> LoadingTaskRunner() override;
RefPtr<WebTaskRunner> LoadingControlTaskRunner() override;
RefPtr<WebTaskRunner> ThrottleableTaskRunner() override;
......@@ -125,6 +127,7 @@ class WebFrameSchedulerImpl : public WebFrameScheduler {
bool page_visible_;
bool frame_paused_;
bool cross_origin_;
WebFrameScheduler::FrameType frame_type_;
int active_connection_count_;
base::WeakPtrFactory<WebFrameSchedulerImpl> weak_factory_;
......
......@@ -38,8 +38,8 @@ class WebFrameSchedulerImplTest : public ::testing::Test {
scheduler_.reset(new RendererSchedulerImpl(delegate_));
web_view_scheduler_.reset(
new WebViewSchedulerImpl(nullptr, nullptr, scheduler_.get(), false));
web_frame_scheduler_ =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr);
web_frame_scheduler_ = web_view_scheduler_->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
}
void TearDown() override {
......
......@@ -6,6 +6,7 @@
#define THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_RENDERER_WEB_VIEW_SCHEDULER_H_
#include "platform/PlatformExport.h"
#include "platform/WebFrameScheduler.h"
#include "platform/wtf/Functional.h"
#include "public/platform/BlameContext.h"
......@@ -13,8 +14,6 @@
namespace blink {
class WebFrameScheduler;
class PLATFORM_EXPORT WebViewScheduler {
public:
class PLATFORM_EXPORT WebViewSchedulerDelegate {
......@@ -31,9 +30,10 @@ class PLATFORM_EXPORT WebViewScheduler {
// Creates a new WebFrameScheduler. The caller is responsible for deleting
// it. All tasks executed by the frame scheduler will be attributed to
// |BlameContext|.
// |blame_context|.
virtual std::unique_ptr<WebFrameScheduler> CreateFrameScheduler(
BlameContext*) = 0;
BlameContext* blame_context,
WebFrameScheduler::FrameType) = 0;
// Instructs this WebViewScheduler to use virtual time. When virtual time is
// enabled the system doesn't actually sleep for the delays between tasks
......
......@@ -139,18 +139,22 @@ void WebViewSchedulerImpl::SetPageVisible(bool page_visible) {
std::unique_ptr<WebFrameSchedulerImpl>
WebViewSchedulerImpl::CreateWebFrameSchedulerImpl(
base::trace_event::BlameContext* blame_context) {
base::trace_event::BlameContext* blame_context,
WebFrameScheduler::FrameType frame_type) {
MaybeInitializeBackgroundCPUTimeBudgetPool();
std::unique_ptr<WebFrameSchedulerImpl> frame_scheduler(
new WebFrameSchedulerImpl(renderer_scheduler_, this, blame_context));
new WebFrameSchedulerImpl(renderer_scheduler_, this, blame_context,
frame_type));
frame_scheduler->SetPageVisible(page_visible_);
frame_schedulers_.insert(frame_scheduler.get());
return frame_scheduler;
}
std::unique_ptr<blink::WebFrameScheduler>
WebViewSchedulerImpl::CreateFrameScheduler(blink::BlameContext* blame_context) {
return CreateWebFrameSchedulerImpl(blame_context);
WebViewSchedulerImpl::CreateFrameScheduler(
blink::BlameContext* blame_context,
WebFrameScheduler::FrameType frame_type) {
return CreateWebFrameSchedulerImpl(blame_context, frame_type);
}
void WebViewSchedulerImpl::Unregister(WebFrameSchedulerImpl* frame_scheduler) {
......
......@@ -45,7 +45,8 @@ class PLATFORM_EXPORT WebViewSchedulerImpl : public WebViewScheduler {
// WebViewScheduler implementation:
void SetPageVisible(bool page_visible) override;
std::unique_ptr<WebFrameScheduler> CreateFrameScheduler(
BlameContext* blame_context) override;
BlameContext* blame_context,
WebFrameScheduler::FrameType frame_type) override;
void EnableVirtualTime() override;
void DisableVirtualTimeForTesting() override;
bool VirtualTimeAllowedToAdvance() const override;
......@@ -62,7 +63,8 @@ class PLATFORM_EXPORT WebViewSchedulerImpl : public WebViewScheduler {
virtual void ReportIntervention(const std::string& message);
std::unique_ptr<WebFrameSchedulerImpl> CreateWebFrameSchedulerImpl(
base::trace_event::BlameContext* blame_context);
base::trace_event::BlameContext* blame_context,
WebFrameScheduler::FrameType frame_type);
void DidStartLoading(unsigned long identifier);
void DidStopLoading(unsigned long identifier);
......
......@@ -46,8 +46,8 @@ class WebViewSchedulerImplTest : public ::testing::Test {
web_view_scheduler_.reset(
new WebViewSchedulerImpl(nullptr, nullptr, scheduler_.get(),
DisableBackgroundTimerThrottling()));
web_frame_scheduler_ =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr);
web_frame_scheduler_ = web_view_scheduler_->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
}
void TearDown() override {
......@@ -69,16 +69,20 @@ class WebViewSchedulerImplTest : public ::testing::Test {
TEST_F(WebViewSchedulerImplTest, TestDestructionOfFrameSchedulersBefore) {
std::unique_ptr<blink::WebFrameScheduler> frame1(
web_view_scheduler_->CreateFrameScheduler(nullptr));
web_view_scheduler_->CreateFrameScheduler(
nullptr, WebFrameScheduler::FrameType::kSubframe));
std::unique_ptr<blink::WebFrameScheduler> frame2(
web_view_scheduler_->CreateFrameScheduler(nullptr));
web_view_scheduler_->CreateFrameScheduler(
nullptr, WebFrameScheduler::FrameType::kSubframe));
}
TEST_F(WebViewSchedulerImplTest, TestDestructionOfFrameSchedulersAfter) {
std::unique_ptr<blink::WebFrameScheduler> frame1(
web_view_scheduler_->CreateFrameScheduler(nullptr));
web_view_scheduler_->CreateFrameScheduler(
nullptr, WebFrameScheduler::FrameType::kSubframe));
std::unique_ptr<blink::WebFrameScheduler> frame2(
web_view_scheduler_->CreateFrameScheduler(nullptr));
web_view_scheduler_->CreateFrameScheduler(
nullptr, WebFrameScheduler::FrameType::kSubframe));
web_view_scheduler_.reset();
}
......@@ -159,7 +163,8 @@ TEST_F(WebViewSchedulerImplTest, RepeatingTimers_OneBackgroundOneForeground) {
std::unique_ptr<WebViewSchedulerImpl> web_view_scheduler2(
new WebViewSchedulerImpl(nullptr, nullptr, scheduler_.get(), false));
std::unique_ptr<WebFrameSchedulerImpl> web_frame_scheduler2 =
web_view_scheduler2->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler2->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
web_view_scheduler_->SetPageVisible(true);
web_view_scheduler2->SetPageVisible(false);
......@@ -415,7 +420,8 @@ TEST_F(WebViewSchedulerImplTest, VirtualTimeSettings_NewWebFrameScheduler) {
web_view_scheduler_->EnableVirtualTime();
std::unique_ptr<WebFrameSchedulerImpl> web_frame_scheduler =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler_->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
web_frame_scheduler->ThrottleableTaskRunner()->PostDelayedTask(
BLINK_FROM_HERE, WTF::Bind(&RunOrderTask, 1, WTF::Unretained(&run_order)),
......@@ -442,7 +448,10 @@ WTF::Closure MakeDeletionTask(T* obj) {
TEST_F(WebViewSchedulerImplTest, DeleteWebFrameSchedulers_InTask) {
for (int i = 0; i < 10; i++) {
WebFrameSchedulerImpl* web_frame_scheduler =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr).release();
web_view_scheduler_
->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe)
.release();
web_frame_scheduler->ThrottleableTaskRunner()->PostDelayedTask(
BLINK_FROM_HERE, MakeDeletionTask(web_frame_scheduler),
TimeDelta::FromMilliseconds(1));
......@@ -460,7 +469,10 @@ TEST_F(WebViewSchedulerImplTest, DeleteThrottledQueue_InTask) {
web_view_scheduler_->SetPageVisible(false);
WebFrameSchedulerImpl* web_frame_scheduler =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr).release();
web_view_scheduler_
->CreateWebFrameSchedulerImpl(nullptr,
WebFrameScheduler::FrameType::kSubframe)
.release();
RefPtr<blink::WebTaskRunner> timer_task_runner =
web_frame_scheduler->ThrottleableTaskRunner();
......@@ -578,9 +590,11 @@ TEST_F(WebViewSchedulerImplTest, ProvisionalLoads) {
EXPECT_TRUE(web_view_scheduler_->VirtualTimeAllowedToAdvance());
std::unique_ptr<WebFrameSchedulerImpl> frame1 =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler_->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
std::unique_ptr<WebFrameSchedulerImpl> frame2 =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler_->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
// Provisional loads are refcounted.
web_view_scheduler_->DidBeginProvisionalLoad(frame1.get());
......@@ -605,7 +619,8 @@ TEST_F(WebViewSchedulerImplTest, WillNavigateBackForwardSoon) {
EXPECT_TRUE(web_view_scheduler_->VirtualTimeAllowedToAdvance());
std::unique_ptr<WebFrameSchedulerImpl> frame =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler_->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
web_view_scheduler_->WillNavigateBackForwardSoon(frame.get());
EXPECT_FALSE(web_view_scheduler_->VirtualTimeAllowedToAdvance());
......@@ -621,7 +636,8 @@ TEST_F(WebViewSchedulerImplTest, PauseTimersWhileVirtualTimeIsPaused) {
std::vector<int> run_order;
std::unique_ptr<WebFrameSchedulerImpl> web_frame_scheduler =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler_->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
web_view_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::PAUSE);
web_view_scheduler_->EnableVirtualTime();
......@@ -737,8 +753,8 @@ TEST_F(WebViewSchedulerImplTest, BackgroundTimerThrottling) {
new WebViewSchedulerImpl(nullptr, nullptr, scheduler_.get(), false));
std::vector<base::TimeTicks> run_times;
web_frame_scheduler_ =
web_view_scheduler_->CreateWebFrameSchedulerImpl(nullptr);
web_frame_scheduler_ = web_view_scheduler_->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
web_view_scheduler_->SetPageVisible(true);
mock_task_runner_->RunUntilTime(base::TimeTicks() +
......@@ -807,9 +823,11 @@ TEST_F(WebViewSchedulerImplTest, OpenWebSocketExemptsFromBudgetThrottling) {
std::vector<base::TimeTicks> run_times;
std::unique_ptr<WebFrameSchedulerImpl> web_frame_scheduler1 =
web_view_scheduler->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
std::unique_ptr<WebFrameSchedulerImpl> web_frame_scheduler2 =
web_view_scheduler->CreateWebFrameSchedulerImpl(nullptr);
web_view_scheduler->CreateWebFrameSchedulerImpl(
nullptr, WebFrameScheduler::FrameType::kSubframe);
web_view_scheduler->SetPageVisible(false);
......
......@@ -28,6 +28,9 @@ class FakeWebFrameScheduler : public WebFrameScheduler {
void SetPaused(bool) override {}
void SetCrossOrigin(bool) override {}
bool IsCrossOrigin() const override { return false; }
WebFrameScheduler::FrameType GetFrameType() const override {
return WebFrameScheduler::FrameType::kSubframe;
}
RefPtr<WebTaskRunner> ThrottleableTaskRunner() override { return nullptr; }
RefPtr<WebTaskRunner> DeferrableTaskRunner() override { return nullptr; }
RefPtr<WebTaskRunner> PausableTaskRunner() override { return nullptr; }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment