Skip to content

Commit

Permalink
Add API for dealing with fragment directives
Browse files Browse the repository at this point in the history
This CL implements, behind a flag, the fragment directive API described
in WICG/scroll-to-text-fragment#160, allowing
authors to read back directives specified in the :~: portion of the URL
fragment, as well as create text-directives URLs using a Range or
Selection.

Because the feature was previously specific to text directives, the
parsing of the URL fragment directive is currently spread across
Document, TextFragmentAnchor and TextFragmentSelector. The semantics of
the new API would make it more convenient to move all this logic into
FragmentDirective itself but this is a sizeable change so we do this in
a separate CL: https://crrev.com/c/3216206.

Bug: 1214791
Change-Id: I189363a5fe9843ac1e55c7078609d61aa33bc26d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3209166
Commit-Queue: David Bokan <[email protected]>
Reviewed-by: Robert Flack <[email protected]>
Cr-Commit-Position: refs/heads/main@{#933676}
  • Loading branch information
bokand authored and Chromium LUCI CQ committed Oct 20, 2021
1 parent 3c8ba5c commit c1d16e9
Show file tree
Hide file tree
Showing 31 changed files with 1,448 additions and 21 deletions.
2 changes: 2 additions & 0 deletions third_party/blink/public/mojom/web_feature/web_feature.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -3370,6 +3370,8 @@ enum WebFeature {
kReadOrWriteWebDatabase = 4061,
kAutoDarkMode = 4062,
kHttpRefreshWhenScriptingDisabled = 4063,
kV8FragmentDirective_Items_AttributeGetter = 4064,
kV8FragmentDirective_CreateSelectorDirective_Method = 4065,

// Add new features immediately above this line. Don't change assigned
// numbers of any item, and don't reuse removed slots.
Expand Down
10 changes: 10 additions & 0 deletions third_party/blink/renderer/bindings/generated_in_core.gni
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ generated_dictionary_sources_in_core = [
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_stream_pipe_options.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_submit_event_init.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_submit_event_init.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_directive_options.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_directive_options.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_format_update_event_init.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_format_update_event_init.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_update_event_init.cc",
Expand Down Expand Up @@ -703,6 +705,8 @@ generated_interface_sources_in_core = [
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_deprecation_report_body.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_dev_tools_host.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_dev_tools_host.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_directive.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_directive.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_document.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_document.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_document_fragment.cc",
Expand Down Expand Up @@ -1153,6 +1157,8 @@ generated_interface_sources_in_core = [
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_security_policy_violation_event.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_selection.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_selection.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_selector_directive.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_selector_directive.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_shadow_root.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_shadow_root.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_shared_worker.cc",
Expand Down Expand Up @@ -1373,6 +1379,8 @@ generated_interface_sources_in_core = [
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_test_report_body.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_directive.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_directive.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_event.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_event.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_text_format_update_event.cc",
Expand Down Expand Up @@ -1621,6 +1629,8 @@ generated_union_sources_in_core = [
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_object_string.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_performancemeasureoptions_string.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_performancemeasureoptions_string.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_range_selection.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_range_selection.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_readablebytestreamcontroller_readablestreamdefaultcontroller.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_readablebytestreamcontroller_readablestreamdefaultcontroller.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_readablestreambyobreader_readablestreamdefaultreader.cc",
Expand Down
4 changes: 4 additions & 0 deletions third_party/blink/renderer/bindings/idl_in_core.gni
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ static_idl_files_in_core = get_path_info(
"//third_party/blink/renderer/core/frame/coop_access_violation_report_body.idl",
"//third_party/blink/renderer/core/frame/csp/csp_violation_report_body.idl",
"//third_party/blink/renderer/core/frame/deprecation_report_body.idl",
"//third_party/blink/renderer/core/frame/directive.idl",
"//third_party/blink/renderer/core/frame/document_policy_violation_report_body.idl",
"//third_party/blink/renderer/core/frame/external.idl",
"//third_party/blink/renderer/core/frame/fragment_directive.idl",
Expand Down Expand Up @@ -304,7 +305,10 @@ static_idl_files_in_core = get_path_info(
"//third_party/blink/renderer/core/frame/scroll_into_view_options.idl",
"//third_party/blink/renderer/core/frame/scroll_options.idl",
"//third_party/blink/renderer/core/frame/scroll_to_options.idl",
"//third_party/blink/renderer/core/frame/selector_directive.idl",
"//third_party/blink/renderer/core/frame/test_report_body.idl",
"//third_party/blink/renderer/core/frame/text_directive.idl",
"//third_party/blink/renderer/core/frame/text_directive_options.idl",
"//third_party/blink/renderer/core/frame/ua_data_values.idl",
"//third_party/blink/renderer/core/frame/user_activation.idl",
"//third_party/blink/renderer/core/frame/visual_viewport.idl",
Expand Down
3 changes: 2 additions & 1 deletion third_party/blink/renderer/core/dom/document.cc
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ Document::Document(const DocumentInit& initializer,
: initializer.UkmSourceId()),
viewport_data_(MakeGarbageCollected<ViewportData>(*this)),
is_for_external_handler_(initializer.IsForExternalHandler()),
fragment_directive_(MakeGarbageCollected<FragmentDirective>()),
fragment_directive_(MakeGarbageCollected<FragmentDirective>(*this)),
display_lock_document_state_(
MakeGarbageCollected<DisplayLockDocumentState>(this)),
font_preload_manager_(MakeGarbageCollected<FontPreloadManager>(*this)),
Expand Down Expand Up @@ -4013,6 +4013,7 @@ void Document::SetURL(const KURL& url) {
// --> "#id". See https://github.com/WICG/scroll-to-text-fragment.
String fragment = new_url.FragmentIdentifier();
wtf_size_t start_pos = fragment.Find(kFragmentDirectivePrefix);
fragment_directive_string_ = String();
if (start_pos != kNotFound) {
fragment_directive_string_ =
fragment.Substring(start_pos + kFragmentDirectivePrefixStringLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace blink {

// This is a wrapper class for a range in flat tree that is relocatable by
// relacating the start and end positions in DOM tree.
// relocating the start and end positions in DOM tree.
class CORE_EXPORT RangeInFlatTree final
: public GarbageCollected<RangeInFlatTree> {
public:
Expand Down
7 changes: 7 additions & 0 deletions third_party/blink/renderer/core/frame/build.gni
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ blink_core_sources_frame = [
"deprecation_report_body.h",
"device_single_window_event_controller.cc",
"device_single_window_event_controller.h",
"directive.cc",
"directive.h",
"display_cutout_client_impl.cc",
"display_cutout_client_impl.h",
"document_policy_violation_report_body.cc",
Expand All @@ -61,6 +63,7 @@ blink_core_sources_frame = [
"permissions_policy_violation_report_body.h",
"find_in_page.cc",
"find_in_page.h",
"fragment_directive.cc",
"fragment_directive.h",
"frame.cc",
"frame.h",
Expand Down Expand Up @@ -185,6 +188,8 @@ blink_core_sources_frame = [
"screen.h",
"screen_metrics_emulator.cc",
"screen_metrics_emulator.h",
"selector_directive.cc",
"selector_directive.h",
"settings.cc",
"settings.h",
"settings_delegate.cc",
Expand All @@ -195,6 +200,8 @@ blink_core_sources_frame = [
"sticky_ad_detector.h",
"test_report_body.cc",
"test_report_body.h",
"text_directive.cc",
"text_directive.h",
"use_counter_impl.cc",
"use_counter_impl.h",
"user_activation.cc",
Expand Down
36 changes: 36 additions & 0 deletions third_party/blink/renderer/core/frame/directive.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2021 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/frame/directive.h"

namespace blink {

Directive::Directive(Type type) : type_(type) {}
Directive::~Directive() = default;

Directive::Type Directive::GetType() const {
return type_;
}

String Directive::type() const {
DEFINE_STATIC_LOCAL(const String, text, ("text"));

switch (type_) {
case kText:
return text;
}

NOTREACHED();
return String();
}

String Directive::toString() const {
return ToStringImpl();
}

void Directive::Trace(Visitor* visitor) const {
ScriptWrappable::Trace(visitor);
}

} // namespace blink
44 changes: 44 additions & 0 deletions third_party/blink/renderer/core/frame/directive.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2021 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_FRAME_DIRECTIVE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_DIRECTIVE_H_

#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"

namespace blink {

// Provides the JavaScript-exposed Directive base class used by
// window.fragmentDirective.items. This is the base interface for all fragment
// directive types.
// See: https://github.com/WICG/scroll-to-text-fragment/issues/160
// TODO(bokan): Update link once we have better public documentation.
class Directive : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();

public:
enum Type { kText };

explicit Directive(Type type);
~Directive() override;

Type GetType() const;
void Trace(Visitor*) const override;

// Web-exposed Directive interface.
String type() const;
String toString() const;

protected:
// Override in subclasses to implement the toString() web-exposed method.
virtual String ToStringImpl() const = 0;

private:
Type type_;
};

} // namespace blink

#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_DIRECTIVE_H_
16 changes: 16 additions & 0 deletions third_party/blink/renderer/core/frame/directive.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2021 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.

// https://github.com/WICG/ScrollToTextFragment
[
RuntimeEnabled=TextFragmentAPI
] enum DirectiveType { "text" };

[
Exposed=Window,
RuntimeEnabled=TextFragmentAPI
] interface Directive {
readonly attribute DirectiveType type;
DOMString toString();
};
137 changes: 137 additions & 0 deletions third_party/blink/renderer/core/frame/fragment_directive.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2021 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/frame/fragment_directive.h"

#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_range_selection.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/range.h"
#include "third_party/blink/renderer/core/editing/dom_selection.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/text_directive.h"
#include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"

namespace blink {

FragmentDirective::FragmentDirective(Document& owner_document)
: owner_document_(&owner_document) {}
FragmentDirective::~FragmentDirective() = default;

void FragmentDirective::ClearDirectives() {
directives_.clear();
}

void FragmentDirective::AddDirective(Directive* directive) {
directives_.push_back(directive);
}

void FragmentDirective::Trace(Visitor* visitor) const {
ScriptWrappable::Trace(visitor);
visitor->Trace(directives_);
visitor->Trace(owner_document_);
}

const HeapVector<Member<Directive>>& FragmentDirective::items() const {
return directives_;
}

namespace {
void RejectWithCode(ScriptPromiseResolver* resolver,
DOMExceptionCode code,
const String& message) {
ScriptState::Scope scope(resolver->GetScriptState());
ExceptionState exception_state(resolver->GetScriptState()->GetIsolate(),
ExceptionState::kExecutionContext,
"FragmentDirective",
"createSelectorDirective");
exception_state.ThrowDOMException(code, message);
resolver->Reject(exception_state);
}
} // namespace

ScriptPromise FragmentDirective::createSelectorDirective(
ScriptState* state,
const V8UnionRangeOrSelection* arg) {
if (ExecutionContext::From(state)->IsContextDestroyed())
return ScriptPromise();

ScriptPromiseResolver* resolver =
MakeGarbageCollected<ScriptPromiseResolver>(state);

// Access the promise first to ensure it is created so that the proper state
// can be changed when it is resolved or rejected.
ScriptPromise promise = resolver->Promise();

Range* range = nullptr;

if (arg->GetContentType() ==
V8UnionRangeOrSelection::ContentType::kSelection) {
DOMSelection* selection = arg->GetAsSelection();
if (selection->rangeCount() == 0) {
RejectWithCode(resolver, DOMExceptionCode::kNotSupportedError,
"Selection must contain a range");
return promise;
}

range = selection->getRangeAt(0, ASSERT_NO_EXCEPTION);
} else {
DCHECK_EQ(arg->GetContentType(),
V8UnionRangeOrSelection::ContentType::kRange);
range = arg->GetAsRange();
}

if (!range || range->collapsed()) {
RejectWithCode(resolver, DOMExceptionCode::kNotSupportedError,
"RangeOrSelector must be non-null and non-collapsed");
return promise;
}

if (range->OwnerDocument() != owner_document_) {
RejectWithCode(resolver, DOMExceptionCode::kWrongDocumentError,
"RangeOrSelector must be from this document");
return promise;
}

LocalFrame* frame = range->OwnerDocument().GetFrame();
if (!frame) {
RejectWithCode(resolver, DOMExceptionCode::kInvalidStateError,
"Document must be attached to frame");
return promise;
}

EphemeralRangeInFlatTree ephemeral_range(range);
RangeInFlatTree* range_in_flat_tree = MakeGarbageCollected<RangeInFlatTree>(
ephemeral_range.StartPosition(), ephemeral_range.EndPosition());

auto* generator = MakeGarbageCollected<TextFragmentSelectorGenerator>(frame);
generator->Generate(
*range_in_flat_tree,
WTF::Bind(
[](ScriptPromiseResolver* resolver,
TextFragmentSelectorGenerator* generator,
const RangeInFlatTree* range,
const TextFragmentSelector& selector) {
if (selector.Type() ==
TextFragmentSelector::SelectorType::kInvalid) {
RejectWithCode(resolver, DOMExceptionCode::kOperationError,
"Failed to generate selector for the given range");
return;
}
TextDirective* dom_text_directive =
MakeGarbageCollected<TextDirective>(selector);
dom_text_directive->DidFinishMatching(range);
resolver->Resolve(dom_text_directive);
},
WrapPersistent(resolver), WrapPersistent(generator),
WrapPersistent(range_in_flat_tree)));

return promise;
}

} // namespace blink
Loading

0 comments on commit c1d16e9

Please sign in to comment.