Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/Components/Web.JS/src/Services/NavigationEnhancement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

import { synchronizeDomContent } from '../Rendering/DomMerging/DomSync';
import { attachProgrammaticEnhancedNavigationHandler, handleClickForNavigationInterception, hasInteractiveRouter, isForSamePath, isSamePageWithHash, notifyEnhancedNavigationListeners, performScrollToElementOnTheSamePage } from './NavigationUtils';
import { attachProgrammaticEnhancedNavigationHandler, handleClickForNavigationInterception, hasInteractiveRouter, isForSamePath, notifyEnhancedNavigationListeners, performScrollToElementOnTheSamePage, isSameUrlWithDifferentHash } from './NavigationUtils';
import { resetScrollAfterNextBatch, resetScrollIfNeeded } from '../Rendering/Renderer';

/*
Expand Down Expand Up @@ -99,7 +99,7 @@ function onDocumentClick(event: MouseEvent) {
handleClickForNavigationInterception(event, absoluteInternalHref => {
const originalLocation = location.href;

const shouldScrollToHash = isSamePageWithHash(absoluteInternalHref);
const shouldScrollToHash = isSameUrlWithDifferentHash(originalLocation, absoluteInternalHref);
history.pushState(null, /* ignored title */ '', absoluteInternalHref);

if (shouldScrollToHash) {
Expand All @@ -120,6 +120,11 @@ function onPopState(state: PopStateEvent) {
return;
}

if (state.state == null && isSameUrlWithDifferentHash(currentContentUrl, location.href)) {
currentContentUrl = location.href;
return;
}

// load the new page
performEnhancedPageLoad(location.href, /* interceptedLink */ false);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Components/Web.JS/src/Services/NavigationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import '@microsoft/dotnet-js-interop';
import { resetScrollAfterNextBatch } from '../Rendering/Renderer';
import { EventDelegator } from '../Rendering/Events/EventDelegator';
import { attachEnhancedNavigationListener, getInteractiveRouterRendererId, handleClickForNavigationInterception, hasInteractiveRouter, hasProgrammaticEnhancedNavigationHandler, isForSamePath, isSamePageWithHash, isWithinBaseUriSpace, performProgrammaticEnhancedNavigation, performScrollToElementOnTheSamePage, scrollToElement, setHasInteractiveRouter, toAbsoluteUri } from './NavigationUtils';
import { attachEnhancedNavigationListener, getInteractiveRouterRendererId, handleClickForNavigationInterception, hasInteractiveRouter, hasProgrammaticEnhancedNavigationHandler, isForSamePath, isSameUrlWithDifferentHash, isWithinBaseUriSpace, performProgrammaticEnhancedNavigation, performScrollToElementOnTheSamePage, scrollToElement, setHasInteractiveRouter, toAbsoluteUri } from './NavigationUtils';
import { WebRendererId } from '../Rendering/WebRendererId';
import { isRendererAttached } from '../Rendering/WebRendererInteropMethods';

Expand Down Expand Up @@ -150,7 +150,7 @@ function performExternalNavigation(uri: string, replace: boolean) {
async function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean, replace: boolean, state: string | undefined = undefined, skipLocationChangingCallback = false) {
ignorePendingNavigation();

if (isSamePageWithHash(absoluteInternalHref)) {
if (isSameUrlWithDifferentHash(location.href, absoluteInternalHref)) {
saveToBrowserHistory(absoluteInternalHref, replace, state);
performScrollToElementOnTheSamePage(absoluteInternalHref);
return;
Expand Down
12 changes: 9 additions & 3 deletions src/Components/Web.JS/src/Services/NavigationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ export function isWithinBaseUriSpace(href: string) {
&& (nextChar === '' || nextChar === '/' || nextChar === '?' || nextChar === '#');
}

export function isSamePageWithHash(absoluteHref: string): boolean {
const url = new URL(absoluteHref);
return url.hash !== '' && location.origin === url.origin && location.pathname === url.pathname && location.search === url.search;
export function isSameUrlWithDifferentHash(oldUrl: string, newUrl: string): boolean {
try {
const a = new URL(oldUrl);
const b = new URL(newUrl);
return a.origin === b.origin && a.pathname === b.pathname
&& a.search === b.search && b.hash !== '';
} catch {
return false;
}
}

export function isForSamePath(url1: string, url2: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNetCore.InternalTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.BiDi.Communication;
using OpenQA.Selenium.DevTools;
using OpenQA.Selenium.Support.Extensions;
using TestServer;
using Xunit.Abstractions;
Expand Down Expand Up @@ -195,6 +196,40 @@ public void CanScrollToHashWithoutPerformingFullNavigation()
.EndsWith("scroll-to-hash", StringComparison.Ordinal));
}

[Fact]
public void NonEnhancedNavCanScrollToHashWithoutFetchingPageAnchor()
{
Navigate($"{ServerPathBase}/nav/scroll-to-hash");
var originalTextElem = Browser.Exists(By.CssSelector("#anchor #text"));
Browser.Equal("Text", () => originalTextElem.Text);

Browser.Exists(By.CssSelector("#anchor #scroll-anchor")).Click();
Browser.True(() => Browser.GetScrollY() > 500);
Browser.True(() => Browser
.Exists(By.CssSelector("#anchor #uri-on-page-load"))
.GetDomAttribute("data-value")
.EndsWith("scroll-to-hash", StringComparison.Ordinal));

Browser.Equal("Text", () => originalTextElem.Text);
}

[Fact]
public void NonEnhancedNavCanScrollToHashWithoutFetchingPageNavLink()
{
Navigate($"{ServerPathBase}/nav/scroll-to-hash");
var originalTextElem = Browser.Exists(By.CssSelector("#navlink #text"));
Browser.Equal("Text", () => originalTextElem.Text);

Browser.Exists(By.CssSelector("#navlink #scroll-anchor")).Click();
Browser.True(() => Browser.GetScrollY() > 500);
Browser.True(() => Browser
.Exists(By.CssSelector("#navlink #uri-on-page-load"))
.GetDomAttribute("data-value")
.EndsWith("scroll-to-hash", StringComparison.Ordinal));

Browser.Equal("Text", () => originalTextElem.Text);
}

[Theory]
[InlineData("server")]
[InlineData("webassembly")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@page "/nav/scroll-to-hash"
@attribute [StreamRendering]
@inject NavigationManager NavigationManager
@using Microsoft.AspNetCore.Components.Forms

<PageTitle>Page for scrolling to hash</PageTitle>

Expand All @@ -13,6 +14,18 @@
<div id="uri-on-page-load" style="display: none" data-value="@uriOnPageLoad"></div>
</p>

<div data-enhance-nav="false" id="anchor">
<a id="scroll-anchor" href="nav/scroll-to-hash#some-content">Scroll via anchor</a>
<div id="uri-on-page-load" style="display: none" data-value="@uriOnPageLoad"></div>
<p id="text">Text</p>
</div>

<div data-enhance-nav="false" id="navlink">
<NavLink id="scroll-anchor" href="nav/scroll-to-hash#some-content">Scroll via NavLink</NavLink>
<div id="uri-on-page-load" style="display: none" data-value="@uriOnPageLoad"></div>
<p id="text">Text</p>
</div>

<div style="height: 2000px; border: 2px dashed red;">spacer</div>

@if (showContent)
Expand Down
Loading