diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-08-30 10:22:43 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-08-30 12:36:28 +0000 |
commit | 271a6c3487a14599023a9106329505597638d793 (patch) | |
tree | e040d58ffc86c1480b79ca8528020ca9ec919bf8 /chromium/third_party/blink/renderer/core/script | |
parent | 7b2ffa587235a47d4094787d72f38102089f402a (diff) | |
download | qtwebengine-chromium-271a6c3487a14599023a9106329505597638d793.tar.gz |
BASELINE: Update Chromium to 77.0.3865.59
Change-Id: I1e89a5f3b009a9519a6705102ad65c92fe736f21
Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/script')
77 files changed, 3327 insertions, 2345 deletions
diff --git a/chromium/third_party/blink/renderer/core/script/BUILD.gn b/chromium/third_party/blink/renderer/core/script/BUILD.gn index 1346b025962..ac7886ede65 100644 --- a/chromium/third_party/blink/renderer/core/script/BUILD.gn +++ b/chromium/third_party/blink/renderer/core/script/BUILD.gn @@ -58,6 +58,8 @@ blink_core_sources("script") { "script_runner.cc", "script_runner.h", "script_scheduling_type.h", + "value_wrapper_synthetic_module_script.cc", + "value_wrapper_synthetic_module_script.h", "worker_modulator_impl.cc", "worker_modulator_impl.h", "worklet_modulator_impl.cc", @@ -73,3 +75,24 @@ blink_core_sources("script") { jumbo_excluded_sources = [ "modulator.cc" ] # https://crbug.com/716395 } + +copy("layered_apis_elements_virtual_scroller_js") { + testonly = true + + sources = [ + "resources/layered_api/elements/virtual-scroller/find-element.mjs", + "resources/layered_api/elements/virtual-scroller/sets.mjs", + "resources/layered_api/elements/virtual-scroller/visibility-manager.mjs", + ] + + outputs = [ + "{{source_gen_dir}}/{{source_file_part}}", + ] +} + +group("js_files_for_web_tests") { + testonly = true + data_deps = [ + ":layered_apis_elements_virtual_scroller_js", + ] +} diff --git a/chromium/third_party/blink/renderer/core/script/classic_pending_script.cc b/chromium/third_party/blink/renderer/core/script/classic_pending_script.cc index 3bd0f996731..67703f93bfd 100644 --- a/chromium/third_party/blink/renderer/core/script/classic_pending_script.cc +++ b/chromium/third_party/blink/renderer/core/script/classic_pending_script.cc @@ -18,10 +18,11 @@ #include "third_party/blink/renderer/core/script/document_write_intervention.h" #include "third_party/blink/renderer/core/script/script_loader.h" #include "third_party/blink/renderer/platform/bindings/script_state.h" -#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/instrumentation/histogram.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/loader/allowed_by_nosniff.h" #include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h" +#include "third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h" #include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h" #include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_client.h" @@ -249,10 +250,13 @@ void ClassicPendingScript::NotifyFinished(Resource* resource) { // It is possible to get back a script resource with integrity metadata // for a request with an empty integrity attribute. In that case, the - // integrity check should be skipped, so this check ensures that the - // integrity attribute isn't empty in addition to checking if the - // resource has empty integrity metadata. - if (!element->IntegrityAttributeValue().IsEmpty()) { + // integrity check should be skipped, as the integrity may not have been + // "meant" for this specific request. If the resource is being served from + // the preload cache however, we know any associated integrity metadata and + // checks were destined for this request, so we cannot skip the integrity + // check. + if (!element->IntegrityAttributeValue().IsEmpty() || + GetResource()->IsLinkPreload()) { integrity_failure_ = GetResource()->IntegrityDisposition() != ResourceIntegrityDisposition::kPassed; } @@ -276,7 +280,7 @@ void ClassicPendingScript::NotifyFinished(Resource* resource) { AdvanceReadyState(error_occurred ? kErrorOccurred : kReady); } -void ClassicPendingScript::Trace(blink::Visitor* visitor) { +void ClassicPendingScript::Trace(Visitor* visitor) { ResourceClient::Trace(visitor); MemoryPressureListener::Trace(visitor); PendingScript::Trace(visitor); @@ -342,7 +346,7 @@ ClassicScript* ClassicPendingScript::GetSource(const KURL& document_url) const { auto* fetcher = GetElement()->GetDocument().ContextDocument()->Fetcher(); // If the MIME check fails, which is considered as load failure. if (!AllowedByNosniff::MimeTypeAsScript( - fetcher->Context(), &fetcher->GetConsoleLogger(), + fetcher->GetUseCounter(), &fetcher->GetConsoleLogger(), resource->GetResponse(), AllowedByNosniff::MimeTypeCheck::kLax)) { return nullptr; } diff --git a/chromium/third_party/blink/renderer/core/script/classic_pending_script.h b/chromium/third_party/blink/renderer/core/script/classic_pending_script.h index c9e135de659..ca495ebdd16 100644 --- a/chromium/third_party/blink/renderer/core/script/classic_pending_script.h +++ b/chromium/third_party/blink/renderer/core/script/classic_pending_script.h @@ -10,8 +10,8 @@ #include "third_party/blink/renderer/core/loader/resource/script_resource.h" #include "third_party/blink/renderer/core/script/classic_script.h" #include "third_party/blink/renderer/core/script/pending_script.h" +#include "third_party/blink/renderer/platform/instrumentation/memory_pressure_listener.h" #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" -#include "third_party/blink/renderer/platform/memory_pressure_listener.h" namespace blink { @@ -58,7 +58,7 @@ class CORE_EXPORT ClassicPendingScript final : public PendingScript, void SetStreamer(ScriptStreamer*); void StreamingFinished(); - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; mojom::ScriptType GetScriptType() const override { return mojom::ScriptType::kClassic; diff --git a/chromium/third_party/blink/renderer/core/script/classic_script.cc b/chromium/third_party/blink/renderer/core/script/classic_script.cc index afe840f1bb7..df884115ba2 100644 --- a/chromium/third_party/blink/renderer/core/script/classic_script.cc +++ b/chromium/third_party/blink/renderer/core/script/classic_script.cc @@ -13,7 +13,7 @@ namespace blink { -void ClassicScript::Trace(blink::Visitor* visitor) { +void ClassicScript::Trace(Visitor* visitor) { Script::Trace(visitor); visitor->Trace(script_source_code_); } diff --git a/chromium/third_party/blink/renderer/core/script/classic_script.h b/chromium/third_party/blink/renderer/core/script/classic_script.h index b5da0ea28d7..e64a2118b1e 100644 --- a/chromium/third_party/blink/renderer/core/script/classic_script.h +++ b/chromium/third_party/blink/renderer/core/script/classic_script.h @@ -24,7 +24,7 @@ class CORE_EXPORT ClassicScript final : public Script { script_source_code_(script_source_code), sanitize_script_errors_(sanitize_script_errors) {} - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; const ScriptSourceCode& GetScriptSourceCode() const { return script_source_code_; diff --git a/chromium/third_party/blink/renderer/core/script/document_write_intervention.cc b/chromium/third_party/blink/renderer/core/script/document_write_intervention.cc index fbe4bfc38f9..e4bb75221db 100644 --- a/chromium/third_party/blink/renderer/core/script/document_write_intervention.cc +++ b/chromium/third_party/blink/renderer/core/script/document_write_intervention.cc @@ -4,7 +4,7 @@ #include "third_party/blink/renderer/core/script/document_write_intervention.h" -#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h" +#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h" #include "third_party/blink/public/platform/web_effective_connection_type.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame_client.h" @@ -34,7 +34,7 @@ void EmitWarningMayBeBlocked(const String& url, Document& document) { document.AddConsoleMessage( ConsoleMessage::Create(mojom::ConsoleMessageSource::kJavaScript, mojom::ConsoleMessageLevel::kWarning, message)); - DVLOG(1) << message.Utf8().data(); + DVLOG(1) << message.Utf8(); } void EmitWarningNotBlocked(const String& url, Document& document) { diff --git a/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver.cc b/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver.cc index b8249e5a431..63ffbe076b0 100644 --- a/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver.cc +++ b/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver.cc @@ -27,7 +27,7 @@ class DynamicImportTreeClient final : public ModuleTreeClient { ScriptPromiseResolver* promise_resolver) : url_(url), modulator_(modulator), promise_resolver_(promise_resolver) {} - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: // Implements ModuleTreeClient: @@ -54,31 +54,30 @@ void DynamicImportTreeClient::NotifyModuleTreeLoadFinished( ScriptState::Scope scope(script_state); v8::Isolate* isolate = script_state->GetIsolate(); - // <spec step="2.5">If result is null, then:</spec> + // <spec step="6">If result is null, then:</spec> if (!module_script) { - // <spec step="2.5.1">Let completion be Completion { [[Type]]: throw, + // <spec step="6.1">Let completion be Completion { [[Type]]: throw, // [[Value]]: a new TypeError, [[Target]]: empty }.</spec> v8::Local<v8::Value> error = V8ThrowException::CreateTypeError( isolate, "Failed to fetch dynamically imported module: " + url_.GetString()); - // <spec step="2.5.2">Perform FinishDynamicImport(referencingScriptOrModule, + // <spec step="6.2">Perform FinishDynamicImport(referencingScriptOrModule, // specifier, promiseCapability, completion).</spec> promise_resolver_->Reject(error); - // <spec step="2.5.3">Return.</spec> + // <spec step="6.3">Return.</spec> return; } - // <spec step="2.6">Run the module script result, with the rethrow errors + // <spec step="7">Run the module script result, with the rethrow errors // boolean set to true.</spec> ScriptValue error = modulator_->ExecuteModule( module_script, Modulator::CaptureEvalErrorFlag::kCapture); - // <spec step="2.7">If running the module script throws an exception, - // ...</spec> + // <spec step="8">If running the module script throws an exception, ...</spec> if (!error.IsEmpty()) { - // <spec step="2.7">... then perform + // <spec step="8">... then perform // FinishDynamicImport(referencingScriptOrModule, specifier, // promiseCapability, the thrown exception completion).</spec> // @@ -93,7 +92,7 @@ void DynamicImportTreeClient::NotifyModuleTreeLoadFinished( return; } - // <spec step="2.8">Otherwise, perform + // <spec step="9">Otherwise, perform // FinishDynamicImport(referencingScriptOrModule, specifier, // promiseCapability, NormalCompletion(undefined)).</spec> // @@ -142,7 +141,7 @@ void DynamicImportTreeClient::NotifyModuleTreeLoadFinished( promise_resolver_->Resolve(module_namespace); } -void DynamicImportTreeClient::Trace(blink::Visitor* visitor) { +void DynamicImportTreeClient::Trace(Visitor* visitor) { visitor->Trace(modulator_); visitor->Trace(promise_resolver_); ModuleTreeClient::Trace(visitor); @@ -150,7 +149,7 @@ void DynamicImportTreeClient::Trace(blink::Visitor* visitor) { } // namespace -void DynamicModuleResolver::Trace(blink::Visitor* visitor) { +void DynamicModuleResolver::Trace(Visitor* visitor) { visitor->Trace(modulator_); } @@ -173,13 +172,10 @@ void DynamicModuleResolver::ResolveDynamically( // (in ResolveModuleSpecifier()) and we need to clear the flag before that. modulator_->ClearIsAcquiringImportMaps(); - // <spec step="1">Let referencing script be + // <spec step="4.1">Let referencing script be // referencingScriptOrModule.[[HostDefined]].</spec> - // <spec step="2">Run the following steps in parallel:</spec> - - // <spec step="2.1">Let url be the result of resolving a module specifier - // given referencing script's base URL and specifier.</spec> + // <spec step="4.3">Set base URL to referencing script's base URL.</spec> KURL base_url = referrer_info.BaseURL(); if (base_url.IsNull()) { // ReferrerScriptInfo::BaseURL returns null if it should defer to referrer @@ -187,25 +183,39 @@ void DynamicModuleResolver::ResolveDynamically( base_url = referrer_resource_url; } if (base_url.IsNull()) { - // In some cases, "referencing script" may not exist. Use the document's - // base URL as last resort. - // TODO(kouhei): Revisit this after - // https://github.com/whatwg/html/issues/3295 resolved. + // The case where "referencing script" doesn't exist. + // + // <spec step="1">Let settings object be the current settings object.</spec> + // + // <spec step="2">Let base URL be settings object's API base URL.</spec> base_url = ExecutionContext::From(modulator_->GetScriptState())->BaseURL(); } DCHECK(!base_url.IsNull()); + // <spec step="5">Fetch an import() module script graph given specifier, base + // URL, settings object, and fetch options. Wait until the algorithm + // asynchronously completes with result.</spec> + // + // <specdef label="fetch-an-import()-module-script-graph" + // href="https://html.spec.whatwg.org/C/#fetch-an-import()-module-script-graph"> + + // <spec label="fetch-an-import()-module-script-graph" step="1">Let url be the + // result of resolving a module specifier given base URL and specifier.</spec> KURL url = modulator_->ResolveModuleSpecifier(specifier, base_url); + + // <spec label="fetch-an-import()-module-script-graph" step="2">If url is + // failure, then asynchronously complete this algorithm with null, and abort + // these steps.</spec> if (!url.IsValid()) { - // <spec step="2.2">If url is failure, then:</spec> + // <spec step="6">If result is null, then:</spec> // - // <spec step="2.2.1">Let completion be Completion { [[Type]]: throw, + // <spec step="6.1">Let completion be Completion { [[Type]]: throw, // [[Value]]: a new TypeError, [[Target]]: empty }.</spec> v8::Isolate* isolate = modulator_->GetScriptState()->GetIsolate(); v8::Local<v8::Value> error = V8ThrowException::CreateTypeError( isolate, "Failed to resolve module specifier '" + specifier + "'"); - // <spec step="2.2.2">Perform FinishDynamicImport(referencingScriptOrModule, + // <spec step="6.2">Perform FinishDynamicImport(referencingScriptOrModule, // specifier, promiseCapability, completion).</spec> // // <spec @@ -215,18 +225,19 @@ void DynamicModuleResolver::ResolveDynamically( // »).</spec> promise_resolver->Reject(error); - // <spec step="2.2.3">Return.</spec> + // <spec step="6.3">Return.</spec> return; } - // <spec step="2.3">Let options be the descendant script fetch options for - // referencing script's fetch options.</spec> + // <spec step="4.4">Set fetch options to the descendant script fetch options + // for referencing script's fetch options.</spec> // // <spec // href="https://html.spec.whatwg.org/C/#descendant-script-fetch-options"> For // any given script fetch options options, the descendant script fetch options // are a new script fetch options whose items all have the same values, except // for the integrity metadata, which is instead the empty string.</spec> + // // TODO(domfarolino): It has not yet been decided how a script's "importance" // should affect its dynamic imports. There is discussion at // https://github.com/whatwg/html/issues/3670, but for now there is no effect, @@ -239,9 +250,12 @@ void DynamicModuleResolver::ResolveDynamically( referrer_info.GetReferrerPolicy(), mojom::FetchImportanceMode::kImportanceAuto); - // <spec step="2.4">Fetch a module script graph given url, referencing - // script's settings object, "script", and options. Wait until the algorithm - // asynchronously completes with result.</spec> + // <spec label="fetch-an-import()-module-script-graph" step="3">Fetch a single + // module script given url, settings object, "script", options, settings + // object, "client", and with the top-level module fetch flag set. If the + // caller of this algorithm specified custom perform the fetch steps, pass + // those along as well. Wait until the algorithm asynchronously completes with + // result.</spec> auto* tree_client = MakeGarbageCollected<DynamicImportTreeClient>( url, modulator_.Get(), promise_resolver); // TODO(kouhei): ExecutionContext::From(modulator_->GetScriptState()) is @@ -254,10 +268,10 @@ void DynamicModuleResolver::ResolveDynamically( mojom::RequestContextType::SCRIPT, options, ModuleScriptCustomFetchType::kNone, tree_client); - // Steps 2.[5-8] are implemented at + // Steps 6-9 are implemented at // DynamicImportTreeClient::NotifyModuleLoadFinished. - // <spec step="3">Return undefined.</spec> + // <spec step="10">Return undefined.</spec> } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver.h b/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver.h index b97df2d5d5b..e73c78d383b 100644 --- a/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver.h +++ b/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver.h @@ -23,7 +23,7 @@ class ScriptPromiseResolver; class CORE_EXPORT DynamicModuleResolver final : public GarbageCollected<DynamicModuleResolver> { public: - void Trace(blink::Visitor*); + void Trace(Visitor*); explicit DynamicModuleResolver(Modulator* modulator) : modulator_(modulator) {} diff --git a/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc b/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc index 90b5bbb5ef7..c85586c2393 100644 --- a/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc +++ b/chromium/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc @@ -47,7 +47,7 @@ class DynamicModuleResolverTestModulator final : public DummyModulator { } bool fetch_tree_was_called() const { return fetch_tree_was_called_; } - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: // Implements Modulator: @@ -101,7 +101,7 @@ class DynamicModuleResolverTestModulator final : public DummyModulator { bool fetch_tree_was_called_ = false; }; -void DynamicModuleResolverTestModulator::Trace(blink::Visitor* visitor) { +void DynamicModuleResolverTestModulator::Trace(Visitor* visitor) { visitor->Trace(script_state_); visitor->Trace(pending_client_); DummyModulator::Trace(visitor); @@ -110,7 +110,7 @@ void DynamicModuleResolverTestModulator::Trace(blink::Visitor* visitor) { // CaptureExportedStringFunction implements a javascript function // with a single argument of type module namespace. // CaptureExportedStringFunction captures the exported string value -// from the module namespace as a blink::String, exposed via CapturedValue(). +// from the module namespace as a WTF::String, exposed via CapturedValue(). class CaptureExportedStringFunction final : public ScriptFunction { public: CaptureExportedStringFunction(ScriptState* script_state, diff --git a/chromium/third_party/blink/renderer/core/script/fetch_client_settings_object_impl.h b/chromium/third_party/blink/renderer/core/script/fetch_client_settings_object_impl.h index 1be2d76fbaf..a01b4a1df40 100644 --- a/chromium/third_party/blink/renderer/core/script/fetch_client_settings_object_impl.h +++ b/chromium/third_party/blink/renderer/core/script/fetch_client_settings_object_impl.h @@ -5,14 +5,14 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_FETCH_CLIENT_SETTINGS_OBJECT_IMPL_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_FETCH_CLIENT_SETTINGS_OBJECT_IMPL_H_ -#include "services/network/public/mojom/referrer_policy.mojom-shared.h" +#include "services/network/public/mojom/referrer_policy.mojom-blink.h" #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/platform/cross_thread_copier.h" #include "third_party/blink/renderer/platform/heap/member.h" #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object.h" #include "third_party/blink/renderer/platform/loader/fetch/https_state.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/cross_thread_copier.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/script/generate_lapi_grdp.py b/chromium/third_party/blink/renderer/core/script/generate_lapi_grdp.py index a05172b2019..fcb15ba5047 100755 --- a/chromium/third_party/blink/renderer/core/script/generate_lapi_grdp.py +++ b/chromium/third_party/blink/renderer/core/script/generate_lapi_grdp.py @@ -5,6 +5,7 @@ import sys import os +import re def main(): @@ -40,42 +41,42 @@ def main(): -->''' resource_list_in_header_file = '' - modules = set() - + # A list of (name, path) + modules = [] for root, _, filenames in sorted(os.walk(input_path)): - relroot = os.path.relpath(root, input_path) - if relroot == '.': - # We don't include top-level files under resources/layered_api, - # including generated resources.grdp. - continue - - # Get e.g. "kKvStorage" for kv-storage. - module_name = relroot.split('/')[0] - module_name = "k" + module_name.title().replace('-', '') - modules.add(module_name) - - for filename in sorted(filenames): - if filename.startswith('.') or filename.startswith( - 'README') or filename.startswith('OWNERS'): - continue - relpath = os.path.relpath(os.path.join(root, filename), input_path) - relpath = relpath.replace('\\', '/') - resource_id = relpath - resource_id = resource_id.replace('/', '_') - resource_id = resource_id.replace('-', '_') - resource_id = resource_id.replace('.', '_') - resource_id = resource_id.upper() - resource_id = "IDR_LAYERED_API_" + resource_id - resource_list_in_header_file += \ - ' {"%s",\n %s,\n Module::%s},\n' % (relpath, resource_id, module_name) - print >> output_grdp_file, ( - ' <include name="%s" file="%s/%s" type="BINDATA" skip_minify="true" compress="gzip"/>' - % (resource_id, input_relative_path, relpath)) - resource_list_in_header_file += '\n' + # A directory represents a built-in module if + # - it contains index.mjs (web-exposed module) or + # - the directory name is 'internal' (private module) + if 'index.mjs' in filenames or re.search(r'\binternal$', root): + # Get e.g. "kKvStorage" for kv-storage. + module_name = os.path.relpath(root, input_path) + module_name = "k" + re.sub(r'\W', '', module_name.title()) + modules.append((module_name, root)) + + for module_name, module_path in modules: + for root, _, filenames in sorted(os.walk(module_path)): + for filename in sorted(filenames): + if filename.startswith('.') or filename.startswith( + 'README') or filename.startswith('OWNERS'): + continue + relpath = os.path.relpath(os.path.join(root, filename), input_path) + relpath = relpath.replace('\\', '/') + resource_id = relpath + resource_id = resource_id.replace('/', '_') + resource_id = resource_id.replace('-', '_') + resource_id = resource_id.replace('.', '_') + resource_id = resource_id.upper() + resource_id = "IDR_LAYERED_API_" + resource_id + resource_list_in_header_file += \ + ' {"%s",\n %s,\n Module::%s},\n' % (relpath, resource_id, module_name) + print >> output_grdp_file, ( + ' <include name="%s" file="%s/%s" type="BINDATA" skip_minify="true" compress="gzip"/>' + % (resource_id, input_relative_path, relpath)) + resource_list_in_header_file += '\n' print >> output_grdp_file, '</grit-part>' module_list_in_header_file = '' - for module in modules: + for module, _ in modules: module_list_in_header_file += (' %s,\n' % module) print >> output_module_header_file, '''// Copyright 2019 The Chromium Authors. All rights reserved. diff --git a/chromium/third_party/blink/renderer/core/script/html_parser_script_runner.cc b/chromium/third_party/blink/renderer/core/script/html_parser_script_runner.cc index 2a22c04baf2..2bd8365bb0e 100644 --- a/chromium/third_party/blink/renderer/core/script/html_parser_script_runner.cc +++ b/chromium/third_party/blink/renderer/core/script/html_parser_script_runner.cc @@ -27,6 +27,7 @@ #include <inttypes.h> #include <memory> +#include "services/metrics/public/cpp/ukm_builders.h" #include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/platform/task_type.h" #include "third_party/blink/renderer/core/dom/document_parser_timing.h" @@ -39,7 +40,7 @@ #include "third_party/blink/renderer/core/script/script_loader.h" #include "third_party/blink/renderer/platform/bindings/microtask.h" #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h" -#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/instrumentation/histogram.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h" @@ -160,6 +161,11 @@ void HTMLParserScriptRunner::Detach() { parser_blocking_script_->Dispose(); parser_blocking_script_ = nullptr; + while (!force_deferred_scripts_.IsEmpty()) { + PendingScript* pending_script = force_deferred_scripts_.TakeFirst(); + pending_script->Dispose(); + } + while (!scripts_to_execute_after_parsing_.IsEmpty()) { PendingScript* pending_script = scripts_to_execute_after_parsing_.TakeFirst(); @@ -299,7 +305,14 @@ void HTMLParserScriptRunner::PendingScriptFinished( return; } - host_->NotifyScriptLoaded(pending_script); + // Posting the script execution part to a new task so that we can allow + // yielding for cooperative scheduling. Cooperative scheduling requires that + // the Blink C++ stack be thin when it executes JavaScript. + document_->GetTaskRunner(TaskType::kInternalContinueScriptLoading) + ->PostTask(FROM_HERE, + WTF::Bind(&HTMLParserScriptRunnerHost::NotifyScriptLoaded, + WrapPersistent(host_.Get()), + WrapPersistent(pending_script))); } // <specdef href="https://html.spec.whatwg.org/C/#scriptEndTag"> @@ -412,38 +425,61 @@ void HTMLParserScriptRunner::ExecuteScriptsWaitingForResources() { ExecuteParsingBlockingScripts(); } +PendingScript* HTMLParserScriptRunner::TryTakeReadyScriptWaitingForParsing( + HeapDeque<Member<PendingScript>>* waiting_scripts) { + DCHECK(!waiting_scripts->IsEmpty()); + + // <spec step="3.1">Spin the event loop until the first script in the list + // of scripts that will execute when the document has finished parsing has + // its "ready to be parser-executed" flag set and the parser's Document has + // no style sheet that is blocking scripts.</spec> + // + // TODO(hiroshige): Add check for style sheet blocking defer scripts + // https://github.com/whatwg/html/issues/3890 + if (!waiting_scripts->front()->IsReady()) { + waiting_scripts->front()->WatchForLoad(this); + TraceParserBlockingScript(waiting_scripts->front().Get(), + !document_->IsScriptExecutionReady()); + waiting_scripts->front()->MarkParserBlockingLoadStartTime(); + return nullptr; + } + return waiting_scripts->TakeFirst(); +} + // <specdef href="https://html.spec.whatwg.org/C/#stop-parsing"> // // <spec step="3">If the list of scripts that will execute when the document has // finished parsing is not empty, run these substeps:</spec> +// +// This will also run any forced deferred scripts before running any developer +// deferred scripts. bool HTMLParserScriptRunner::ExecuteScriptsWaitingForParsing() { TRACE_EVENT0("blink", "HTMLParserScriptRunner::executeScriptsWaitingForParsing"); - while (!scripts_to_execute_after_parsing_.IsEmpty()) { + while (!force_deferred_scripts_.IsEmpty() || + !scripts_to_execute_after_parsing_.IsEmpty()) { DCHECK(!IsExecutingScript()); DCHECK(!HasParserBlockingScript()); - DCHECK(scripts_to_execute_after_parsing_.front()->IsExternalOrModule()); - - // <spec step="3.1">Spin the event loop until the first script in the list - // of scripts that will execute when the document has finished parsing has - // its "ready to be parser-executed" flag set and the parser's Document has - // no style sheet that is blocking scripts.</spec> - // - // TODO(hiroshige): Is the latter part checked anywhere? - if (!scripts_to_execute_after_parsing_.front()->IsReady()) { - scripts_to_execute_after_parsing_.front()->WatchForLoad(this); - TraceParserBlockingScript(scripts_to_execute_after_parsing_.front().Get(), - !document_->IsScriptExecutionReady()); - scripts_to_execute_after_parsing_.front() - ->MarkParserBlockingLoadStartTime(); - return false; - } + DCHECK(scripts_to_execute_after_parsing_.IsEmpty() || + scripts_to_execute_after_parsing_.front()->IsExternalOrModule()); // <spec step="3.3">Remove the first script element from the list of scripts // that will execute when the document has finished parsing (i.e. shift out // the first entry in the list).</spec> - PendingScript* first = scripts_to_execute_after_parsing_.TakeFirst(); + PendingScript* first = nullptr; + + // First execute the scripts that were forced-deferred. If no such scripts + // are present, then try executing scripts that were deferred by the web + // developer. + if (!force_deferred_scripts_.IsEmpty()) { + first = TryTakeReadyScriptWaitingForParsing(&force_deferred_scripts_); + } else { + first = TryTakeReadyScriptWaitingForParsing( + &scripts_to_execute_after_parsing_); + } + if (!first) + return false; // <spec step="3.2">Execute the first script in the list of scripts that // will execute when the document has finished parsing.</spec> @@ -494,6 +530,7 @@ void HTMLParserScriptRunner::RequestDeferredScript( pending_script->StartStreamingIfPossible(); } + DCHECK(!script_loader->IsForceDeferred()); DCHECK(pending_script->IsExternalOrModule()); // <spec href="https://html.spec.whatwg.org/C/#prepare-a-script" @@ -503,6 +540,25 @@ void HTMLParserScriptRunner::RequestDeferredScript( scripts_to_execute_after_parsing_.push_back(pending_script); } +void HTMLParserScriptRunner::RequestForceDeferredScript( + ScriptLoader* script_loader) { + PendingScript* pending_script = + script_loader->TakePendingScript(ScriptSchedulingType::kForceDefer); + if (!pending_script) + return; + + if (!pending_script->IsReady()) { + pending_script->StartStreamingIfPossible(); + } + + DCHECK(script_loader->IsForceDeferred()); + + // Add the element to the end of the list of forced deferred scripts that will + // execute when the document has finished parsing associated with the Document + // of the parser that created the element. + force_deferred_scripts_.push_back(pending_script); +} + // The initial steps for 'An end tag whose tag name is "script"' // <specdef href="https://html.spec.whatwg.org/C/#scriptEndTag"> // <specdef label="prepare-a-script" @@ -544,7 +600,11 @@ void HTMLParserScriptRunner::ProcessScriptElementInternal( return; if (script_loader->WillExecuteWhenDocumentFinishedParsing()) { + // Developer deferred. RequestDeferredScript(script_loader); + } else if (script_loader->IsForceDeferred()) { + // Force defer this otherwise parser-blocking script. + RequestForceDeferredScript(script_loader); } else if (script_loader->ReadyToBeParserExecuted()) { // <spec label="prepare-a-script" step="26.E">... it's an HTML parser // whose script nesting level is not greater than one, ...</spec> @@ -589,10 +649,48 @@ void HTMLParserScriptRunner::ProcessScriptElementInternal( } } -void HTMLParserScriptRunner::Trace(blink::Visitor* visitor) { +void HTMLParserScriptRunner::RecordMetricsAtParseEnd() const { + // This method is called just before starting execution of force defer + // scripts in order to capture the all force deferred scripts in + // |force_deferred_scripts_| before any are popped for execution. + + if (!document_->GetFrame()) + return; + + if (!force_deferred_scripts_.IsEmpty()) { + uint32_t force_deferred_external_script_count = 0; + for (const auto& pending_script : force_deferred_scripts_) { + if (pending_script->IsExternal()) + force_deferred_external_script_count++; + } + if (document_->IsInMainFrame()) { + UMA_HISTOGRAM_COUNTS_100("Blink.Script.ForceDeferredScripts.Mainframe", + force_deferred_scripts_.size()); + UMA_HISTOGRAM_COUNTS_100( + "Blink.Script.ForceDeferredScripts.Mainframe.External", + force_deferred_external_script_count); + if (document_->UkmRecorder()) { + ukm::builders::PreviewsDeferAllScript(document_->UkmSourceID()) + .Setforce_deferred_scripts_mainframe(force_deferred_scripts_.size()) + .Setforce_deferred_scripts_mainframe_external( + force_deferred_external_script_count) + .Record(document_->UkmRecorder()); + } + } else { + UMA_HISTOGRAM_COUNTS_100("Blink.Script.ForceDeferredScripts.Subframe", + force_deferred_scripts_.size()); + UMA_HISTOGRAM_COUNTS_100( + "Blink.Script.ForceDeferredScripts.Subframe.External", + force_deferred_external_script_count); + } + } +} + +void HTMLParserScriptRunner::Trace(Visitor* visitor) { visitor->Trace(document_); visitor->Trace(host_); visitor->Trace(parser_blocking_script_); + visitor->Trace(force_deferred_scripts_); visitor->Trace(scripts_to_execute_after_parsing_); PendingScriptClient::Trace(visitor); } diff --git a/chromium/third_party/blink/renderer/core/script/html_parser_script_runner.h b/chromium/third_party/blink/renderer/core/script/html_parser_script_runner.h index f9b1dce95a2..a3f88c0dff3 100644 --- a/chromium/third_party/blink/renderer/core/script/html_parser_script_runner.h +++ b/chromium/third_party/blink/renderer/core/script/html_parser_script_runner.h @@ -93,6 +93,8 @@ class HTMLParserScriptRunner final void ExecuteScriptsWaitingForResources(); // Invoked when parsing is stopping, to execute any deferred scripts. + // This includes forced deferred scripts as well as developer deferred + // scripts. bool ExecuteScriptsWaitingForParsing(); bool HasParserBlockingScript() const; @@ -100,7 +102,11 @@ class HTMLParserScriptRunner final return !!reentry_permit_->ScriptNestingLevel(); } - void Trace(blink::Visitor*) override; + // Records metrics related to the parsing phase. To be called when parsing + // is preparing to stop but before |ExecuteScriptsWaitingForParsing|. + void RecordMetricsAtParseEnd() const; + + void Trace(Visitor*) override; const char* NameInHeapSnapshot() const override { return "HTMLParserScriptRunner"; } @@ -115,6 +121,7 @@ class HTMLParserScriptRunner final void RequestParsingBlockingScript(ScriptLoader*); void RequestDeferredScript(ScriptLoader*); + void RequestForceDeferredScript(ScriptLoader*); // Processes the provided script element, but does not execute any // parsing-blocking scripts that may remain after execution. @@ -129,6 +136,12 @@ class HTMLParserScriptRunner final void PossiblyFetchBlockedDocWriteScript(PendingScript*); + // Takes and returns the first PendingScript from |waiting_scripts| if it is + // ready for execution. Otherwise, informs it that |this| is a + // PendingScriptClient to be informed when it is ready. + PendingScript* TryTakeReadyScriptWaitingForParsing( + HeapDeque<Member<PendingScript>>* waiting_scripts); + scoped_refptr<HTMLParserReentryPermit> reentry_permit_; Member<Document> document_; Member<HTMLParserScriptRunnerHost> host_; @@ -136,6 +149,13 @@ class HTMLParserScriptRunner final // https://html.spec.whatwg.org/C/#pending-parsing-blocking-script Member<PendingScript> parser_blocking_script_; + // Scripts that were force deferred by the defer all script optimization. + // These scripts will be executed after parsing but before + // |scripts_to_execute_after_parsing_|. This is an ordered list. + // https://crbug.com/976061 + HeapDeque<Member<PendingScript>> force_deferred_scripts_; + + // Scripts that were deferred by the web developer. This is an ordered list. // https://html.spec.whatwg.org/C/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing HeapDeque<Member<PendingScript>> scripts_to_execute_after_parsing_; diff --git a/chromium/third_party/blink/renderer/core/script/html_parser_script_runner_host.h b/chromium/third_party/blink/renderer/core/script/html_parser_script_runner_host.h index dd5f4fbe1c4..1a4770c0a2a 100644 --- a/chromium/third_party/blink/renderer/core/script/html_parser_script_runner_host.h +++ b/chromium/third_party/blink/renderer/core/script/html_parser_script_runner_host.h @@ -37,7 +37,7 @@ class PendingScript; class CORE_EXPORT HTMLParserScriptRunnerHost : public GarbageCollectedMixin { public: virtual ~HTMLParserScriptRunnerHost() = default; - void Trace(blink::Visitor* visitor) override {} + void Trace(Visitor* visitor) override {} virtual void NotifyScriptLoaded(PendingScript*) = 0; virtual HTMLInputStream& InputStream() = 0; diff --git a/chromium/third_party/blink/renderer/core/script/ignore_destructive_write_count_incrementer.h b/chromium/third_party/blink/renderer/core/script/ignore_destructive_write_count_incrementer.h index cedad851068..02738ad8bc5 100644 --- a/chromium/third_party/blink/renderer/core/script/ignore_destructive_write_count_incrementer.h +++ b/chromium/third_party/blink/renderer/core/script/ignore_destructive_write_count_incrementer.h @@ -28,7 +28,7 @@ #include "base/macros.h" #include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/script/import_map.cc b/chromium/third_party/blink/renderer/core/script/import_map.cc index fceaddd75a9..c78d79dda49 100644 --- a/chromium/third_party/blink/renderer/core/script/import_map.cc +++ b/chromium/third_party/blink/renderer/core/script/import_map.cc @@ -187,7 +187,7 @@ ImportMap* ImportMap::Create(const Modulator& modulator_for_built_in_modules, values.clear(); } if (values.size() == 2) { - if (blink::layered_api::GetBuiltinPath(values[0]).IsNull()) { + if (layered_api::GetBuiltinPath(values[0]).IsNull()) { AddIgnoredValueMessage( logger, entry.first, "Fallback from a non-builtin URL is not yet supported."); @@ -282,8 +282,8 @@ base::Optional<KURL> ImportMap::Resolve(const ParsedSpecifier& parsed_specifier, for (const KURL& value : matched->value) { const KURL complete_url = postfix.IsEmpty() ? value : KURL(value, postfix); - if (blink::layered_api::ResolveFetchingURL(*modulator_for_built_in_modules_, - complete_url) + if (layered_api::ResolveFetchingURL(*modulator_for_built_in_modules_, + complete_url) .IsValid()) { *debug_message = "Import Map: \"" + key + "\" matches with \"" + matched->key + "\" and is mapped to " + diff --git a/chromium/third_party/blink/renderer/core/script/js_module_script.cc b/chromium/third_party/blink/renderer/core/script/js_module_script.cc index d53409bf7c6..8b12c6ac851 100644 --- a/chromium/third_party/blink/renderer/core/script/js_module_script.cc +++ b/chromium/third_party/blink/renderer/core/script/js_module_script.cc @@ -6,8 +6,6 @@ #include "third_party/blink/renderer/bindings/core/v8/script_value.h" #include "third_party/blink/renderer/core/script/module_record_resolver.h" -#include "third_party/blink/renderer/core/workers/worker_global_scope.h" -#include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h" #include "third_party/blink/renderer/platform/bindings/script_state.h" #include "v8/include/v8.h" @@ -166,21 +164,6 @@ JSModuleScript::JSModuleScript(Modulator* settings_object, start_position_(start_position), produce_cache_data_(produce_cache_data) {} -void JSModuleScript::RunScriptOnWorker(WorkerGlobalScope& worker_global_scope) { - DCHECK(worker_global_scope.IsContextThread()); - - WorkerReportingProxy& worker_reporting_proxy = - worker_global_scope.ReportingProxy(); - - worker_reporting_proxy.WillEvaluateModuleScript(); - // This |error| is always null because the second argument is |kReport|. - // TODO(nhiroki): Catch an error when an evaluation error happens. - // (https://crbug.com/680046) - ScriptValue error = SettingsObject()->ExecuteModule( - this, Modulator::CaptureEvalErrorFlag::kReport); - worker_reporting_proxy.DidEvaluateModuleScript(error.IsEmpty()); -} - String JSModuleScript::InlineSourceTextForCSP() const { return source_text_.ToString(); } @@ -199,7 +182,7 @@ void JSModuleScript::ProduceCache() { produce_cache_data_ = nullptr; } -void JSModuleScript::Trace(blink::Visitor* visitor) { +void JSModuleScript::Trace(Visitor* visitor) { visitor->Trace(produce_cache_data_); ModuleScript::Trace(visitor); } diff --git a/chromium/third_party/blink/renderer/core/script/js_module_script.h b/chromium/third_party/blink/renderer/core/script/js_module_script.h index db070c73340..61089f896de 100644 --- a/chromium/third_party/blink/renderer/core/script/js_module_script.h +++ b/chromium/third_party/blink/renderer/core/script/js_module_script.h @@ -57,7 +57,7 @@ class CORE_EXPORT JSModuleScript final : public ModuleScript, void ProduceCache() override; - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; const char* NameInHeapSnapshot() const override { return "JSModuleScript"; } private: @@ -74,7 +74,6 @@ class CORE_EXPORT JSModuleScript final : public ModuleScript, ModuleRecordProduceCacheData* produce_cache_data); const TextPosition& StartPosition() const { return start_position_; } - void RunScriptOnWorker(WorkerGlobalScope&) override; String InlineSourceTextForCSP() const override; // For CSP check. diff --git a/chromium/third_party/blink/renderer/core/script/layered_api_module.h b/chromium/third_party/blink/renderer/core/script/layered_api_module.h index 28809a991c0..7dd7d687dab 100644 --- a/chromium/third_party/blink/renderer/core/script/layered_api_module.h +++ b/chromium/third_party/blink/renderer/core/script/layered_api_module.h @@ -15,7 +15,10 @@ namespace layered_api { enum class Module { kBlank, - kVirtualScroller, + kElementsInternal, + kElementsSwitch, + kElementsToast, + kElementsVirtualScroller, kKvStorage, }; diff --git a/chromium/third_party/blink/renderer/core/script/layered_api_resources.h b/chromium/third_party/blink/renderer/core/script/layered_api_resources.h index f822934e6ff..98be4b31e1a 100644 --- a/chromium/third_party/blink/renderer/core/script/layered_api_resources.h +++ b/chromium/third_party/blink/renderer/core/script/layered_api_resources.h @@ -29,6 +29,35 @@ struct LayeredAPIResource { const LayeredAPIResource kLayeredAPIResources[] = { {"blank/index.mjs", IDR_LAYERED_API_BLANK_INDEX_MJS, Module::kBlank}, + {"elements/internal/reflection.mjs", + IDR_LAYERED_API_ELEMENTS_INTERNAL_REFLECTION_MJS, + Module::kElementsInternal}, + + {"elements/switch/face_utils.mjs", + IDR_LAYERED_API_ELEMENTS_SWITCH_FACE_UTILS_MJS, Module::kElementsSwitch}, + {"elements/switch/index.mjs", IDR_LAYERED_API_ELEMENTS_SWITCH_INDEX_MJS, + Module::kElementsSwitch}, + {"elements/switch/style.mjs", IDR_LAYERED_API_ELEMENTS_SWITCH_STYLE_MJS, + Module::kElementsSwitch}, + {"elements/switch/track.mjs", IDR_LAYERED_API_ELEMENTS_SWITCH_TRACK_MJS, + Module::kElementsSwitch}, + + {"elements/toast/index.mjs", IDR_LAYERED_API_ELEMENTS_TOAST_INDEX_MJS, + Module::kElementsToast}, + + {"elements/virtual-scroller/find-element.mjs", + IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_FIND_ELEMENT_MJS, + Module::kElementsVirtualScroller}, + {"elements/virtual-scroller/index.mjs", + IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_INDEX_MJS, + Module::kElementsVirtualScroller}, + {"elements/virtual-scroller/sets.mjs", + IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_SETS_MJS, + Module::kElementsVirtualScroller}, + {"elements/virtual-scroller/visibility-manager.mjs", + IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_VISIBILITY_MANAGER_MJS, + Module::kElementsVirtualScroller}, + {"kv-storage/async_iterator.mjs", IDR_LAYERED_API_KV_STORAGE_ASYNC_ITERATOR_MJS, Module::kKvStorage}, {"kv-storage/idb_utils.mjs", IDR_LAYERED_API_KV_STORAGE_IDB_UTILS_MJS, @@ -36,28 +65,6 @@ const LayeredAPIResource kLayeredAPIResources[] = { {"kv-storage/index.mjs", IDR_LAYERED_API_KV_STORAGE_INDEX_MJS, Module::kKvStorage}, - {"virtual-scroller/index.mjs", IDR_LAYERED_API_VIRTUAL_SCROLLER_INDEX_MJS, - Module::kVirtualScroller}, - {"virtual-scroller/item-source.mjs", - IDR_LAYERED_API_VIRTUAL_SCROLLER_ITEM_SOURCE_MJS, - Module::kVirtualScroller}, - {"virtual-scroller/virtual-repeater.mjs", - IDR_LAYERED_API_VIRTUAL_SCROLLER_VIRTUAL_REPEATER_MJS, - Module::kVirtualScroller}, - {"virtual-scroller/virtual-scroller.mjs", - IDR_LAYERED_API_VIRTUAL_SCROLLER_VIRTUAL_SCROLLER_MJS, - Module::kVirtualScroller}, - - {"virtual-scroller/layouts/layout-1d-base.mjs", - IDR_LAYERED_API_VIRTUAL_SCROLLER_LAYOUTS_LAYOUT_1D_BASE_MJS, - Module::kVirtualScroller}, - {"virtual-scroller/layouts/layout-1d-grid.mjs", - IDR_LAYERED_API_VIRTUAL_SCROLLER_LAYOUTS_LAYOUT_1D_GRID_MJS, - Module::kVirtualScroller}, - {"virtual-scroller/layouts/layout-1d.mjs", - IDR_LAYERED_API_VIRTUAL_SCROLLER_LAYOUTS_LAYOUT_1D_MJS, - Module::kVirtualScroller}, - }; } // namespace diff --git a/chromium/third_party/blink/renderer/core/script/layered_api_test.cc b/chromium/third_party/blink/renderer/core/script/layered_api_test.cc index 28ac45737df..f318f7fc807 100644 --- a/chromium/third_party/blink/renderer/core/script/layered_api_test.cc +++ b/chromium/third_party/blink/renderer/core/script/layered_api_test.cc @@ -16,9 +16,7 @@ namespace { class LayeredAPITestModulator final : public DummyModulator { public: bool BuiltInModuleInfraEnabled() const override { return true; } - bool BuiltInModuleEnabled(blink::layered_api::Module) const override { - return true; - } + bool BuiltInModuleEnabled(layered_api::Module) const override { return true; } }; class LayeredAPITest : public testing::Test { diff --git a/chromium/third_party/blink/renderer/core/script/mock_script_element_base.h b/chromium/third_party/blink/renderer/core/script/mock_script_element_base.h index 2afb66aefb4..2722f3a7971 100644 --- a/chromium/third_party/blink/renderer/core/script/mock_script_element_base.h +++ b/chromium/third_party/blink/renderer/core/script/mock_script_element_base.h @@ -55,9 +55,7 @@ class MockScriptElementBase void(HTMLScriptElementOrSVGScriptElement&)); MOCK_CONST_METHOD0(Loader, ScriptLoader*()); - void Trace(blink::Visitor* visitor) override { - ScriptElementBase::Trace(visitor); - } + void Trace(Visitor* visitor) override { ScriptElementBase::Trace(visitor); } }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/script/modulator.h b/chromium/third_party/blink/renderer/core/script/modulator.h index 350858d9972..9f67b4e5afa 100644 --- a/chromium/third_party/blink/renderer/core/script/modulator.h +++ b/chromium/third_party/blink/renderer/core/script/modulator.h @@ -6,7 +6,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_MODULATOR_H_ #include "base/single_thread_task_runner.h" -#include "services/network/public/mojom/referrer_policy.mojom-shared.h" +#include "services/network/public/mojom/referrer_policy.mojom-blink.h" #include "third_party/blink/public/platform/web_url_request.h" #include "third_party/blink/renderer/bindings/core/v8/module_record.h" #include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h" @@ -44,7 +44,7 @@ class CORE_EXPORT SingleModuleClient public NameClient { public: virtual ~SingleModuleClient() = default; - virtual void Trace(blink::Visitor* visitor) {} + virtual void Trace(Visitor* visitor) {} const char* NameInHeapSnapshot() const override { return "SingleModuleClient"; } @@ -59,7 +59,7 @@ class CORE_EXPORT ModuleTreeClient public NameClient { public: virtual ~ModuleTreeClient() = default; - virtual void Trace(blink::Visitor* visitor) {} + virtual void Trace(Visitor* visitor) {} const char* NameInHeapSnapshot() const override { return "ModuleTreeClient"; } virtual void NotifyModuleTreeLoadFinished(ModuleScript*) = 0; @@ -107,7 +107,7 @@ class CORE_EXPORT Modulator : public GarbageCollectedFinalized<Modulator>, static void SetModulator(ScriptState*, Modulator*); static void ClearModulator(ScriptState*); - void Trace(blink::Visitor* visitor) override {} + void Trace(Visitor* visitor) override {} const char* NameInHeapSnapshot() const override { return "Modulator"; } virtual ModuleRecordResolver* GetModuleRecordResolver() = 0; @@ -122,8 +122,8 @@ class CORE_EXPORT Modulator : public GarbageCollectedFinalized<Modulator>, virtual bool IsScriptingDisabled() const = 0; virtual bool BuiltInModuleInfraEnabled() const = 0; - virtual bool BuiltInModuleEnabled(blink::layered_api::Module) const = 0; - virtual void BuiltInModuleUseCount(blink::layered_api::Module) const = 0; + virtual bool BuiltInModuleEnabled(layered_api::Module) const = 0; + virtual void BuiltInModuleUseCount(layered_api::Module) const = 0; // https://html.spec.whatwg.org/C/#fetch-a-module-script-tree // https://html.spec.whatwg.org/C/#fetch-a-module-worker-script-tree diff --git a/chromium/third_party/blink/renderer/core/script/modulator_impl_base.cc b/chromium/third_party/blink/renderer/core/script/modulator_impl_base.cc index 441862b5a36..a3bcbe4b6ae 100644 --- a/chromium/third_party/blink/renderer/core/script/modulator_impl_base.cc +++ b/chromium/third_party/blink/renderer/core/script/modulator_impl_base.cc @@ -7,7 +7,6 @@ #include "third_party/blink/public/platform/task_type.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h" -#include "third_party/blink/renderer/core/frame/use_counter.h" #include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h" #include "third_party/blink/renderer/core/loader/modulescript/module_tree_linker.h" #include "third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.h" @@ -18,6 +17,7 @@ #include "third_party/blink/renderer/core/script/module_record_resolver_impl.h" #include "third_party/blink/renderer/core/script/parsed_specifier.h" #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h" +#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" namespace blink { @@ -52,33 +52,49 @@ bool ModulatorImplBase::BuiltInModuleInfraEnabled() const { GetExecutionContext()); } -bool ModulatorImplBase::BuiltInModuleEnabled( - blink::layered_api::Module module) const { +bool ModulatorImplBase::BuiltInModuleEnabled(layered_api::Module module) const { DCHECK(BuiltInModuleInfraEnabled()); + if (RuntimeEnabledFeatures::BuiltInModuleAllEnabled()) + return true; switch (module) { - case blink::layered_api::Module::kBlank: + case layered_api::Module::kBlank: return true; - case blink::layered_api::Module::kVirtualScroller: + case layered_api::Module::kKvStorage: + return RuntimeEnabledFeatures::BuiltInModuleKvStorageEnabled( + GetExecutionContext()); + case layered_api::Module::kElementsInternal: + // Union of conditions of KElementsSwitch and kElementsToast. + return RuntimeEnabledFeatures::BuiltInModuleSwitchElementEnabled(); + case layered_api::Module::kElementsSwitch: + return RuntimeEnabledFeatures::BuiltInModuleSwitchElementEnabled(); + case layered_api::Module::kElementsToast: return RuntimeEnabledFeatures::BuiltInModuleAllEnabled(); - case blink::layered_api::Module::kKvStorage: - return RuntimeEnabledFeatures::BuiltInModuleAllEnabled() || - RuntimeEnabledFeatures::BuiltInModuleKvStorageEnabled( - GetExecutionContext()); + case layered_api::Module::kElementsVirtualScroller: + return false; } } void ModulatorImplBase::BuiltInModuleUseCount( - blink::layered_api::Module module) const { + layered_api::Module module) const { DCHECK(BuiltInModuleInfraEnabled()); DCHECK(BuiltInModuleEnabled(module)); switch (module) { - case blink::layered_api::Module::kBlank: + case layered_api::Module::kBlank: break; - case blink::layered_api::Module::kVirtualScroller: + case layered_api::Module::kElementsInternal: + break; + case layered_api::Module::kElementsSwitch: + UseCounter::Count(GetExecutionContext(), + WebFeature::kBuiltInModuleSwitchImported); + break; + case layered_api::Module::kElementsToast: + UseCounter::Count(GetExecutionContext(), WebFeature::kBuiltInModuleToast); + break; + case layered_api::Module::kElementsVirtualScroller: UseCounter::Count(GetExecutionContext(), WebFeature::kBuiltInModuleVirtualScroller); break; - case blink::layered_api::Module::kKvStorage: + case layered_api::Module::kKvStorage: UseCounter::Count(GetExecutionContext(), WebFeature::kBuiltInModuleKvStorage); break; @@ -96,32 +112,9 @@ void ModulatorImplBase::FetchTree( const ScriptFetchOptions& options, ModuleScriptCustomFetchType custom_fetch_type, ModuleTreeClient* client) { - // <spec label="fetch-a-module-script-tree" step="2">Perform the internal - // module script graph fetching procedure given url, settings object, - // destination, options, settings object, visited set, "client", and with the - // top-level module fetch flag set. If the caller of this algorithm specified - // custom perform the fetch steps, pass those along as well.</spec> - - // <spec label="fetch-a-module-worker-script-tree" step="3">Perform the - // internal module script graph fetching procedure given url, fetch client - // settings object, destination, options, module map settings object, visited - // set, "client", and with the top-level module fetch flag set. If the caller - // of this algorithm specified custom perform the fetch steps, pass those - // along as well.</spec> - ModuleTreeLinker::Fetch(url, fetch_client_settings_object_fetcher, destination, options, this, custom_fetch_type, tree_linker_registry_, client); - - // <spec label="fetch-a-module-script-tree" step="3">When the internal module - // script graph fetching procedure asynchronously completes with result, - // asynchronously complete this algorithm with result.</spec> - - // <spec label="fetch-a-module-worker-script-tree" step="4">When the internal - // module script graph fetching procedure asynchronously completes with - // result, asynchronously complete this algorithm with result.</spec> - - // Note: We delegate to ModuleTreeLinker to notify ModuleTreeClient. } void ModulatorImplBase::FetchDescendantsForInlineScript( @@ -265,14 +258,14 @@ ModuleImportMeta ModulatorImplBase::HostGetImportMetaProperties( ModuleRecord record) const { // <spec step="1">Let module script be moduleRecord.[[HostDefined]].</spec> const ModuleScript* module_script = - module_record_resolver_->GetHostDefined(record); + module_record_resolver_->GetModuleScriptFromModuleRecord(record); DCHECK(module_script); - // <spec step="2">Let urlString be module script's base URL, + // <spec step="3">Let urlString be module script's base URL, // serialized.</spec> String url_string = module_script->BaseURL().GetString(); - // <spec step="3">Return « Record { [[Key]]: "url", [[Value]]: urlString } + // <spec step="4">Return « Record { [[Key]]: "url", [[Value]]: urlString } // ».</spec> return ModuleImportMeta(url_string); } @@ -416,7 +409,7 @@ ScriptValue ModulatorImplBase::ExecuteModule( return ScriptValue(); } -void ModulatorImplBase::Trace(blink::Visitor* visitor) { +void ModulatorImplBase::Trace(Visitor* visitor) { visitor->Trace(script_state_); visitor->Trace(map_); visitor->Trace(tree_linker_registry_); diff --git a/chromium/third_party/blink/renderer/core/script/modulator_impl_base.h b/chromium/third_party/blink/renderer/core/script/modulator_impl_base.h index 53780b19229..6721f1cb8af 100644 --- a/chromium/third_party/blink/renderer/core/script/modulator_impl_base.h +++ b/chromium/third_party/blink/renderer/core/script/modulator_impl_base.h @@ -27,7 +27,7 @@ class ScriptState; class ModulatorImplBase : public Modulator { public: ~ModulatorImplBase() override; - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; protected: explicit ModulatorImplBase(ScriptState*); @@ -42,8 +42,8 @@ class ModulatorImplBase : public Modulator { bool IsScriptingDisabled() const override; bool BuiltInModuleInfraEnabled() const override; - bool BuiltInModuleEnabled(blink::layered_api::Module) const override; - void BuiltInModuleUseCount(blink::layered_api::Module) const override; + bool BuiltInModuleEnabled(layered_api::Module) const override; + void BuiltInModuleUseCount(layered_api::Module) const override; ModuleRecordResolver* GetModuleRecordResolver() override { return module_record_resolver_.Get(); diff --git a/chromium/third_party/blink/renderer/core/script/module_map.cc b/chromium/third_party/blink/renderer/core/script/module_map.cc index f8fc91cf97f..d38ecfd0038 100644 --- a/chromium/third_party/blink/renderer/core/script/module_map.cc +++ b/chromium/third_party/blink/renderer/core/script/module_map.cc @@ -25,7 +25,7 @@ class ModuleMap::Entry final : public GarbageCollectedFinalized<Entry>, explicit Entry(ModuleMap*); ~Entry() override {} - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; const char* NameInHeapSnapshot() const override { return "ModuleMap::Entry"; } // Notify fetched |m_moduleScript| to the client asynchronously. @@ -53,7 +53,7 @@ ModuleMap::Entry::Entry(ModuleMap* map) : map_(map) { DCHECK(map_); } -void ModuleMap::Entry::Trace(blink::Visitor* visitor) { +void ModuleMap::Entry::Trace(Visitor* visitor) { visitor->Trace(module_script_); visitor->Trace(map_); visitor->Trace(clients_); @@ -100,7 +100,7 @@ ModuleMap::ModuleMap(Modulator* modulator) DCHECK(modulator); } -void ModuleMap::Trace(blink::Visitor* visitor) { +void ModuleMap::Trace(Visitor* visitor) { visitor->Trace(map_); visitor->Trace(modulator_); visitor->Trace(loader_registry_); @@ -137,7 +137,7 @@ void ModuleMap::FetchSingleModuleScript( // <spec step="3">If moduleMap[url] exists, asynchronously complete this // algorithm with moduleMap[url], and abort these steps.</spec> // - // <spec step="12">Set moduleMap[url] to module script, and asynchronously + // <spec step="14">Set moduleMap[url] to module script, and asynchronously // complete this algorithm with module script.</spec> if (client) entry->AddClient(client); diff --git a/chromium/third_party/blink/renderer/core/script/module_map.h b/chromium/third_party/blink/renderer/core/script/module_map.h index fa0018a3cf9..46d98e67aa3 100644 --- a/chromium/third_party/blink/renderer/core/script/module_map.h +++ b/chromium/third_party/blink/renderer/core/script/module_map.h @@ -33,7 +33,7 @@ class CORE_EXPORT ModuleMap final : public GarbageCollected<ModuleMap>, public: explicit ModuleMap(Modulator*); - void Trace(blink::Visitor*); + void Trace(Visitor*); const char* NameInHeapSnapshot() const override { return "ModuleMap"; } // https://html.spec.whatwg.org/C/#fetch-a-single-module-script diff --git a/chromium/third_party/blink/renderer/core/script/module_map_test.cc b/chromium/third_party/blink/renderer/core/script/module_map_test.cc index da2f4ab72f2..6df17a931d6 100644 --- a/chromium/third_party/blink/renderer/core/script/module_map_test.cc +++ b/chromium/third_party/blink/renderer/core/script/module_map_test.cc @@ -30,7 +30,7 @@ class TestSingleModuleClient final : public SingleModuleClient { TestSingleModuleClient() = default; ~TestSingleModuleClient() override {} - void Trace(blink::Visitor* visitor) override { + void Trace(Visitor* visitor) override { visitor->Trace(module_script_); SingleModuleClient::Trace(visitor); } @@ -64,7 +64,8 @@ class TestModuleRecordResolver final : public ModuleRecordResolver { FAIL() << "UnregisterModuleScript shouldn't be called in ModuleMapTest"; } - const ModuleScript* GetHostDefined(const ModuleRecord&) const override { + const ModuleScript* GetModuleScriptFromModuleRecord( + const ModuleRecord&) const override { NOTREACHED(); return nullptr; } @@ -87,7 +88,7 @@ class ModuleMapTestModulator final : public DummyModulator { explicit ModuleMapTestModulator(ScriptState*); ~ModuleMapTestModulator() override {} - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; TestModuleRecordResolver* GetTestModuleRecordResolver() { return resolver_.Get(); @@ -117,12 +118,12 @@ class ModuleMapTestModulator final : public DummyModulator { TestRequest* test_request = MakeGarbageCollected<TestRequest>( ModuleScriptCreationParams( request.Url(), ParkableString(String("").ReleaseImpl()), nullptr, - request.GetResourceRequest().GetFetchCredentialsMode()), + request.GetResourceRequest().GetCredentialsMode()), client); modulator_->test_requests_.push_back(test_request); } String DebugName() const override { return "TestModuleScriptFetcher"; } - void Trace(blink::Visitor* visitor) override { + void Trace(Visitor* visitor) override { ModuleScriptFetcher::Trace(visitor); visitor->Trace(modulator_); } @@ -152,7 +153,7 @@ class ModuleMapTestModulator final : public DummyModulator { client_->NotifyFetchFinished(*params_, HeapVector<Member<ConsoleMessage>>()); } - void Trace(blink::Visitor* visitor) { visitor->Trace(client_); } + void Trace(Visitor* visitor) { visitor->Trace(client_); } private: base::Optional<ModuleScriptCreationParams> params_; @@ -168,7 +169,7 @@ ModuleMapTestModulator::ModuleMapTestModulator(ScriptState* script_state) : script_state_(script_state), resolver_(MakeGarbageCollected<TestModuleRecordResolver>()) {} -void ModuleMapTestModulator::Trace(blink::Visitor* visitor) { +void ModuleMapTestModulator::Trace(Visitor* visitor) { visitor->Trace(test_requests_); visitor->Trace(script_state_); visitor->Trace(resolver_); @@ -198,8 +199,7 @@ class ModuleMapTest : public PageTestBase { void ModuleMapTest::SetUp() { PageTestBase::SetUp(IntSize(500, 500)); - GetDocument().SetURL(KURL("https://example.com")); - GetDocument().SetSecurityOrigin(SecurityOrigin::Create(GetDocument().Url())); + NavigateTo(KURL("https://example.com")); modulator_ = MakeGarbageCollected<ModuleMapTestModulator>( ToScriptStateForMainWorld(&GetFrame())); map_ = MakeGarbageCollected<ModuleMap>(modulator_); diff --git a/chromium/third_party/blink/renderer/core/script/module_pending_script.cc b/chromium/third_party/blink/renderer/core/script/module_pending_script.cc index cfe0735ec4f..29449ea2056 100644 --- a/chromium/third_party/blink/renderer/core/script/module_pending_script.cc +++ b/chromium/third_party/blink/renderer/core/script/module_pending_script.cc @@ -31,7 +31,7 @@ void ModulePendingScriptTreeClient::NotifyModuleTreeLoadFinished( pending_script_->NotifyModuleTreeLoadFinished(); } -void ModulePendingScriptTreeClient::Trace(blink::Visitor* visitor) { +void ModulePendingScriptTreeClient::Trace(Visitor* visitor) { visitor->Trace(module_script_); visitor->Trace(pending_script_); ModuleTreeClient::Trace(visitor); @@ -54,7 +54,7 @@ void ModulePendingScript::DisposeInternal() { module_tree_client_ = nullptr; } -void ModulePendingScript::Trace(blink::Visitor* visitor) { +void ModulePendingScript::Trace(Visitor* visitor) { visitor->Trace(module_tree_client_); PendingScript::Trace(visitor); } diff --git a/chromium/third_party/blink/renderer/core/script/module_pending_script.h b/chromium/third_party/blink/renderer/core/script/module_pending_script.h index b0253af0d6c..230d4632c4d 100644 --- a/chromium/third_party/blink/renderer/core/script/module_pending_script.h +++ b/chromium/third_party/blink/renderer/core/script/module_pending_script.h @@ -29,7 +29,7 @@ class ModulePendingScriptTreeClient final : public ModuleTreeClient { ModuleScript* GetModuleScript() const { return module_script_; } - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: // Implements ModuleTreeClient @@ -55,7 +55,7 @@ class CORE_EXPORT ModulePendingScript : public PendingScript { return module_tree_client_->GetModuleScript(); } - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; private: // PendingScript diff --git a/chromium/third_party/blink/renderer/core/script/module_record_resolver.h b/chromium/third_party/blink/renderer/core/script/module_record_resolver.h index fb075478d8f..5fb8d6c3b7f 100644 --- a/chromium/third_party/blink/renderer/core/script/module_record_resolver.h +++ b/chromium/third_party/blink/renderer/core/script/module_record_resolver.h @@ -26,7 +26,7 @@ class CORE_EXPORT ModuleRecordResolver : public GarbageCollectedFinalized<ModuleRecordResolver> { public: virtual ~ModuleRecordResolver() = default; - virtual void Trace(blink::Visitor* visitor) {} + virtual void Trace(Visitor* visitor) {} // Notifies the ModuleRecordResolver that a ModuleScript exists. // This hook gives a chance for the resolver impl to populate module record @@ -36,8 +36,8 @@ class CORE_EXPORT ModuleRecordResolver // Notifies the ModuleRecordResolver to clear its ModuleScript mapping. virtual void UnregisterModuleScript(const ModuleScript*) = 0; - // Corresponds to the spec concept "[[HostDefined]]". - virtual const ModuleScript* GetHostDefined(const ModuleRecord&) const = 0; + virtual const ModuleScript* GetModuleScriptFromModuleRecord( + const ModuleRecord&) const = 0; // Implements "Runtime Semantics: HostResolveImportedModule" // https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule diff --git a/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl.cc b/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl.cc index 212ee50da5e..46b15431539 100644 --- a/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl.cc +++ b/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl.cc @@ -40,7 +40,7 @@ void ModuleRecordResolverImpl::UnregisterModuleScript( record_to_module_script_map_.erase(module_script->Record()); } -const ModuleScript* ModuleRecordResolverImpl::GetHostDefined( +const ModuleScript* ModuleRecordResolverImpl::GetModuleScriptFromModuleRecord( const ModuleRecord& record) const { const auto it = record_to_module_script_map_.find(record); CHECK_NE(it, record_to_module_script_map_.end()) @@ -58,38 +58,47 @@ ModuleRecord ModuleRecordResolverImpl::Resolve( ExceptionState& exception_state) { DVLOG(1) << "ModuleRecordResolverImpl::resolve(specifier=\"" << specifier << ", referrer.hash=" << ModuleRecordHash::GetHash(referrer) << ")"; + // <spec step="3">If referencingScriptOrModule is not null, then:</spec> + // + // Currently this function implements the spec before + // https://github.com/tc39/proposal-dynamic-import is applied, i.e. where + // |referencingScriptOrModule| was always a non-null module script. - // <spec step="1">Let referencing script be - // referencingScriptOrModule.[[HostDefined]].</spec> - const ModuleScript* referrer_module = GetHostDefined(referrer); - - // <spec step="2">Let moduleMap be referencing script's settings object's - // module map.</spec> + // <spec step="3.2">Set settings object to referencing script's settings + // object.</spec> // - // Note: Blink finds out "module script's settings object" - // (Modulator) from context where HostResolveImportedModule was called. + // <spec step="4">Let moduleMap be settings object's module map.</spec> + // + // These are |modulator_| and |this|, respectively, because module script's + // settings object is always the current settings object in Blink. + + // <spec step="3.1">Let referencing script be + // referencingScriptOrModule.[[HostDefined]].</spec> + const ModuleScript* referrer_module = + GetModuleScriptFromModuleRecord(referrer); - // <spec step="3">Let url be the result of resolving a module specifier given - // referencing script's base URL and specifier.</spec> + // <spec step="3.3">Set base URL to referencing script's base URL.</spec> + // <spec step="5">Let url be the result of resolving a module specifier given + // base URL and specifier.</spec> KURL url = referrer_module->ResolveModuleSpecifier(specifier); - // <spec step="4">Assert: url is never failure, because resolving a module + // <spec step="6">Assert: url is never failure, because resolving a module // specifier must have been previously successful with these same two - // arguments.</spec> + // arguments ...</spec> DCHECK(url.IsValid()); - // <spec step="5">Let resolved module script be moduleMap[url]. (This entry + // <spec step="7">Let resolved module script be moduleMap[url]. (This entry // must exist for us to have gotten to this point.)</spec> ModuleScript* module_script = modulator_->GetFetchedModuleScript(url); - // <spec step="6">Assert: resolved module script is a module script (i.e., is + // <spec step="8">Assert: resolved module script is a module script (i.e., is // not null or "fetching").</spec> // - // <spec step="7">Assert: resolved module script's record is not null.</spec> + // <spec step="9">Assert: resolved module script's record is not null.</spec> DCHECK(module_script); CHECK(!module_script->Record().IsNull()); - // <spec step="8">Return resolved module script's record.</spec> + // <spec step="10">Return resolved module script's record.</spec> return module_script->Record(); } @@ -99,7 +108,7 @@ void ModuleRecordResolverImpl::ContextDestroyed(ExecutionContext*) { record_to_module_script_map_.clear(); } -void ModuleRecordResolverImpl::Trace(blink::Visitor* visitor) { +void ModuleRecordResolverImpl::Trace(Visitor* visitor) { ModuleRecordResolver::Trace(visitor); ContextLifecycleObserver::Trace(visitor); visitor->Trace(record_to_module_script_map_); diff --git a/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl.h b/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl.h index ecd381f27e3..ee9ec555b52 100644 --- a/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl.h +++ b/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl.h @@ -30,7 +30,7 @@ class CORE_EXPORT ModuleRecordResolverImpl final ExecutionContext* execution_context) : ContextLifecycleObserver(execution_context), modulator_(modulator) {} - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; USING_GARBAGE_COLLECTED_MIXIN(ModuleRecordResolverImpl); private: @@ -38,7 +38,8 @@ class CORE_EXPORT ModuleRecordResolverImpl final void RegisterModuleScript(const ModuleScript*) final; void UnregisterModuleScript(const ModuleScript*) final; - const ModuleScript* GetHostDefined(const ModuleRecord&) const final; + const ModuleScript* GetModuleScriptFromModuleRecord( + const ModuleRecord&) const final; // Implements "Runtime Semantics: HostResolveImportedModule" per HTML spec. // https://html.spec.whatwg.org/C/#hostresolveimportedmodule(referencingscriptormodule,-specifier)) diff --git a/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl_test.cc b/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl_test.cc index d369141fc7b..0b59125c96c 100644 --- a/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl_test.cc +++ b/chromium/third_party/blink/renderer/core/script/module_record_resolver_impl_test.cc @@ -25,7 +25,7 @@ class ModuleRecordResolverImplTestModulator final : public DummyModulator { ModuleRecordResolverImplTestModulator() {} ~ModuleRecordResolverImplTestModulator() override {} - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; void SetScriptState(ScriptState* script_state) { script_state_ = script_state; @@ -57,7 +57,7 @@ class ModuleRecordResolverImplTestModulator final : public DummyModulator { Member<ModuleScript> module_script_; }; -void ModuleRecordResolverImplTestModulator::Trace(blink::Visitor* visitor) { +void ModuleRecordResolverImplTestModulator::Trace(Visitor* visitor) { visitor->Trace(script_state_); visitor->Trace(module_script_); DummyModulator::Trace(visitor); diff --git a/chromium/third_party/blink/renderer/core/script/module_script.cc b/chromium/third_party/blink/renderer/core/script/module_script.cc index 4879fe71667..16fbb40347e 100644 --- a/chromium/third_party/blink/renderer/core/script/module_script.cc +++ b/chromium/third_party/blink/renderer/core/script/module_script.cc @@ -6,6 +6,8 @@ #include "third_party/blink/renderer/bindings/core/v8/script_value.h" #include "third_party/blink/renderer/core/script/module_record_resolver.h" +#include "third_party/blink/renderer/core/workers/worker_global_scope.h" +#include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h" #include "third_party/blink/renderer/platform/bindings/script_state.h" #include "v8/include/v8.h" @@ -90,7 +92,7 @@ KURL ModuleScript::ResolveModuleSpecifier(const String& module_request, return url; } -void ModuleScript::Trace(blink::Visitor* visitor) { +void ModuleScript::Trace(Visitor* visitor) { visitor->Trace(settings_object_); visitor->Trace(record_.UnsafeCast<v8::Value>()); visitor->Trace(parse_error_); @@ -104,6 +106,21 @@ void ModuleScript::RunScript(LocalFrame* frame, const SecurityOrigin*) { Modulator::CaptureEvalErrorFlag::kReport); } +void ModuleScript::RunScriptOnWorker(WorkerGlobalScope& worker_global_scope) { + DCHECK(worker_global_scope.IsContextThread()); + + WorkerReportingProxy& worker_reporting_proxy = + worker_global_scope.ReportingProxy(); + + worker_reporting_proxy.WillEvaluateModuleScript(); + // This |error| is always null because the second argument is |kReport|. + // TODO(nhiroki): Catch an error when an evaluation error happens. + // (https://crbug.com/680046) + ScriptValue error = SettingsObject()->ExecuteModule( + this, Modulator::CaptureEvalErrorFlag::kReport); + worker_reporting_proxy.DidEvaluateModuleScript(error.IsEmpty()); +} + std::ostream& operator<<(std::ostream& stream, const ModuleScript& module_script) { stream << "ModuleScript[" << &module_script; diff --git a/chromium/third_party/blink/renderer/core/script/module_script.h b/chromium/third_party/blink/renderer/core/script/module_script.h index 45e7fe86874..50d32f0ed33 100644 --- a/chromium/third_party/blink/renderer/core/script/module_script.h +++ b/chromium/third_party/blink/renderer/core/script/module_script.h @@ -48,7 +48,7 @@ class CORE_EXPORT ModuleScript : public Script { KURL ResolveModuleSpecifier(const String& module_request, String* failure_reason = nullptr) const; - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; virtual void ProduceCache() {} @@ -67,6 +67,7 @@ class CORE_EXPORT ModuleScript : public Script { return mojom::ScriptType::kModule; } void RunScript(LocalFrame*, const SecurityOrigin*) override; + void RunScriptOnWorker(WorkerGlobalScope&) override; friend class ModuleTreeLinkerTestModulator; diff --git a/chromium/third_party/blink/renderer/core/script/module_script_test.cc b/chromium/third_party/blink/renderer/core/script/module_script_test.cc index 204391421b7..6d1849c14f5 100644 --- a/chromium/third_party/blink/renderer/core/script/module_script_test.cc +++ b/chromium/third_party/blink/renderer/core/script/module_script_test.cc @@ -11,6 +11,7 @@ #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" #include "third_party/blink/renderer/core/script/js_module_script.h" +#include "third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h" #include "third_party/blink/renderer/core/testing/dummy_modulator.h" #include "third_party/blink/renderer/core/testing/page_test_base.h" #include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h" @@ -35,7 +36,7 @@ class ModuleScriptTestModulator final : public DummyModulator { return Vector<ModuleRequest>(); } - void Trace(blink::Visitor* visitor) override { + void Trace(Visitor* visitor) override { visitor->Trace(script_state_); DummyModulator::Trace(visitor); } @@ -82,6 +83,14 @@ class ModuleScriptTest : public ::testing::Test { ScriptFetchOptions()); } + static ValueWrapperSyntheticModuleScript* + CreateValueWrapperSyntheticModuleScript(Modulator* modulator, + v8::Local<v8::Value> local_value) { + return ValueWrapperSyntheticModuleScript::CreateWithDefaultExport( + local_value, modulator, KURL("https://fox.url/script.js"), + KURL("https://fox.url/"), ScriptFetchOptions()); + } + // Tests |window.foo| is set correctly, and reset |window.foo| for the next // test. static void TestFoo(V8TestingScope& scope) { @@ -239,4 +248,14 @@ TEST_F(ModuleScriptTest, V8CodeCache) { V8CodeCache::TagForCodeCache(cache_handler))); } -} // namespace blink +TEST_F(ModuleScriptTest, ValueWrapperSyntheticModuleScript) { + V8TestingScope scope; + v8::Local<v8::Value> local_value(v8::Number::New(scope.GetIsolate(), 1234)); + Modulator* modulator = + MakeGarbageCollected<ModuleScriptTestModulator>(scope.GetScriptState()); + ValueWrapperSyntheticModuleScript* module_script = + CreateValueWrapperSyntheticModuleScript(modulator, local_value); + ASSERT_FALSE(module_script->Record().IsNull()); +} + +} // namespace blink
\ No newline at end of file diff --git a/chromium/third_party/blink/renderer/core/script/pending_script.cc b/chromium/third_party/blink/renderer/core/script/pending_script.cc index b43e498c25a..b8fe2d8ab33 100644 --- a/chromium/third_party/blink/renderer/core/script/pending_script.cc +++ b/chromium/third_party/blink/renderer/core/script/pending_script.cc @@ -25,15 +25,15 @@ #include "third_party/blink/renderer/core/script/pending_script.h" -#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-shared.h" +#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h" #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document_parser_timing.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/local_frame.h" -#include "third_party/blink/renderer/core/frame/use_counter.h" #include "third_party/blink/renderer/core/script/ignore_destructive_write_count_incrementer.h" #include "third_party/blink/renderer/core/script/script_element_base.h" +#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" namespace blink { @@ -72,7 +72,7 @@ void PendingScript::Dispose() { DCHECK(!IsWatchingForLoad()); starting_position_ = TextPosition::BelowRangePosition(); - parser_blocking_load_start_time_ = TimeTicks(); + parser_blocking_load_start_time_ = base::TimeTicks(); DisposeInternal(); element_ = nullptr; @@ -122,7 +122,7 @@ ScriptElementBase* PendingScript::GetElement() const { void PendingScript::MarkParserBlockingLoadStartTime() { DCHECK(parser_blocking_load_start_time_.is_null()); - parser_blocking_load_start_time_ = CurrentTimeTicks(); + parser_blocking_load_start_time_ = base::TimeTicks::Now(); } // <specdef href="https://html.spec.whatwg.org/C/#execute-the-script-block"> @@ -172,7 +172,7 @@ void PendingScript::ExecuteScriptBlock(const KURL& document_url) { const bool was_canceled = WasCanceled(); const bool is_external = IsExternal(); const bool created_during_document_write = WasCreatedDuringDocumentWrite(); - const TimeTicks parser_blocking_load_start_time = + const base::TimeTicks parser_blocking_load_start_time = ParserBlockingLoadStartTime(); const bool is_controlled_by_script_runner = IsControlledByScriptRunner(); ScriptElementBase* element = element_; @@ -192,7 +192,7 @@ void PendingScript::ExecuteScriptBlockInternal( bool was_canceled, bool is_external, bool created_during_document_write, - TimeTicks parser_blocking_load_start_time, + base::TimeTicks parser_blocking_load_start_time, bool is_controlled_by_script_runner) { Document& element_document = element->GetDocument(); Document* context_document = element_document.ContextDocument(); @@ -204,17 +204,17 @@ void PendingScript::ExecuteScriptBlockInternal( return; } - if (parser_blocking_load_start_time > TimeTicks()) { + if (parser_blocking_load_start_time > base::TimeTicks()) { DocumentParserTiming::From(element_document) .RecordParserBlockedOnScriptLoadDuration( - CurrentTimeTicks() - parser_blocking_load_start_time, + base::TimeTicks::Now() - parser_blocking_load_start_time, created_during_document_write); } if (was_canceled) return; - TimeTicks script_exec_start_time = CurrentTimeTicks(); + base::TimeTicks script_exec_start_time = base::TimeTicks::Now(); { if (element->ElementHasDuplicateAttributes()) { @@ -235,48 +235,51 @@ void PendingScript::ExecuteScriptBlockInternal( IgnoreDestructiveWriteCountIncrementer incrementer( needs_increment ? context_document : nullptr); - // <spec step="4">Let old script element be the value to which the script - // element's node document's currentScript object was most recently + // <spec step="4.A.1">Let old script element be the value to which the + // script element's node document's currentScript object was most recently // set.</spec> // // This is implemented as push/popCurrentScript(). - // <spec step="5">Switch on the script's type:</spec> - // - // <spec step="5.A">"classic"</spec> + // <spec step="4">Switch on the script's type:</spec> + + // <spec step="4.A">"classic"</spec> // - // <spec step="5.A.1">If the script element's root is not a shadow root, + // <spec step="4.A.2">If the script element's root is not a shadow root, // then set the script element's node document's currentScript attribute to // the script element. Otherwise, set it to null.</spec> // - // <spec step="5.B">"module"</spec> + // Note: The shadow root check is implemented in + // HTMLScriptElement::SetScriptElementForBinding(). + + // <spec step="4.B">"module"</spec> // - // <spec step="5.B.1">Set the script element's node document's currentScript - // attribute to null.</spec> + // <spec step="4.B.1">Assert: The script element's node document's + // currentScript attribute is null.</spec> ScriptElementBase* current_script = nullptr; if (script->GetScriptType() == mojom::ScriptType::kClassic) current_script = element; context_document->PushCurrentScript(current_script); - // <spec step="5.A">"classic"</spec> + // <spec step="4.A">"classic"</spec> // - // <spec step="5.A.2">Run the classic script given by the script's + // <spec step="4.A.3">Run the classic script given by the script's // script.</spec> // // Note: This is where the script is compiled and actually executed. // - // <spec step="5.B">"module"</spec> + // <spec step="4.B">"module"</spec> // - // <spec step="5.B.2">Run the module script given by the script's + // <spec step="4.B.2">Run the module script given by the script's // script.</spec> script->RunScript(context_document->GetFrame(), element_document.GetSecurityOrigin()); - // <spec step="6">Set the script element's node document's currentScript + // <spec step="4.A.4">Set the script element's node document's currentScript // attribute to old script element.</spec> context_document->PopCurrentScript(current_script); - // <spec step="7">Decrement the ignore-destructive-writes counter of + // <spec step="5">Decrement the ignore-destructive-writes counter of // neutralized doc, if it was incremented in the earlier step.</spec> // // Implemented as the scope out of IgnoreDestructiveWriteCountIncrementer. @@ -289,17 +292,17 @@ void PendingScript::ExecuteScriptBlockInternal( if (!is_controlled_by_script_runner) { DocumentParserTiming::From(element_document) .RecordParserBlockedOnScriptExecutionDuration( - CurrentTimeTicks() - script_exec_start_time, + base::TimeTicks::Now() - script_exec_start_time, created_during_document_write); } - // <spec step="8">If the script is from an external file, then fire an event + // <spec step="6">If the script is from an external file, then fire an event // named load at the script element.</spec> if (is_external) element->DispatchLoadEvent(); } -void PendingScript::Trace(blink::Visitor* visitor) { +void PendingScript::Trace(Visitor* visitor) { visitor->Trace(element_); visitor->Trace(client_); visitor->Trace(original_context_document_); @@ -315,6 +318,7 @@ bool PendingScript::IsControlledByScriptRunner() const { case ScriptSchedulingType::kParserBlocking: case ScriptSchedulingType::kParserBlockingInline: case ScriptSchedulingType::kImmediate: + case ScriptSchedulingType::kForceDefer: return false; case ScriptSchedulingType::kInOrder: diff --git a/chromium/third_party/blink/renderer/core/script/pending_script.h b/chromium/third_party/blink/renderer/core/script/pending_script.h index cfc8a018ca8..08b44d08616 100644 --- a/chromium/third_party/blink/renderer/core/script/pending_script.h +++ b/chromium/third_party/blink/renderer/core/script/pending_script.h @@ -53,7 +53,7 @@ class CORE_EXPORT PendingScriptClient : public GarbageCollectedMixin { // streaming finishes. virtual void PendingScriptFinished(PendingScript*) = 0; - void Trace(blink::Visitor* visitor) override {} + void Trace(Visitor* visitor) override {} }; // A container for an script after "prepare a script" until it is executed. @@ -73,7 +73,7 @@ class CORE_EXPORT PendingScript // Returns the time the load of this script started blocking the parser, or // zero if this script hasn't yet blocked the parser, in // monotonicallyIncreasingTime. - TimeTicks ParserBlockingLoadStartTime() const { + base::TimeTicks ParserBlockingLoadStartTime() const { return parser_blocking_load_start_time_; } @@ -85,7 +85,7 @@ class CORE_EXPORT PendingScript virtual mojom::ScriptType GetScriptType() const = 0; - virtual void Trace(blink::Visitor*); + virtual void Trace(Visitor*); const char* NameInHeapSnapshot() const override { return "PendingScript"; } // Returns nullptr when "script's script is null", i.e. an error occurred. @@ -153,7 +153,7 @@ class CORE_EXPORT PendingScript bool was_canceled, bool is_external, bool created_during_document_write, - TimeTicks parser_blocking_load_start_time, + base::TimeTicks parser_blocking_load_start_time, bool is_controlled_by_script_runner); // |m_element| must points to the corresponding ScriptLoader's @@ -162,7 +162,7 @@ class CORE_EXPORT PendingScript Member<ScriptElementBase> element_; TextPosition starting_position_; // Only used for inline script tags. - TimeTicks parser_blocking_load_start_time_; + base::TimeTicks parser_blocking_load_start_time_; ScriptSchedulingType scheduling_type_ = ScriptSchedulingType::kNotSet; diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/PRESUBMIT.py b/chromium/third_party/blink/renderer/core/script/resources/layered_api/PRESUBMIT.py new file mode 100644 index 00000000000..4364aafa726 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/PRESUBMIT.py @@ -0,0 +1,39 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +import sys + + +def _CommonChecks(input_api, output_api): + results = [] + # TODO(tkent): {kv-storage,toast,virtual-scroller}/.eslintrc.js specify babel-eslint parser, which + # is not in third_party/node/node_modules/. + mjs_files = input_api.AffectedFiles( + file_filter=lambda f: (f.LocalPath().endswith('.mjs') and + not '/virtual-scroller/' in f.LocalPath() and + not '/kv-storage/' in f.LocalPath() and + not '/toast/' in f.LocalPath()), + include_deletes=False) + if not mjs_files: + return results + try: + old_sys_path = sys.path[:] + cwd = input_api.PresubmitLocalPath() + sys.path += [input_api.os_path.join(cwd, '..', '..', '..', '..', '..', + '..', '..', 'tools')] + import web_dev_style.js_checker + checker = web_dev_style.js_checker.JSChecker(input_api, output_api) + results += checker.RunEsLintChecks(mjs_files) + finally: + sys.path = old_sys_path + return results + + +def CheckChangeOnUpload(input_api, output_api): + return _CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return _CommonChecks(input_api, output_api) diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/README.md b/chromium/third_party/blink/renderer/core/script/resources/layered_api/README.md index 270d8117aa5..479818efa35 100644 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/README.md +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/README.md @@ -22,11 +22,27 @@ and commit these files together with the changes under resources/layered_api/. ## Which files are bundled -All files under this directory will be included in the grdp and thus bundled -in the Chromium binary, except for +All files under -- Files directly under `core/script/resources/layered_api`, or -- Files starting with '.', 'README', or 'OWNERS'. +- Sub-directories which have 'index.mjs' or +- Directories of which last path component is 'internal' + +will be included in the grdp and thus bundled in the Chrome binary, +except for files starting with '.', 'README', or 'OWNERS'. So be careful about binary size increase when you add new files or add more contents to existing files. + +## What are exposed + +All bundled resources are mapped to `std-internal://path-relative-to-here`, and +`std-internal:` resources are not accessible from the web. Resources loaded as +`std-internal:` can import other `std-internal:` resources. + +For example, `layered_api/foo/bar/baz.mjs` is mapped to +`std-internal://foo/bar/baz.mjs`. + +All `index.mjs` resources are mapped to `std:directory-name-relative-to-here` +too, and they are web-exposed. For example, +`layered_api/elements/toast/index.mjs` is mapped to `std:elements/toast` as +well as `std-internal://elements/toast/index.mjs`. diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/internal/reflection.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/internal/reflection.mjs new file mode 100644 index 00000000000..989a4aa1ff1 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/internal/reflection.mjs @@ -0,0 +1,63 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @file Manage attribute-property reflections. + * https://html.spec.whatwg.org/C/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes + */ + +/** + * Add a bool reflection property to the specified prototype for the specified + * attribute. + * + * @param {!Object} proto An element prototype + * @param {string} attrName An attribute name + * @param {string} propName An optional property name. attrName will be used if + * this argument is omitted. + */ +export function installBool(proto, attrName, propName = attrName) { + let getter = function() { + return this.hasAttribute(attrName); + }; + let setter = function(value) { + this.toggleAttribute(attrName, Boolean(value)); + }; + Object.defineProperty( + getter, 'name', + {configurable: true, enumerable: false, value: 'get ' + propName}); + Object.defineProperty( + setter, 'name', + {configurable: true, enumerable: false, value: 'set ' + propName}); + Object.defineProperty( + proto, propName, + {configurable: true, enumerable: true, get: getter, set: setter}); +} + +/** + * Add a DOMString reflection property to the specified prototype for the + * specified attribute. + * + * @param {!Element} element An element prototype + * @param {string} attrName An attribute name + * @param {string} propName An optional property name. attrName will be used if + * this argument is omitted. + */ +export function installString(proto, attrName, propName = attrName) { + let getter = function() { + let value = this.getAttribute(attrName); + return value === null ? '' : value; + }; + let setter = function(value) { + this.setAttribute(attrName, value); + }; + Object.defineProperty( + getter, 'name', + {configurable: true, enumerable: false, value: 'get ' + propName}); + Object.defineProperty( + setter, 'name', + {configurable: true, enumerable: false, value: 'set ' + propName}); + Object.defineProperty( + proto, propName, + {configurable: true, enumerable: true, get: getter, set: setter}); +} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/README.md b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/README.md new file mode 100644 index 00000000000..c8962f767cb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/README.md @@ -0,0 +1,3 @@ +# Switch control element + +https://github.com/tkent-google/std-switch/blob/master/README.md diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/face_utils.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/face_utils.mjs new file mode 100644 index 00000000000..8db1f4177ff --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/face_utils.mjs @@ -0,0 +1,75 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @file Utilities for form-associated custom elements + */ + +import * as reflection from '../internal/reflection.mjs'; + +function installGetter(proto, propName, getter) { + Object.defineProperty( + getter, 'name', + {configurable: true, enumerable: false, value: 'get ' + propName}); + Object.defineProperty( + proto, propName, {configurable: true, enumerable: true, get: getter}); +} + +/** + * Add the following properties to |proto|. + * - disabled + * - name + * - type + * - form + * - willValidate + * - validity + * - validationMessage + * - labels + * - checkValidity() + * - reportValidity() + * - setCustomValidity(error) + * + * @param {!Object} proto An Element prototype which will have properties + * @param {!Symbol} internals A Symbol of the ElementInternals property of the + * element + */ +export function installPropertiesAndFunctions(proto, internals) { + reflection.installBool(proto, 'disabled'); + reflection.installString(proto, 'name'); + installGetter(proto, 'type', function() { + if (!(this instanceof proto.constructor)) { + throw TypeError( + 'The context object is not an instance of ' + proto.contructor.name); + } + return this.localName; + }); + + installGetter(proto, 'form', function() { + return this[internals].form; + }); + installGetter(proto, 'willValidate', function() { + return this[internals].willValidate; + }); + installGetter(proto, 'validity', function() { + return this[internals].validity; + }); + installGetter(proto, 'validationMessage', function() { + return this[internals].validationMessage; + }); + installGetter(proto, 'labels', function() { + return this[internals].labels; + }); + proto.checkValidity = function() { + return this[internals].checkValidity(); + }; + proto.reportValidity = function() { + return this[internals].reportValidity(); + }; + proto.setCustomValidity = function(error) { + if (error === undefined) { + throw new TypeError('Too few arguments'); + } + this[internals].setValidity({customError: true}, error); + }; +} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/index.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/index.mjs new file mode 100644 index 00000000000..6c7be3d46fc --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/index.mjs @@ -0,0 +1,128 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import * as face from './face_utils.mjs'; +import * as reflection from '../internal/reflection.mjs'; +import { SwitchTrack } from './track.mjs'; +import * as style from './style.mjs'; + +// https://github.com/tkent-google/std-switch/issues/2 +const STATE_ATTR = 'on'; + +// Private property symbols +// TODO(tkent): Use private fields. +const _internals = Symbol(); +const _track = Symbol(); +const _rippleElement = Symbol(); +const _containerElement = Symbol(); + +export class StdSwitchElement extends HTMLElement { + // TODO(tkent): The following should be |static fooBar = value;| + // after enabling babel-eslint. + static get formAssociated() { + return true; + } + static get observedAttributes() { + return [STATE_ATTR]; + } + + constructor() { + super(); + if (new.target !== StdSwitchElement) { + throw new TypeError('Illegal constructor: StdSwitchElement is not ' + + 'extensible for now'); + } + this[_internals] = this.attachInternals(); + this._initializeDOM(); + + this.addEventListener('click', this._onClick); + this.addEventListener('keypress', this._onKeyPress); + } + + attributeChangedCallback(attrName, oldValue, newValue) { + if (attrName == STATE_ATTR) { + this[_track].value = newValue !== null; + // TODO(tkent): We should not add aria-checked attribute. + // https://github.com/WICG/aom/issues/127 + this.setAttribute('aria-checked', newValue !== null ? 'true' : 'false'); + } + } + + connectedCallback() { + // TODO(tkent): We should not add tabindex attribute. + // https://github.com/w3c/webcomponents/issues/762 + if (!this.hasAttribute('tabindex')) { + this.setAttribute('tabindex', '0'); + } + + // TODO(tkent): We should not add role attribute. + // https://github.com/WICG/aom/issues/127 + if (!this.hasAttribute('role')) { + this.setAttribute('role', 'switch'); + } + } + + // TODO(tkent): Make this private. + _initializeDOM() { + let factory = this.ownerDocument; + let root = this.attachShadow({mode: 'closed'}); + this[_containerElement] = factory.createElement('span'); + this[_containerElement].id = 'container'; + // Shadow elements should be invisible for a11y technologies. + this[_containerElement].setAttribute('aria-hidden', 'true'); + root.appendChild(this[_containerElement]); + + this[_track] = new SwitchTrack(factory); + this[_containerElement].appendChild(this[_track].element); + this[_track].value = this.on; + + let thumbElement = this[_containerElement].appendChild(factory.createElement('span')); + thumbElement.id = 'thumb'; + thumbElement.part.add('thumb'); + + this[_rippleElement] = thumbElement.appendChild(factory.createElement('span')); + this[_rippleElement].id = 'ripple'; + + root.adoptedStyleSheets = [style.styleSheetFactory()()]; + } + + // TODO(tkent): Make this private. + _onClick(event) { + for (let element of this[_containerElement].querySelectorAll('*')) { + style.markTransition(element); + } + this.on = !this.on; + this.dispatchEvent(new Event('input', {bubbles: true})); + this.dispatchEvent(new Event('change', {bubbles: true})); + } + + // TODO(tkent): Make this private. + _onKeyPress(event) { + if (event.code == 'Space') { + // Do not scroll the page. + event.preventDefault(); + this._onClick(event); + } + } +} + +reflection.installBool(StdSwitchElement.prototype, STATE_ATTR); +reflection.installBool( + StdSwitchElement.prototype, 'default' + STATE_ATTR, + 'default' + STATE_ATTR.charAt(0).toUpperCase() + STATE_ATTR.substring(1)); +face.installPropertiesAndFunctions(StdSwitchElement.prototype, _internals); + +// This is necessary for anyObject.toString.call(switchInstance). +Object.defineProperty(StdSwitchElement.prototype, Symbol.toStringTag, { + configurable: true, + enumerable: false, + value: 'StdSwitchElement', + writable: false +}); + +customElements.define('std-switch', StdSwitchElement); +delete StdSwitchElement.formAssociated; +delete StdSwitchElement.observedAttributes; +delete StdSwitchElement.prototype.attributeChangedCallback; +delete StdSwitchElement.prototype.connectedCallback; diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/style.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/style.mjs new file mode 100644 index 00000000000..6b0bc2eca78 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/style.mjs @@ -0,0 +1,224 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Style constant values. +const COLOR_ON = '#0077FF'; +const TRACK_RADIUS = '13px'; +const TRACK_BORDER_WIDTH = '2px'; +const THUMB_HEIGHT = '22px'; +const THUMB_WIDTH = '22px'; +const THUMB_MARGIN_START = '2px'; +const THUMB_MARGIN_END = '2px'; + +// Returns a function returning a CSSStyleSheet(). +// TODO(tkent): Share this stylesheet factory feature with elements/toast/. +export function styleSheetFactory() { + let styleSheet; + return () => { + if (!styleSheet) { + styleSheet = new CSSStyleSheet(); + styleSheet.replaceSync(` +:host { + block-size: 26px; + border: none; + box-sizing: border-box; + display: inline-block; + inline-size: 54px; + user-select: none; + vertical-align: middle; +} + +#container { + align-items: center; + block-size: 100%; + display: inline-flex; + inline-size: 100%; +} + +#thumb { + background: white; + block-size: ${THUMB_HEIGHT}; + border-radius: calc(${THUMB_HEIGHT} / 2); + border: 1px solid black; + box-sizing: border-box; + display: inline-block; + margin-inline-start: calc(-100% + ${THUMB_MARGIN_START}); + inline-size: ${THUMB_WIDTH}; +} + +/* :host::part(thumb-transitioning) doesn't work. crbug.com/980506 */ +#thumb[part~="thumb-transitioning"] { + transition: all linear 0.1s; +} + +:host([on]) #thumb { + border: 1px solid ${COLOR_ON}; + margin-inline-start: calc(0px - ${THUMB_WIDTH} - ${THUMB_MARGIN_END}); +} + +#track { + block-size: 100%; + border-radius: ${TRACK_RADIUS}; + border: ${TRACK_BORDER_WIDTH} solid #dddddd; + box-shadow: 0 0 0 1px #f8f8f8; + box-sizing: border-box; + display: inline-block; + inline-size: 100%; + overflow: hidden; + padding: 0px; +} + +#trackFill { + background: ${COLOR_ON}; + block-size: 100%; + border-radius: calc(${TRACK_RADIUS}) - ${TRACK_BORDER_WIDTH}); + box-shadow: none; + box-sizing: border-box; + display: inline-block; + inline-size: 0%; + vertical-align: top; +} + +#trackFill[part~="track-fill-transitioning"] { + transition: all linear 0.1s; +} + +:host([on]) #track { + border: ${TRACK_BORDER_WIDTH} solid ${COLOR_ON}; +} + +:host(:focus) { + outline-offset: 4px; +} + +:host(:focus) #track { + box-shadow: 0 0 0 2px #f8f8f8; +} + +:host([on]:focus) #track { + box-shadow: 0 0 0 2px #dddddd; +} + +:host(:focus) #thumb { + border: 2px solid black; +} + +:host([on]:focus) #thumb { + border: 2px solid ${COLOR_ON}; +} + +:host(:not(:focus-visible):focus) { + outline: none; +} + +:host(:not(:disabled):hover) #thumb { + inline-size: 26px; +} + +:host([on]:not(:disabled):hover) #thumb { + margin-inline-start: calc(0px - 26px - ${THUMB_MARGIN_END}); +} + +:host(:active) #track { + background: #dddddd; +} + +:host([on]:active) #track { + border: 2px solid #77bbff; + box-shadow: 0 0 0 2px #f8f8f8; +} + +:host([on]:active) #trackFill { + background: #77bbff; +} + +:host(:disabled) { + opacity: 0.38; +} + +/* + * display:inline-block in the :host ruleset overrides 'hidden' handling + * by the user agent. + */ +:host([hidden]) { + display: none; +} + +`); + } + return styleSheet; + }; +} + +/** + * @param {!Element} element + */ +function setupTransitionCounter(element) { + if (element.runningTransitions !== undefined) { + return; + } + element.runningTransitions = 0; + element.addEventListener('transitionrun', e => { + if (e.target === element) { + ++element.runningTransitions; + } + }); + let handleEndOrCancel = e => { + // Need to check runningTransitions>0 due to superfluous transitioncancel + // events; crbug.com/979556. + if (e.target === element && element.runningTransitions > 0) { + --element.runningTransitions; + } + }; + element.addEventListener('transitionend', handleEndOrCancel); + element.addEventListener('transitioncancel', handleEndOrCancel); +} + +/** + * Add '$part-transitioning' part to the element, and remove it on 'transitionend' + * event or remove it immediately if the element has no transitions. + * + * TODO(tkent): We should apply custom state. + * + * @param {!Element} element + */ +export function markTransition(element) { + // Should check hasAttribute() to avoid creating a DOMTokenList instance. + if (!element.hasAttribute('part') || element.part.length < 1) { + return; + } + + setupTransitionCounter(element); + + const partName = element.part[0] + '-transitioning'; + if (element.part.contains(partName)) { + return; + } + // The style with partName might have transitions, and might have no + // transitions. We need to add partName anyway because we have no other + // ways to check existence of transitions. Then, we need to remove + // partName because state change without markTransition() should have + // style without partName. + // + // We add partName in an animation frame, and continue to request + // animation frames until runningTransitions becomes 0. If the style with + // partName has no transitions, runningTransitions keeps 0, and the second + // animation frame removes partName. + window.requestAnimationFrame(() => { + element.part.add(partName); + + // If the element has a transition, it must start on the rendering just + // after this rAF callback. So we check runningTransitions in the next + // frame. + const removeIfNoTransitions = () => { + // No transitions started, or all transitions were completed. + if (element.runningTransitions === 0) { + element.part.remove(partName); + } else { + window.requestAnimationFrame(removeIfNoTransitions); + } + }; + window.requestAnimationFrame(removeIfNoTransitions); + }); +} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/track.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/track.mjs new file mode 100644 index 00000000000..aa8fc00c3d1 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/track.mjs @@ -0,0 +1,74 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Private property symbols +// TODO(tkent): Use private fields. +const _value = Symbol(); +const _trackElement = Symbol(); +const _fillElement = Symbol(); +const _slotElement = Symbol(); + +export class SwitchTrack { + + /** + * @param {!Document} factory A factory for elements created for this track. + */ + constructor(factory) { + this[_value] = false; + this._initializeDOM(factory); + } + + /** + * @return {!Element} + */ + get element() { + return this[_trackElement]; + } + + /** + * @param {Boolean} newValue + */ + set value(newValue) { + let oldValue = this[_value]; + this[_value] = Boolean(newValue); + + let bar = this[_fillElement]; + if (bar) { + bar.style.inlineSize = this[_value] ? '100%' : '0%'; + if (oldValue != this[_value]) { + this._addSlot(); + } + } + } + + // TODO(tkent): Use private fields. #initializeDOM = factory => { ...}; + /** + * @param {!Document} factory A factory for elements created for this track. + */ + _initializeDOM(factory) { + this[_trackElement] = factory.createElement('div'); + this[_trackElement].id = 'track'; + this[_trackElement].part.add('track'); + this[_fillElement] = factory.createElement('span'); + this[_fillElement].id = 'trackFill'; + this[_fillElement].part.add('track-fill'); + this[_trackElement].appendChild(this[_fillElement]); + this[_slotElement] = factory.createElement('slot'); + this._addSlot(); + } + + /** + * Add the <slot> + * - next to _fillElement if _value is true + * - as a child of _fillElement if _value is false + * This behavior is helpful to show text in the track. + */ + _addSlot() { + if (this[_value]) { + this[_fillElement].appendChild(this[_slotElement]); + } else { + this[_trackElement].appendChild(this[_slotElement]); + } + } +} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/.eslintrc.js b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/.eslintrc.js new file mode 100644 index 00000000000..8f9a67630c4 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/.eslintrc.js @@ -0,0 +1,338 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module.exports = { + root: true, + env: { + es6: true, + browser: true + }, + 'parser': 'babel-eslint', + parserOptions: { + sourceType: 'module', + ecmaVersion: 2019 + }, + rules: { + 'for-direction': 'error', + 'getter-return': 'error', + 'no-async-promise-executor': 'error', + 'no-await-in-loop': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': ['error', 'except-parens'], + 'no-console': 'error', + 'no-constant-condition': ['error', {checkLoops: false}], + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-dupe-args': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty': 'error', + 'no-empty-character-class': 'error', + 'no-ex-assign': 'error', + 'no-extra-boolean-cast': 'error', + 'no-extra-parens': [ + 'error', + 'all', + { + conditionalAssign: false, + nestedBinaryExpressions: false, + returnAssign: false + } + ], + 'no-extra-semi': 'error', + 'no-func-assign': 'error', + 'no-inner-declarations': 'off', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-misleading-character-class': 'error', + 'no-obj-calls': 'error', + 'no-prototype-builtins': 'error', + 'no-regex-spaces': 'error', + 'no-sparse-arrays': 'error', + 'no-template-curly-in-string': 'error', + 'no-unexpected-multiline': 'error', + 'no-unreachable': 'error', + 'no-unsafe-finally': 'off', + 'no-unsafe-negation': 'error', + 'use-isnan': 'error', + 'valid-typeof': 'error', + 'accessor-pairs': 'error', + 'array-callback-return': 'error', + 'block-scoped-var': 'off', + 'class-methods-use-this': 'off', + 'complexity': 'off', + 'consistent-return': 'error', + 'curly': ['error', 'all'], + 'default-case': 'off', + 'dot-location': ['error', 'property'], + 'dot-notation': 'error', + 'eqeqeq': 'error', + 'guard-for-in': 'off', + 'no-alert': 'error', + 'no-caller': 'error', + 'no-case-declarations': 'error', + 'no-div-regex': 'off', + 'no-else-return': 'error', + 'no-empty-function': 'off', + 'no-empty-pattern': 'error', + 'no-eq-null': 'error', + 'no-eval': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-label': 'error', + 'no-fallthrough': 'error', + 'no-floating-decimal': 'error', + 'no-global-assign': 'error', + 'no-implicit-coercion': 'error', + 'no-implicit-globals': 'error', + 'no-implied-eval': 'error', + 'no-invalid-this': 'error', + 'no-iterator': 'error', + 'no-labels': ['error', {allowLoop: true}], + 'no-lone-blocks': 'error', + 'no-loop-func': 'error', + 'no-magic-numbers': ['error', {ignore: [0, 1]}], + 'no-multi-spaces': ['error', {ignoreEOLComments: true}], + 'no-multi-str': 'error', + 'no-new': 'error', + 'no-new-func': 'error', + 'no-new-wrappers': 'error', + 'no-octal': 'error', + 'no-octal-escape': 'error', + 'no-param-reassign': 'off', + 'no-process-env': 'error', + 'no-proto': 'error', + 'no-redeclare': 'error', + 'no-restricted-properties': 'off', + 'no-return-assign': ['error', 'except-parens'], + 'no-return-await': 'error', + 'no-script-url': 'off', + 'no-self-assign': 'error', + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-throw-literal': 'error', + 'no-unmodified-loop-condition': 'error', + 'no-unused-expressions': 'error', + 'no-unused-labels': 'error', + 'no-useless-call': 'error', + 'no-useless-concat': 'error', + 'no-useless-escape': 'error', + 'no-useless-return': 'error', + 'no-void': 'error', + 'no-warning-comments': 'off', + 'no-with': 'error', + 'prefer-promise-reject-errors': 'error', + 'radix': ['error', 'as-needed'], + 'require-await': 'off', + 'vars-on-top': 'off', + 'wrap-iife': ['error', 'outside'], + 'yoda': ['error', 'never'], + 'strict': ['error', 'global'], + 'init-declarations': 'off', + 'no-delete-var': 'error', + 'no-label-var': 'error', + 'no-restricted-globals': 'off', + 'no-shadow': 'error', + 'no-shadow-restricted-names': 'error', + 'no-undef': 'error', + 'no-undef-init': 'error', + 'no-undefined': 'off', + 'no-unused-vars': 'error', + 'no-use-before-define': ['error', 'nofunc'], + 'callback-return': 'off', + 'global-require': 'error', + 'handle-callback-err': 'error', + 'no-buffer-constructor': 'error', + 'no-mixed-requires': ['error', true], + 'no-new-require': 'error', + 'no-path-concat': 'error', + 'no-process-exit': 'error', + 'no-restricted-modules': 'off', + 'no-sync': 'off', + 'array-bracket-newline': ['error', {multiline: true}], + 'array-bracket-spacing': ['error', 'never'], + 'array-element-newline': 'off', + 'block-spacing': ['error', 'always'], + 'brace-style': [ + 'error', + '1tbs', + {allowSingleLine: false} + ], + camelcase: ['error', {properties: 'always'}], + 'capitalized-comments': 'off', + 'comma-dangle': ['error', 'always-multiline'], + 'comma-spacing': [ + 'error', + { + before: false, + after: true + } + ], + 'comma-style': ['error', 'last'], + 'computed-property-spacing': ['error', 'never'], + 'consistent-this': 'off', + 'eol-last': 'error', + 'func-call-spacing': ['error', 'never'], + 'func-name-matching': 'error', + 'func-names': 'off', + 'func-style': ['error', 'declaration'], + 'function-paren-newline': 'off', + 'id-blacklist': 'off', + 'id-length': 'off', + 'id-match': 'off', + indent: 'off', // not really compatible with clang-format + 'jsx-quotes': 'off', + 'key-spacing': [ + 'error', + { + beforeColon: false, + afterColon: true, + mode: 'strict' + } + ], + 'keyword-spacing': [ + 'error', + { + before: true, + after: true + } + ], + 'line-comment-position': 'off', + 'linebreak-style': ['error', 'unix'], + 'lines-around-comment': 'off', + 'max-depth': 'off', + 'max-len': 'off', + 'max-lines': 'off', + 'max-nested-callbacks': 'off', + 'max-params': 'off', + 'max-statements': 'off', + 'max-statements-per-line': ['error', {max: 1}], + 'multiline-ternary': ['error', 'always-multiline'], + 'new-cap': 'error', + 'new-parens': 'error', + 'newline-per-chained-call': 'off', + 'no-array-constructor': 'error', + 'no-bitwise': 'off', + 'no-continue': 'off', + 'no-inline-comments': 'off', + 'no-lonely-if': 'error', + 'no-mixed-operators': [ + 'error', + { + groups: [ + ['&', '|', '^', '~', '<<', '>>', '>>>'], + ['==', '!=', '===', '!==', '>', '>=', '<', '<='], + ['&&', '||'], + ['in', 'instanceof'] + ] + } + ], + 'no-mixed-spaces-and-tabs': 'error', + 'no-multi-assign': 'off', + 'no-multiple-empty-lines': 'error', + 'no-negated-condition': 'off', + 'no-nested-ternary': 'error', + 'no-new-object': 'error', + 'no-plusplus': 'off', + 'no-restricted-syntax': 'off', + 'no-tabs': 'error', + 'no-ternary': 'off', + 'no-trailing-spaces': 'error', + 'no-underscore-dangle': 'off', + 'no-unneeded-ternary': 'error', + 'no-whitespace-before-property': 'error', + 'nonblock-statement-body-position': 'error', + 'object-curly-newline': ['error', {consistent: true}], + 'object-curly-spacing': ['error', 'never'], + 'object-property-newline': 'off', + 'one-var': ['error', 'never'], + 'one-var-declaration-per-line': ['error', 'initializations'], + 'operator-assignment': ['error', 'always'], + 'operator-linebreak': ['error', 'after'], + 'padded-blocks': ['error', 'never'], + 'padding-line-between-statements': 'off', + 'quote-props': ['error', 'as-needed'], + quotes: [ + 'error', + 'single', + { + avoidEscape: true, + allowTemplateLiterals: true + } + ], + semi: ['error', 'always'], + 'semi-spacing': 'error', + 'semi-style': 'error', + 'sort-keys': 'off', + 'sort-vars': 'off', + 'space-before-blocks': ['error', 'always'], + 'space-before-function-paren': [ + 'error', + { + anonymous: 'always', + named: 'never' + } + ], + 'space-in-parens': ['error', 'never'], + 'space-infix-ops': 'error', + 'space-unary-ops': [ + 'error', + { + words: true, + nonwords: false + } + ], + 'spaced-comment': ['error', 'always'], + 'switch-colon-spacing': 'error', + 'template-tag-spacing': 'error', + 'unicode-bom': 'error', + 'wrap-regex': 'off', + 'arrow-body-style': 'off', + 'arrow-parens': ['error', 'as-needed'], + 'arrow-spacing': 'error', + 'constructor-super': 'error', + 'generator-star-spacing': ['error', 'neither'], + 'no-class-assign': 'error', + 'no-confusing-arrow': 'off', + 'no-const-assign': 'error', + 'no-dupe-class-members': 'error', + 'no-duplicate-imports': 'error', + 'no-new-symbol': 'error', + 'no-restricted-imports': 'off', + 'no-this-before-super': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': 'error', + 'no-var': 'error', + 'object-shorthand': 'error', + 'prefer-arrow-callback': 'error', + 'prefer-const': ['error', {ignoreReadBeforeAssign: true}], + 'prefer-destructuring': [ + 'error', + { + VariableDeclarator: { + array: false, + object: true + }, + AssignmentExpression: { + array: false, + object: false + } + }, + { + enforceForRenamedProperties: false + } + ], + 'prefer-numeric-literals': 'error', + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'prefer-template': 'off', + 'require-yield': 'error', + 'rest-spread-spacing': 'error', + 'sort-imports': 'off', + 'symbol-description': 'error', + 'template-curly-spacing': ['error', 'never'], + 'yield-star-spacing': ['error', 'after'] + } +}; diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/OWNERS b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/OWNERS new file mode 100644 index 00000000000..d76cdfa11f0 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/OWNERS @@ -0,0 +1 @@ +fergal@chromium.org diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs new file mode 100644 index 00000000000..69232f8475a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs @@ -0,0 +1,280 @@ +/** + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * @fileoverview This file defines the class for the Standard Toast LAPI + * and the accompanying showToast() function. + * EXPLAINER: https://github.com/jackbsteinberg/std-toast + * TEST PATH: /chromium/src/third_party/blink/web_tests/external/wpt/std-toast/* + * @package + */ + +import * as reflection from '../internal/reflection.mjs'; + +const DEFAULT_DURATION = 3000; +const TYPES = new Set(['success', 'warning', 'error']); + +function stylesheetFactory() { + let stylesheet; + return function generate() { + if (!stylesheet) { + stylesheet = new CSSStyleSheet(); + stylesheet.replaceSync(` + :host { + position: fixed; + bottom: 1em; + right: 1em; + border: solid; + padding: 1em; + background: white; + color: black; + z-index: 1; + } + + :host(:not([open])) { + display: none; + } + + .default-closebutton { + user-select: none; + } + + :host([type=success i]) { + border-color: green; + } + + :host([type=warning i]) { + border-color: orange; + } + + :host([type=error i]) { + border-color: red; + } + `); + // TODO(jacksteinberg): use offset-block-end: / offset-inline-end: over bottom: / right: + // when implemented https://bugs.chromium.org/p/chromium/issues/detail?id=538475 + } + return stylesheet; + }; +} + +const generateStylesheet = stylesheetFactory(); + +export class StdToastElement extends HTMLElement { + static observedAttributes = ['open', 'closebutton']; + #shadow = this.attachShadow({mode: 'closed'}); + #timeoutID; + #actionSlot; + #closeButtonElement; + #setCloseTimeout = duration => { + clearTimeout(this.#timeoutID); + + if (duration === Infinity) { + this.#timeoutID = null; + } else { + this.#timeoutID = setTimeout(() => { + this.removeAttribute('open'); + }, duration); + } + }; + + constructor(message) { + super(); + + this.#shadow.adoptedStyleSheets = [generateStylesheet()]; + + this.#shadow.appendChild(document.createElement('slot')); + + this.#actionSlot = document.createElement('slot'); + this.#actionSlot.setAttribute('name', 'action'); + this.#shadow.appendChild(this.#actionSlot); + + this.#closeButtonElement = document.createElement('button'); + this.#closeButtonElement.setAttribute('part', 'closebutton'); + setDefaultCloseButton(this.#closeButtonElement); + this.#shadow.appendChild(this.#closeButtonElement); + + this.#closeButtonElement.addEventListener('click', () => { + this.hide(); + }); + + if (message !== undefined) { + this.textContent = message; + } + } + + connectedCallback() { + if (!this.hasAttribute('role')) { + this.setAttribute('role', 'status'); + } + // TODO(jacksteinberg): use https://github.com/whatwg/html/pull/4658 when implemented + } + + get action() { + return this.#actionSlot.assignedNodes().length !== 0 ? + this.#actionSlot.assignedNodes()[0] : + null; + } + + set action(val) { + const previousAction = this.action; + if (val !== null) { + if (!isElement(val)) { + throw new TypeError('Invalid argument: must be type Element'); + } + + val.setAttribute('slot', 'action'); + this.insertBefore(val, previousAction); + } + + if (previousAction !== null) { + previousAction.remove(); + } + } + + get closeButton() { + if (this.hasAttribute('closebutton')) { + const closeAttr = this.getAttribute('closebutton'); + return closeAttr === '' ? true : closeAttr; + } + return false; + } + + set closeButton(val) { + if (val === true) { + this.setAttribute('closebutton', ''); + } else if (val === false) { + this.removeAttribute('closebutton'); + } else { + this.setAttribute('closebutton', val); + } + } + + get type() { + const typeAttr = this.getAttribute('type'); + if (typeAttr === null) { + return ''; + } + + const typeAttrLower = typeAttr.toLowerCase(); + + if (TYPES.has(typeAttrLower)) { + return typeAttrLower; + } + + return ''; + } + + set type(val) { + this.setAttribute('type', val); + } + + show({duration = DEFAULT_DURATION} = {}) { + if (duration <= 0) { + throw new RangeError(`Invalid Argument: duration must be greater than 0 [${duration} given]`); + } + + this.setAttribute('open', ''); + this.#setCloseTimeout(duration); + } + + hide() { + this.removeAttribute('open'); + } + + toggle(force) { + this.toggleAttribute('open', force); + } + + attributeChangedCallback(name, oldValue, newValue) { + switch (name) { + case 'open': + if (newValue !== null && oldValue === null) { + this.dispatchEvent(new Event('show')); + } else if (newValue === null) { + this.dispatchEvent(new Event('hide')); + this.#setCloseTimeout(Infinity); + } + break; + case 'closebutton': + if (newValue !== null) { + if (newValue === '') { + setDefaultCloseButton(this.#closeButtonElement); + } else { + replaceDefaultCloseButton(this.#closeButtonElement, newValue); + } + } + // if newValue === null we do nothing, since CSS will hide the button + break; + } + } +} + +reflection.installBool(StdToastElement.prototype, 'open'); + +customElements.define('std-toast', StdToastElement); + +delete StdToastElement.prototype.attributeChangedCallback; +delete StdToastElement.prototype.observedAttributes; +delete StdToastElement.prototype.connectedCallback; + +export function showToast(message, options = {}) { + const toast = new StdToastElement(message); + + const { + action, + closeButton, + type, + ...showOptions + } = options; + + if (isElement(action)) { + toast.action = action; + } else if (action !== undefined) { + const actionButton = document.createElement('button'); + + // Unlike String(), this performs the desired JavaScript ToString operation. + // https://gist.github.com/domenic/82adbe7edc4a33a70f42f255479cec39 + actionButton.textContent = `${action}`; + + actionButton.setAttribute('slot', 'action'); + toast.appendChild(actionButton); + } + + if (closeButton !== undefined) { + toast.closeButton = closeButton; + } + + if (type !== undefined) { + toast.type = type; + } + + document.body.append(toast); + toast.show(showOptions); + + return toast; +} + +const idGetter = + Object.getOwnPropertyDescriptor(Element.prototype, 'id').get; +function isElement(value) { + try { + idGetter.call(value); + return true; + } catch { + return false; + } +} + +function setDefaultCloseButton(closeButton) { + closeButton.setAttribute('aria-label', 'close'); + closeButton.setAttribute('class', 'default-closebutton'); + closeButton.textContent = '×'; +} + +function replaceDefaultCloseButton(closeButton, value) { + closeButton.textContent = value; + closeButton.removeAttribute('aria-label'); + closeButton.removeAttribute('class'); +} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/.eslintrc.js b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/.eslintrc.js new file mode 100644 index 00000000000..90e626a41fc --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/.eslintrc.js @@ -0,0 +1,337 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module.exports = { + root: true, + env: { + es6: true, + browser: true + }, + 'parser': 'babel-eslint', + parserOptions: { + sourceType: 'module', + ecmaVersion: 2019 + }, + rules: { + 'for-direction': 'error', + 'getter-return': 'error', + 'no-async-promise-executor': 'error', + 'no-await-in-loop': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': ['error', 'except-parens'], + 'no-console': 'error', + 'no-constant-condition': ['error', {checkLoops: false}], + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-dupe-args': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty': 'error', + 'no-empty-character-class': 'error', + 'no-ex-assign': 'error', + 'no-extra-boolean-cast': 'error', + 'no-extra-parens': [ + 'error', + 'all', + { + conditionalAssign: false, + nestedBinaryExpressions: false, + returnAssign: false + } + ], + 'no-extra-semi': 'error', + 'no-func-assign': 'error', + 'no-inner-declarations': 'off', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-misleading-character-class': 'error', + 'no-obj-calls': 'error', + 'no-prototype-builtins': 'error', + 'no-regex-spaces': 'error', + 'no-sparse-arrays': 'error', + 'no-template-curly-in-string': 'error', + 'no-unexpected-multiline': 'error', + 'no-unreachable': 'error', + 'no-unsafe-finally': 'off', + 'no-unsafe-negation': 'error', + 'use-isnan': 'error', + 'valid-typeof': 'error', + 'accessor-pairs': 'error', + 'array-callback-return': 'error', + 'block-scoped-var': 'off', + 'class-methods-use-this': 'off', + 'complexity': 'off', + 'consistent-return': 'error', + 'curly': ['error', 'all'], + 'default-case': 'off', + 'dot-location': ['error', 'property'], + 'dot-notation': 'error', + 'eqeqeq': 'error', + 'guard-for-in': 'off', + 'no-alert': 'error', + 'no-caller': 'error', + 'no-case-declarations': 'error', + 'no-div-regex': 'off', + 'no-empty-function': 'off', + 'no-empty-pattern': 'error', + 'no-eq-null': 'error', + 'no-eval': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-label': 'error', + 'no-fallthrough': 'error', + 'no-floating-decimal': 'error', + 'no-global-assign': 'error', + 'no-implicit-coercion': 'error', + 'no-implicit-globals': 'error', + 'no-implied-eval': 'error', + 'no-iterator': 'error', + 'no-labels': ['error', {allowLoop: true}], + 'no-lone-blocks': 'error', + 'no-loop-func': 'error', + 'no-magic-numbers': ['error', {ignore: [0, 1, 2]}], + 'no-multi-spaces': ['error', {ignoreEOLComments: true}], + 'no-multi-str': 'error', + 'no-new': 'error', + 'no-new-func': 'error', + 'no-new-wrappers': 'error', + 'no-octal': 'error', + 'no-octal-escape': 'error', + 'no-param-reassign': 'off', + 'no-process-env': 'error', + 'no-proto': 'error', + 'no-redeclare': 'error', + 'no-restricted-properties': 'off', + 'no-return-assign': ['error', 'except-parens'], + 'no-return-await': 'error', + 'no-script-url': 'off', + 'no-self-assign': 'error', + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-throw-literal': 'error', + 'no-unmodified-loop-condition': 'error', + 'no-unused-expressions': 'error', + 'no-unused-labels': 'error', + 'no-useless-call': 'error', + 'no-useless-concat': 'error', + 'no-useless-escape': 'error', + 'no-useless-return': 'error', + 'no-void': 'error', + 'no-warning-comments': 'off', + 'no-with': 'error', + 'prefer-promise-reject-errors': 'error', + 'radix': ['error', 'as-needed'], + 'require-await': 'off', + 'vars-on-top': 'off', + 'wrap-iife': ['error', 'outside'], + 'yoda': ['error', 'never'], + 'strict': ['error', 'global'], + 'init-declarations': 'off', + 'no-delete-var': 'error', + 'no-label-var': 'error', + 'no-restricted-globals': 'off', + 'no-shadow': 'error', + 'no-shadow-restricted-names': 'error', + 'no-undef': 'error', + 'no-undef-init': 'error', + 'no-undefined': 'off', + 'no-unused-vars': 'error', + 'no-use-before-define': ['error', 'nofunc'], + 'callback-return': 'off', + 'global-require': 'error', + 'handle-callback-err': 'error', + 'no-buffer-constructor': 'error', + 'no-mixed-requires': ['error', true], + 'no-new-require': 'error', + 'no-path-concat': 'error', + 'no-process-exit': 'error', + 'no-restricted-modules': 'off', + 'no-sync': 'off', + 'array-bracket-newline': ['error', {multiline: true}], + 'array-bracket-spacing': ['error', 'never'], + 'array-element-newline': 'off', + 'block-spacing': ['error', 'always'], + 'brace-style': [ + 'error', + '1tbs', + {allowSingleLine: false} + ], + camelcase: ['error', {properties: 'always'}], + 'capitalized-comments': 'off', + 'comma-dangle': ['error', 'always-multiline'], + 'comma-spacing': [ + 'error', + { + before: false, + after: true + } + ], + 'comma-style': ['error', 'last'], + 'computed-property-spacing': ['error', 'never'], + 'consistent-this': 'off', + 'eol-last': 'error', + 'func-call-spacing': ['error', 'never'], + 'func-name-matching': 'error', + 'func-names': 'off', + 'func-style': ['error', 'declaration'], + 'function-paren-newline': 'off', + 'id-blacklist': 'off', + 'id-length': 'off', + 'id-match': 'off', + indent: 'off', // not really compatible with clang-format + 'jsx-quotes': 'off', + 'key-spacing': [ + 'error', + { + beforeColon: false, + afterColon: true, + mode: 'strict' + } + ], + 'keyword-spacing': [ + 'error', + { + before: true, + after: true + } + ], + 'line-comment-position': 'off', + 'linebreak-style': ['error', 'unix'], + 'lines-around-comment': 'off', + 'max-depth': 'off', + 'max-len': ['error', { + tabWidth: 2, + ignorePattern: "(^import |// eslint-disable-line )"}], + 'max-lines': 'off', + 'max-nested-callbacks': 'off', + 'max-params': 'off', + 'max-statements': 'off', + 'max-statements-per-line': ['error', {max: 1}], + 'multiline-ternary': ['error', 'always-multiline'], + 'new-cap': 'error', + 'new-parens': 'error', + 'newline-per-chained-call': 'off', + 'no-array-constructor': 'error', + 'no-bitwise': 'off', + 'no-continue': 'off', + 'no-inline-comments': 'off', + 'no-mixed-operators': [ + 'error', + { + groups: [ + ['&', '|', '^', '~', '<<', '>>', '>>>'], + ['==', '!=', '===', '!==', '>', '>=', '<', '<='], + ['&&', '||'], + ['in', 'instanceof'] + ] + } + ], + 'no-mixed-spaces-and-tabs': 'error', + 'no-multi-assign': 'off', + 'no-multiple-empty-lines': 'error', + 'no-negated-condition': 'off', + 'no-nested-ternary': 'error', + 'no-new-object': 'error', + 'no-plusplus': 'off', + 'no-restricted-syntax': 'off', + 'no-tabs': 'error', + 'no-ternary': 'off', + 'no-trailing-spaces': 'error', + 'no-underscore-dangle': 'off', + 'no-unneeded-ternary': 'error', + 'no-whitespace-before-property': 'error', + 'nonblock-statement-body-position': 'error', + 'object-curly-newline': ['error', {consistent: true}], + 'object-curly-spacing': ['error', 'never'], + 'object-property-newline': 'off', + 'one-var': ['error', 'never'], + 'one-var-declaration-per-line': ['error', 'initializations'], + 'operator-assignment': ['error', 'always'], + 'operator-linebreak': ['error', 'after'], + 'padded-blocks': ['error', 'never'], + 'padding-line-between-statements': 'off', + 'quote-props': ['error', 'as-needed'], + quotes: [ + 'error', + 'single', + { + avoidEscape: true, + allowTemplateLiterals: true + } + ], + semi: ['error', 'always'], + 'semi-spacing': 'error', + 'semi-style': 'error', + 'sort-keys': 'off', + 'sort-vars': 'off', + 'space-before-blocks': ['error', 'always'], + 'space-before-function-paren': [ + 'error', + { + anonymous: 'always', + named: 'never' + } + ], + 'space-in-parens': ['error', 'never'], + 'space-infix-ops': 'error', + 'space-unary-ops': [ + 'error', + { + words: true, + nonwords: false + } + ], + 'spaced-comment': ['error', 'always'], + 'switch-colon-spacing': 'error', + 'template-tag-spacing': 'error', + 'unicode-bom': 'error', + 'wrap-regex': 'off', + 'arrow-body-style': 'off', + 'arrow-parens': ['error', 'as-needed'], + 'arrow-spacing': 'error', + 'constructor-super': 'error', + 'generator-star-spacing': ['error', 'neither'], + 'no-class-assign': 'error', + 'no-confusing-arrow': 'off', + 'no-const-assign': 'error', + 'no-dupe-class-members': 'error', + 'no-duplicate-imports': 'error', + 'no-new-symbol': 'error', + 'no-restricted-imports': 'off', + 'no-this-before-super': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': 'error', + 'no-var': 'error', + 'object-shorthand': 'error', + 'prefer-arrow-callback': 'error', + 'prefer-const': ['error', {ignoreReadBeforeAssign: true}], + 'prefer-destructuring': [ + 'error', + { + VariableDeclarator: { + array: false, + object: true + }, + AssignmentExpression: { + array: false, + object: false + } + }, + { + enforceForRenamedProperties: false + } + ], + 'prefer-numeric-literals': 'error', + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'prefer-template': 'off', + 'require-yield': 'error', + 'rest-spread-spacing': 'error', + 'sort-imports': 'off', + 'symbol-description': 'error', + 'template-curly-spacing': ['error', 'never'], + 'yield-star-spacing': ['error', 'after'] + } +}; diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/OWNERS b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/OWNERS new file mode 100644 index 00000000000..d76cdfa11f0 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/OWNERS @@ -0,0 +1 @@ +fergal@chromium.org diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/docs/README.md b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/docs/README.md new file mode 100644 index 00000000000..c5ea6fc86c3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/docs/README.md @@ -0,0 +1,297 @@ +# Virtual Scroller Notes + +This is Blink's implementation of the [`<virtual-scroller>`](https://github.com/WICG/virtual-scroller) element. + +## Status + +This is a prototype with many known issues. + +## Principles + +- Avoid operations that are O(number of children) +- Ensure that we only perform operations that cause DOM, style or rendering work in the browser + that is O(number of visible elements). +- Avoid forcing layout from JS. + +## Current Implementation Strategy + +The prototype uses a custom element implemented in JS. +It only handles vertical block layout, +so this talks only about height. +This custom element manages the display-locking status of its child items. +It considers a range of pixels to be the "unlocked range". +This range is determined from the window size. + +TODO(crbug.com/983052): Be smarter about determining the buffer size, +given that the window can be resized. + +Items which are within the unlocked range are left unlocked. +All other items are locked +so that they do not incur style and rendering costs for their contents. +They are locked with a height that is our best guess at their rendered height. +This guess is based on +- a fixed default +- a previously measured height of this item +- an average of previous observed heights of items + +The virtual scroller listens to observers to know when it needs to reconsider which items should be locked. +It keeps a resize observer on itself since if it is resized, +items may need to be newly locked or unlocked. +It keeps an intersection observer on all unlocked child items. +Along with the buffer around the screen, +this allows it to know when the state has changed such that we may need to lock or unlock different items. + +TODO(crbug.com/983050): Observe one item above and below the unlocked items too, +without this, if the edge item is larger than the window, +we can scroll empty space into the visible region. + +TODO(crbug.com/983046): Keep an intersection observer on the scroller. +This allows us to know whether the scroller is offscreen. +In that case we lock all elements +and pause all activity. +This includes if the scroller is contained in a locked element +(e.g. nested in another virtual scroller). + +The virtual scroller keeps a mutation observer on itself +so that it can react to elements being added, removed or moved around. +When dealing with mutations, +elements which are newly added are treated differently +to elements which are removed and re-added. +Newly added elements are immediately locked. +This allows a large number of elements to be added, +without a large rendering/style cost for these elements. + +The virtual scroller does not listen directly for scroll events +and does not know or care whether it is in a scrollable region. +It is tempting to try discover whether the scroller is contained in a scrolling region +and then listen for scroll events, +however the scroller may be contained in any number of nested scrollable regions +and DOM changes can cause it to be reparented. +Also, we only need to change state when an item enters or leaves the visible range +but scroll events may occur much more frequently than that. + +The virtual scroller takes no action directly in response to these events. +It simply ensures that a `requestAnimationFrame` (RAF) callback will run this frame +(max one per frame). +This callback is the "sync" callback. +It will attempt to react to the new state of the world +and try to ensure that the scroller is in sync with it. +This is where we determine which elements to lock or unlock. + +When determining which items should be locked and which should be unlocked, +the virtual scroller uses `getBoundingClientRect` +to get the coordinates of its items, +relative to the root viewport. +It binary searches on the array of items +to find the element at the top and bottom of the visible range. +At the end of the sync, +these and the elements in between them will be unlocked +and all other elements will be locked. +The scroller knows which elements are currently unlocked +and does the minimal amount of locking and unlocking +to bring it to the desired state. +Also the intersection observers will be updated to match this range of elements. + +It is very possible that the range of elements we have unlocked +is too big or too small for the viewport. +We cannot know the rendered size of these elements +until we have unlocked and measured them. +In order to avoid extra forced layouts, +we simply queue another sync callback for the next frame. +We continue to queue sync callbacks until one of them makes no changes. + +Internally, the virtual scroller slots all children into a single slot. +It changes the locked state of the light tree children directly. + +## Known issues specific to the current prototype + +### Display locking items directly + +The current prototype changes the locked state of the light tree children directly. +This means that the scroller's actions can be detected from outside. +It also means that an author cannot (in general) safely use display locking +on the children of a virtual scroller. + +Other approaches do not have this issue, +because they slot the items into more than one slot, +however, this requires either manipulating the `slot` attribute on the items +or [imperative slotting](https://www.chromestatus.com/feature/5711021289242624). + +### Binary searching the items + +The first problem with this is that it requires forced layout (see below). + +The second (potential) problem is that +while JS presents an `Array` interface for the children of the virtual scroller, +the reality is that in Blink, +the elements are stored in a linked list +with some extra logic to remember the most recent indexed access +and use that to speed up subsequent index accesses +(e.g. accessing `[n+1]` after `[n]` is fast). +This makes the binary search actually O(number of children) +because the C++ code must traverse a linked list. +However C++ traversing a link list is very fast +so this tends not to be noticeable. + +### Locked elements still have a cost + +While the descendants of locked elements are skipped for style and layout, +the locked elements themselves are still traversed +and participate in style and layout. +In the scroller, +the elements are locked with a size +and behave just as an empty div with that size. +This means that when we add many children to the virtual scroller, +even though most of them are locked, +there can still be quite a large style and rendering cost +from the locked elements in aggregate. + +## Known issues common to many approaches + +### Intersection observers are slow + +Intersection observers signal the intersection state *after* it has happened. +This means that by only reacting to intersection observers, +the scroller may react late to scrolls and jumps. +Once a scroll begins, +it's likely that the element will keep up, +since it is not using the details of the events to compute the state of the scroller. + +### Forced layouts to measure item sizes + +After unlocking, the scroller needs to know the rendered size of an element. +In order to do that it must call `getBoundingClientRect`. +This forces the browser to have clean style and layout +for all unlocked elements in the document +(whether this is expensive depends on what has changed since the last time layout). +It may be better to perform these measurements in a +[post animation frame callback](https://github.com/WICG/requestPostAnimationFrame/blob/master/explainer.md) +when that feature becomes available +because this callback guarantees that style and layout are clean, +so measuring elements should be inexpensive. + +## Alternative approaches + +### Unlocked tree of divs with `<slot>`s for leaves (rejected) + +We could avoid the O(number of children) run of locked divs +by building a tree of divs (e.g. a binary tree) +and locking all of the slots except those containing the visible items. + +*PROBLEM:* This breaks margin collapsing between items +and maybe other layout or style features. +In order to make margin collapsing work correctly +we would need to make all of the divs in this tree have style `display: contents` +but for layout, this would have exactly the same performance problem as placing them all as siblings in one slot. + +### One visible slot in the middle (rejected) + +We could assign all of the visible items to a single slot, +ensuring they behave correctly with respect to margin collapsing +and keep all other items in other slots. + +We need to ensure that elements are slotted in their light-tree order +for find-in-page, accessibility etc to work correctly. +The simplest form of this is a three-slot approach +with one slot for the invisible items at the top, +another slot for the visible items in the middle +and a third slot for the invisible items at the bottom. +The top and bottom slot remain locked always +and items are moved into the visible slot when they should be visible. +The top and bottom slots are inside a locked div. +These divs are locked with a size estimated to be the correct aggregate size for their slotted elements. + +*PROBLEM:* Gradually scrolling down moves items one at a time +from the bottom slot, to the visible slot and then into the top slot +but long-range jumps require moving O(number of children) items between slots. +E.g. if the top items are visible and we jump to make the bottom items visible, +then we have to assign almost all of the items that were in the bottom slot to the top slot. + +### Roaming visible slot (not preferred) + +This is similar to [One visible slot in the middle](#One-visible-slot-in-the-middle) approach +but rather than a top and bottom slot, +item assignment is divided over many slots. +Each item has a natural "leaf" slot that it should be assigned to. +All of the leaf slots are kept locked. +There is a single visible slot that contains the currently unlocked items +and it roams around between the leaf slots +to ensure that order is correct for find-in-page etc. +If we keep the number of items per slot low, +then long jumps never cause a large number of items to be reassigned, +at worst we need to reassign the currently visible items back to their leaf slots, +move the visible slot to a new place +and fill it with newly visible items. + +It's possible that there will be so many slots +that once again we run into the problem of large numbers of empty divs. +So we may need to maintain a tree of divs and `<slot>`s. +Most of this tree would be locked +but the path from the visible slot to the root would be unlocked. + +It looks like this: + +![Diagram of a sample roaming visible slot tree](roaming-slot.png) + +Original diagram [here](https://docs.google.com/drawings/d/1oZ8U16GzkxO3GaYLDygPsrmXiHO7SLNFXvVUK6WkG4I/edit) + +*CHALLENGES:* This approach does not seem to have any insurmountable problems, +however it is not simple: +- We need to ensure that the tree remains balanced in the face of mutations of the virtual scrollers contents. +- A tree may introduce a large number of tree-only elements. + With a binary tree, this would be equal to the number of items in the scroller. + It might be worth using a larger branching factor, to reduce this cost. + E.g. in a 10-way tree the overhead is only 1/9 the number of leaves. +- There is a lot of re-slotting of items. +- The visible slot may contain a large number of elements (if elements are small) + and Blink has some [optimizations](https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_slot_element.cc?q=symbol:HTMLSlotElement::NotifySlottedNodesOfFlatTreeChange) + that only work when slots contain <= 16 elements. +- Adding slots and/or assigning elements to slots currently involves operations that are O(number of children). + Adding N slots can take O(N^2) time. + This was previously not a concern in Blink + because, in general, the style and layout costs dominated. + However display locking eliminates these costs, + leaving the slotting costs as the bottleneck when there are large numbers of items. + +### One slot per item (most promising alternative) + +This is similar to [Roaming visible slot](#Roaming-visible-slot). +We have a tree of divs with slots as leaves +but in this approach we have exactly one slot per item. +For any item that should be unlocked, +we ensure that all of its tree ancestors are unlocked +and have style `display: contents`. +All other nodes in the tree have style `display: block; contain: style layout`, +to allow them to be locked. + +This has the effect of making all of the visible items siblings, +from the perspective of layout, +so that margin collapsing, etc. works correctly. +It also ensures that not only are the invisible items and tree divs locked +but they are *inside* locked ancestors, +so do not incur *any* costs. + +There is no need to maintain spacer divs above and below the visible region. +Instead, when we lock any tree element, +we set its locked size to be its current visible size. +If its contents don't change then this element has the correct aggregate size for its contents. +If this element's parent is unlocked then this correct size will be used in sizing the parent. + +Mutations to the tree require size updates to propagate up the ancestor chain. +E.g. inserting an element causes all of the elements ancestors to grow by our best guess at its size. +So, the initial state of the tree +(which comes from an initial insert) +is that everything is an estimate or sum of estimates, +with the sizes getting more accurate +as more elements are rendered, measured and re-locked with correct sizes. + +It's likely that the sizes above will be off by some amount due to margins +but they should be good enough for maintaining a usable scrollbar. + +*CHALLENGES:* This approach does not seem to have any insurmountable problems, +however it is not so simple. +It has a subset of the challenges of [Roaming visible slot](#Roaming-visible-slot): + +- Needs a balanced tree. +- Possible large number of tree-only elements. +- Slot performance issues. diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/docs/roaming-slot.png b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/docs/roaming-slot.png Binary files differnew file mode 100644 index 00000000000..2f9b6844694 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/docs/roaming-slot.png diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/find-element.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/find-element.mjs new file mode 100644 index 00000000000..c25edc0a7fd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/find-element.mjs @@ -0,0 +1,104 @@ +/** + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * @fileoverview Utilities for binary searching by layed-out pixed offset in a + * list of elements. + * @package + */ + +/** Symbols for use with @see findElement */ +export const BIAS_LOW = Symbol('BIAS_LOW'); +export const BIAS_HIGH = Symbol('BIAS_HIGH'); + +function getBound(elements, edgeIndex) { + const element = elements[Math.floor(edgeIndex / 2)]; + const rect = element.getBoundingClientRect(); + return edgeIndex % 2 ? rect.bottom : rect.top; +} + +/** + * Does the actual work of binary searching. This searches amongst the 2*N edges + * of the N elements. Returns the index of an edge found, 2i is the low edge of + * the ith element, 2i+1 is the high edge of the ith element. If |bias| is low + * then we find the index of the lowest edge >= offset. Otherwise we find index + * of the highest edge > offset. + */ +function findEdgeIndex(elements, offset, bias) { + let low = 0; + let high = elements.length * 2 - 1; + while (low < high) { + const i = Math.floor((low + high) / 2); + const bound = getBound(elements, i); + if (bias === BIAS_LOW) { + if (bound < offset) { + low = i + 1; + } else { + high = i; + } + } else { + if (offset < bound) { + high = i; + } else { + low = i + 1; + } + } + } + return low; +} + +/** + * Binary searches inside the array |elements| to find an element containing or + * nearest to |offset| (based on @see Element#getBoundingClientRect()). Assumes + * that the elements are already sorted in increasing pixel order. |bias| + * controls what happens if |offset| is not contained within any element or if + * |offset| is contained with 2 elements (this only happens if there is no + * margin between the elements). If |bias| is BIAS_LOW, then this selects the + * lower element nearest |offset|, otherwise it selects the higher element. + * + * Returns null if |offset| is not within any element. + * + * @param {!Element[]} elements An array of Elements in display order, + * i.e. the pixel offsets of later element are higher than those of earlier + * elements. + * @param {!number} offset The target offset in pixels to search for. + * @param {!Symbol} bias Controls whether we prefer a higher or lower element + * when there is a choice between two elements. + */ +export function findElement(elements, offset, bias) { + if (elements.length === 0) { + return null; + } + // Check if the offset is outside the range entirely. + if (offset < getBound(elements, 0) || + offset > getBound(elements, elements.length * 2 - 1)) { + return null; + } + + let edgeIndex = findEdgeIndex(elements, offset, bias); + + // Fix up edge cases. + if (bias === BIAS_LOW) { + // bound(0)..bound(edgeIndex) < offset <= bound(edgeIndex+1) ... + // If we bias low and we got a low edge and we weren't exactly on the edge + // then we want to select the element that's lower. + if (edgeIndex % 2 === 0) { + const bound = getBound(elements, edgeIndex); + if (offset < bound) { + edgeIndex--; + } + } + } else { + // bound(0)..bound(edgeIndex - 1) <= offset < bound(edgeIndex) ... + // If we bias high and we got a low edge, we need to check if we were + // exactly on the edge of the previous element. + if (edgeIndex % 2 === 0) { + const bound = getBound(elements, edgeIndex - 1); + if (offset === bound) { + edgeIndex--; + } + } + } + return elements[Math.floor(edgeIndex / 2)]; +} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/index.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/index.mjs new file mode 100644 index 00000000000..daef60f7232 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/index.mjs @@ -0,0 +1,57 @@ +/** + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * @fileoverview This file defines virtual-scroller element. + * EXPLAINER: https://github.com/fergald/virtual-scroller/blob/master/README.md + * TEST PATH: third_party/blink/web_tests/http/tests/virtual-scroller/* + * third_party/blink/web_tests/wpt_internal/virtual-scroller/* + * @package + */ +import {VisibilityManager} from './visibility-manager.mjs'; + +function styleSheetFactory() { + let styleSheet; + return () => { + if (!styleSheet) { + styleSheet = new CSSStyleSheet(); + styleSheet.replaceSync(` +:host { + display: block; +} + +::slotted(*) { + display: block !important; + contain: layout style; +} +`); + } + return styleSheet; + }; +} + +/** + * The class backing the virtual-scroller custom element. + */ +export class VirtualScrollerElement extends HTMLElement { + constructor() { + super(); + + const shadowRoot = this.attachShadow({mode: 'closed'}); + shadowRoot.adoptedStyleSheets = [styleSheetFactory()()]; + shadowRoot.appendChild(document.createElement('slot')); + + const visibilityManager = new VisibilityManager(this.children); + + new ResizeObserver(() => { + visibilityManager.scheduleSync(); + }).observe(this); + + new MutationObserver(records => { + visibilityManager.applyMutationObserverRecords(records); + }).observe(this, {childList: true}); + } +} + +customElements.define('virtual-scroller', VirtualScrollerElement); diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/sets.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/sets.mjs new file mode 100644 index 00000000000..9b1aff8eca1 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/sets.mjs @@ -0,0 +1,24 @@ +/** + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * @fileoverview Utility functions for set operations. + * @package + */ + +/* + * Returns the set of elements in |a| that are not in |b|. + * + * @param {!Set} a A set of elements. + * @param {!Set} b A set of elements. +*/ +export function difference(a, b) { + const result = new Set(); + for (const element of a) { + if (!b.has(element)) { + result.add(element); + } + } + return result; +} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/visibility-manager.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/visibility-manager.mjs new file mode 100644 index 00000000000..ecc160f9f0d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/elements/virtual-scroller/visibility-manager.mjs @@ -0,0 +1,407 @@ +/** + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * @fileoverview This file provides the class backing the virtual-scroller + * element. + * @package + */ +import * as sets from './sets.mjs'; +import * as findElement from './find-element.mjs'; + + +// This controls how much above and below the current screen we +// reveal, e.g. 1 = 1 screen of content. +const BUFFER = 0.2; +// When we know about the heights of elements we default this height. +const DEFAULT_HEIGHT_ESTIMATE_PX = 100; +// When we lock an element, we use this as the width. We use 1px because locked +// items will not resize when their container changes and so could result in a +// horizontal scroll-bar appearing if it they are wide enough. +const LOCKED_WIDTH_PX = 1; + +/** + * Represents a range of elements from |low| to |high|, inclusive. + * If either |low| or |high| are null then we treat this as an empty range. + */ +class ElementBounds { + /** @const {Element} */ + low; + /** @const {Element} */ + high; + + constructor(low, high) { + this.low = low; + this.high = high; + } + + // Returns a Set containing all of the elements from low to high. + elementSet() { + const result = new Set(); + if (this.low === null || this.high === null) { + return result; + } + let element = this.low; + while (element) { + result.add(element); + if (element === this.high) { + break; + } + element = element.nextElementSibling; + } + return result; + } +} + +const EMPTY_ELEMENT_BOUNDS = new ElementBounds(null, null); + +/** + * Manages measuring and estimating sizes of elements. + * + * This tracks an average measured element size as elements are added + * and removed. +*/ +class SizeManager { + #sizes = new WeakMap(); + + #totalMeasuredSize = 0; + #measuredCount = 0; + + /** + * Measures and stores |element|'s size. If |element| was measured + * previously, this updates everything to use the new current size. + * + * @param {!Element} element The element to measure. + */ + measure(element) { + let oldSize = this.#sizes.get(element); + if (oldSize === undefined) { + oldSize = 0; + this.#measuredCount++; + } + const newSize = element.getBoundingClientRect().height; + this.#totalMeasuredSize += newSize - oldSize; + this.#sizes.set(element, newSize); + } + + /** + * Returns a size for |element|, either the last stored size or an + * estimate based on all other previously measured elements or a + * default. + * + * @param {!Element} element The element to produce a size for. + */ + getHopefulSize(element) { + const size = this.#sizes.get(element); + return size === undefined ? this.#getAverageSize() : size; + } + + #getAverageSize = () => { + return this.#measuredCount > 0 ? + this.#totalMeasuredSize / this.#measuredCount : + DEFAULT_HEIGHT_ESTIMATE_PX; + } + + /** + * Removes all data related to |element| from the manager. + * + * @param {!Element} element The element to remove. + */ + remove(element) { + const oldSize = this.#sizes.get(element); + if (oldSize === undefined) { + return; + } + this.#totalMeasuredSize -= oldSize; + this.#measuredCount--; + this.#sizes.delete(element); + } +} + +/** + * Manages the visibility (locked/unlocked state) of a list of + * elements. This list of elements is assumed to be in vertical + * display order (e.g. from lowest to highest offset). + * + * It uses resize and intersection observers on all of the visible + * elements to ensure that changes that impact visibility cause us to + * recalulate things (e.g. scrolling, restyling). +*/ +export class VisibilityManager { + #sizeManager = new SizeManager(); + #elements; + #syncRAFToken; + + #elementIntersectionObserver; + #elementResizeObserver; + + #revealed = new Set(); + + constructor(elements) { + this.#elements = elements; + + // We want to sync if any element's size changes or if it becomes + // more/less visible. + this.#elementIntersectionObserver = new IntersectionObserver(() => { + this.scheduleSync(); + }); + // TODO(fergal): Remove this? I'm not sure that we need the resize + // observer. Any resize that is important to us seems like it will + // also involve an intersection change. + this.#elementResizeObserver = new ResizeObserver(() => { + this.scheduleSync(); + }); + + for (const element of this.#elements) { + this.#didAdd(element); + } + this.scheduleSync(); + } + + /** + * Attempts to unlock a range of elements suitable for the current + * viewport. This causes one forced layout. + */ + #sync = () => { + if (this.#elements.length === 0) { + return; + } + + // The basic idea is ... + // The forced layout occurs at the start. We then use the laid out + // coordinates (which are based on a mix of real sizes for + // unlocked elements and the estimated sizes at the time of + // locking for locked elements) to calculate a set of elements + // which should be revealed. We use unlock/lock to move to this + // new set of revealed elements. We will check in the next frame + // whether we got it correct. + + // This causes a forced layout and takes measurements of all + // currently revealed elements. + this.#measureRevealed(); + + // Compute the pixel bounds of what we would like to reveal. Then + // find the elements corresponding to these bounds. + // TODO(fergal): Use nearest scrolling ancestor? + const desiredLow = 0 - window.innerHeight * BUFFER; + const desiredHigh = window.innerHeight + window.innerHeight * BUFFER; + const newBounds = this.#findElementBounds(desiredLow, desiredHigh); + const newRevealed = newBounds.elementSet(); + + // TODO(fergal): We need to observe 1 element off the end of the + // list, to cope with e.g. the scrolling region suddenly growing. + + // Lock and unlock the minimal set of elements to get us to the + // new state. + const toHide = sets.difference(this.#revealed, newRevealed); + toHide.forEach(e => this.#hide(e)); + const toReveal = sets.difference(newRevealed, this.#revealed); + toReveal.forEach(e => this.#reveal(e)); + + // Now we have revealed what we hope will fill the screen. It + // could be incorrect. Rather than measuring now and correcting it + // which would involve an unknown number of forced layouts, we + // come back next frame and try to make it better. We know we can + // stop when we didn't hide or reveal any elements. + if (toHide.size > 0 || toReveal.size > 0) { + this.scheduleSync(); + } + } + + /** + * Searches within the managed elements and returns an ElementBounds + * object. This object may represent an empty range or a range whose low + * element contains or is lower than |low| (or the lowest element + * possible). Similarly for |high|. + * + * @param {!number} low The lower bound to locate. + * @param {!number} high The upper bound to locate. + */ + #findElementBounds = (low, high) => { + const lowElement = findElement.findElement( + this.#elements, low, findElement.BIAS_LOW); + const highElement = findElement.findElement( + this.#elements, high, findElement.BIAS_HIGH); + + if (lowElement === null) { + if (highElement === null) { + return EMPTY_ELEMENT_BOUNDS; + } else { + return new ElementBounds(this.#elements[0], highElement); + } + } else if (highElement === null) { + return new ElementBounds( + lowElement, this.#elements[this.#elements.length - 1]); + } + return new ElementBounds(lowElement, highElement); + } + + /** + * Updates the size manager with all of the currently revealed + * elements' sizes. This will cause a forced layout. + */ + #measureRevealed = () => { + for (const element of this.#revealed) { + this.#sizeManager.measure(element); + } + } + + /** + * Reveals |element| so that it can be rendered. This includes + * unlocking and adding to various observers. + * + * @param {!Element} element The element to reveal. + */ + #reveal = element => { + this.#revealed.add(element); + this.#elementIntersectionObserver.observe(element); + this.#elementResizeObserver.observe(element); + this.#unlock(element); + } + + #logLockingError = (operation, reason, element) => { + // TODO: Figure out the LAPIs error/warning logging story. + console.error('Rejected: ', operation, element, reason); // eslint-disable-line no-console + } + + /** + * Unlocks |element|. + * + * @param {!Element} element The element to unlock. + */ + #unlock = element => { + element.displayLock.commit().catch(reason => { + // Only warn if the unlocked failed and we should be revealed. + if (this.#revealed.has(element)) { + this.#logLockingError('Commit', reason, element); + } + }); + } + + /** + * Hides |element| so that it cannot be rendered. This includes + * locking and removing from various observers. + * + * @param {!Element} element The element to hide. + */ + #hide = element => { + this.#revealed.delete(element); + this.#elementIntersectionObserver.unobserve(element); + this.#elementResizeObserver.unobserve(element); + element.displayLock.acquire({ + timeout: Infinity, + activatable: true, + size: [LOCKED_WIDTH_PX, this.#sizeManager.getHopefulSize(element)], + }).catch(reason => { + // Only warn if the lock failed and we should be locked. + if (!this.#revealed.has(element)) { + this.#logLockingError('Acquire', reason, element); + } + }); + } + + /** + * Notify the manager that |element| has been added to the list of + * managed elements. + * + * @param {!Element} element The element that was added. + */ + #didAdd = element => { + // Added children should be invisible initially. We want to make them + // invisible at this MutationObserver timing, so that there is no + // frame where the browser is asked to render all of the children + // (which could be a lot). + this.#hide(element); + } + + /** + * Notify the manager that |element| has been removed from the list + * of managed elements. + * + * @param {!Element} element The element that was removed. + */ + #didRemove = element => { + // Removed children should be made visible again. We should stop + // observing them and discard any size info we have for them as it + // may have become incorrect. + // + // TODO(fergal): Decide whether to also unlock if + // displayLock.locked is true. That would only be necessary if we + // got out of sync between this.#revealed and the locked state. So + // for now, assume are not buggy. + if (this.#revealed.has(element)) { + this.#unlock(element); + } + this.#revealed.delete(element); + this.#elementIntersectionObserver.unobserve(element); + this.#elementResizeObserver.unobserve(element); + this.#sizeManager.remove(element); + } + + /** + * Ensures that @see #sync() will be called at the next animation frame. + */ + scheduleSync() { + if (this.#syncRAFToken !== undefined) { + return; + } + + this.#syncRAFToken = window.requestAnimationFrame(() => { + this.#syncRAFToken = undefined; + this.#sync(); + }); + } + + /** + * Applys |records| generated by a mutation event to the manager. + * This computes the elements that were newly added/removed and + * notifies the managers for each. + * + * @param {!Object} records The mutations records. + */ + applyMutationObserverRecords(records) { + // It's unclear if we can support children which are not + // elements. We cannot control their visibility using display + // locking but we can just leave them alone. + // + // Relevant mutations are any additions or removals, including + // non-elements and also elements that are removed and then + // re-added as this may impact element bounds. + let relevantMutation = false; + const toRemove = new Set(); + for (const record of records) { + relevantMutation = relevantMutation || record.removedNodes.length > 0; + for (const node of record.removedNodes) { + if (node.nodeType === Node.ELEMENT_NODE) { + toRemove.add(node); + } + } + } + + const toAdd = new Set(); + for (const record of records) { + relevantMutation = relevantMutation || record.addedNodes.length > 0; + for (const node of record.addedNodes) { + if (node.nodeType === Node.ELEMENT_NODE) { + if (toRemove.has(node)) { + toRemove.delete(node); + } else { + toAdd.add(node); + } + } + } + } + for (const node of toRemove) { + this.#didRemove(node); + } + for (const node of toAdd) { + this.#didAdd(node); + } + + if (relevantMutation) { + this.scheduleSync(); + } + } +} + diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/kv-storage/async_iterator.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/kv-storage/async_iterator.mjs index 0da4854b99d..a83dba41c20 100644 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/kv-storage/async_iterator.mjs +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/kv-storage/async_iterator.mjs @@ -16,6 +16,8 @@ const AsyncIteratorPrototype = Object.getPrototypeOf( const StorageAreaAsyncIteratorPrototype = { __proto__: AsyncIteratorPrototype, + [Symbol.toStringTag]: 'StorageArea AsyncIterator', + next() { const performDatabaseOperation = _performDatabaseOperation.get(this); if (!performDatabaseOperation) { @@ -42,6 +44,12 @@ const StorageAreaAsyncIteratorPrototype = { }, }; +Object.defineProperty( + StorageAreaAsyncIteratorPrototype, + Symbol.toStringTag, + {writable: false, enumerable: false} +); + function getNextIterResult(iter, performDatabaseOperation) { return performDatabaseOperation(async (transaction, store) => { const lastKey = _lastKey.get(iter); diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/kv-storage/index.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/kv-storage/index.mjs index b5272ea5d7b..88ef25e818a 100644 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/kv-storage/index.mjs +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/kv-storage/index.mjs @@ -5,16 +5,18 @@ import {createStorageAreaAsyncIterator} from './async_iterator.mjs'; import {promiseForRequest, promiseForTransaction, throwForDisallowedKey} from './idb_utils.mjs'; -// TODOs/spec-noncompliances: +// Overall TODOs/spec-noncompliances: // - Susceptible to tampering of built-in prototypes and globals. We want to // work on tooling to ameliorate that. const DEFAULT_STORAGE_AREA_NAME = 'default'; const DEFAULT_IDB_STORE_NAME = 'store'; +// TODO(crbug.com/977470): this should be handled via infrastructure that +// avoids putting it in the module map entirely, not as a runtime check. +// Several web platform tests fail because of this. if (!self.isSecureContext) { - throw new DOMException( - 'KV Storage is only available in secure contexts', 'SecurityError'); + throw new TypeError('KV Storage is only available in secure contexts'); } export class StorageArea { @@ -132,8 +134,25 @@ export class StorageArea { } StorageArea.prototype[Symbol.asyncIterator] = StorageArea.prototype.entries; - -export const storage = new StorageArea(DEFAULT_STORAGE_AREA_NAME); +StorageArea.prototype[Symbol.toStringTag] = 'StorageArea'; + +// Override the defaults that are implied by using class declarations and +// assignment, to be more Web IDL-ey. +// https://github.com/heycam/webidl/issues/738 may modify these a bit. +Object.defineProperties(StorageArea.prototype, { + set: {enumerable: true}, + get: {enumerable: true}, + delete: {enumerable: true}, + clear: {enumerable: true}, + keys: {enumerable: true}, + values: {enumerable: true}, + entries: {enumerable: true}, + backingStore: {enumerable: true}, + [Symbol.asyncIterator]: {enumerable: false}, + [Symbol.toStringTag]: {writable: false, enumerable: false} +}); + +export default new StorageArea(DEFAULT_STORAGE_AREA_NAME); async function performDatabaseOperation( promise, setPromise, name, mode, steps) { @@ -145,7 +164,9 @@ async function performDatabaseOperation( const transaction = database.transaction(DEFAULT_IDB_STORE_NAME, mode); const store = transaction.objectStore(DEFAULT_IDB_STORE_NAME); - return steps(transaction, store); + const result = steps(transaction, store); + transaction.commit(); + return result; } function initializeDatabasePromise(setPromise, databaseName) { diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/resources.grdp b/chromium/third_party/blink/renderer/core/script/resources/layered_api/resources.grdp index 2a7e3b38f14..255d05543d1 100644 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/resources.grdp +++ b/chromium/third_party/blink/renderer/core/script/resources/layered_api/resources.grdp @@ -7,14 +7,17 @@ third_party/blink/public/blink_resources.grd. --> <include name="IDR_LAYERED_API_BLANK_INDEX_MJS" file="../renderer/core/script/resources/layered_api/blank/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_INTERNAL_REFLECTION_MJS" file="../renderer/core/script/resources/layered_api/elements/internal/reflection.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_SWITCH_FACE_UTILS_MJS" file="../renderer/core/script/resources/layered_api/elements/switch/face_utils.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_SWITCH_INDEX_MJS" file="../renderer/core/script/resources/layered_api/elements/switch/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_SWITCH_STYLE_MJS" file="../renderer/core/script/resources/layered_api/elements/switch/style.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_SWITCH_TRACK_MJS" file="../renderer/core/script/resources/layered_api/elements/switch/track.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_TOAST_INDEX_MJS" file="../renderer/core/script/resources/layered_api/elements/toast/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_FIND_ELEMENT_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/find-element.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_INDEX_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_SETS_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/sets.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> + <include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_VISIBILITY_MANAGER_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/visibility-manager.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> <include name="IDR_LAYERED_API_KV_STORAGE_ASYNC_ITERATOR_MJS" file="../renderer/core/script/resources/layered_api/kv-storage/async_iterator.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> <include name="IDR_LAYERED_API_KV_STORAGE_IDB_UTILS_MJS" file="../renderer/core/script/resources/layered_api/kv-storage/idb_utils.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> <include name="IDR_LAYERED_API_KV_STORAGE_INDEX_MJS" file="../renderer/core/script/resources/layered_api/kv-storage/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> - <include name="IDR_LAYERED_API_VIRTUAL_SCROLLER_INDEX_MJS" file="../renderer/core/script/resources/layered_api/virtual-scroller/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> - <include name="IDR_LAYERED_API_VIRTUAL_SCROLLER_ITEM_SOURCE_MJS" file="../renderer/core/script/resources/layered_api/virtual-scroller/item-source.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> - <include name="IDR_LAYERED_API_VIRTUAL_SCROLLER_VIRTUAL_REPEATER_MJS" file="../renderer/core/script/resources/layered_api/virtual-scroller/virtual-repeater.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> - <include name="IDR_LAYERED_API_VIRTUAL_SCROLLER_VIRTUAL_SCROLLER_MJS" file="../renderer/core/script/resources/layered_api/virtual-scroller/virtual-scroller.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> - <include name="IDR_LAYERED_API_VIRTUAL_SCROLLER_LAYOUTS_LAYOUT_1D_BASE_MJS" file="../renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d-base.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> - <include name="IDR_LAYERED_API_VIRTUAL_SCROLLER_LAYOUTS_LAYOUT_1D_GRID_MJS" file="../renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d-grid.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> - <include name="IDR_LAYERED_API_VIRTUAL_SCROLLER_LAYOUTS_LAYOUT_1D_MJS" file="../renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> </grit-part> diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/README.chromium b/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/README.chromium deleted file mode 100644 index e8b4d5d0007..00000000000 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/README.chromium +++ /dev/null @@ -1,12 +0,0 @@ -Name: virtual-scroller Layered API -URL: https://github.com/valdrinkoshi/virtual-scroller -Version: 58659cee10c5d9237821d5c475dac89720bd995d -Security Critical: no - -Description: -Temporarily, the files under this directory are authored by Chromium Authors -on a github repository, and then imported to Chromium repository directly here, -until a long-term Layered API development plan is settled. - -Local Modifications: -None (except for renaming virtual-scroller-element.mjs to index.mjs) diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/index.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/index.mjs deleted file mode 100644 index 8cdad8091f7..00000000000 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/index.mjs +++ /dev/null @@ -1,213 +0,0 @@ -import {_item, _key, ItemSource} from './item-source.mjs'; -import {default as Layout1dGrid} from './layouts/layout-1d-grid.mjs'; -import {default as Layout1d} from './layouts/layout-1d.mjs'; -import {VirtualScroller} from './virtual-scroller.mjs'; - -export {ItemSource}; - -/** Properties */ -const _scroller = Symbol(); -const _createElement = Symbol(); -const _updateElement = Symbol(); -const _recycleElement = Symbol(); -const _nodePool = Symbol(); -const _rawItemSource = Symbol(); -const _itemSource = Symbol(); -const _elementSource = Symbol(); -const _firstConnected = Symbol(); -/** Functions */ -const _render = Symbol(); - -export class VirtualScrollerElement extends HTMLElement { - constructor() { - super(); - this[_scroller] = null; - // Default create/update/recycleElement. - this[_nodePool] = []; - let childTemplate = null; - this[_createElement] = () => { - if (this[_nodePool] && this[_nodePool].length) { - return this[_nodePool].pop(); - } - if (!childTemplate) { - const template = this.querySelector('template'); - childTemplate = template && template.content.firstElementChild ? - template.content.firstElementChild : - document.createElement('div'); - } - return childTemplate.cloneNode(true); - }; - this[_updateElement] = (element, item) => element.textContent = - item.toString(); - this[_recycleElement] = (element) => this[_nodePool].push(element); - - this[_itemSource] = this[_rawItemSource] = null; - this[_elementSource] = {}; - - this[_firstConnected] = false; - } - - connectedCallback() { - if (!this[_firstConnected]) { - this.attachShadow({mode: 'open'}).innerHTML = ` -<style> - :host { - display: block; - position: relative; - contain: strict; - height: 150px; - overflow: auto; - } - :host([hidden]) { - display: none; - } - ::slotted(*) { - box-sizing: border-box; - } - :host([layout=vertical]) ::slotted(*) { - width: 100%; - } - :host([layout=horizontal]) ::slotted(*) { - height: 100%; - } -</style> -<slot></slot>`; - // Set default values. - if (!this.layout) { - this.layout = 'vertical'; - } - // Enables rendering. - this[_firstConnected] = true; - } - this[_render](); - } - - static get observedAttributes() { - return ['layout']; - } - - attributeChangedCallback(name, oldVal, newVal) { - this[_render](); - } - - get layout() { - return this.getAttribute('layout'); - } - set layout(layout) { - this.setAttribute('layout', layout); - } - - get itemSource() { - return this[_itemSource]; - } - set itemSource(itemSource) { - // No Change. - if (this[_rawItemSource] === itemSource) { - return; - } - this[_rawItemSource] = itemSource; - this[_itemSource] = Array.isArray(itemSource) ? - ItemSource.fromArray(itemSource) : - itemSource; - this[_render](); - } - - get createElement() { - return this[_createElement]; - } - set createElement(fn) { - // Resets default recycling. - if (this[_nodePool]) { - this.recycleElement = null; - } - this[_createElement] = fn; - // Invalidate wrapped function. - this[_elementSource].createElement = null; - this[_render](); - } - - get updateElement() { - return this[_updateElement]; - } - set updateElement(fn) { - this[_updateElement] = fn; - // Invalidate wrapped function. - this[_elementSource].updateElement = null; - this[_render](); - } - - get recycleElement() { - return this[_recycleElement]; - } - set recycleElement(fn) { - // Marks default recycling changed. - this[_nodePool] = null; - this[_recycleElement] = fn; - // Invalidate wrapped function. - this[_elementSource].recycleElement = null; - this[_render](); - } - - itemsChanged() { - if (this[_scroller]) { - // Render because length might have changed. - this[_render](); - // Request reset because items might have changed. - this[_scroller].requestReset(); - } - } - - scrollToIndex(index, { position = 'start' } = {}) { - if (this[_scroller]) { - this[_scroller].layout.scrollToIndex(index, position); - } - } - - [_render]() { - // Wait first connected as scroller needs to measure - // sizes of container and children. - if (!this[_firstConnected] || !this.createElement) { - return; - } - if (!this[_scroller]) { - this[_scroller] = - new VirtualScroller({container: this, scrollTarget: this}); - } - const scroller = this[_scroller]; - - const layoutAttr = this.layout; - const Layout = layoutAttr.endsWith('-grid') ? Layout1dGrid : Layout1d; - const direction = - layoutAttr.startsWith('horizontal') ? 'horizontal' : 'vertical'; - const layout = scroller.layout instanceof Layout && - scroller.layout.direction === direction ? - scroller.layout : - new Layout({direction}); - - let {createElement, updateElement, recycleElement} = this[_elementSource]; - if (!createElement) { - createElement = this[_elementSource].createElement = (index) => - this.createElement(this.itemSource[_item](index), index); - } - if (this.updateElement && !updateElement) { - updateElement = this[_elementSource].updateElement = (element, index) => - this.updateElement(element, this.itemSource[_item](index), index); - } - if (this.recycleElement && !recycleElement) { - recycleElement = this[_elementSource].recycleElement = (element, index) => - this.recycleElement(element, this.itemSource[_item](index), index); - } - - const elementKey = this.itemSource ? this.itemSource[_key] : null; - const totalItems = this.itemSource ? this.itemSource.length : 0; - Object.assign(scroller, { - layout, - createElement, - updateElement, - recycleElement, - elementKey, - totalItems - }); - } -} -customElements.define('virtual-scroller', VirtualScrollerElement); diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/item-source.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/item-source.mjs deleted file mode 100644 index 255e97b245c..00000000000 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/item-source.mjs +++ /dev/null @@ -1,47 +0,0 @@ -export const _getLength = Symbol(); -export const _item = Symbol(); -export const _key = Symbol(); - -export class ItemSource { - constructor({getLength, item, key}) { - if (typeof getLength !== 'function') { - throw new TypeError('getLength option must be a function'); - } - if (typeof item !== 'function') { - throw new TypeError('item option must be a function'); - } - if (typeof key !== 'function') { - throw new TypeError('key option must be a function'); - } - - this[_getLength] = getLength; - this[_item] = item; - this[_key] = key; - } - - static fromArray(array, key) { - if (!Array.isArray(array)) { - throw new TypeError('First argument to fromArray() must be an array'); - } - if (typeof key !== 'function' && key !== undefined) { - throw new TypeError( - 'Second argument to fromArray() must be a function or undefined'); - } - - return new this({ - getLength() { - return array.length; - }, - item(index) { - return array[index]; - }, - key(index) { - return key ? key(array[index], index) : array[index]; - } - }); - } - - get length() { - return this[_getLength](); - } -} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d-base.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d-base.mjs deleted file mode 100644 index 89a9a9e8106..00000000000 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d-base.mjs +++ /dev/null @@ -1,320 +0,0 @@ -export default class Layout extends EventTarget { - constructor(config) { - super(); - - this._physicalMin = 0; - this._physicalMax = 0; - - this._first = -1; - this._last = -1; - - this._latestCoords = {left: 0, top: 0}; - - this._itemSize = {width: 100, height: 100}; - this._spacing = 0; - - this._sizeDim = 'height'; - this._secondarySizeDim = 'width'; - this._positionDim = 'top'; - this._secondaryPositionDim = 'left'; - this._direction = 'vertical'; - - this._scrollPosition = 0; - this._scrollError = 0; - this._viewportSize = {width: 0, height: 0}; - this._totalItems = 0; - - this._scrollSize = 1; - - this._overhang = 150; - - this._pendingReflow = false; - - this._scrollToIndex = -1; - this._scrollToAnchor = 0; - - Object.assign(this, config); - } - - // public properties - - get totalItems() { - return this._totalItems; - } - set totalItems(num) { - if (num !== this._totalItems) { - this._totalItems = num; - this._maxIdx = num - 1; - this._scheduleReflow(); - } - } - - get direction() { - return this._direction; - } - set direction(dir) { - // Force it to be either horizontal or vertical. - dir = (dir === 'horizontal') ? dir : 'vertical'; - if (dir !== this._direction) { - this._direction = dir; - this._sizeDim = (dir === 'horizontal') ? 'width' : 'height'; - this._secondarySizeDim = (dir === 'horizontal') ? 'height' : 'width'; - this._positionDim = (dir === 'horizontal') ? 'left' : 'top'; - this._secondaryPositionDim = (dir === 'horizontal') ? 'top' : 'left'; - this._scheduleReflow(); - } - } - - get itemSize() { - return this._itemSize; - } - set itemSize(dims) { - const {_itemDim1, _itemDim2} = this; - Object.assign(this._itemSize, dims); - if (_itemDim1 !== this._itemDim1 || _itemDim2 !== this._itemDim2) { - if (_itemDim2 !== this._itemDim2) { - this._itemDim2Changed(); - } else { - this._scheduleReflow(); - } - } - } - - get spacing() { - return this._spacing; - } - set spacing(px) { - if (px !== this._spacing) { - this._spacing = px; - this._scheduleReflow(); - } - } - - get viewportSize() { - return this._viewportSize; - } - set viewportSize(dims) { - const {_viewDim1, _viewDim2} = this; - Object.assign(this._viewportSize, dims); - if (_viewDim2 !== this._viewDim2) { - this._viewDim2Changed(); - } else if (_viewDim1 !== this._viewDim1) { - this._checkThresholds(); - } - } - - get viewportScroll() { - return this._latestCoords; - } - set viewportScroll(coords) { - Object.assign(this._latestCoords, coords); - const oldPos = this._scrollPosition; - this._scrollPosition = this._latestCoords[this._positionDim]; - if (oldPos !== this._scrollPosition) { - this._scrollPositionChanged(oldPos, this._scrollPosition); - } - this._checkThresholds(); - } - - // private properties - - get _delta() { - return this._itemDim1 + this._spacing; - } - - get _itemDim1() { - return this._itemSize[this._sizeDim]; - } - - get _itemDim2() { - return this._itemSize[this._secondarySizeDim]; - } - - get _viewDim1() { - return this._viewportSize[this._sizeDim]; - } - - get _viewDim2() { - return this._viewportSize[this._secondarySizeDim]; - } - - get _num() { - if (this._first === -1 || this._last === -1) { - return 0; - } - return this._last - this._first + 1; - } - - // public methods - - reflowIfNeeded() { - if (this._pendingReflow) { - this._pendingReflow = false; - this._reflow(); - } - } - - scrollToIndex(index, position = 'start') { - if (!Number.isFinite(index)) - return; - index = Math.min(this.totalItems, Math.max(0, index)); - this._scrollToIndex = index; - if (position === 'nearest') { - position = index > this._first + this._num / 2 ? 'end' : 'start'; - } - switch (position) { - case 'start': - this._scrollToAnchor = 0; - break; - case 'center': - this._scrollToAnchor = 0.5; - break; - case 'end': - this._scrollToAnchor = 1; - break; - default: - throw new TypeError( - 'position must be one of: start, center, end, nearest'); - } - this._scheduleReflow(); - this.reflowIfNeeded(); - } - - /// - - _scheduleReflow() { - this._pendingReflow = true; - } - - _reflow() { - const {_first, _last, _scrollSize} = this; - - this._updateScrollSize(); - this._getActiveItems(); - this._scrollIfNeeded(); - - if (this._scrollSize !== _scrollSize) { - this._emitScrollSize(); - } - - if (this._first === -1 && this._last === -1) { - this._emitRange(); - } else if ( - this._first !== _first || this._last !== _last || - this._spacingChanged) { - this._emitRange(); - this._emitChildPositions(); - } - this._emitScrollError(); - } - - _updateScrollSize() { - // Ensure we have at least 1px - this allows getting at least 1 item to be - // rendered. - this._scrollSize = Math.max(1, this._totalItems * this._delta); - } - - _checkThresholds() { - if (this._viewDim1 === 0 && this._num > 0) { - this._scheduleReflow(); - } else { - const min = Math.max(0, this._scrollPosition - this._overhang); - const max = Math.min( - this._scrollSize, - this._scrollPosition + this._viewDim1 + this._overhang); - if (this._physicalMin > min || this._physicalMax < max) { - this._scheduleReflow(); - } - } - } - - _scrollIfNeeded() { - if (this._scrollToIndex === -1) { - return; - } - const index = this._scrollToIndex; - const anchor = this._scrollToAnchor; - const pos = this._getItemPosition(index)[this._positionDim]; - const size = this._getItemSize(index)[this._sizeDim]; - - const curAnchorPos = this._scrollPosition + this._viewDim1 * anchor; - const newAnchorPos = pos + size * anchor; - // Ensure scroll position is an integer within scroll bounds. - const scrollPosition = Math.floor(Math.min( - this._scrollSize - this._viewDim1, - Math.max(0, this._scrollPosition - curAnchorPos + newAnchorPos))); - this._scrollError += this._scrollPosition - scrollPosition; - this._scrollPosition = scrollPosition; - } - - _emitRange(inProps) { - const detail = Object.assign( - { - first: this._first, - last: this._last, - num: this._num, - stable: true, - }, - inProps); - this.dispatchEvent(new CustomEvent('rangechange', {detail})); - } - - _emitScrollSize() { - const detail = { - [this._sizeDim]: this._scrollSize, - }; - this.dispatchEvent(new CustomEvent('scrollsizechange', {detail})); - } - - _emitScrollError() { - if (this._scrollError) { - const detail = { - [this._positionDim]: this._scrollError, - [this._secondaryPositionDim]: 0, - }; - this.dispatchEvent(new CustomEvent('scrollerrorchange', {detail})); - this._scrollError = 0; - } - } - - _emitChildPositions() { - const detail = {}; - for (let idx = this._first; idx <= this._last; idx++) { - detail[idx] = this._getItemPosition(idx); - } - this.dispatchEvent(new CustomEvent('itempositionchange', {detail})); - } - - _itemDim2Changed() { - // Override - } - - _viewDim2Changed() { - // Override - } - - _scrollPositionChanged(oldPos, newPos) { - // When both values are bigger than the max scroll position, keep the - // current _scrollToIndexx, otherwise invalidate it. - const maxPos = this._scrollSize - this._viewDim1; - if (oldPos < maxPos || newPos < maxPos) { - this._scrollToIndex = -1; - } - } - - _getActiveItems() { - // Override - } - - _getItemPosition(idx) { - // Override. - } - - _getItemSize(idx) { - // Override. - return { - [this._sizeDim]: this._itemDim1, - [this._secondarySizeDim]: this._itemDim2, - }; - } -}
\ No newline at end of file diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d-grid.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d-grid.mjs deleted file mode 100644 index c9c00d021da..00000000000 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d-grid.mjs +++ /dev/null @@ -1,64 +0,0 @@ -import Layout1dBase from './layout-1d-base.mjs'; - -export default class Layout extends Layout1dBase { - constructor(config) { - super(config); - this._rolumns = 1; - } - - updateItemSizes(sizes) { - // Assume all items have the same size. - const size = Object.values(sizes)[0]; - if (size) { - this.itemSize = size; - } - } - - _viewDim2Changed() { - this._defineGrid(); - } - - _itemDim2Changed() { - this._defineGrid(); - } - - _getActiveItems() { - const min = Math.max(0, this._scrollPosition - this._overhang); - const max = Math.min( - this._scrollSize, - this._scrollPosition + this._viewDim1 + this._overhang); - const firstCow = Math.floor(min / this._delta); - const lastCow = Math.ceil(max / this._delta) - 1; - - this._first = firstCow * this._rolumns; - this._last = - Math.min(((lastCow + 1) * this._rolumns) - 1, this._totalItems); - this._physicalMin = this._delta * firstCow; - this._physicalMax = this._delta * (lastCow + 1); - } - - _getItemPosition(idx) { - return { - [this._positionDim]: Math.floor(idx / this._rolumns) * this._delta, - [this._secondaryPositionDim]: this._spacing + - ((idx % this._rolumns) * (this._spacing + this._itemDim2)) - } - } - - - _defineGrid() { - const {_spacing} = this; - this._rolumns = Math.max(1, Math.floor(this._viewDim2 / this._itemDim2)); - if (this._rolumns > 1) { - this._spacing = (this._viewDim2 % (this._rolumns * this._itemDim2)) / - (this._rolumns + 1); - } - this._spacingChanged = !(_spacing === this._spacing); - this._scheduleReflow(); - } - - _updateScrollSize() { - this._scrollSize = - Math.max(1, Math.ceil(this._totalItems / this._rolumns) * this._delta); - } -}
\ No newline at end of file diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d.mjs deleted file mode 100644 index 6f5d181cb0e..00000000000 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/layouts/layout-1d.mjs +++ /dev/null @@ -1,349 +0,0 @@ -import Layout1dBase from './layout-1d-base.mjs'; - -export default class Layout extends Layout1dBase { - constructor(config) { - super(config); - this._physicalItems = new Map(); - this._newPhysicalItems = new Map(); - - this._metrics = new Map(); - - this._anchorIdx = null; - this._anchorPos = null; - this._stable = true; - - this._needsRemeasure = false; - - this._nMeasured = 0; - this._tMeasured = 0; - - this._estimate = true; - } - - updateItemSizes(sizes) { - Object.keys(sizes).forEach((key) => { - const metrics = sizes[key], mi = this._getMetrics(key), - prevSize = mi[this._sizeDim]; - - // TODO(valdrin) Handle margin collapsing. - // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing - mi.width = metrics.width + (metrics.marginLeft || 0) + - (metrics.marginRight || 0); - mi.height = metrics.height + (metrics.marginTop || 0) + - (metrics.marginBottom || 0); - - const size = mi[this._sizeDim]; - const item = this._getPhysicalItem(Number(key)); - if (item) { - let delta; - - if (size !== undefined) { - item.size = size; - if (prevSize === undefined) { - delta = size; - this._nMeasured++; - } else { - delta = size - prevSize; - } - } - this._tMeasured = this._tMeasured + delta; - } else { - // console.debug(`Could not find physical item for key ${key}`); - } - }); - if (!this._nMeasured) { - console.warn(`No items measured yet.`); - } else { - this._updateItemSize(); - this._scheduleReflow(); - } - } - - _updateItemSize() { - // Keep integer values. - this._itemSize[this._sizeDim] = - Math.round(this._tMeasured / this._nMeasured); - } - - // - - _getMetrics(idx) { - return (this._metrics[idx] = this._metrics[idx] || {}); - } - - _getPhysicalItem(idx) { - return this._newPhysicalItems.get(idx) || this._physicalItems.get(idx); - } - - _getSize(idx) { - const item = this._getPhysicalItem(idx); - return item && item.size; - } - - _getPosition(idx) { - const item = this._physicalItems.get(idx); - return item ? item.pos : (idx * (this._delta)) + this._spacing; - } - - _calculateAnchor(lower, upper) { - if (lower === 0) { - return 0; - } - if (upper > this._scrollSize - this._viewDim1) { - return this._maxIdx; - } - return Math.max( - 0, - Math.min( - this._maxIdx, Math.floor(((lower + upper) / 2) / this._delta))); - } - - _getAnchor(lower, upper) { - if (this._physicalItems.size === 0) { - return this._calculateAnchor(lower, upper); - } - if (this._first < 0) { - console.error('_getAnchor: negative _first'); - return this._calculateAnchor(lower, upper); - } - if (this._last < 0) { - console.error('_getAnchor: negative _last'); - return this._calculateAnchor(lower, upper); - } - - const firstItem = this._getPhysicalItem(this._first), - lastItem = this._getPhysicalItem(this._last), - firstMin = firstItem.pos, firstMax = firstMin + firstItem.size, - lastMin = lastItem.pos, lastMax = lastMin + lastItem.size; - - if (lastMax < lower) { - // Window is entirely past physical items, calculate new anchor - return this._calculateAnchor(lower, upper); - } - if (firstMin > upper) { - // Window is entirely before physical items, calculate new anchor - return this._calculateAnchor(lower, upper); - } - if (firstMin >= lower || firstMax >= lower) { - // First physical item overlaps window, choose it - return this._first; - } - if (lastMax <= upper || lastMin <= upper) { - // Last physical overlaps window, choose it - return this._last; - } - // Window contains a physical item, but not the first or last - let maxIdx = this._last, minIdx = this._first; - - while (true) { - let candidateIdx = Math.round((maxIdx + minIdx) / 2), - candidate = this._physicalItems.get(candidateIdx), - cMin = candidate.pos, cMax = cMin + candidate.size; - - if ((cMin >= lower && cMin <= upper) || - (cMax >= lower && cMax <= upper)) { - return candidateIdx; - } else if (cMax < lower) { - minIdx = candidateIdx + 1; - } else if (cMin > upper) { - maxIdx = candidateIdx - 1; - } - } - } - - _getActiveItems() { - if (this._viewDim1 === 0 || this._totalItems === 0) { - this._clearItems(); - } else { - const upper = Math.min( - this._scrollSize, - this._scrollPosition + this._viewDim1 + this._overhang), - lower = Math.max(0, upper - this._viewDim1 - (2 * this._overhang)); - - this._getItems(lower, upper); - } - } - - _clearItems() { - this._first = -1; - this._last = -1; - this._physicalMin = 0; - this._physicalMax = 0; - const items = this._newPhysicalItems; - this._newPhysicalItems = this._physicalItems; - this._newPhysicalItems.clear(); - this._physicalItems = items; - this._stable = true; - } - - _getItems(lower, upper) { - const items = this._newPhysicalItems; - - // The anchorIdx is the anchor around which we reflow. - // It is designed to allow jumping to any point of the scroll size. - // We choose it once and stick with it until stable. first and last are - // deduced around it. - if (this._anchorIdx === null || this._anchorPos === null) { - this._anchorIdx = this._getAnchor(lower, upper); - this._anchorPos = this._getPosition(this._anchorIdx); - } - - let anchorSize = this._getSize(this._anchorIdx); - if (anchorSize === undefined) { - anchorSize = this._itemDim1; - } - - // Anchor might be outside bounds, so prefer correcting the error and keep - // that anchorIdx. - let anchorErr = 0; - - if (this._anchorPos + anchorSize + this._spacing < lower) { - anchorErr = lower - (this._anchorPos + anchorSize + this._spacing); - } - - if (this._anchorPos > upper) { - anchorErr = upper - this._anchorPos; - } - - if (anchorErr) { - this._scrollPosition -= anchorErr; - lower -= anchorErr; - upper -= anchorErr; - this._scrollError += anchorErr; - } - - items.set(this._anchorIdx, {pos: this._anchorPos, size: anchorSize}); - - this._first = (this._last = this._anchorIdx); - this._physicalMin = (this._physicalMax = this._anchorPos); - - this._stable = true; - - while (this._physicalMin > lower && this._first > 0) { - let size = this._getSize(--this._first); - if (size === undefined) { - this._stable = false; - size = this._itemDim1; - } - const pos = (this._physicalMin -= size + this._spacing); - items.set(this._first, {pos, size}); - if (this._stable === false && this._estimate === false) { - break; - } - } - - while (this._physicalMax < upper && this._last < this._totalItems) { - let size = this._getSize(this._last); - if (size === undefined) { - this._stable = false; - size = this._itemDim1; - } - items.set(this._last++, {pos: this._physicalMax, size}); - if (this._stable === false && this._estimate === false) { - break; - } else { - this._physicalMax += size + this._spacing; - } - } - - this._last--; - - // This handles the cases where we were relying on estimated sizes. - const extentErr = this._calculateError(); - if (extentErr) { - this._physicalMin -= extentErr; - this._physicalMax -= extentErr; - this._anchorPos -= extentErr; - this._scrollPosition -= extentErr; - items.forEach(item => item.pos -= extentErr); - this._scrollError += extentErr; - } - - if (this._stable) { - this._newPhysicalItems = this._physicalItems; - this._newPhysicalItems.clear(); - this._physicalItems = items; - } - } - - _calculateError() { - if (this._first === 0) { - return this._physicalMin; - } else if (this._physicalMin <= 0) { - return this._physicalMin - (this._first * this._delta); - } else if (this._last === this._maxIdx) { - return this._physicalMax - this._scrollSize; - } else if (this._physicalMax >= this._scrollSize) { - return ( - (this._physicalMax - this._scrollSize) + - ((this._maxIdx - this._last) * this._delta)); - } - return 0; - } - - _updateScrollSize() { - // Reuse previously calculated physical max, as it might be - // higher than the estimated size. - super._updateScrollSize(); - this._scrollSize = Math.max(this._physicalMax, this._scrollSize); - } - - // TODO: Can this be made to inherit from base, with proper hooks? - _reflow() { - const {_first, _last, _scrollSize} = this; - - this._updateScrollSize(); - this._getActiveItems(); - this._scrollIfNeeded(); - - if (this._scrollSize !== _scrollSize) { - this._emitScrollSize(); - } - - this._emitRange(); - if (this._first === -1 && this._last === -1) { - this._resetReflowState(); - } else if ( - this._first !== _first || this._last !== _last || - this._needsRemeasure) { - this._emitChildPositions(); - this._emitScrollError(); - } else { - this._emitChildPositions(); - this._emitScrollError(); - this._resetReflowState(); - } - } - - _resetReflowState() { - this._anchorIdx = null; - this._anchorPos = null; - this._stable = true; - } - - _getItemPosition(idx) { - return { - [this._positionDim]: this._getPosition(idx), - [this._secondaryPositionDim]: 0 - } - } - - _getItemSize(idx) { - return { - [this._sizeDim]: this._getSize(idx) || this._itemDim1, - [this._secondarySizeDim]: this._itemDim2, - }; - } - - _viewDim2Changed() { - this._needsRemeasure = true; - this._scheduleReflow(); - } - - _emitRange() { - const remeasure = this._needsRemeasure; - const stable = this._stable; - this._needsRemeasure = false; - super._emitRange({remeasure, stable}); - } -} diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/virtual-repeater.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/virtual-repeater.mjs deleted file mode 100644 index 3d3cb6aa7c1..00000000000 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/virtual-repeater.mjs +++ /dev/null @@ -1,531 +0,0 @@ -export const Repeats = Superclass => class extends Superclass { - constructor(config) { - super(); - - this._createElementFn = null; - this._updateElementFn = null; - this._recycleElementFn = null; - this._elementKeyFn = null; - - this._measureCallback = null; - - this._totalItems = 0; - // Consider renaming this. firstVisibleIndex? - this._first = 0; - // Consider renaming this. count? visibleElements? - this._num = Infinity; - - this.__incremental = false; - - // used only internally.. - // legacy from 1st approach to preact integration - this._manageDom = true; - // used to check if it is more perf if you don't care of dom order? - this._maintainDomOrder = true; - - this._last = 0; - this._prevFirst = 0; - this._prevLast = 0; - - this._needsReset = false; - this._needsRemeasure = false; - this._pendingRender = null; - - // Contains child nodes in the rendered order. - this._ordered = []; - // this._pool = []; - this._active = new Map(); - this._prevActive = new Map(); - // Both used for recycling purposes. - this._keyToChild = new Map(); - this._childToKey = new WeakMap(); - // Used to keep track of measures by index. - this._indexToMeasure = {}; - // Used to debounce _measureChildren calls. - this._measuringId = -1; - - if (config) { - Object.assign(this, config); - } - } - - // API - - get container() { - return this._container; - } - set container(container) { - if (container === this._container) { - return; - } - if (this._container) { - // Remove children from old container. - this._ordered.forEach((child) => this._removeChild(child)); - } - - this._container = container; - - if (container) { - // Insert children in new container. - this._ordered.forEach((child) => this._insertBefore(child, null)); - } else { - this._ordered.length = 0; - this._active.clear(); - this._prevActive.clear(); - } - this.requestReset(); - } - - get createElement() { - return this._createElementFn; - } - set createElement(fn) { - if (fn !== this._createElementFn) { - this._createElementFn = fn; - this._keyToChild.clear(); - this.requestReset(); - } - } - - get updateElement() { - return this._updateElementFn; - } - set updateElement(fn) { - if (fn !== this._updateElementFn) { - this._updateElementFn = fn; - this.requestReset(); - } - } - - get recycleElement() { - return this._recycleElementFn; - } - set recycleElement(fn) { - if (fn !== this._recycleElementFn) { - this._recycleElementFn = fn; - this.requestReset(); - } - } - - get elementKey() { - return this._elementKeyFn; - } - set elementKey(fn) { - if (fn !== this._elementKeyFn) { - this._elementKeyFn = fn; - this._keyToChild.clear(); - this.requestReset(); - } - } - - get first() { - return this._first; - } - - set first(idx) { - if (typeof idx === 'number') { - const newFirst = Math.max(0, Math.min(idx, this._totalItems - this._num)); - if (newFirst !== this._first) { - this._first = newFirst; - this._scheduleRender(); - } - } - } - - get num() { - return this._num; - } - - set num(n) { - if (typeof n === 'number') { - if (n !== this._num) { - this._num = n; - this.first = this._first; - this._scheduleRender(); - } - } - } - - get totalItems() { - return this._totalItems; - } - - set totalItems(num) { - // TODO(valdrin) should we check if it is a finite number? - // Technically, Infinity would break Layout, not VirtualRepeater. - if (typeof num === 'number' && num !== this._totalItems) { - this._totalItems = num; - this.first = this._first; - this.requestReset(); - } - } - - get _incremental() { - return this.__incremental; - } - - set _incremental(inc) { - if (inc !== this.__incremental) { - this.__incremental = inc; - this._scheduleRender(); - } - } - - requestReset() { - this._needsReset = true; - this._scheduleRender(); - } - - requestRemeasure() { - this._needsRemeasure = true; - this._scheduleRender(); - } - - // Core functionality - - /** - * @protected - */ - _shouldRender() { - return Boolean(this.container && this.createElement); - } - - /** - * @private - */ - _scheduleRender() { - if (!this._pendingRender) { - this._pendingRender = requestAnimationFrame(() => { - this._pendingRender = null; - if (this._shouldRender()) { - this._render(); - } - }); - } - } - - /** - * Returns those children that are about to be displayed and that - * require to be positioned. If reset or remeasure has been triggered, - * all children are returned. - * @return {{indices:Array<number>,children:Array<Element>}} - * @private - */ - get _toMeasure() { - return this._ordered.reduce((toMeasure, c, i) => { - const idx = this._first + i; - if (this._needsReset || this._needsRemeasure || idx < this._prevFirst || - idx > this._prevLast) { - toMeasure.indices.push(idx); - toMeasure.children.push(c); - } - return toMeasure; - }, {indices: [], children: []}); - } - - /** - * Measures each child bounds and builds a map of index/bounds to be passed to - * the `_measureCallback` - * @private - */ - _measureChildren({indices, children}) { - let pm = children.map( - (c, i) => this._indexToMeasure[indices[i]] || this._measureChild(c)); - const mm = /** @type {{ number: { width: number, height: number } }} */ - (pm.reduce((out, cur, i) => { - out[indices[i]] = this._indexToMeasure[indices[i]] = cur; - return out; - }, {})); - this._measureCallback(mm); - } - - /** - * @protected - */ - _render() { - const rangeChanged = - this._first !== this._prevFirst || this._num !== this._prevNum; - // Create/update/recycle DOM. - if (rangeChanged || this._needsReset) { - this._last = - this._first + Math.min(this._num, this._totalItems - this._first) - 1; - if (this._num || this._prevNum) { - if (this._needsReset) { - this._reset(this._first, this._last); - } else { - this._discardHead(); - this._discardTail(); - this._addHead(); - this._addTail(); - } - } - } - if (this._needsRemeasure || this._needsReset) { - this._indexToMeasure = {}; - } - // Retrieve DOM to be measured. - // Do it right before cleanup and reset of properties. - const shouldMeasure = this._num > 0 && this._measureCallback && - (rangeChanged || this._needsRemeasure || this._needsReset); - const toMeasure = shouldMeasure ? this._toMeasure : null; - - // Cleanup. - if (!this._incremental) { - this._prevActive.forEach((idx, child) => this._unassignChild(child, idx)); - this._prevActive.clear(); - } - // Reset internal properties. - this._prevFirst = this._first; - this._prevLast = this._last; - this._prevNum = this._num; - this._needsReset = false; - this._needsRemeasure = false; - - // Notify render completed. - this._didRender(); - // Measure DOM. - if (toMeasure) { - this._measureChildren(toMeasure); - } - } - - /** - * Invoked after DOM is updated, and before it gets measured. - * @protected - */ - _didRender() { - } - - /** - * @private - */ - _discardHead() { - const o = this._ordered; - for (let idx = this._prevFirst; o.length && idx < this._first; idx++) { - this._unassignChild(o.shift(), idx); - } - } - - /** - * @private - */ - _discardTail() { - const o = this._ordered; - for (let idx = this._prevLast; o.length && idx > this._last; idx--) { - this._unassignChild(o.pop(), idx); - } - } - - /** - * @private - */ - _addHead() { - const start = this._first; - const end = Math.min(this._last, this._prevFirst - 1); - for (let idx = end; idx >= start; idx--) { - const child = this._assignChild(idx); - if (this._manageDom) { - if (this._maintainDomOrder || !this._childIsAttached(child)) { - this._insertBefore(child, this._firstChild); - } - } - if (this.updateElement) { - this.updateElement(child, idx); - } - this._ordered.unshift(child); - } - } - - /** - * @private - */ - _addTail() { - const start = Math.max(this._first, this._prevLast + 1); - const end = this._last; - for (let idx = start; idx <= end; idx++) { - const child = this._assignChild(idx); - if (this._manageDom) { - if (this._maintainDomOrder || !this._childIsAttached(child)) { - this._insertBefore(child, null); - } - } - if (this.updateElement) { - this.updateElement(child, idx); - } - this._ordered.push(child); - } - } - - /** - * @param {number} first - * @param {number} last - * @private - */ - _reset(first, last) { - const len = last - first + 1; - // Explain why swap prevActive with active - affects _assignChild. - const prevActive = this._active; - this._active = this._prevActive; - this._prevActive = prevActive; - let currentMarker = this._manageDom && this._firstChild; - this._ordered.length = 0; - for (let n = 0; n < len; n++) { - const idx = first + n; - const child = this._assignChild(idx); - this._ordered.push(child); - if (this._manageDom) { - if (currentMarker && this._maintainDomOrder) { - if (currentMarker === this._node(child)) { - currentMarker = this._nextSibling(child); - } else { - this._insertBefore(child, currentMarker); - } - } else if (!this._childIsAttached(child)) { - this._insertBefore(child, null); - } - } - if (this.updateElement) { - this.updateElement(child, idx); - } - } - } - - /** - * @param {number} idx - * @private - */ - _assignChild(idx) { - const key = this.elementKey ? this.elementKey(idx) : idx; - let child; - if (child = this._keyToChild.get(key)) { - this._prevActive.delete(child); - } else { - child = this.createElement(idx); - this._keyToChild.set(key, child); - this._childToKey.set(child, key); - } - this._showChild(child); - this._active.set(child, idx); - return child; - } - - /** - * @param {*} child - * @param {number} idx - * @private - */ - _unassignChild(child, idx) { - this._hideChild(child); - if (this._incremental) { - this._active.delete(child); - this._prevActive.set(child, idx); - } else { - const key = this._childToKey.get(child); - this._childToKey.delete(child); - this._keyToChild.delete(key); - this._active.delete(child); - if (this.recycleElement) { - this.recycleElement(child, idx); - } else if (this._node(child).parentNode) { - this._removeChild(child); - } - } - } - - // TODO: Is this the right name? - /** - * @private - */ - get _firstChild() { - return this._ordered.length && this._childIsAttached(this._ordered[0]) ? - this._node(this._ordered[0]) : - null; - } - - // Overridable abstractions for child manipulation - /** - * @protected - */ - _node(child) { - return child; - } - /** - * @protected - */ - _nextSibling(child) { - return child.nextSibling; - } - /** - * @protected - */ - _insertBefore(child, referenceNode) { - this._container.insertBefore(child, referenceNode); - } - /** - * @protected - */ - _childIsAttached(child) { - const node = this._node(child); - return node && node.parentNode === this._container; - } - /** - * @protected - */ - _hideChild(child) { - if (child.style) { - child.style.display = 'none'; - } - } - /** - * @protected - */ - _showChild(child) { - if (child.style) { - child.style.display = null; - } - } - - /** - * - * @param {!Element} child - * @return {{width: number, height: number, marginTop: number, marginBottom: number, marginLeft: number, marginRight: number}} childMeasures - * @protected - */ - _measureChild(child) { - // offsetWidth doesn't take transforms in consideration, - // so we use getBoundingClientRect which does. - const {width, height} = child.getBoundingClientRect(); - // console.debug(`_measureChild #${this._container.id} > #${ - // child.id}: height: ${height}px`); - return Object.assign({width, height}, getMargins(child)); - } - - /** - * Remove child. - * Override to control child removal. - * - * @param {*} child - * @protected - */ - _removeChild(child) { - child.parentNode.removeChild(child); - } -} - -function getMargins(el) { - const style = window.getComputedStyle(el); - // console.log(el.id, style.position); - return { - marginLeft: getMarginValue(style.marginLeft), - marginRight: getMarginValue(style.marginRight), - marginTop: getMarginValue(style.marginTop), - marginBottom: getMarginValue(style.marginBottom), - }; -} - -function getMarginValue(value) { - value = value ? parseFloat(value) : NaN; - return value !== value ? 0 : value; -} - -export const VirtualRepeater = Repeats(class {});
\ No newline at end of file diff --git a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/virtual-scroller.mjs b/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/virtual-scroller.mjs deleted file mode 100644 index 049501e6ec9..00000000000 --- a/chromium/third_party/blink/renderer/core/script/resources/layered_api/virtual-scroller/virtual-scroller.mjs +++ /dev/null @@ -1,444 +0,0 @@ -import {Repeats} from './virtual-repeater.mjs'; - -export class RangeChangeEvent extends Event { - constructor(type, init) { - super(type, init); - this._first = Math.floor(init.first || 0); - this._last = Math.floor(init.last || 0); - } - get first() { - return this._first; - } - get last() { - return this._last; - } -} - -export const RepeatsAndScrolls = Superclass => class extends Repeats -(Superclass) { - constructor(config) { - super(); - this._num = 0; - this._first = -1; - this._last = -1; - this._prevFirst = -1; - this._prevLast = -1; - - this._needsUpdateView = false; - this._containerElement = null; - this._layout = null; - this._scrollTarget = null; - // Keep track of original inline style of the container, - // so it can be restored when container is changed. - this._containerInlineStyle = null; - // A sentinel element that sizes the container when - // it is a scrolling element. - this._sizer = null; - // Layout provides these values, we set them on _render(). - this._scrollSize = null; - this._scrollErr = null; - this._childrenPos = null; - - this._containerSize = null; - this._containerRO = new ResizeObserver( - (entries) => this._containerSizeChanged(entries[0].contentRect)); - - this._skipNextChildrenSizeChanged = false; - this._childrenRO = - new ResizeObserver((entries) => this._childrenSizeChanged(entries)); - - if (config) { - Object.assign(this, config); - } - } - - get container() { - return this._container; - } - set container(container) { - super.container = container; - - const oldEl = this._containerElement; - // Consider document fragments as shadowRoots. - const newEl = - (container && container.nodeType === Node.DOCUMENT_FRAGMENT_NODE) ? - container.host : - container; - if (oldEl === newEl) { - return; - } - - this._containerRO.disconnect(); - this._containerSize = null; - - if (oldEl) { - if (this._containerInlineStyle) { - oldEl.setAttribute('style', this._containerInlineStyle); - } else { - oldEl.removeAttribute('style'); - } - this._containerInlineStyle = null; - if (oldEl === this._scrollTarget) { - oldEl.removeEventListener('scroll', this, {passive: true}); - this._sizer && this._sizer.remove(); - } - } else { - // First time container was setup, add listeners only now. - addEventListener('scroll', this, {passive: true}); - } - - this._containerElement = newEl; - - if (newEl) { - this._containerInlineStyle = newEl.getAttribute('style') || null; - if (newEl === this._scrollTarget) { - this._sizer = this._sizer || this._createContainerSizer(); - this._container.prepend(this._sizer); - } - this._scheduleUpdateView(); - this._containerRO.observe(newEl); - } - } - - get layout() { - return this._layout; - } - set layout(layout) { - if (layout === this._layout) { - return; - } - - if (this._layout) { - this._measureCallback = null; - this._layout.removeEventListener('scrollsizechange', this); - this._layout.removeEventListener('scrollerrorchange', this); - this._layout.removeEventListener('itempositionchange', this); - this._layout.removeEventListener('rangechange', this); - // Reset container size so layout can get correct viewport size. - if (this._containerElement) { - this._sizeContainer(); - } - } - - this._layout = layout; - - if (this._layout) { - if (typeof this._layout.updateItemSizes === 'function') { - this._measureCallback = this._layout.updateItemSizes.bind(this._layout); - this.requestRemeasure(); - } - this._layout.addEventListener('scrollsizechange', this); - this._layout.addEventListener('scrollerrorchange', this); - this._layout.addEventListener('itempositionchange', this); - this._layout.addEventListener('rangechange', this); - this._scheduleUpdateView(); - } - } - - /** - * The element that generates scroll events and defines the container - * viewport. The value `null` (default) corresponds to `window` as scroll - * target. - * @type {Element|null} - */ - get scrollTarget() { - return this._scrollTarget; - } - /** - * @param {Element|null} target - */ - set scrollTarget(target) { - // Consider window as null. - if (target === window) { - target = null; - } - if (this._scrollTarget === target) { - return; - } - if (this._scrollTarget) { - this._scrollTarget.removeEventListener('scroll', this, {passive: true}); - if (this._sizer && this._scrollTarget === this._containerElement) { - this._sizer.remove(); - } - } - - this._scrollTarget = target; - - if (target) { - target.addEventListener('scroll', this, {passive: true}); - if (target === this._containerElement) { - this._sizer = this._sizer || this._createContainerSizer(); - this._container.prepend(this._sizer); - } - } - } - - /** - * @protected - */ - _render() { - // console.time(`render ${this._containerElement.localName}#${ - // this._containerElement.id}`); - - this._childrenRO.disconnect(); - - // Update layout properties before rendering to have correct - // first, num, scroll size, children positions. - this._layout.totalItems = this.totalItems; - if (this._needsUpdateView) { - this._needsUpdateView = false; - this._updateView(); - } - this._layout.reflowIfNeeded(); - // Keep rendering until there is no more scheduled renders. - while (true) { - if (this._pendingRender) { - cancelAnimationFrame(this._pendingRender); - this._pendingRender = null; - } - // Update scroll size and correct scroll error before rendering. - this._sizeContainer(this._scrollSize); - if (this._scrollErr) { - // This triggers a 'scroll' event (async) which triggers another - // _updateView(). - this._correctScrollError(this._scrollErr); - this._scrollErr = null; - } - // Position children (_didRender()), and provide their measures to layout. - super._render(); - this._layout.reflowIfNeeded(); - // If layout reflow did not provoke another render, we're done. - if (!this._pendingRender) { - break; - } - } - // We want to skip the first ResizeObserver callback call as we already - // measured the children. - this._skipNextChildrenSizeChanged = true; - this._kids.forEach(child => this._childrenRO.observe(child)); - - // console.timeEnd(`render ${this._containerElement.localName}#${ - // this._containerElement.id}`); - } - - /** - * Position children before they get measured. - * Measuring will force relayout, so by positioning - * them first, we reduce computations. - * @protected - */ - _didRender() { - if (this._childrenPos) { - this._positionChildren(this._childrenPos); - this._childrenPos = null; - } - } - - /** - * @param {!Event} event - * @private - */ - handleEvent(event) { - switch (event.type) { - case 'scroll': - if (!this._scrollTarget || event.target === this._scrollTarget) { - this._scheduleUpdateView(); - } - break; - case 'scrollsizechange': - this._scrollSize = event.detail; - this._scheduleRender(); - break; - case 'scrollerrorchange': - this._scrollErr = event.detail; - this._scheduleRender(); - break; - case 'itempositionchange': - this._childrenPos = event.detail; - this._scheduleRender(); - break; - case 'rangechange': - this._adjustRange(event.detail); - break; - default: - console.warn('event not handled', event); - } - } - /** - * @return {!Element} - * @private - */ - _createContainerSizer() { - const sizer = document.createElement('div'); - // When the scrollHeight is large, the height - // of this element might be ignored. - // Setting content and font-size ensures the element - // has a size. - Object.assign(sizer.style, { - position: 'absolute', - margin: '-2px 0 0 0', - padding: 0, - visibility: 'hidden', - fontSize: '2px', - }); - sizer.innerHTML = ' '; - return sizer; - } - - // Rename _ordered to _kids? - /** - * @protected - */ - get _kids() { - return this._ordered; - } - /** - * @private - */ - _scheduleUpdateView() { - this._needsUpdateView = true; - this._scheduleRender(); - } - /** - * @private - */ - _updateView() { - let width, height, top, left; - if (this._scrollTarget === this._containerElement) { - width = this._containerSize.width; - height = this._containerSize.height; - left = this._containerElement.scrollLeft; - top = this._containerElement.scrollTop; - } else { - const containerBounds = this._containerElement.getBoundingClientRect(); - const scrollBounds = this._scrollTarget ? - this._scrollTarget.getBoundingClientRect() : - {top: 0, left: 0, width: innerWidth, height: innerHeight}; - const scrollerWidth = scrollBounds.width; - const scrollerHeight = scrollBounds.height; - const xMin = Math.max( - 0, Math.min(scrollerWidth, containerBounds.left - scrollBounds.left)); - const yMin = Math.max( - 0, Math.min(scrollerHeight, containerBounds.top - scrollBounds.top)); - const xMax = this._layout.direction === 'vertical' ? - Math.max( - 0, - Math.min( - scrollerWidth, containerBounds.right - scrollBounds.left)) : - scrollerWidth; - const yMax = this._layout.direction === 'vertical' ? - scrollerHeight : - Math.max( - 0, - Math.min( - scrollerHeight, containerBounds.bottom - scrollBounds.top)); - width = xMax - xMin; - height = yMax - yMin; - left = Math.max(0, -(containerBounds.x - scrollBounds.left)); - top = Math.max(0, -(containerBounds.y - scrollBounds.top)); - } - this._layout.viewportSize = {width, height}; - this._layout.viewportScroll = {top, left}; - } - /** - * @private - */ - _sizeContainer(size) { - if (this._scrollTarget === this._containerElement) { - const left = size && size.width ? size.width - 1 : 0; - const top = size && size.height ? size.height - 1 : 0; - this._sizer.style.transform = `translate(${left}px, ${top}px)`; - } else { - const style = this._containerElement.style; - style.minWidth = size && size.width ? size.width + 'px' : null; - style.minHeight = size && size.height ? size.height + 'px' : null; - } - } - /** - * @private - */ - _positionChildren(pos) { - const kids = this._kids; - Object.keys(pos).forEach(key => { - const idx = key - this._first; - const child = kids[idx]; - if (child) { - const {top, left} = pos[key]; - // console.debug(`_positionChild #${this._container.id} > - // #${child.id}: top ${top}`); - child.style.position = 'absolute'; - child.style.transform = `translate(${left}px, ${top}px)`; - } - }); - } - /** - * @private - */ - _adjustRange(range) { - this.num = range.num; - this.first = range.first; - this._incremental = !(range.stable); - if (range.remeasure) { - this.requestRemeasure(); - } else if (range.stable) { - this._notifyStable(); - } - } - /** - * @protected - */ - _shouldRender() { - if (!super._shouldRender() || !this._layout) { - return false; - } - // NOTE: we're about to render, but the ResizeObserver didn't execute yet. - // Since we want to keep rAF timing, we compute _containerSize now. - // Would be nice to have a way to flush ResizeObservers - if (this._containerSize === null) { - const {width, height} = this._containerElement.getBoundingClientRect(); - this._containerSize = {width, height}; - } - return this._containerSize.width > 0 || this._containerSize.height > 0; - } - /** - * @private - */ - _correctScrollError(err) { - if (this._scrollTarget) { - this._scrollTarget.scrollTop -= err.top; - this._scrollTarget.scrollLeft -= err.left; - } else { - window.scroll(window.scrollX - err.left, window.scrollY - err.top); - } - } - /** - * @protected - */ - _notifyStable() { - const {first, num} = this; - const last = first + num - 1; - this._container.dispatchEvent( - new RangeChangeEvent('rangechange', {first, last})); - } - /** - * @private - */ - _containerSizeChanged(size) { - const {width, height} = size; - this._containerSize = {width, height}; - // console.debug('container changed size', this._containerSize); - this._scheduleUpdateView(); - } - /** - * @private - */ - _childrenSizeChanged() { - if (this._skipNextChildrenSizeChanged) { - this._skipNextChildrenSizeChanged = false; - } else { - this.requestRemeasure(); - } - } -}; - -export const VirtualScroller = RepeatsAndScrolls(class {}); diff --git a/chromium/third_party/blink/renderer/core/script/script.h b/chromium/third_party/blink/renderer/core/script/script.h index 2e99f96cb9c..d7951dc1f0a 100644 --- a/chromium/third_party/blink/renderer/core/script/script.h +++ b/chromium/third_party/blink/renderer/core/script/script.h @@ -21,7 +21,7 @@ class WorkerGlobalScope; // https://html.spec.whatwg.org/C/#concept-script class CORE_EXPORT Script : public GarbageCollectedFinalized<Script> { public: - virtual void Trace(blink::Visitor* visitor) {} + virtual void Trace(Visitor* visitor) {} virtual ~Script() {} diff --git a/chromium/third_party/blink/renderer/core/script/script_loader.cc b/chromium/third_party/blink/renderer/core/script/script_loader.cc index 41d5609b5f2..4666d857aed 100644 --- a/chromium/third_party/blink/renderer/core/script/script_loader.cc +++ b/chromium/third_party/blink/renderer/core/script/script_loader.cc @@ -33,7 +33,6 @@ #include "third_party/blink/renderer/core/dom/text.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/local_frame.h" -#include "third_party/blink/renderer/core/frame/use_counter.h" #include "third_party/blink/renderer/core/html/imports/html_import.h" #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" #include "third_party/blink/renderer/core/html_names.h" @@ -52,7 +51,8 @@ #include "third_party/blink/renderer/core/script/script_runner.h" #include "third_party/blink/renderer/core/svg_names.h" #include "third_party/blink/renderer/platform/bindings/parkable_string.h" -#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/instrumentation/histogram.h" +#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h" #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" @@ -71,7 +71,8 @@ ScriptLoader::ScriptLoader(ScriptElementBase* element, bool already_started) : element_(element), will_be_parser_executed_(false), - will_execute_when_document_finished_parsing_(false) { + will_execute_when_document_finished_parsing_(false), + force_deferred_(false) { // <spec href="https://html.spec.whatwg.org/C/#already-started">... The // cloning steps for script elements must set the "already started" flag on // the copy if it is set on the element being cloned.</spec> @@ -97,7 +98,7 @@ ScriptLoader::ScriptLoader(ScriptElementBase* element, ScriptLoader::~ScriptLoader() {} -void ScriptLoader::Trace(blink::Visitor* visitor) { +void ScriptLoader::Trace(Visitor* visitor) { visitor->Trace(element_); visitor->Trace(pending_script_); visitor->Trace(prepared_pending_script_); @@ -279,17 +280,17 @@ bool ScriptLoader::BlockForNoModule(mojom::ScriptType script_type, // https://html.spec.whatwg.org/C/#prepare-a-script // - Step 6 of obtaining a preloaded module script // https://html.spec.whatwg.org/C/#link-type-modulepreload. -network::mojom::FetchCredentialsMode ScriptLoader::ModuleScriptCredentialsMode( +network::mojom::CredentialsMode ScriptLoader::ModuleScriptCredentialsMode( CrossOriginAttributeValue cross_origin) { switch (cross_origin) { case kCrossOriginAttributeNotSet: case kCrossOriginAttributeAnonymous: - return network::mojom::FetchCredentialsMode::kSameOrigin; + return network::mojom::CredentialsMode::kSameOrigin; case kCrossOriginAttributeUseCredentials: - return network::mojom::FetchCredentialsMode::kInclude; + return network::mojom::CredentialsMode::kInclude; } NOTREACHED(); - return network::mojom::FetchCredentialsMode::kOmit; + return network::mojom::CredentialsMode::kOmit; } // https://github.com/WICG/feature-policy/issues/135 @@ -449,7 +450,7 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, // <spec step="17">Let module script credentials mode be the module script // credentials mode for the element's crossorigin content attribute.</spec> - network::mojom::FetchCredentialsMode credentials_mode = + network::mojom::CredentialsMode credentials_mode = ModuleScriptCredentialsMode(cross_origin); // <spec step="18">Let cryptographic nonce be the element's @@ -583,7 +584,7 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, // <spec step="24.6.B">"module" // - // Fetch a module script graph given url, settings object, "script", and + // Fetch an external module script graph given url, settings object, and // options.</spec> Modulator* modulator = Modulator::From( ToScriptStateForMainWorld(context_document->GetFrame())); @@ -623,7 +624,7 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, // <spec step="25.2">Switch on the script's type:</spec> switch (GetScriptType()) { - // <spec step="25.2.A">"classic"</spec> + // <spec step="25.2.A">"classic"</spec> case mojom::ScriptType::kClassic: { // <spec step="25.2.A.1">Let script be the result of creating a classic // script using source text, settings object, base URL, and @@ -650,28 +651,38 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, break; } - // <spec step="25.2.B">"module"</spec> + // <spec step="25.2.B">"module"</spec> case mojom::ScriptType::kModule: { - // <spec step="25.2.B.1">Let script be the result of creating a module - // script using source text, settings object, base URL, and - // options.</spec> + // <spec step="25.2.B.1">Fetch an inline module script graph, given + // source text, base URL, settings object, and options. When this + // asynchronously completes, set the script's script to the result. At + // that time, the script is ready.</spec> + // + // <specdef label="fetch-an-inline-module-script-graph" + // href="https://html.spec.whatwg.org/C/#fetch-an-inline-module-script-graph"> const KURL& source_url = element_document.Url(); Modulator* modulator = Modulator::From( ToScriptStateForMainWorld(context_document->GetFrame())); + + // <spec label="fetch-an-inline-module-script-graph" step="1">Let script + // be the result of creating a JavaScript module script using source + // text, settings object, base URL, and options.</spec> ModuleScript* module_script = JSModuleScript::Create( ParkableString(element_->TextFromChildren().Impl()), nullptr, ScriptSourceLocationType::kInline, modulator, source_url, base_url, options, position); - // <spec step="25.2.B.2">If this returns null, set the script's script - // to null and return; the script is ready.</spec> + // <spec label="fetch-an-inline-module-script-graph" step="2">If script + // is null, asynchronously complete this algorithm with null, and abort + // these steps.</spec> if (!module_script) return false; - // <spec step="25.2.B.3">Fetch the descendants of and instantiate - // script, given settings object and the destination "script". When this - // asynchronously completes, set the script's script to the result. At - // that time, the script is ready.</spec> + // <spec label="fetch-an-inline-module-script-graph" step="4">Fetch the + // descendants of and instantiate script, given settings object, the + // destination "script", and visited set. When this asynchronously + // completes with final result, asynchronously complete this algorithm + // with final result.</spec> auto* module_tree_client = MakeGarbageCollected<ModulePendingScriptTreeClient>(); modulator->FetchDescendantsForInlineScript( @@ -718,6 +729,25 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, return true; } + // Check for external script that should be force deferred. + if (GetScriptType() == mojom::ScriptType::kClassic && + element_->HasSourceAttribute() && + context_document->GetFrame()->ShouldForceDeferScript() && + context_document->IsHTMLDocument() && parser_inserted_ && + !element_->AsyncAttributeValue()) { + // In terms of ScriptLoader flags, force deferred scripts behave like + // parser-blocking scripts, except that |force_deferred_| is set. + // The caller of PrepareScript() + // - Force-defers such scripts if the caller supports force-defer + // (i.e., HTMLParserScriptRunner); or + // - Ignores the |force_deferred_| flag and handles such scripts as + // parser-blocking scripts (e.g., XMLParserScriptRunner). + force_deferred_ = true; + will_be_parser_executed_ = true; + + return true; + } + // <spec step="26.B">If the script's type is "classic", and the element has a // src attribute, and the element has been flagged as "parser-inserted", and // the element does not have an async attribute ...</spec> @@ -793,6 +823,14 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, DCHECK_EQ(GetScriptType(), mojom::ScriptType::kClassic); DCHECK(!is_external_script_); + // Check for inline script that should be force deferred. + if (context_document->GetFrame()->ShouldForceDeferScript() && + context_document->IsHTMLDocument() && parser_inserted_) { + force_deferred_ = true; + will_be_parser_executed_ = true; + return true; + } + // <spec step="26.E">If the element does not have a src attribute, and the // element has been flagged as "parser-inserted", and either the parser that // created the script is an XML parser or it's an HTML parser whose script @@ -861,7 +899,7 @@ void ScriptLoader::FetchModuleScriptTree( const ScriptFetchOptions& options) { // <spec step="24.6.B">"module" // - // Fetch a module script graph given url, settings object, "script", and + // Fetch an external module script graph given url, settings object, and // options.</spec> auto* module_tree_client = MakeGarbageCollected<ModulePendingScriptTreeClient>(); @@ -880,23 +918,6 @@ PendingScript* ScriptLoader::TakePendingScript( EnumerationHistogram, scheduling_type_histogram, ("Blink.Script.SchedulingType", kLastScriptSchedulingType + 1)); scheduling_type_histogram.Count(static_cast<int>(scheduling_type)); - - switch (scheduling_type) { - case ScriptSchedulingType::kAsync: - case ScriptSchedulingType::kInOrder: - // As ClassicPendingScript keeps a reference to ScriptResource, - // the ScriptResource is anyway kept alive until evaluation, - // and can be garbage-collected after that (together with - // ClassicPendingScript). - resource_keep_alive_ = nullptr; - break; - - default: - // ScriptResource is kept alive by resource_keep_alive_ - // until ScriptLoader is garbage collected. - break; - } - PendingScript* pending_script = prepared_pending_script_; prepared_pending_script_ = nullptr; pending_script->SetSchedulingType(scheduling_type); @@ -908,6 +929,19 @@ void ScriptLoader::PendingScriptFinished(PendingScript* pending_script) { DCHECK_EQ(pending_script_, pending_script); DCHECK_EQ(pending_script_->GetScriptType(), GetScriptType()); DCHECK(pending_script->IsControlledByScriptRunner()); + DCHECK(pending_script_->GetSchedulingType() == ScriptSchedulingType::kAsync || + pending_script_->GetSchedulingType() == + ScriptSchedulingType::kInOrder); + // Historically we clear |resource_keep_alive_| when the scheduling type is + // kAsync or kInOrder (crbug.com/778799). But if the script resource was + // served via signed exchange, the script may not be in the HTTPCache, + // therefore will need to be refetched over network if it's evicted from the + // memory cache not be in the HTTPCache. So we keep |resource_keep_alive_| to + // keep the resource in the memory cache. + if (resource_keep_alive_ && + !resource_keep_alive_->GetResponse().IsSignedExchangeInnerResponse()) { + resource_keep_alive_ = nullptr; + } Document* context_document = element_->GetDocument().ContextDocument(); if (!context_document) { diff --git a/chromium/third_party/blink/renderer/core/script/script_loader.h b/chromium/third_party/blink/renderer/core/script/script_loader.h index ae9e7b85a7a..0ac49da3bc0 100644 --- a/chromium/third_party/blink/renderer/core/script/script_loader.h +++ b/chromium/third_party/blink/renderer/core/script/script_loader.h @@ -56,7 +56,7 @@ class CORE_EXPORT ScriptLoader final public: ScriptLoader(ScriptElementBase*, bool created_by_parser, bool is_evaluated); ~ScriptLoader() override; - void Trace(blink::Visitor*) override; + void Trace(Visitor*) override; const char* NameInHeapSnapshot() const override { return "ScriptLoader"; } enum LegacyTypeSupport { @@ -78,7 +78,7 @@ class CORE_EXPORT ScriptLoader final static bool BlockForNoModule(mojom::ScriptType, bool nomodule); - static network::mojom::FetchCredentialsMode ModuleScriptCredentialsMode( + static network::mojom::CredentialsMode ModuleScriptCredentialsMode( CrossOriginAttributeValue); // https://html.spec.whatwg.org/C/#prepare-a-script @@ -96,6 +96,7 @@ class CORE_EXPORT ScriptLoader final bool WillExecuteWhenDocumentFinishedParsing() const { return will_execute_when_document_finished_parsing_; } + bool IsForceDeferred() const { return force_deferred_; } bool IsParserInserted() const { return parser_inserted_; } bool AlreadyStarted() const { return already_started_; } bool IsNonBlocking() const { return non_blocking_; } @@ -145,33 +146,27 @@ class CORE_EXPORT ScriptLoader final // https://html.spec.whatwg.org/C/#script-processing-model // "A script element has several associated pieces of state.": - // <spec - // href="https://html.spec.whatwg.org/C/#already-started"> - // ... Initially, script elements must have this flag unset ...</spec> + // <spec href="https://html.spec.whatwg.org/C/#already-started">... Initially, + // script elements must have this flag unset ...</spec> bool already_started_ = false; - // <spec - // href="https://html.spec.whatwg.org/C/#parser-inserted"> - // ... Initially, script elements must have this flag unset. ...</spec> + // <spec href="https://html.spec.whatwg.org/C/#parser-inserted">... Initially, + // script elements must have this flag unset. ...</spec> bool parser_inserted_ = false; - // <spec - // href="https://html.spec.whatwg.org/C/#non-blocking"> - // ... Initially, script elements must have this flag set. ...</spec> + // <spec href="https://html.spec.whatwg.org/C/#non-blocking">... Initially, + // script elements must have this flag set. ...</spec> bool non_blocking_ = true; - // <spec - // href="https://html.spec.whatwg.org/C/#ready-to-be-parser-executed"> + // <spec href="https://html.spec.whatwg.org/C/#ready-to-be-parser-executed"> // ... Initially, script elements must have this flag unset ...</spec> bool ready_to_be_parser_executed_ = false; - // <spec - // href="https://html.spec.whatwg.org/C/#concept-script-type"> - // ... It is determined when the script is prepared, ...</spec> + // <spec href="https://html.spec.whatwg.org/C/#concept-script-type">... It is + // determined when the script is prepared, ...</spec> mojom::ScriptType script_type_ = mojom::ScriptType::kClassic; - // <spec - // href="https://html.spec.whatwg.org/C/#concept-script-external"> + // <spec href="https://html.spec.whatwg.org/C/#concept-script-external"> // ... It is determined when the script is prepared, ...</spec> bool is_external_script_ = false; @@ -180,6 +175,9 @@ class CORE_EXPORT ScriptLoader final bool will_execute_when_document_finished_parsing_; + // The script will be force deferred (https://crbug.com/976061). + bool force_deferred_; + // A PendingScript is first created in PrepareScript() and stored in // |prepared_pending_script_|. // Later, TakePendingScript() is called, and its caller holds a reference diff --git a/chromium/third_party/blink/renderer/core/script/script_runner.cc b/chromium/third_party/blink/renderer/core/script/script_runner.cc index 6e76a6417ec..c3e12f4ab8f 100644 --- a/chromium/third_party/blink/renderer/core/script/script_runner.cc +++ b/chromium/third_party/blink/renderer/core/script/script_runner.cc @@ -256,7 +256,7 @@ void ScriptRunner::ExecuteTask() { #endif } -void ScriptRunner::Trace(blink::Visitor* visitor) { +void ScriptRunner::Trace(Visitor* visitor) { visitor->Trace(document_); visitor->Trace(pending_in_order_scripts_); visitor->Trace(pending_async_scripts_); diff --git a/chromium/third_party/blink/renderer/core/script/script_runner.h b/chromium/third_party/blink/renderer/core/script/script_runner.h index 54ad3d801f9..75b69989ca9 100644 --- a/chromium/third_party/blink/renderer/core/script/script_runner.h +++ b/chromium/third_party/blink/renderer/core/script/script_runner.h @@ -58,7 +58,7 @@ class CORE_EXPORT ScriptRunner final static void MovePendingScript(Document&, Document&, ScriptLoader*); - void Trace(blink::Visitor*); + void Trace(Visitor*); const char* NameInHeapSnapshot() const override { return "ScriptRunner"; } private: diff --git a/chromium/third_party/blink/renderer/core/script/script_scheduling_type.h b/chromium/third_party/blink/renderer/core/script/script_scheduling_type.h index 928143ec895..744f0af5e93 100644 --- a/chromium/third_party/blink/renderer/core/script/script_scheduling_type.h +++ b/chromium/third_party/blink/renderer/core/script/script_scheduling_type.h @@ -62,7 +62,14 @@ enum class ScriptSchedulingType { kAsync, // Inline <script> executed immediately within prepare-a-script. - kImmediate + kImmediate, + + // Force deferred scripts controlled by HTMLParserScriptRunner. + // These are otherwise parser-blocking scripts that are being forced to + // execute after parsing completes (due to a ForceDeferScriptIntervention). + // + // Spec: not yet spec'ed. https://crbug.com/976061 + kForceDefer }; static const int kLastScriptSchedulingType = diff --git a/chromium/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.cc b/chromium/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.cc new file mode 100644 index 00000000000..8ee3c96acf6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.cc @@ -0,0 +1,153 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h" + +#include "third_party/blink/public/platform/web_vector.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" +#include "third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h" +#include "third_party/blink/renderer/core/script/modulator.h" +#include "third_party/blink/renderer/core/script/module_record_resolver.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/text/text_position.h" +#include "v8/include/v8.h" + +namespace blink { + +ValueWrapperSyntheticModuleScript* +ValueWrapperSyntheticModuleScript::CreateJSONWrapperSyntheticModuleScript( + const base::Optional<ModuleScriptCreationParams>& params, + Modulator* settings_object, + const ScriptFetchOptions options_) { + DCHECK(settings_object->HasValidContext()); + ScriptState::Scope scope(settings_object->GetScriptState()); + v8::Local<v8::Context> context = + settings_object->GetScriptState()->GetContext(); + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch try_catch(isolate); + v8::Local<v8::String> original_json = + V8String(isolate, params->GetSourceText().ToString()); + v8::Local<v8::Value> parsed_json; + ExceptionState exception_state(isolate, ExceptionState::kExecutionContext, + "ModuleScriptLoader", + "CreateJSONWrapperSyntheticModuleScript"); + // Step 1. "Let script be a new module script that this algorithm will + // subsequently initialize." + // [spec text] + // Step 2. "Set script's settings object to settings." + // [spec text] + // Step 3. "Set script's base URL and fetch options to null." + // [spec text] + // Step 4. "Set script's parse error and error to rethrow to null." + // [spec text] + // Step 5. "Let json be ? Call(%JSONParse%, undefined, « source »). + // If this throws an exception, set script's parse error to that exception, + // and return script." + // [spec text] + if (!v8::JSON::Parse(context, original_json).ToLocal(&parsed_json)) { + DCHECK(try_catch.HasCaught()); + exception_state.RethrowV8Exception(try_catch.Exception()); + v8::Local<v8::Value> error = exception_state.GetException(); + exception_state.ClearException(); + return ValueWrapperSyntheticModuleScript::CreateWithError( + parsed_json, settings_object, params->GetResponseUrl(), + params->GetResponseUrl(), options_, error); + } else { + return ValueWrapperSyntheticModuleScript::CreateWithDefaultExport( + parsed_json, settings_object, params->GetResponseUrl(), + params->GetResponseUrl(), options_); + } +} + +ValueWrapperSyntheticModuleScript* +ValueWrapperSyntheticModuleScript::CreateWithDefaultExport( + v8::Local<v8::Value> value, + Modulator* settings_object, + const KURL& source_url, + const KURL& base_url, + const ScriptFetchOptions& fetch_options, + const TextPosition& start_position) { + v8::Isolate* isolate = settings_object->GetScriptState()->GetIsolate(); + std::vector<v8::Local<v8::String>> export_names{V8String(isolate, "default")}; + v8::Local<v8::Module> v8_synthetic_module = v8::Module::CreateSyntheticModule( + isolate, V8String(isolate, source_url.GetString()), export_names, + ValueWrapperSyntheticModuleScript::EvaluationSteps); + // Step 6. "Set script's record to the result of creating a synthetic module + // record with a default export of json with settings." + // [spec text] + ModuleRecord record = ModuleRecord(isolate, v8_synthetic_module, source_url); + + ValueWrapperSyntheticModuleScript* value_wrapper_module_script = + MakeGarbageCollected<ValueWrapperSyntheticModuleScript>( + settings_object, record, source_url, base_url, fetch_options, value, + start_position); + settings_object->GetModuleRecordResolver()->RegisterModuleScript( + value_wrapper_module_script); + // Step 7. "Return script." + // [spec text] + return value_wrapper_module_script; +} + +ValueWrapperSyntheticModuleScript* +ValueWrapperSyntheticModuleScript::CreateWithError( + v8::Local<v8::Value> value, + Modulator* settings_object, + const KURL& source_url, + const KURL& base_url, + const ScriptFetchOptions& fetch_options, + v8::Local<v8::Value> error, + const TextPosition& start_position) { + ValueWrapperSyntheticModuleScript* value_wrapper_module_script = + MakeGarbageCollected<ValueWrapperSyntheticModuleScript>( + settings_object, ModuleRecord(), source_url, base_url, fetch_options, + value, start_position); + settings_object->GetModuleRecordResolver()->RegisterModuleScript( + value_wrapper_module_script); + value_wrapper_module_script->SetParseErrorAndClearRecord( + ScriptValue(settings_object->GetScriptState(), error)); + // Step 7. "Return script." + // [spec text] + return value_wrapper_module_script; +} + +ValueWrapperSyntheticModuleScript::ValueWrapperSyntheticModuleScript( + Modulator* settings_object, + ModuleRecord record, + const KURL& source_url, + const KURL& base_url, + const ScriptFetchOptions& fetch_options, + v8::Local<v8::Value> value, + const TextPosition& start_position) + : ModuleScript(settings_object, + record, + source_url, + base_url, + fetch_options), + export_value_(v8::Isolate::GetCurrent(), value) {} + +// TODO(sasebree) Implement this method +v8::MaybeLocal<v8::Value> ValueWrapperSyntheticModuleScript::EvaluationSteps( + v8::Local<v8::Context> context, + v8::Local<v8::Module> module) { + NOTREACHED(); + return v8::MaybeLocal<v8::Value>(); +} + +String ValueWrapperSyntheticModuleScript::InlineSourceTextForCSP() const { + // We don't construct a ValueWrapperSyntheticModuleScript with the original + // source, but instead construct it from the originally parsed + // text. If a need arises for the original module source to be used later, + // ValueWrapperSyntheticModuleScript will need to be modified such that its + // constructor takes this source text as an additional parameter and stashes + // it on the ValueWrapperSyntheticModuleScript. + NOTREACHED(); + return ""; +} + +void ValueWrapperSyntheticModuleScript::Trace(Visitor* visitor) { + visitor->Trace(export_value_); + ModuleScript::Trace(visitor); +} + +} // namespace blink
\ No newline at end of file diff --git a/chromium/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h b/chromium/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h new file mode 100644 index 00000000000..ae19c095cd9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h @@ -0,0 +1,78 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_VALUE_WRAPPER_SYNTHETIC_MODULE_SCRIPT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_VALUE_WRAPPER_SYNTHETIC_MODULE_SCRIPT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/script/module_script.h" + +namespace WTF { +class TextPosition; +} // namespace WTF + +namespace blink { + +class KURL; +class Modulator; +class ModuleScriptCreationParams; + +// ValueWrapperSyntheticModuleScript is a module script +// (https://html.spec.whatwg.org/C/#module-script) that default-exports a single +// v8::Value, for example JSON Module Script: +// https://html.spec.whatwg.org/multipage/webappapis.html#json-module-script +class CORE_EXPORT ValueWrapperSyntheticModuleScript final + : public ModuleScript { + public: + static ValueWrapperSyntheticModuleScript* + CreateJSONWrapperSyntheticModuleScript( + const base::Optional<ModuleScriptCreationParams>& params, + Modulator* settings_object, + const ScriptFetchOptions options_); + + static ValueWrapperSyntheticModuleScript* CreateWithDefaultExport( + v8::Local<v8::Value> value, + Modulator* settings_object, + const KURL& source_url, + const KURL& base_url, + const ScriptFetchOptions& fetch_options, + const TextPosition& start_position = TextPosition::MinimumPosition()); + + static ValueWrapperSyntheticModuleScript* CreateWithError( + v8::Local<v8::Value> value, + Modulator* settings_object, + const KURL& source_url, + const KURL& base_url, + const ScriptFetchOptions& fetch_options, + v8::Local<v8::Value> error, + const TextPosition& start_position = TextPosition::MinimumPosition()); + + ValueWrapperSyntheticModuleScript(Modulator* settings_object, + ModuleRecord record, + const KURL& source_url, + const KURL& base_url, + const ScriptFetchOptions& fetch_options, + v8::Local<v8::Value> value, + const TextPosition& start_position); + + // <specdef + // href="https://heycam.github.io/webidl/#synthetic-module-record"> + // An abstract operation that will be performed upon evaluation of the module, + // taking the Synthetic Module Record as its sole argument. These will usually + // set up the exported values, by using SetSyntheticModuleExport. They must + // not modify [[ExportNames]]. They may return an abrupt completion. + static v8::MaybeLocal<v8::Value> EvaluationSteps( + v8::Local<v8::Context> context, + v8::Local<v8::Module> module); + + String InlineSourceTextForCSP() const override; + void Trace(blink::Visitor* visitor) override; + + private: + TraceWrapperV8Reference<v8::Value> export_value_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_VALUE_WRAPPER_SYNTHETIC_MODULE_SCRIPT_H_
\ No newline at end of file |