Skip to content

Commit

Permalink
Defer link interception until Router is initialized
Browse files Browse the repository at this point in the history
Fixes #9834
  • Loading branch information
pranavkm committed May 17, 2019
1 parent 818dc3b commit 0dd246b
Show file tree
Hide file tree
Showing 28 changed files with 311 additions and 120 deletions.
Expand Up @@ -28,7 +28,7 @@ internal WebAssemblyUriHelper()
protected override void EnsureInitialized()
{
WebAssemblyJSRuntime.Instance.Invoke<object>(
Interop.EnableNavigationInterception,
Interop.ListenForNavigationEvents,
typeof(WebAssemblyUriHelper).Assembly.GetName().Name,
nameof(NotifyLocationChanged));

Expand All @@ -54,10 +54,10 @@ protected override void NavigateToCore(string uri, bool forceLoad)
/// For framework use only.
/// </summary>
[JSInvokable(nameof(NotifyLocationChanged))]
public static void NotifyLocationChanged(string newAbsoluteUri)
public static void NotifyLocationChanged(string newAbsoluteUri, bool isInterceptedLink)
{
Instance.SetAbsoluteUri(newAbsoluteUri);
Instance.TriggerOnLocationChanged();
Instance.TriggerOnLocationChanged(isInterceptedLink);
}

/// <summary>
Expand Down
64 changes: 39 additions & 25 deletions src/Components/Browser.JS/dist/Debug/blazor.server.js
Expand Up @@ -14885,62 +14885,66 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
};
Object.defineProperty(exports, "__esModule", { value: true });
__webpack_require__(/*! @dotnet/jsinterop */ "../node_modules/@dotnet/jsinterop/dist/Microsoft.JSInterop.js");
var hasRegisteredEventListeners = false;
var hasRegisteredNavigationInterception = false;
var hasRegisteredNavigationEventListeners = false;
// Will be initialized once someone registers
var notifyLocationChangedCallback = null;
// These are the functions we're making available for invocation from .NET
exports.internalFunctions = {
listenForNavigationEvents: listenForNavigationEvents,
enableNavigationInterception: enableNavigationInterception,
navigateTo: navigateTo,
getBaseURI: function () { return document.baseURI; },
getLocationHref: function () { return location.href; },
};
function enableNavigationInterception(assemblyName, functionName) {
if (hasRegisteredEventListeners || assemblyName === undefined || functionName === undefined) {
function listenForNavigationEvents(assemblyName, functionName) {
if (hasRegisteredNavigationEventListeners || assemblyName === undefined || functionName === undefined) {
return;
}
notifyLocationChangedCallback = { assemblyName: assemblyName, functionName: functionName };
hasRegisteredEventListeners = true;
hasRegisteredNavigationEventListeners = true;
window.addEventListener('popstate', function () { return notifyLocationChanged(false); });
}
function enableNavigationInterception() {
if (hasRegisteredNavigationInterception) {
return;
}
hasRegisteredNavigationInterception = true;
document.addEventListener('click', function (event) {
if (event.button !== 0 || eventHasSpecialKey(event)) {
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
return;
}
// Intercept clicks on all <a> elements where the href is within the <base href> URI space
// We must explicitly check if it has an 'href' attribute, because if it doesn't, the result might be null or an empty string depending on the browser
var anchorTarget = findClosestAncestor(event.target, 'A');
var hrefAttributeName = 'href';
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName) && event.button === 0) {
var href = anchorTarget.getAttribute(hrefAttributeName);
var absoluteHref = toAbsoluteUri(href);
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName)) {
var targetAttributeValue = anchorTarget.getAttribute('target');
var opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
if (isWithinBaseUriSpace(absoluteHref) && !eventHasSpecialKey(event) && opensInSameFrame) {
if (!opensInSameFrame) {
return;
}
var href = anchorTarget.getAttribute(hrefAttributeName);
var absoluteHref = toAbsoluteUri(href);
if (isWithinBaseUriSpace(absoluteHref)) {
event.preventDefault();
performInternalNavigation(absoluteHref);
performInternalNavigation(absoluteHref, true);
}
}
});
window.addEventListener('popstate', handleInternalNavigation);
}
function navigateTo(uri, forceLoad) {
var absoluteUri = toAbsoluteUri(uri);
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
performInternalNavigation(absoluteUri);
}
else {
location.href = uri;
}
}
exports.navigateTo = navigateTo;
function performInternalNavigation(absoluteInternalHref) {
function performInternalNavigation(absoluteInternalHref, interceptedLink) {
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
handleInternalNavigation();
notifyLocationChanged(interceptedLink);
}
function handleInternalNavigation() {
function notifyLocationChanged(interceptedLink) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!notifyLocationChangedCallback) return [3 /*break*/, 2];
return [4 /*yield*/, DotNet.invokeMethodAsync(notifyLocationChangedCallback.assemblyName, notifyLocationChangedCallback.functionName, location.href)];
return [4 /*yield*/, DotNet.invokeMethodAsync(notifyLocationChangedCallback.assemblyName, notifyLocationChangedCallback.functionName, location.href, interceptedLink)];
case 1:
_a.sent();
_a.label = 2;
Expand All @@ -14949,6 +14953,16 @@ function handleInternalNavigation() {
});
});
}
function navigateTo(uri, forceLoad) {
var absoluteUri = toAbsoluteUri(uri);
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
performInternalNavigation(absoluteUri, false);
}
else {
location.href = uri;
}
}
exports.navigateTo = navigateTo;
var testAnchor;
function toAbsoluteUri(relativeUri) {
testAnchor = testAnchor || document.createElement('a');
Expand Down
64 changes: 39 additions & 25 deletions src/Components/Browser.JS/dist/Debug/blazor.webassembly.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Components/Browser.JS/dist/Release/blazor.server.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

53 changes: 36 additions & 17 deletions src/Components/Browser.JS/src/Services/UriHelper.ts
@@ -1,69 +1,88 @@
import '@dotnet/jsinterop';

let hasRegisteredEventListeners = false;
let hasRegisteredNavigationInterception = false;
let hasRegisteredNavigationEventListeners = false;

// Will be initialized once someone registers
let notifyLocationChangedCallback: { assemblyName: string; functionName: string } | null = null;

// These are the functions we're making available for invocation from .NET
export const internalFunctions = {
listenForNavigationEvents,
enableNavigationInterception,
navigateTo,
getBaseURI: () => document.baseURI,
getLocationHref: () => location.href,
};

function enableNavigationInterception(assemblyName: string, functionName: string) {
if (hasRegisteredEventListeners || assemblyName === undefined || functionName === undefined) {
function listenForNavigationEvents(assemblyName: string, functionName: string) {
if (hasRegisteredNavigationEventListeners || assemblyName === undefined || functionName === undefined) {
return;
}

notifyLocationChangedCallback = { assemblyName, functionName };
hasRegisteredEventListeners = true;

hasRegisteredNavigationEventListeners = true;
window.addEventListener('popstate', () => notifyLocationChanged(false));
}

function enableNavigationInterception() {
if (hasRegisteredNavigationInterception) {
return;
}

hasRegisteredNavigationInterception = true;

document.addEventListener('click', event => {
if (event.button !== 0 || eventHasSpecialKey(event)) {
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
return;
}

// Intercept clicks on all <a> elements where the href is within the <base href> URI space
// We must explicitly check if it has an 'href' attribute, because if it doesn't, the result might be null or an empty string depending on the browser
const anchorTarget = findClosestAncestor(event.target as Element | null, 'A') as HTMLAnchorElement;
const hrefAttributeName = 'href';
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName) && event.button === 0) {
const href = anchorTarget.getAttribute(hrefAttributeName)!;
const absoluteHref = toAbsoluteUri(href);
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName)) {
const targetAttributeValue = anchorTarget.getAttribute('target');
const opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
if (!opensInSameFrame) {
return;
}

// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
if (isWithinBaseUriSpace(absoluteHref) && !eventHasSpecialKey(event) && opensInSameFrame) {
const href = anchorTarget.getAttribute(hrefAttributeName)!;
const absoluteHref = toAbsoluteUri(href);

if (isWithinBaseUriSpace(absoluteHref)) {
event.preventDefault();
performInternalNavigation(absoluteHref);
performInternalNavigation(absoluteHref, true);
}
}
});

window.addEventListener('popstate', handleInternalNavigation);
}

export function navigateTo(uri: string, forceLoad: boolean) {
const absoluteUri = toAbsoluteUri(uri);

if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
performInternalNavigation(absoluteUri);
performInternalNavigation(absoluteUri, false);
} else {
location.href = uri;
}
}

function performInternalNavigation(absoluteInternalHref: string) {
function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean) {
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
handleInternalNavigation();
notifyLocationChanged(interceptedLink);
}

async function handleInternalNavigation() {
async function notifyLocationChanged(interceptedLink: boolean) {
if (notifyLocationChangedCallback) {
await DotNet.invokeMethodAsync(
notifyLocationChangedCallback.assemblyName,
notifyLocationChangedCallback.functionName,
location.href
location.href,
interceptedLink
);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/Components/Browser/src/BrowserUriHelperInterop.cs
Expand Up @@ -8,6 +8,8 @@ internal static class BrowserUriHelperInterop
{
private static readonly string Prefix = "Blazor._internal.uriHelper.";

public static readonly string ListenForNavigationEvents = Prefix + "listenForNavigationEvents";

public static readonly string EnableNavigationInterception = Prefix + "enableNavigationInterception";

public static readonly string GetLocationHref = Prefix + "getLocationHref";
Expand Down
Expand Up @@ -367,7 +367,7 @@ public partial class InjectAttribute : System.Attribute
}
public partial interface IUriHelper
{
event System.EventHandler<string> OnLocationChanged;
event System.EventHandler<Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs> OnLocationChanged;
string GetAbsoluteUri();
string GetBaseUri();
void NavigateTo(string uri);
Expand Down Expand Up @@ -599,7 +599,7 @@ public partial class UIWheelEventArgs : Microsoft.AspNetCore.Components.UIMouseE
public abstract partial class UriHelperBase : Microsoft.AspNetCore.Components.IUriHelper
{
protected UriHelperBase() { }
public event System.EventHandler<string> OnLocationChanged { add { } remove { } }
public event System.EventHandler<Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs> OnLocationChanged { add { } remove { } }
protected virtual void EnsureInitialized() { }
public string GetAbsoluteUri() { throw null; }
public virtual string GetBaseUri() { throw null; }
Expand All @@ -611,7 +611,7 @@ public abstract partial class UriHelperBase : Microsoft.AspNetCore.Components.IU
protected void SetAbsoluteUri(string uri) { }
public System.Uri ToAbsoluteUri(string href) { throw null; }
public string ToBaseRelativePath(string baseUri, string locationAbsolute) { throw null; }
protected void TriggerOnLocationChanged() { }
protected void TriggerOnLocationChanged(bool isinterceptedLink) { }
}
}
namespace Microsoft.AspNetCore.Components.Forms
Expand Down Expand Up @@ -849,6 +849,15 @@ public enum RenderTreeFrameType
}
namespace Microsoft.AspNetCore.Components.Routing
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct LocationChangedEventArgs
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public LocationChangedEventArgs(string location, bool isNavigationIntercepted) { throw null; }
public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
public enum NavLinkMatch
{
Prefix = 0,
Expand Down

0 comments on commit 0dd246b

Please sign in to comment.