Open ToratEmetInWord opened 5 months ago
Hi @pcinfogmach, what behaviors that is currently supported by the Edge Browser are you thinking of that is not currently supported in WebView2? Could you provide screenshots / more details?
thats the thing i am not looking for behaviours that are accesible in edge i am looking for the pdf reader to be exposed to code the same way that webview itself is exposed to code interaction through programming it should be so since webview is such an amazing tool for develpors to use
some sample behaviours are: accesing js window.find does not work setting default page on open acces to text of currently open document or at least to currently selected text
many thanks
@pcinfogmach I see. Could you elaborate further in the feature request on each of these behaviors, and why do you need that behavior? Else it will be difficult for us to track the request
accesing js window.find - will allow me to implement a search function even if i choose to hide the pdf toolbar. setting default page on open will allow showing indexed search results efficiently acces to text of currently open document or at least to currently selected text - wiil allow copying text dirctly from source to the research paper
these might seem trivial at first but when doing extensive reseach it simply piles uo and becomes gruesomly annoying
many thanks
perhaps a link to my app might be prudent
https://github.com/pcinfogmach/ToratEmetInWord/releases/tag/ToratEmetInWord
another good feature will be this https://www.google.com/url?q=https://github.com/MicrosoftEdge/WebView2Feedback/issues/4475%23event-12409835530&source=gmail&ust=1712775496208000&usg=AOvVaw0tY7191rw8l7DyRtIbdoaW very frustrating that it does not exits many thanks
@ToratEmetInWord I found a solution. It's hackish and could break with later versions of chrome but it works at the moment.
You have to send js code to be executed into the context of the pdf renedering element. This is the context that you can see if you right-click/inspect into the displayed pdf. Using the inspector, you should be able to find how to achieve the task you intend. For instance, to jump to a page, the command would be window.viewer.currentController_.goToPage(pagenumberindexedfromzero)
.
The difficulty is to send the code into the right context. If you use ExecuteScriptAsync
it is sent to the context of the root of the page, that contains an embed element which is a black box containing all of the pdf viewer and its ui elements. This is not the right context.
Here is how I managed to get it to work:
class WebViewWrapper
{
WebView2 Viewer;
DevToolsProtocolHelper devtools; // initialized to Viewer.CoreWebView2.GetDevToolsProtocolHelper();
Dictionary<string, string> sessionids = new Dictionary<string, string>();
Task<T> Invoke<T>(Func<Task<T>> f)
{
Task<T> res = null;
Viewer.Invoke(new Action(() => res = f()));
return res;
}
public async Task<string> Eval(string command)
{
var targets = await devtools.Target.GetTargetsAsync();
string session = null;
foreach (var tgt in targets)
{
// find the "target" corresponding to the pdf viewing extension
if (tgt.Type == "webview" && tgt.Url.Contains("edge_pdf"))
{
if (tgt.Attached)
{
session = sessionids[tgt.TargetId];
}
else
{
// recover its session id
session = await Invoke(async() => await devtools.Target.AttachToTargetAsync(tgt.TargetId, true));
sessionids[tgt.TargetId] = session;
}
break;
}
}
if (session == null) return null;
// now we can use CoreWebView2.CallDevToolsProtocolMethodForSessionAsync with the session id above
dynamic val = new ExpandoObject();
val.expression = command;
string parametersAsJson = JsonSerializer.Serialize<object>(val);
return await Invoke(() => Viewer.CoreWebView2.CallDevToolsProtocolMethodForSessionAsync(session, "Runtime.evaluate", parametersAsJson));
}
}
@trebahl can you please provide a more complete sample i couldnt get yours to work.
@ToratEmetInWord
Here's a complete example to run as an exe. Replace "f:/tmp/a.pdf" by a path to some pdf you have, then clicking the "nextpage" button should do what its name implies.
using Microsoft.Web.WebView2.Core.DevToolsProtocolExtension;
using Microsoft.Web.WebView2.WinForms;
using System.Dynamic;
using System.Text.Json;
namespace WinFormsApp1
{
class WebViewWrapper
{
public readonly WebView2 Viewer = new WebView2()
{
CreationProperties = new CoreWebView2CreationProperties()
{
UserDataFolder = System.IO.Path.Combine(Application.CommonAppDataPath, "WebView")
}
};
public WebViewWrapper()
{
Viewer.CoreWebView2InitializationCompleted += (o, e) =>
{
devtools = Viewer.CoreWebView2.GetDevToolsProtocolHelper();
};
}
DevToolsProtocolHelper devtools; // initialized to Viewer.CoreWebView2.GetDevToolsProtocolHelper();
Dictionary<string, string> sessionids = new Dictionary<string, string>();
Task<T> Invoke<T>(Func<Task<T>> f)
{
Task<T> res = null;
Viewer.Invoke(new Action(() => res = f()));
return res;
}
public async Task<string> Eval(string command)
{
var targets = await devtools.Target.GetTargetsAsync();
string session = null;
foreach (var tgt in targets)
{
// find the "target" corresponding to the pdf viewing extension
if (tgt.Type == "webview" && tgt.Url.Contains("edge_pdf"))
{
if (tgt.Attached)
{
session = sessionids[tgt.TargetId];
}
else
{
// recover its session id
session = await Invoke(async () => await devtools.Target.AttachToTargetAsync(tgt.TargetId, true));
sessionids[tgt.TargetId] = session;
}
break;
}
}
if (session == null) return null;
// now we can use CoreWebView2.CallDevToolsProtocolMethodForSessionAsync with the session id above
dynamic val = new ExpandoObject();
val.expression = command;
string parametersAsJson = JsonSerializer.Serialize<object>(val);
return await Invoke(() => Viewer.CoreWebView2.CallDevToolsProtocolMethodForSessionAsync(session, "Runtime.evaluate", parametersAsJson));
}
public void GoToPage(int p)
{
Eval($"window.viewer.currentController_.goToPage({p - 1})");
}
}
internal static class Program
{
[STAThread]
static void Main()
{
using (var f = new Form())
{
var wv = new WebViewWrapper();
wv.Viewer.Dock = DockStyle.Fill;
f.Controls.Add(wv.Viewer);
wv.Viewer.EnsureCoreWebView2Async();
var t = new System.Windows.Forms.Timer();
t.Tick += (o, e) =>
{
if (wv.Viewer.CoreWebView2 == null) return;
t.Stop();
wv.Viewer.Source = new Uri("file:///f:/tmp/a.pdf");
};
t.Interval = 1000;
t.Start();
var b = new Button() { Text = "nextpage", Dock = DockStyle.Top };
f.Controls.Add(b);
var p = 1;
b.Click += (o, e) =>
{
wv.GoToPage(++p);
};
Application.Run(f);
}
}
}
}
I have delved deeper into this since I first posted, and identified some difficulties:
You have to wait until the pdf viewer is fully initialized before you can pass commands. At the moment, my best detection is
function IsPDFUIInitialized() { var psel = document.getElementById('pageselector'); return window.viewer !== undefined && document.getElementById('plugin') !== null && psel !== null && psel.value != 0; }
The toolbar doesn't show up if the window is too small at the time the pdf is loaded (it won't show up later even if the window is enlarged). The threshold dimensions are window.PDFViewer.TOOLBAR_WINDOW_MIN_HEIGHT and window.PDFViewer.TOOLBAR_WINDOW_MINWIDTH. In that case, you have to run `window.viewer.initializeToolbar();` to be able to have a toolbar. Then it will at least show up but without being pinned; I haven't yet found a fix for that.
PS: here's how to examine the result of the js code you send to be executed.
var t = wv.Eval("...."); t.ContinueWith(tt => { / check for tt.Exception and if null you should have access to tt.Result; beware that this is run on another thread / });
PPS: I've ran into a nightmare of conflicts of versions of dependencies while using WebView2.Core.DevToolsProtocolExtension. I ended up using CoreWebView2.CallDevToolsProtocolMethodForSessionAsync directly instead, using Newtonsoft.Json for the serialization/deserialization. (The code above still uses DevToolsProtocolExtension for simplicity.)
@trebahl apoligies i am but a begginner in coding and i still cant get it going here is my (wpf) code:
using Microsoft.Web.WebView2.Wpf;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace Webview2.Pdf
{
class WebViewWrapper : ContentControl
{
public readonly WebView2 Viewer = new WebView2()
{
CreationProperties = new CoreWebView2CreationProperties()
{
UserDataFolder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebView")
}
};
public WebViewWrapper()
{
Viewer.CoreWebView2InitializationCompleted += async (o, e) =>
{
var devTools = Viewer.CoreWebView2.GetDevToolsProtocolHelper();
// Initialization logic if necessary
};
Content = Viewer;
intializePdf();
}
async void intializePdf()
{
await WaitForPDFInitialization();
await EnsureToolbarVisible();
}
Dictionary<string, string> sessionIds = new Dictionary<string, string>();
Task<T> Invoke<T>(Func<Task<T>> f)
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
Viewer.Dispatcher.Invoke(async () =>
{
try
{
T result = await f();
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
return tcs.Task;
}
public async Task<string> Eval(string command)
{
var targetsJson = await Viewer.CoreWebView2.CallDevToolsProtocolMethodAsync("Target.getTargets", "{}");
var targets = JsonConvert.DeserializeObject<JObject>(targetsJson)["targetInfos"];
string session = null;
foreach (var tgt in targets)
{
if (tgt["type"].ToString() == "webview" && tgt["url"].ToString().Contains("edge_pdf"))
{
var targetId = tgt["targetId"].ToString();
if (sessionIds.ContainsKey(targetId))
{
session = sessionIds[targetId];
}
else
{
var attachParams = new { targetId = targetId, flatten = true };
var attachResponse = await Viewer.CoreWebView2.CallDevToolsProtocolMethodAsync("Target.attachToTarget", JsonConvert.SerializeObject(attachParams));
session = JsonConvert.DeserializeObject<JObject>(attachResponse)["sessionId"].ToString();
sessionIds[targetId] = session;
}
break;
}
}
if (session == null) return null;
var evalParams = new { expression = command };
return await Viewer.CoreWebView2.CallDevToolsProtocolMethodForSessionAsync(session, "Runtime.evaluate", JsonConvert.SerializeObject(evalParams));
}
public async Task WaitForPDFInitialization()
{
while (true)
{
string initialized = await Eval("IsPDFUIInitialized()");
if (initialized.Contains("\"result\":{\"value\":true}"))
break;
await Task.Delay(100); // Check every 100ms
}
}
public async Task EnsureToolbarVisible()
{
string script = @"
if (window.innerHeight < window.PDFViewer.TOOLBAR_WINDOW_MIN_HEIGHT ||
window.innerWidth < window.PDFViewer.TOOLBAR_WINDOW_MIN_WIDTH) {
window.viewer.initializeToolbar_();
}
";
await Eval(script);
}
public async Task GoToPage(int p)
{
await Eval($"window.viewer.currentController_.goToPage({p - 1})");
}
}
}
Describe the feature/enhancement you need
when loading a pdf file interaction with file through code is very limited such as setting the default page to open to or gettng the selected text in the pdf file through code
The scenario/use case where you would use this feature
i am using webview as part of a reaserch program my users need a lot of flexibilty when viewng and accesing pdfs through search or any other way
How important is this request to you?
Critical. My app's basic functions wouldn't work without it.
Suggested implementation
No response
What does your app do? Is there a pending deadline for this request?
i am using webview as part of a reaserch program my users need a lot of flexibilty when viewng and accesing pdfs through search or any other way people are getting very frustrated