Skip to content

Commit

Permalink
Implement navigateEvent.intercept() and navigateEvent.canIntercept
Browse files Browse the repository at this point in the history
Follows WICG/navigation-api#235

intercept() works very similarly to transitionWhile(), except that
instead of taking a mandatory Promise, it takes an optional handler
function. If a function is provided and it returns a promise,
navigation finish will be delayed until the Promise resolve, just as
transitionWhile() delays navigation finish for its Promise.

canIntercept is identical to canTransition.

Intent to ship: https://groups.google.com/a/chromium.org/g/blink-dev/c/jyWqjAEv5LU

Bug: 1336000
Change-Id: I94edd7fdc727080594f16fe4511cb7c302d88941
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3688177
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
Commit-Queue: Nate Chapin <japhet@chromium.org>
Reviewed-by: Domenic Denicola <domenic@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1026325}
NOKEYCHECK=True
GitOrigin-RevId: d53817e29d5e857782ff8d977dbc3b89eb53831b
  • Loading branch information
natechapin authored and copybara-github committed Jul 20, 2022
1 parent ff0fd4e commit 81213ed
Show file tree
Hide file tree
Showing 473 changed files with 9,494 additions and 155 deletions.
1 change: 1 addition & 0 deletions blink/public/devtools_protocol/browser_protocol.pdl
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ experimental domain Audits
LocalCSSFileExtensionRejected
MediaSourceAbortRemove
MediaSourceDurationTruncatingBuffered
NavigateEventTransitionWhile
NoSysexWebMIDIWithoutPermission
NotificationInsecureOrigin
NotificationPermissionRequestedIframe
Expand Down
1 change: 1 addition & 0 deletions blink/public/mojom/use_counter/metrics/web_feature.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -3621,6 +3621,7 @@ enum WebFeature {
kCookieDomainNonASCII = 4300,
kClientHintsMetaEquivDelegateCH = 4301,
kExpectCTHeader = 4302,
kNavigateEventTransitionWhile = 4303,

// Add new features immediately above this line. Don't change assigned
// numbers of any item, and don't reuse removed slots.
Expand Down
4 changes: 4 additions & 0 deletions blink/renderer/bindings/generated_in_core.gni
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ generated_callback_function_sources_in_core = [
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_mojo_watch_callback.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_mutation_callback.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_mutation_callback.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_handler.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_handler.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_no_argument_constructor.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_no_argument_constructor.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_performance_observer_callback.cc",
Expand Down Expand Up @@ -97,6 +99,8 @@ generated_dictionary_sources_in_core = [
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigate_event_init.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_current_entry_change_event_init.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_current_entry_change_event_init.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_options.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_options.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_navigate_options.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_navigate_options.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_options.cc",
Expand Down
1 change: 1 addition & 0 deletions blink/renderer/bindings/idl_in_core.gni
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ static_idl_files_in_core = get_path_info(
"//third_party/blink/renderer/core/navigation_api/navigation_current_entry_change_event.idl",
"//third_party/blink/renderer/core/navigation_api/navigation_destination.idl",
"//third_party/blink/renderer/core/navigation_api/navigation_history_entry.idl",
"//third_party/blink/renderer/core/navigation_api/navigation_intercept_options.idl",
"//third_party/blink/renderer/core/navigation_api/navigation_navigate_options.idl",
"//third_party/blink/renderer/core/navigation_api/navigation_options.idl",
"//third_party/blink/renderer/core/navigation_api/navigation_reload_options.idl",
Expand Down
3 changes: 3 additions & 0 deletions blink/renderer/core/frame/deprecation/deprecation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ const DeprecationInfo GetDeprecationInfo(WebFeature feature) {
case WebFeature::kIdentityInCanMakePaymentEvent:
return DeprecationInfo::WithTranslation(
feature, DeprecationIssueType::kIdentityInCanMakePaymentEvent);
case WebFeature::kNavigateEventTransitionWhile:
return DeprecationInfo::WithTranslation(
feature, DeprecationIssueType::kNavigateEventTransitionWhile);
default:
return DeprecationInfo::NotDeprecated(feature);
}
Expand Down
4 changes: 4 additions & 0 deletions blink/renderer/core/inspector/inspector_audits_issue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@ void AuditsIssue::ReportDeprecationIssue(ExecutionContext* execution_context,
type = protocol::Audits::DeprecationIssueTypeEnum::
MediaSourceDurationTruncatingBuffered;
break;
case DeprecationIssueType::kNavigateEventTransitionWhile:
type = protocol::Audits::DeprecationIssueTypeEnum::
NavigateEventTransitionWhile;
break;
case DeprecationIssueType::kNoSysexWebMIDIWithoutPermission:
type = protocol::Audits::DeprecationIssueTypeEnum::
NoSysexWebMIDIWithoutPermission;
Expand Down
1 change: 1 addition & 0 deletions blink/renderer/core/inspector/inspector_audits_issue.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ enum class DeprecationIssueType {
kLocalCSSFileExtensionRejected,
kMediaSourceAbortRemove,
kMediaSourceDurationTruncatingBuffered,
kNavigateEventTransitionWhile,
kNoSysexWebMIDIWithoutPermission,
kNotDeprecated,
kNotificationInsecureOrigin,
Expand Down
2 changes: 2 additions & 0 deletions blink/renderer/core/loader/document_loader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,8 @@ void DocumentLoader::UpdateForSameDocumentNavigation(

if (auto* navigation_api = NavigationApi::navigation(*frame_->DomWindow()))
navigation_api->UpdateForNavigation(*history_item_, type);
if (!frame_)
return;

// Aything except a history.pushState/replaceState is considered a new
// navigation that resets whether the user has scrolled and fires popstate.
Expand Down
86 changes: 62 additions & 24 deletions blink/renderer/core/navigation_api/navigate_event.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

#include "third_party/blink/public/mojom/devtools/console_message.mojom-shared.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_navigate_event_init.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_handler.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_navigation_transition_while_options.h"
#include "third_party/blink/renderer/core/dom/abort_signal.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/event_interface_names.h"
#include "third_party/blink/renderer/core/event_type_names.h"
#include "third_party/blink/renderer/core/frame/deprecation/deprecation.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/html/forms/form_data.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
Expand All @@ -27,7 +30,7 @@ NavigateEvent::NavigateEvent(ExecutionContext* context,
ExecutionContextClient(context),
navigation_type_(init->navigationType()),
destination_(init->destination()),
can_transition_(init->canTransition()),
can_intercept_(init->canIntercept()),
user_initiated_(init->userInitiated()),
hash_change_(init->hashChange()),
signal_(init->signal()),
Expand All @@ -40,52 +43,51 @@ NavigateEvent::NavigateEvent(ExecutionContext* context,
DCHECK(IsA<LocalDOMWindow>(context));
}

void NavigateEvent::transitionWhile(ScriptState* script_state,
ScriptPromise newNavigationAction,
NavigationTransitionWhileOptions* options,
ExceptionState& exception_state) {
bool NavigateEvent::PerformSharedInteceptChecksAndSetup(
NavigationTransitionWhileOptions* options,
const String& function_name,
ExceptionState& exception_state) {
if (!DomWindow()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"transitionWhile() may not be called in a "
"detached window.");
return;
function_name + "() may not be called in a detached window.");
return false;
}

if (!isTrusted()) {
exception_state.ThrowSecurityError(
"transitionWhile() may only be called on a "
"trusted event.");
return;
function_name + "() may only be called on a trusted event.");
return false;
}

if (!can_transition_) {
if (!can_intercept_) {
exception_state.ThrowSecurityError(
"A navigation with URL '" + url_.ElidedString() +
"' cannot be intercepted by transitionWhile() in a window with origin "
"'" +
"' cannot be intercepted by in a window with origin '" +
DomWindow()->GetSecurityOrigin()->ToString() + "' and URL '" +
DomWindow()->Url().ElidedString() + "'.");
return;
return false;
}

if (!IsBeingDispatched()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"transitionWhile() may only be called while the navigate event is "
"being dispatched.");
function_name +
"() may only be called while the navigate event is "
"being dispatched.");
return false;
}

if (defaultPrevented()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"transitionWhile() may not be called if the event has been canceled.");
return;
function_name + "() may not be called if the event has been canceled.");
return false;
}

if (navigation_action_promises_list_.IsEmpty())
if (!HasNavigationActions()) {
DomWindow()->document()->AddFocusedElementChangeObserver(this);
navigation_action_promises_list_.push_back(newNavigationAction);
}

if (options->hasFocusReset()) {
if (focus_reset_behavior_ &&
Expand Down Expand Up @@ -117,12 +119,47 @@ void NavigateEvent::transitionWhile(ScriptState* script_state,
}
scroll_restoration_behavior_ = options->scrollRestoration();
}
return true;
}

void NavigateEvent::transitionWhile(ScriptPromise newNavigationAction,
NavigationTransitionWhileOptions* options,
ExceptionState& exception_state) {
if (DomWindow()) {
Deprecation::CountDeprecation(DomWindow(),
WebFeature::kNavigateEventTransitionWhile);
}

if (PerformSharedInteceptChecksAndSetup(options, "transitionWhile",
exception_state)) {
has_navigation_actions_ = true;
navigation_action_promises_list_.push_back(newNavigationAction);
}
}

void NavigateEvent::intercept(NavigationInterceptOptions* options,
ExceptionState& exception_state) {
if (PerformSharedInteceptChecksAndSetup(options, "intercept",
exception_state)) {
has_navigation_actions_ = true;
if (options->hasHandler())
navigation_action_handlers_list_.push_back(options->handler());
}
}

void NavigateEvent::FinalizeNavigationActionPromisesList() {
for (auto& function : navigation_action_handlers_list_) {
ScriptPromise result;
if (function->Invoke(this).To(&result))
navigation_action_promises_list_.push_back(result);
}
navigation_action_handlers_list_.clear();
}

void NavigateEvent::ResetFocusIfNeeded() {
// We only do focus reset if transitionWhile() was called, opting us into the
// new default behavior which the navigation API provides.
if (navigation_action_promises_list_.IsEmpty())
if (!HasNavigationActions())
return;
auto* document = DomWindow()->document();
document->RemoveFocusedElementChangeObserver(this);
Expand Down Expand Up @@ -150,12 +187,12 @@ void NavigateEvent::ResetFocusIfNeeded() {
}

void NavigateEvent::DidChangeFocus() {
DCHECK(!navigation_action_promises_list_.IsEmpty());
DCHECK(HasNavigationActions());
did_change_focus_during_transition_while_ = true;
}

bool NavigateEvent::ShouldSendAxEvents() const {
return !navigation_action_promises_list_.IsEmpty();
return HasNavigationActions();
}

void NavigateEvent::restoreScroll(ExceptionState& exception_state) {
Expand Down Expand Up @@ -231,6 +268,7 @@ void NavigateEvent::Trace(Visitor* visitor) const {
visitor->Trace(form_data_);
visitor->Trace(info_);
visitor->Trace(navigation_action_promises_list_);
visitor->Trace(navigation_action_handlers_list_);
}

} // namespace blink
19 changes: 15 additions & 4 deletions blink/renderer/core/navigation_api/navigate_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ namespace blink {
class AbortSignal;
class NavigationDestination;
class NavigateEventInit;
class NavigationInterceptOptions;
class NavigationTransitionWhileOptions;
class ExceptionState;
class FormData;
class ScriptPromise;
class V8NavigationInterceptHandler;

class NavigateEvent final : public Event,
public ExecutionContextClient,
Expand All @@ -50,25 +52,28 @@ class NavigateEvent final : public Event,

String navigationType() { return navigation_type_; }
NavigationDestination* destination() { return destination_; }
bool canTransition() const { return can_transition_; }
bool canIntercept() const { return can_intercept_; }
bool userInitiated() const { return user_initiated_; }
bool hashChange() const { return hash_change_; }
AbortSignal* signal() { return signal_; }
FormData* formData() const { return form_data_; }
String downloadRequest() const { return download_request_; }
ScriptValue info() const { return info_; }

void transitionWhile(ScriptState*,
ScriptPromise newNavigationAction,
void transitionWhile(ScriptPromise newNavigationAction,
NavigationTransitionWhileOptions*,
ExceptionState&);
void intercept(NavigationInterceptOptions*, ExceptionState&);

void restoreScroll(ExceptionState&);
void RestoreScrollAfterTransitionIfNeeded();

const HeapVector<ScriptPromise>& GetNavigationActionPromisesList() {
return navigation_action_promises_list_;
}
bool HasNavigationActions() const { return has_navigation_actions_; }
void FinalizeNavigationActionPromisesList();

void ResetFocusIfNeeded();
bool ShouldSendAxEvents() const;

Expand All @@ -81,12 +86,15 @@ class NavigateEvent final : public Event,
void Trace(Visitor*) const final;

private:
bool PerformSharedInteceptChecksAndSetup(NavigationTransitionWhileOptions*,
const String& function_name,
ExceptionState&);
void RestoreScrollInternal();
bool InManualScrollRestorationMode();

String navigation_type_;
Member<NavigationDestination> destination_;
bool can_transition_;
bool can_intercept_;
bool user_initiated_;
bool hash_change_;
Member<AbortSignal> signal_;
Expand All @@ -99,7 +107,10 @@ class NavigateEvent final : public Event,
absl::optional<HistoryItem::ViewState> history_item_view_state_;

KURL url_;
bool has_navigation_actions_ = false;
HeapVector<ScriptPromise> navigation_action_promises_list_;
HeapVector<Member<V8NavigationInterceptHandler>>
navigation_action_handlers_list_;

enum class ManualRestoreState { kNotRestored, kRestored, kDone };
ManualRestoreState restore_state_ = ManualRestoreState::kNotRestored;
Expand Down
6 changes: 4 additions & 2 deletions blink/renderer/core/navigation_api/navigate_event.idl
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@

readonly attribute NavigationNavigationType navigationType;
readonly attribute NavigationDestination destination;
readonly attribute boolean canTransition;
[ImplementedAs=canIntercept] readonly attribute boolean canTransition;
readonly attribute boolean canIntercept;
readonly attribute boolean userInitiated;
readonly attribute boolean hashChange;
readonly attribute AbortSignal signal;
readonly attribute FormData? formData;
readonly attribute DOMString? downloadRequest;
readonly attribute any info;

[CallWith=ScriptState, RaisesException] void transitionWhile(Promise<void> newNavigationAction, optional NavigationTransitionWhileOptions options = {});
[RaisesException] void transitionWhile(Promise<void> newNavigationAction, optional NavigationTransitionWhileOptions options = {});
[RaisesException] void intercept(optional NavigationInterceptOptions options = {});
[RaisesException] void restoreScroll();
};
2 changes: 1 addition & 1 deletion blink/renderer/core/navigation_api/navigate_event_init.idl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ enum NavigationNavigationType {
dictionary NavigateEventInit : EventInit {
NavigationNavigationType navigationType = "push";
required NavigationDestination destination;
boolean canTransition = false;
boolean canIntercept = false;
boolean userInitiated = false;
boolean hashChange = false;
required AbortSignal signal;
Expand Down
Loading

0 comments on commit 81213ed

Please sign in to comment.