Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Photino applications to open links in external browser #113

Closed
wants to merge 1 commit into from

Conversation

andrew-bedford
Copy link

@andrew-bedford andrew-bedford commented Mar 2, 2024

This commit provides a workaround for issue #111. Users were not able to open links in an external browser window, only inside the WebView itself. We now intercept web requests that use the application's scheme (e.g., app://) and, if it does not contain 0.0.0.0 or localhost, then we open the link in the browser.

Partially resolves #111

This commit provides a workaround for issue tryphotino#111. Users were not able to
open links in an external browser window, only inside the WebView itself.
We now intercept web requests that use the application's scheme (e.g., `app://`)
and, if it does not contain `0.0.0.0` or `localhost`, then we open the link
in the browser.

Resolves tryphotino#111
@Jinjinov
Copy link

Jinjinov commented Mar 3, 2024

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
    Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
    Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
    Process.Start("open", url);
}

@andrew-bedford
Copy link
Author

Thanks for the suggestion @Jinjinov! I've only tested this change on Linux and it looks like xdg-open isn't required, but I guess that it could vary from distro to distro. I'll give it a go.

@Jinjinov
Copy link

Jinjinov commented Mar 4, 2024

I didn't have the time to check it on every OS, but I got it from here:

https://stackoverflow.com/a/43232486

and I left out url = url.Replace("&", "^&"); since https://github.com/dotnet/corefx/issues/10361 is closed.

@Jinjinov
Copy link

Jinjinov commented Mar 4, 2024

I tested your code, but unfortunately clicking on <a href="https://www.google.com/">Google</a> doesn't call HandleWebRequest(object sender, string schema, string url, out string contentType)

Since I am making a Markdown app, I want the links in the user's Markdown notes to be clickable.

I am generating the Markdown with https://github.com/xoofx/markdig and changing the generation of <a> tags would be too difficult, so I have no control over the generated links.

@andrew-bedford
Copy link
Author

andrew-bedford commented Mar 4, 2024

Yes, the HandleWebRequest appears to be only called when the registered schema is used (i.e., app://). I'm also using Markdig and I am replacing the instances of "http://" and "https://" in links with "app://".

this.Html = Markdown.ToHtml(this.Text, pipeline);

// Photino workaround to allow links to be opened in external browser windows
this.Html = this.Html.Replace("<a href=\"http://", "<a href=\"app://");
this.Html = this.Html.Replace("<a href=\"https://", "<a href=\"app://");

Not ideal, I know, but it works for now.

@Jinjinov
Copy link

Jinjinov commented Mar 4, 2024

Interesting workaround, thank you for sharing :)

But my core Blazor code is shared between several project:
Blazor WASM - for web
Blazor Maui - for Windows, Android, iOS and MacOS
Photino Blazor - for Linux

I would have to change the method that is using Markdig to work differently for Photino.

@Jinjinov
Copy link

Jinjinov commented Mar 4, 2024

I don't know why, but in my app, <a href="app://www.google.com/">Google</a> doesn't call HandleWebRequest.

EDIT: because I am developing on Windows:

// On Windows, we can't use a custom scheme to host the initial HTML,
// because webview2 won't let you do top-level navigation to such a URL.
// On Linux/Mac, we must use a custom scheme, because their webviews
// don't have a way to intercept http:// scheme requests.
public static readonly string BlazorAppScheme = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
    ? "http"
    : "app";

public static readonly string AppBaseUri = $"{BlazorAppScheme}://localhost/";

It seems like that your solution works on Linux and Mac, but not on Windows.

I looked at the call stack and the call is coming from OnCustomScheme(string url, out int numBytes, out string contentType) in class PhotinoWindow in namespace PhotinoNET, but that method is called from native code.

I guess that a solution that would work on every OS would have to be implemented in both https://github.com/tryphotino/photino.Native and https://github.com/tryphotino/photino.NET

WebResourceRequestedCallback *CustomSchemeHandler; is declared in Photino.Native/Photino.h and used in Photino.Native/Photino.Linux.cpp and Photino.Native/Photino.Mac.mm and Photino.Native/Photino.Windows.cpp

@philippjbauer
Copy link
Member

I'm not quite sold on this solution tbh. It is confusing to have to use the app:// scheme to call web resources in my opinion. How about sending a message through window.external.sendMessage? You could bind a Javascript event to the a-tag and open the link inside the RegisterWebMessageReceivedHandler EventHandler on the C# end ...

@andrew-bedford
Copy link
Author

Thanks @philippjbauer , that does indeed sound like a better solution and I agree that it shouldn't be merged in as is. I'll try your suggestion and let you guys know if it works.

@Jinjinov
Copy link

Jinjinov commented Mar 8, 2024

I apologize for the off-topic comment, but this solution might help someone else who wants to open Markdown links in the default browser:

OnClickMarkdownExtension.cs

using Markdig;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;

namespace OpenMarkdownLinksInDefaultBrowser;

public class OnClickMarkdownExtension : IMarkdownExtension
{
    public void Setup(MarkdownPipelineBuilder pipeline)
    {
        pipeline.DocumentProcessed += AddOnClickToLinks;
    }

    public void AddOnClickToLinks(MarkdownDocument document)
    {
        foreach (LinkInline link in document.Descendants<LinkInline>())
        {
            link.GetAttributes().AddProperty("onclick", $"return openLink('{link.Url}');");
        }
    }

    public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
    {
    }
}

index.html

<script>
    function openLink(url) {
        event.preventDefault();
        DotNet.invokeMethodAsync('OpenMarkdownLinksInDefaultBrowser', 'OpenLink', url);
        return false;
    }
</script>

Program.cs

static void Main(string[] args)
{
    MarkdownPipeline _markdownPipeline = new MarkdownPipelineBuilder()
        .UseAdvancedExtensions()
        .UseSoftlineBreakAsHardlineBreak()
        .Use<OnClickMarkdownExtension>()
        .Build();
}

[JSInvokable]
public static void OpenLink(string url)
{
    Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
}

@MikeYeager
Copy link
Collaborator

@andrew-bedford Looking forward to a new pull request. We're going to close this one for now. When you create the new pull request, please do it against the debug branch as we never merge directly into master. Thank you!

@MikeYeager MikeYeager closed this Mar 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Intercept <a href> navigation
4 participants