diff --git a/third_party/blink/public/platform/web_navigation_body_loader.h b/third_party/blink/public/platform/web_navigation_body_loader.h index 5db2104b6f3af8..68628252a22b09 100644 --- a/third_party/blink/public/platform/web_navigation_body_loader.h +++ b/third_party/blink/public/platform/web_navigation_body_loader.h @@ -14,6 +14,7 @@ #include "third_party/blink/public/platform/web_common.h" #include "third_party/blink/public/platform/web_loader_freeze_mode.h" #include "third_party/blink/public/platform/web_url_error.h" +#include "third_party/blink/renderer/platform/wtf/functional.h" namespace blink { @@ -54,6 +55,16 @@ class BLINK_EXPORT WebNavigationBodyLoader { int64_t total_decoded_body_length, bool should_report_corb_blocking, const absl::optional& error) = 0; + + // The client can return a ProcessBackgroundDataCallback which will be + // called on a background thread with the decoded data. The returned + // callback will be called on a background thread with the same decoded data + // which will be given to DecodedBodyDataReceived(). + using ProcessBackgroundDataCallback = + WTF::CrossThreadRepeatingFunction; + virtual ProcessBackgroundDataCallback TakeProcessBackgroundDataCallback() { + return ProcessBackgroundDataCallback(); + } }; // This method fills navigation params related to the navigation request, diff --git a/third_party/blink/renderer/core/dom/document_encoding_data.h b/third_party/blink/renderer/core/dom/document_encoding_data.h index cebf5a9d3fb590..f7608637579124 100644 --- a/third_party/blink/renderer/core/dom/document_encoding_data.h +++ b/third_party/blink/renderer/core/dom/document_encoding_data.h @@ -31,6 +31,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_DOCUMENT_ENCODING_DATA_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_DOCUMENT_ENCODING_DATA_H_ +#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" @@ -38,7 +39,7 @@ namespace blink { class TextResourceDecoder; struct WebEncodingData; -class DocumentEncodingData { +class CORE_EXPORT DocumentEncodingData { DISALLOW_NEW(); public: diff --git a/third_party/blink/renderer/core/dom/document_parser.h b/third_party/blink/renderer/core/dom/document_parser.h index 3745b278a7a0b9..6aec948ee67e0e 100644 --- a/third_party/blink/renderer/core/dom/document_parser.h +++ b/third_party/blink/renderer/core/dom/document_parser.h @@ -25,12 +25,14 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_DOCUMENT_PARSER_H_ #include +#include "base/callback.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/document_encoding_data.h" #include "third_party/blink/renderer/platform/bindings/name_client.h" #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/functional.h" namespace blink { @@ -63,6 +65,11 @@ class CORE_EXPORT DocumentParser : public GarbageCollected, virtual void SetHasAppendedData() {} virtual void AppendDecodedData(const String& data, const DocumentEncodingData& encoding_data) {} + using BackgroundScanCallback = + WTF::CrossThreadRepeatingFunction; + virtual BackgroundScanCallback TakeBackgroundScanCallback() { + return BackgroundScanCallback(); + } // FIXME: append() should be private, but DocumentLoader and DOMPatchSupport // uses it for now. diff --git a/third_party/blink/renderer/core/dom/scriptable_document_parser.cc b/third_party/blink/renderer/core/dom/scriptable_document_parser.cc index 850bc1bf09aefa..8c8ac80ed797be 100644 --- a/third_party/blink/renderer/core/dom/scriptable_document_parser.cc +++ b/third_party/blink/renderer/core/dom/scriptable_document_parser.cc @@ -67,6 +67,12 @@ InlineScriptStreamer* ScriptableDocumentParser::TakeInlineScriptStreamer( return nullptr; } +bool ScriptableDocumentParser::HasInlineScriptStreamerForTesting( + const String& source) { + base::AutoLock lock(streamers_lock_); + return inline_script_streamers_.Contains(source); +} + void ScriptableDocumentParser::AddCSSTokenizer( const String& source, std::unique_ptr tokenizer) { diff --git a/third_party/blink/renderer/core/dom/scriptable_document_parser.h b/third_party/blink/renderer/core/dom/scriptable_document_parser.h index 535337734755b3..0f90ee5ce60be2 100644 --- a/third_party/blink/renderer/core/dom/scriptable_document_parser.h +++ b/third_party/blink/renderer/core/dom/scriptable_document_parser.h @@ -78,6 +78,7 @@ class CORE_EXPORT ScriptableDocumentParser : public DecodedDataDocumentParser { // The returned streamer is guaranteed to be correct for script text that // matches the passed in |source|. InlineScriptStreamer* TakeInlineScriptStreamer(const String& source); + bool HasInlineScriptStreamerForTesting(const String& source); // Adds a tokenizer for |source| which can be later retrieved with // TakeCSSTokenizer(). This may be called on any thread. diff --git a/third_party/blink/renderer/core/html/parser/background_html_scanner_test.cc b/third_party/blink/renderer/core/html/parser/background_html_scanner_test.cc index 183873c885e27c..56c25fe4dc270a 100644 --- a/third_party/blink/renderer/core/html/parser/background_html_scanner_test.cc +++ b/third_party/blink/renderer/core/html/parser/background_html_scanner_test.cc @@ -114,10 +114,10 @@ TEST_F(BackgroundHTMLScannerTest, InsideHTMLPreloadScanner) { .task_runner = task_runner_, .min_size = 0u, .enabled = true}, /*pretokenize_css_params=*/ OptimizationParams{ - .task_runner = task_runner_, .min_size = 0u, .enabled = true})); - preload_scanner.ScanInBackground( - "", GetDocument().ValidBaseElementURL(), + .task_runner = task_runner_, .min_size = 0u, .enabled = true}), CrossThreadBindRepeating([](std::unique_ptr) {})); + preload_scanner.ScanInBackground("", + GetDocument().ValidBaseElementURL()); FlushTaskRunner(); EXPECT_NE(parser->TakeInlineScriptStreamer("foo"), nullptr); } diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.cc b/third_party/blink/renderer/core/html/parser/html_document_parser.cc index 58d28da079f942..2d07a994dcd606 100644 --- a/third_party/blink/renderer/core/html/parser/html_document_parser.cc +++ b/third_party/blink/renderer/core/html/parser/html_document_parser.cc @@ -90,11 +90,19 @@ class EndIfDelayedForbiddenScope; class ShouldCompleteScope; class AttemptToEndForbiddenScope; -bool ThreadedPreloadScannerEnabled() { +enum class FeatureResetMode { + kUseCached, + kResetForTesting, +}; + +bool ThreadedPreloadScannerEnabled( + FeatureResetMode reset_mode = FeatureResetMode::kUseCached) { // Cache the feature value since checking for each parser regresses some micro // benchmarks. - static const bool kEnabled = + static bool kEnabled = base::FeatureList::IsEnabled(features::kThreadedPreloadScanner); + if (reset_mode == FeatureResetMode::kResetForTesting) + kEnabled = base::FeatureList::IsEnabled(features::kThreadedPreloadScanner); return kEnabled; } @@ -106,11 +114,14 @@ bool TimedParserBudgetEnabled() { return kEnabled; } -bool PrecompileInlineScriptsEnabled() { +bool PrecompileInlineScriptsEnabled( + FeatureResetMode reset_mode = FeatureResetMode::kUseCached) { // Cache the feature value since checking for each parser regresses some micro // benchmarks. - static const bool kEnabled = + static bool kEnabled = base::FeatureList::IsEnabled(features::kPrecompileInlineScripts); + if (reset_mode == FeatureResetMode::kResetForTesting) + kEnabled = base::FeatureList::IsEnabled(features::kPrecompileInlineScripts); return kEnabled; } @@ -627,7 +638,7 @@ void HTMLDocumentParser::Detach() { preload_scanner_.reset(); insertion_preload_scanner_.reset(); background_script_scanner_.Reset(); - background_scanner_.Reset(); + background_scanner_.reset(); // Oilpan: HTMLTokenProducer may allocate a fair amount of memory. Destroy // it to ensure that memory is released. token_producer_.reset(); @@ -1364,6 +1375,20 @@ void HTMLDocumentParser::NotifyNoRemainingAsyncScripts() { AttemptToRunDeferredScriptsAndEnd(); } +// static +void HTMLDocumentParser::ResetCachedFeaturesForTesting() { + ThreadedPreloadScannerEnabled(FeatureResetMode::kResetForTesting); + PrecompileInlineScriptsEnabled(FeatureResetMode::kResetForTesting); +} + +// static +void HTMLDocumentParser::FlushPreloadScannerThreadForTesting() { + base::RunLoop run_loop; + GetPreloadScannerThread()->GetTaskRunner()->PostTask(FROM_HERE, + run_loop.QuitClosure()); + run_loop.Run(); +} + void HTMLDocumentParser::ExecuteScriptsWaitingForResources() { TRACE_EVENT0("blink", "HTMLDocumentParser::ExecuteScriptsWaitingForResources"); @@ -1567,6 +1592,13 @@ std::string HTMLDocumentParser::GetPreloadHistogramSuffix() { have_seen_first_byte ? ".NonInitial" : ".Initial"}); } +DocumentParser::BackgroundScanCallback +HTMLDocumentParser::TakeBackgroundScanCallback() { + if (!background_scan_fn_) + return BackgroundScanCallback(); + return CrossThreadBindRepeating(std::move(background_scan_fn_), KURL()); +} + void HTMLDocumentParser::ScanInBackground(const String& source) { if (task_runner_state_->IsSynchronous() || !GetDocument()->Url().IsValid()) return; @@ -1580,17 +1612,31 @@ void HTMLDocumentParser::ScanInBackground(const String& source) { // is already available. DCHECK(!preload_scanner_); if (!background_scanner_) { + // See comment on NavigationBodyLoader::StartLoadingBodyInBackground() for + // details on how the preload scanner flow works when the body data is + // being loaded in the background. background_scanner_ = HTMLPreloadScanner::CreateBackground( - this, options_, GetPreloadScannerThread()->GetTaskRunner()); + this, options_, GetPreloadScannerThread()->GetTaskRunner(), + CrossThreadBindRepeating( + &HTMLDocumentParser::AddPreloadDataOnBackgroundThread, + WrapCrossThreadWeakPersistent(this), + GetDocument()->GetTaskRunner(TaskType::kInternalLoading))); + + background_scan_fn_ = CrossThreadBindRepeating( + [](base::WeakPtr scanner, + scoped_refptr task_runner, + const KURL& url, const String& data) { + PostCrossThreadTask( + *task_runner, FROM_HERE, + CrossThreadBindOnce(&HTMLPreloadScanner::ScanInBackground, + std::move(scanner), data, url)); + }, + background_scanner_->AsWeakPtr(), + GetPreloadScannerThread()->GetTaskRunner()); } - background_scanner_.AsyncCall(&HTMLPreloadScanner::ScanInBackground) - .WithArgs( - source, GetDocument()->ValidBaseElementURL(), - CrossThreadBindRepeating( - &HTMLDocumentParser::AddPreloadDataOnBackgroundThread, - WrapCrossThreadPersistent(this), - GetDocument()->GetTaskRunner(TaskType::kInternalLoading))); + if (background_scan_fn_) + background_scan_fn_.Run(GetDocument()->ValidBaseElementURL(), source); return; } diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.h b/third_party/blink/renderer/core/html/parser/html_document_parser.h index 3eb88c5e3f72f4..f00f7eed5f6520 100644 --- a/third_party/blink/renderer/core/html/parser/html_document_parser.h +++ b/third_party/blink/renderer/core/html/parser/html_document_parser.h @@ -126,6 +126,9 @@ class CORE_EXPORT HTMLDocumentParser : public ScriptableDocumentParser, void SetDecoder(std::unique_ptr) final; void NotifyNoRemainingAsyncScripts() final; + static void ResetCachedFeaturesForTesting(); + static void FlushPreloadScannerThreadForTesting(); + protected: HTMLDocumentParser(HTMLDocument&, ParserSynchronizationPolicy, @@ -167,6 +170,7 @@ class CORE_EXPORT HTMLDocumentParser : public ScriptableDocumentParser, void DocumentElementAvailable() override; void CommitPreloadedData() override; void FlushPendingPreloads() override; + BackgroundScanCallback TakeBackgroundScanCallback() override; // HTMLParserScriptRunnerHost void NotifyScriptLoaded() final; @@ -250,7 +254,10 @@ class CORE_EXPORT HTMLDocumentParser : public ScriptableDocumentParser, // A scanner used only for input provided to the insert() method. std::unique_ptr insertion_preload_scanner_; WTF::SequenceBound background_script_scanner_; - WTF::SequenceBound background_scanner_; + HTMLPreloadScanner::BackgroundPtr background_scanner_; + using BackgroundScanFn = + WTF::CrossThreadRepeatingFunction; + BackgroundScanFn background_scan_fn_; scoped_refptr loading_task_runner_; diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser_test.cc b/third_party/blink/renderer/core/html/parser/html_document_parser_test.cc index e626e1a6525b18..f1f6f118f4b1f7 100644 --- a/third_party/blink/renderer/core/html/parser/html_document_parser_test.cc +++ b/third_party/blink/renderer/core/html/parser/html_document_parser_test.cc @@ -189,6 +189,62 @@ TEST_P(HTMLDocumentParserTest, AppendNoPrefetch) { static_cast(parser)->StopParsing(); } +class HTMLDocumentParserThreadedPreloadScannerTest : public PageTestBase { + protected: + HTMLDocumentParserThreadedPreloadScannerTest() { + scoped_feature_list_.InitWithFeatures( + {features::kThreadedPreloadScanner, features::kPrecompileInlineScripts}, + {}); + HTMLDocumentParser::ResetCachedFeaturesForTesting(); + } + + ~HTMLDocumentParserThreadedPreloadScannerTest() override { + scoped_feature_list_.Reset(); + HTMLDocumentParser::ResetCachedFeaturesForTesting(); + } + + void SetUp() override { + PageTestBase::SetUp(); + GetDocument().SetURL(KURL("https://example.test")); + } + + HTMLDocumentParser* CreateParser(HTMLDocument& document) { + return MakeGarbageCollected(document, + kAllowDeferredParsing); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(HTMLDocumentParserThreadedPreloadScannerTest, + TakeBackgroundScanCallback) { + auto& document = To(GetDocument()); + HTMLDocumentParser* parser = CreateParser(document); + ScopedParserDetacher detacher(parser); + + // First append "foo" script which should be passed through to the scanner. + parser->AppendDecodedData("", DocumentEncodingData()); + HTMLDocumentParser::FlushPreloadScannerThreadForTesting(); + EXPECT_TRUE(parser->HasInlineScriptStreamerForTesting("foo")); + + // Now take the callback. + auto callback = + static_cast(parser)->TakeBackgroundScanCallback(); + + // Append "bar" script which should not be passed to the scanner. + parser->AppendDecodedData("", DocumentEncodingData()); + HTMLDocumentParser::FlushPreloadScannerThreadForTesting(); + EXPECT_FALSE(parser->HasInlineScriptStreamerForTesting("bar")); + + // Append "baz" script to the callback which should be passed to the scanner. + callback.Run(""); + HTMLDocumentParser::FlushPreloadScannerThreadForTesting(); + EXPECT_TRUE(parser->HasInlineScriptStreamerForTesting("baz")); + + static_cast(parser)->StopParsing(); +} + class HTMLDocumentParserProcessImmediatelyTest : public PageTestBase { protected: void SetUp() override { diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc b/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc index dcc7a2f19ad1e4..04722bdc57a7a1 100644 --- a/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc +++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc @@ -1069,18 +1069,22 @@ std::unique_ptr HTMLPreloadScanner::Create( } // static -WTF::SequenceBound HTMLPreloadScanner::CreateBackground( +HTMLPreloadScanner::BackgroundPtr HTMLPreloadScanner::CreateBackground( HTMLDocumentParser* parser, HTMLParserOptions options, - scoped_refptr task_runner) { + scoped_refptr task_runner, + TakePreloadFn take_preload) { auto* document = parser->GetDocument(); - return WTF::SequenceBound( - std::move(task_runner), std::make_unique(options), - options.priority_hints_origin_trial_enabled, document->Url(), - std::make_unique(document), - MediaValuesCached::MediaValuesCachedData(*document), - TokenPreloadScanner::ScannerType::kMainDocument, - BackgroundHTMLScanner::ScriptTokenScanner::Create(parser)); + return BackgroundPtr( + new HTMLPreloadScanner( + std::make_unique(options), + options.priority_hints_origin_trial_enabled, document->Url(), + std::make_unique(document), + MediaValuesCached::MediaValuesCachedData(*document), + TokenPreloadScanner::ScannerType::kMainDocument, + BackgroundHTMLScanner::ScriptTokenScanner::Create(parser), + std::move(take_preload)), + Deleter{task_runner}); } HTMLPreloadScanner::HTMLPreloadScanner( @@ -1091,14 +1095,16 @@ HTMLPreloadScanner::HTMLPreloadScanner( const MediaValuesCached::MediaValuesCachedData& media_values_cached_data, const TokenPreloadScanner::ScannerType scanner_type, std::unique_ptr - script_token_scanner) + script_token_scanner, + TakePreloadFn take_preload) : scanner_(document_url, std::move(document_parameters), media_values_cached_data, scanner_type, priority_hints_origin_trial_enabled), tokenizer_(std::move(tokenizer)), - script_token_scanner_(std::move(script_token_scanner)) {} + script_token_scanner_(std::move(script_token_scanner)), + take_preload_(std::move(take_preload)) {} HTMLPreloadScanner::~HTMLPreloadScanner() = default; @@ -1107,8 +1113,7 @@ void HTMLPreloadScanner::AppendToEnd(const SegmentedString& source) { } std::unique_ptr HTMLPreloadScanner::Scan( - const KURL& starting_base_element_url, - const TakePreloadFn& take_preload) { + const KURL& starting_base_element_url) { auto pending_data = std::make_unique(); TRACE_EVENT1("blink", "HTMLPreloadScanner::scan", "source_length", @@ -1147,19 +1152,19 @@ std::unique_ptr HTMLPreloadScanner::Scan( return pending_data; } // Incrementally add preloads when scanning in the background. - if (take_preload && !pending_data->requests.empty()) { - take_preload.Run(std::move(pending_data)); + if (take_preload_ && !pending_data->requests.empty()) { + take_preload_.Run(std::move(pending_data)); pending_data = std::make_unique(); } } return pending_data; } -void HTMLPreloadScanner::ScanInBackground(const String& source, - const KURL& document_base_element_url, - const TakePreloadFn& take_preload) { +void HTMLPreloadScanner::ScanInBackground( + const String& source, + const KURL& document_base_element_url) { source_.Append(source); - take_preload.Run(Scan(document_base_element_url, take_preload)); + take_preload_.Run(Scan(document_base_element_url)); } CachedDocumentParameters::CachedDocumentParameters(Document* document) { diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner.h b/third_party/blink/renderer/core/html/parser/html_preload_scanner.h index 35a3d54831554a..816d888a1f8639 100644 --- a/third_party/blink/renderer/core/html/parser/html_preload_scanner.h +++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner.h @@ -31,6 +31,7 @@ #include #include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" #include "services/network/public/cpp/client_hints.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/core_export.h" @@ -157,7 +158,7 @@ class TokenPreloadScanner { PictureData picture_data_; size_t template_count_; std::unique_ptr document_parameters_; - Persistent media_values_; + CrossThreadPersistent media_values_; ScannerType scanner_type_; // TODO(domfarolino): Remove this once Priority Hints is no longer in Origin // Trial (see https://crbug.com/821464). This member exists because @@ -167,7 +168,8 @@ class TokenPreloadScanner { bool priority_hints_origin_trial_enabled_; }; -class CORE_EXPORT HTMLPreloadScanner { +class CORE_EXPORT HTMLPreloadScanner + : public base::SupportsWeakPtr { USING_FAST_MALLOC(HTMLPreloadScanner); public: @@ -177,11 +179,23 @@ class CORE_EXPORT HTMLPreloadScanner { HTMLParserOptions options, TokenPreloadScanner::ScannerType scanner_type); + using TakePreloadFn = WTF::CrossThreadRepeatingFunction)>; + // Creates a HTMLPreloadScanner which will be bound to |task_runner|. - static WTF::SequenceBound CreateBackground( + struct Deleter { + void operator()(const HTMLPreloadScanner* ptr) { + if (ptr) + task_runner_->DeleteSoon(FROM_HERE, ptr); + } + scoped_refptr task_runner_; + }; + using BackgroundPtr = std::unique_ptr; + static BackgroundPtr CreateBackground( HTMLDocumentParser* parser, HTMLParserOptions options, - scoped_refptr task_runner); + scoped_refptr task_runner, + TakePreloadFn take_preload); HTMLPreloadScanner(std::unique_ptr, bool priority_hints_origin_trial_enabled, @@ -190,23 +204,20 @@ class CORE_EXPORT HTMLPreloadScanner { const MediaValuesCached::MediaValuesCachedData&, const TokenPreloadScanner::ScannerType, std::unique_ptr - script_token_scanner); + script_token_scanner, + TakePreloadFn take_preload = TakePreloadFn()); HTMLPreloadScanner(const HTMLPreloadScanner&) = delete; HTMLPreloadScanner& operator=(const HTMLPreloadScanner&) = delete; ~HTMLPreloadScanner(); void AppendToEnd(const SegmentedString&); - using TakePreloadFn = WTF::CrossThreadRepeatingFunction)>; std::unique_ptr Scan( - const KURL& document_base_element_url, - const TakePreloadFn& take_preload = TakePreloadFn()); + const KURL& document_base_element_url); // Scans |source| and calls |take_preload| with the generated preload data. void ScanInBackground(const String& source, - const KURL& document_base_element_url, - const TakePreloadFn& take_preload); + const KURL& document_base_element_url); private: TokenPreloadScanner scanner_; @@ -215,6 +226,7 @@ class CORE_EXPORT HTMLPreloadScanner { std::unique_ptr tokenizer_; std::unique_ptr script_token_scanner_; + TakePreloadFn take_preload_; }; } // namespace blink diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc index 11a93e18d2bf02..f93fcdb42c2b7a 100644 --- a/third_party/blink/renderer/core/loader/document_loader.cc +++ b/third_party/blink/renderer/core/loader/document_loader.cc @@ -147,6 +147,7 @@ #include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" #include "third_party/blink/renderer/platform/weborigin/security_policy.h" +#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" #include "third_party/blink/renderer/platform/wtf/hash_map.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" @@ -1029,6 +1030,17 @@ void DocumentLoader::DecodedBodyDataReceived( BodyDataReceivedImpl(body_data); } +DocumentLoader::ProcessBackgroundDataCallback +DocumentLoader::TakeProcessBackgroundDataCallback() { + auto callback = parser_->TakeBackgroundScanCallback(); + if (!callback) + return ProcessBackgroundDataCallback(); + return CrossThreadBindRepeating( + [](const DocumentParser::BackgroundScanCallback& callback, + const WebString& data) { callback.Run(data); }, + std::move(callback)); +} + void DocumentLoader::BodyDataReceivedImpl(BodyData& data) { TRACE_EVENT0("loading", "DocumentLoader::BodyDataReceived"); base::span encoded_data = data.EncodedData(); diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h index 1e9fc2898e2cbe..26ad99b088f32b 100644 --- a/third_party/blink/renderer/core/loader/document_loader.h +++ b/third_party/blink/renderer/core/loader/document_loader.h @@ -548,6 +548,7 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected, int64_t total_decoded_body_length, bool should_report_corb_blocking, const absl::optional& error) override; + ProcessBackgroundDataCallback TakeProcessBackgroundDataCallback() override; void ApplyClientHintsConfig( const WebVector& diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.cc index a94e20239f0191..82f2357aac2819 100644 --- a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.cc +++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.cc @@ -8,6 +8,7 @@ #include "base/metrics/field_trial_params.h" #include "base/metrics/histogram_functions.h" #include "base/numerics/safe_conversions.h" +#include "base/run_loop.h" #include "base/strings/strcat.h" #include "base/trace_event/trace_event.h" #include "services/network/public/cpp/features.h" @@ -34,6 +35,12 @@ namespace blink { namespace { +bool ShouldSendDirectlyToPreloadScanner() { + static const base::FeatureParam kSendToScannerParam{ + &features::kThreadedBodyLoader, "send-to-scanner", true}; + return kSendToScannerParam.Get(); +} + // A chunk of data read by the OffThreadBodyReader. This will be created on a // background thread and processed on the main thread. struct DataChunk { @@ -130,11 +137,36 @@ class NavigationBodyLoader::OffThreadBodyReader : public BodyReader { return std::move(data_chunks_); } + void StoreProcessBackgroundDataCallback(Client* client) { + DCHECK(IsMainThread()); + if (background_callback_set_) + return; + + auto callback = client->TakeProcessBackgroundDataCallback(); + if (!callback) + return; + + background_callback_set_ = true; + + base::AutoLock lock(lock_); + process_background_data_callback_ = std::move(callback); + + // Process any existing data to make sure we don't miss any. + for (const auto& chunk : data_chunks_) + process_background_data_callback_.Run(chunk.decoded_data); + } + void Delete() const { DCHECK(IsMainThread()); reader_task_runner_->DeleteSoon(FROM_HERE, this); } + void FlushForTesting() { + base::RunLoop run_loop; + reader_task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure()); + run_loop.Run(); + } + private: // BodyReader: bool ShouldContinueReading() override { @@ -185,6 +217,9 @@ class NavigationBodyLoader::OffThreadBodyReader : public BodyReader { bool post_task; { base::AutoLock lock(lock_); + if (decoded_data && process_background_data_callback_) + process_background_data_callback_.Run(decoded_data); + // If |data_chunks_| is not empty, there is already a task posted which // will consume the data, so no need to post another one. post_task = data_chunks_.empty(); @@ -214,6 +249,11 @@ class NavigationBodyLoader::OffThreadBodyReader : public BodyReader { bool has_seen_end_of_data_ = false; base::Lock lock_; + // This bool is used on the main thread to avoid locking when the callback has + // already been set. + bool background_callback_set_ = false; + Client::ProcessBackgroundDataCallback process_background_data_callback_ + GUARDED_BY(lock_); std::vector data_chunks_ GUARDED_BY(lock_); }; @@ -269,7 +309,9 @@ NavigationBodyLoader::NavigationBodyLoader( task_runner_), resource_load_info_notifier_wrapper_( std::move(resource_load_info_notifier_wrapper)), - original_url_(original_url) {} + original_url_(original_url), + should_send_directly_to_preload_scanner_( + ShouldSendDirectlyToPreloadScanner()) {} NavigationBodyLoader::~NavigationBodyLoader() { if (!has_received_completion_ || !has_seen_end_of_data_) { @@ -360,6 +402,12 @@ void NavigationBodyLoader::StartLoadingBodyInBackground( should_keep_encoded_data)); } +void NavigationBodyLoader::FlushOffThreadBodyReaderForTesting() { + if (!off_thread_body_reader_) + return; + off_thread_body_reader_->FlushForTesting(); +} + void NavigationBodyLoader::BindURLLoaderAndContinue() { url_loader_.Bind(std::move(endpoints_->url_loader), task_runner_); url_loader_client_receiver_.Bind(std::move(endpoints_->url_loader_client), @@ -419,6 +467,9 @@ void NavigationBodyLoader::ProcessOffThreadData() { } } NotifyCompletionIfAppropriate(); + + if (weak_self && should_send_directly_to_preload_scanner_) + off_thread_body_reader_->StoreProcessBackgroundDataCallback(client_); } void NavigationBodyLoader::ReadFromDataPipe() { diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h index 72ec6573098db4..a100fce277c4e5 100644 --- a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h +++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h @@ -62,9 +62,25 @@ class PLATFORM_EXPORT NavigationBodyLoader // callbacks will not be called until StartLoadingBody() is called. If // |should_keep_encoded_data| is true, the original data will be copied from // the background thread and passed to DecodedBodyDataReceived(). + // + // If the preload scanner is being used in the parser, the flow of data to the + // scanner will go like this: + // 1. NavigationBodyLoader calls DecodedBodyDataReceived(), which will + // end up getting forwarded to HTMLDocumentParser::Append(). + // 2. HTMLDocumentParser::Append() will cause the background preload + // scanner to be created and scan the initial data passed to Append(). + // 3. NavigationBodyLoader calls TakeProcessBackgroundDataCallback() + // which tells HTMLDocumentParser to stop sending data to the + // preload scanner in Append(). + // 4. NavigationBodyLoader will pass data directly to the callback + // taken from TakeProcessBackgroundDataCallback(), which avoids + // hitting the main thread at all. HTMLDocumentParser will still + // receive data through Append() calls. void StartLoadingBodyInBackground(std::unique_ptr decoder, bool should_keep_encoded_data); + void FlushOffThreadBodyReaderForTesting(); + private: // The loading flow is outlined below. NavigationBodyLoader can be safely // deleted at any moment, and it will record cancelation stats, but will not @@ -176,6 +192,7 @@ class PLATFORM_EXPORT NavigationBodyLoader using OffThreadBodyReaderPtr = std::unique_ptr; OffThreadBodyReaderPtr off_thread_body_reader_; + bool should_send_directly_to_preload_scanner_ = false; base::WeakPtrFactory weak_factory_{this}; }; diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader_unittest.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader_unittest.cc index 75c6d1b2494c86..e18fde5797eb48 100644 --- a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader_unittest.cc +++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader_unittest.cc @@ -27,6 +27,7 @@ #include "third_party/blink/renderer/platform/loader/fetch/code_cache_host.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" #include "third_party/blink/renderer/platform/weborigin/referrer.h" +#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" namespace blink { @@ -143,6 +144,10 @@ class NavigationBodyLoaderTest : public ::testing::Test, run_loop_->Quit(); } + ProcessBackgroundDataCallback TakeProcessBackgroundDataCallback() override { + return std::move(process_background_data_callback_); + } + void TakeActions() { if (!buffer_to_write_.empty()) { std::string buffer = buffer_to_write_; @@ -227,6 +232,7 @@ class NavigationBodyLoaderTest : public ::testing::Test, bool destroy_loader_ = false; std::string data_received_; absl::optional error_; + ProcessBackgroundDataCallback process_background_data_callback_; }; TEST_F(NavigationBodyLoaderTest, SetDefersBeforeStart) { @@ -246,6 +252,41 @@ TEST_F(NavigationBodyLoaderTest, DecodedDataReceived) { EXPECT_EQ("HELLO", TakeDataReceived()); } +TEST_F(NavigationBodyLoaderTest, ProcessBackgroundData) { + CreateBodyLoader(); + StartLoadingInBackground(); + // First flush data to the off thread reader. The background data callback + // should not see this since it is not set yet. + Write("hello"); + To(loader_.get())->FlushOffThreadBodyReaderForTesting(); + + String background_data = ""; + process_background_data_callback_ = CrossThreadBindRepeating( + [](String* background_data, const WebString& data) { + *background_data = *background_data + String(data); + }, + CrossThreadUnretained(&background_data)); + + ExpectDecodedDataReceived(); + StartLoading(); + Wait(); + EXPECT_EQ("HELLO", TakeDataReceived()); + EXPECT_EQ("", background_data); + + // Now write more data with the background data callback set. + ExpectDecodedDataReceived(); + Write("hello2"); + Wait(); + EXPECT_EQ("HELLO2", TakeDataReceived()); + EXPECT_EQ("HELLO2", background_data); + + ExpectDecodedDataReceived(); + Write("hello3"); + Wait(); + EXPECT_EQ("HELLO3", TakeDataReceived()); + EXPECT_EQ("HELLO2HELLO3", background_data); +} + TEST_F(NavigationBodyLoaderTest, DataReceived) { CreateBodyLoader(); StartLoading();