9

I am using the new Webview2 control to render Pdf files in my WPF application.

This is working well but I would like to customize the toolbar to hide for example the save buttons on some criteria. I did not find methods or properties to do that directly from the Webview2/CoreWebView2 object.

But if I inspect the web page code generated when the pdf is rendered I can see the part where the save buttons are

enter image description here

Is it possible to intercept the whole page before it is rendered and alter the html ? I know it is dirty to do this as it will require to look for the class Id that is subject to change but it will work for now as a temporary solution.

shawty
  • 5,729
  • 2
  • 37
  • 71
Alex R
  • 347
  • 2
  • 10

3 Answers3

5

WebView2 now offers a HiddenPdfToolbarItems setting. Here's the code I used in the code behind:

using Microsoft.Web.WebView2.Core;
[...]
        InitializeComponent();
        webView.CoreWebView2InitializationCompleted += (sender, e) =>
        {
            if (e.IsSuccess)
            {
                webView.CoreWebView2.Settings.HiddenPdfToolbarItems =
                    CoreWebView2PdfToolbarItems.Bookmarks
                    | CoreWebView2PdfToolbarItems.FitPage
                    | CoreWebView2PdfToolbarItems.PageLayout
                    | CoreWebView2PdfToolbarItems.PageSelector
                    | CoreWebView2PdfToolbarItems.Print
                    | CoreWebView2PdfToolbarItems.Rotate
                    | CoreWebView2PdfToolbarItems.Save
                    | CoreWebView2PdfToolbarItems.SaveAs
                    | CoreWebView2PdfToolbarItems.Search
                    | CoreWebView2PdfToolbarItems.ZoomIn
                    | CoreWebView2PdfToolbarItems.ZoomOut;
            }
        };

Replace webView with the name of your WebView2 control and remove the names of any Toolbar Items you still need. Hope that helps!

jpine
  • 51
  • 1
  • 3
  • 2
    At the time of writing this comment, WinUI uses an old build of WebView2 which does not contain the full gamut of flags in the above comment; only Save, SaveAs and Print are available. If the intention is to remove the toolbar in its entirety, one can simply add #toolbar=0 to the PDF url and this will cause WebView2 to natively hide the toolbar. – Brad Morris Feb 26 '23 at 19:59
4

Yes it is 100% possible to add scripts into your environment, that can intercept and control the content you are rendering.

I'm using WebView2 in windows forms, so I'll show you how I do it in a desktop windows forms application, the process will be similar for other environments, and is almost identical for WPF projects.

Step 1

Setup and initialise the default settings for your webview2 control

public FrmMainForm()
{
  InitializeComponent();

  // Resize/position client to a standard 720p HD TV and make room for tools
  var toolBarSize =
    PnlToolPanelTopRow.Height +
    PnlToolPanelLowerRow.Height;

  SetClientSizeCore(1280, toolBarSize + 720);
  //webview.Top = toolBarSize;
  webview.Height = 720;

  // Webview initialisation handler, called when control instatiated and ready
  webview.CoreWebView2InitializationCompleted += Webview_CoreWebView2InitializationCompleted;

  // Set defaults for tools
  TxtAppUrl.Text = "http://app/";
  TxtUserAgent.Text = DEFAULTUA;
}

Note the "initialization handler" attached to the Initialization completed event.

Step 2

In your initialization event handler, you need to load in the JavaScripts you wish to run every time a browser view is initialised. These scripts are ALWAYS executed before any of the scripts in the loaded page, or before the dom has loaded, they are executed after the page navigation however, so you cannot use them to control the navigation cycle.

Here's an example, from one of my projects

private void Webview_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
  // Custom URL handler (All URLS starting "http://app/" are intercepted directly by the application
  webview.CoreWebView2.AddWebResourceRequestedFilter("http://app/*", CoreWebView2WebResourceContext.All);
  webview.CoreWebView2.WebResourceRequested += WebResourceRequested;

  // Load in our custom JS API files to create the HbbTv JS environment
  webview.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(JsLoader.LoadApi("BrowserOverrides.js"));

  // Assign any back end C sharp classes to support our JS api's
  webview.CoreWebView2.AddHostObjectToScript("channelData", new ChannelData());

  // Event handler for notification of dom content load finished
  //webview.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;

  // Show dev tools by default
  webview.CoreWebView2.OpenDevToolsWindow();

  // Other misc settings
  webview.CoreWebView2.Settings.UserAgent = DEFAULTUA;

}

The line you need to pay attention too is the one that reads:

webview.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(JsLoader.LoadApi("BrowserOverrides.js"));

I use a custom loader class, that grabs my java script file from resources compiled into my application, but you however do not need to, the one and only parameter that is passed to the AddScript call is just a plain string of java script code, for example:

webview.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("alert('hello world');");

Will cause your webview2 session to display an alert after every navigation, but before the dom & scripts in the page are loaded and run, for every single page browsed to in the webview.

The JS code has full access to the browser environment, so it's simply a case of attaching a regular JS handler to one of the "dom loaded" or "page loaded" regular java script events, then using standard dom manipulation to find the elements you want and setting their display style to "none".

Step 3 (optional)

You'll see also from my second code snippet, that there are 2 further hook points you can use.

webview.CoreWebView2.AddHostObjectToScript("channelData", new ChannelData());

Adds my custom C# class type "ChannelData" to the browser environment, this is then made available to the browser via the following JS code

chrome.webview.hostObjects.channelData.(method name or property name accessible here)

The "channelData" name, after "hostObjects" is the same as used in the first parameter of the "AddHostObject" call, the second news up a regular C# class which MUST be set up as follows:

using HbbTvBrowser.DataClasses;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

namespace JsSupportClasses
{
  [ClassInterface(ClassInterfaceType.AutoDual)]
  [ComVisible(true)]
  public class (Class Name Here)
  {
    public string SomeMethod()
    {
      return JsonConvert.SerializeObject(Some C# objects to return);
    }
  }
}

If the class interface and com visible attributes are not provided the class will not be seen by web view and will not be callable.

Any data returned by your methods back to JS code, MUST be a simple data type such as int, bool or string, this means that you cannot return complex objects without first serialising them, then de-serialising them once your back in JS land.

You'll note that there is also a C# level dom ready handler. This is fired as as soon as the HTML is loaded and ready to be accessed, but before images, scripts, css or anything else like that is loaded. You cannot however alter the dom content from C#, at least not at the moment anyway, I do believe it may be on the teams radar for sometime this year, but I cannot confirm that.

While I have not had need to do so my self, I do believe however it is possible to obtain a copy in C# of the loaded HTML, but it is just that "A copy", altering it does not alter the actual loaded dom.

shawty
  • 5,729
  • 2
  • 37
  • 71
  • 1
    I tried to put some scripts like you proposed and the JS is running (I have the alerts) , however if I try to get the content of the page by executing the following script `await webView.ExecuteScriptAsync("document.body.innerHTML");` I always get some content like this `"\u003Cembed name=\"EC1D17810B3C396428F857C146BE0D15\" style=\"position:absolute; left: 0; top: 0;\" width=\"100%\" height=\"100%\" src=\"about:blank\" type=\"application/pdf\" internalid=\"EC1D17810B3C396428F857C146BE0D15\">"` which is not what I expected. I tried every events, but maybe javascript like this cannot work – Alex R Feb 22 '21 at 17:00
  • I'm confused as to why you are trying to execute the contents of the main HTML document as a script? "document.body.innerHTML" is NOT likely to be a runnable JavaScript, what you need to do is inject the script you made, then call that script, the script you made should use standard JavaScript code to interact with the DOM nodes you are interested in, so you can manipulate them. – shawty Feb 24 '21 at 12:27
  • 4
    The techniques in this answer unfortunately don't work when working with a pdf because the pdf is an `` displayed within a separate html document. You can see this if you navigate to a pdf and call `webview.CoreWebView2.OpenDevToolsWindow()`. The html shown there will be different than what is shown using right-click -> inspect. The webview2 javascript injection is acting on the wrong html document. – Dan Jurgella Mar 10 '21 at 15:15
0

In addition to jpine's answer: If your version of CoreWebView2PdfToolbarItems is missing enum values, you can define your own enum and cast the constructed value to CoreWebView2PdfToolbarItems:

/// <summary>
/// A newer version of the enum <see cref="CoreWebView2PdfToolbarItems"/> with more options.
/// See also <see href=https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2pdftoolbaritems/>.
/// </summary>
[Flags]
public enum PdfToolbarItems : uint
{
    /// <summary>
    /// No item. By default the CoreWebView2Settings.HiddenPdfToolbarItems equal to this value.
    /// </summary>
    None = 0x0,
    /// <summary>
    /// The save button on PDF toolbar.
    /// </summary>
    Save = 0x1,
    /// <summary>
    /// The print button on PDF toolbar.
    /// </summary>
    Print = 0x2,
    /// <summary>
    /// The save as button on PDF toolbar.
    /// </summary>
    SaveAs = 0x4,
    /// <summary>
    /// The zoom in button on PDF toolbar.
    /// </summary>
    ZoomIn = 0x8,
    /// <summary>
    /// The zoom out button on PDF toolbar.
    /// </summary>
    ZoomOut = 0x10,
    /// <summary>
    /// The rotate button on PDF toolbar.
    /// </summary>
    Rotate = 0x20,
    /// <summary>
    /// The fit to width button on PDF toolbar.
    /// </summary>
    FitPage = 0x40,
    /// <summary>
    /// The page view button on PDF toolbar.
    /// </summary>
    PageLayout = 0x80,
    /// <summary>
    /// The contents button on PDF toolbar.
    /// </summary>
    Bookmarks = 0x100,
    /// <summary>
    /// The page number button on PDF toolbar.
    /// </summary>
    PageSelector = 0x200,
    /// <summary>
    /// The search button on PDF toolbar.
    /// </summary>
    Search = 0x400,
    /// <summary>
    /// The full screen button on PDF toolbar.
    /// </summary>
    FullScreen = 0x800,
    /// <summary>
    /// The setting and more button on PDF toolbar.
    /// </summary>
    MoreSettings = 0x1000            
}

Note that in my case (.NET MAUI 7.0), the values 0x100 and higher didn't work or completely hid the toolbar.

kwl
  • 495
  • 6
  • 13