Merge branch 'webview_improved_handlers' of https://github.com/TcT2k/wxWidgets

WebView handler improvements for advanced requests and Edge handler
implementation.

See #22797.
This commit is contained in:
Vadim Zeitlin 2022-09-22 18:21:39 +02:00
commit 6f615cbd09
11 changed files with 1039 additions and 54 deletions

View file

@ -153,7 +153,7 @@ wx_add_sample(uiaction DEPENDS wxUSE_UIACTIONSIMULATOR)
wx_add_sample(validate validate.cpp validate.h DEPENDS wxUSE_VALIDATORS)
wx_add_sample(vscroll vstest.cpp)
wx_add_sample(webview LIBRARIES wxwebview
DATA ../help/doc.zip:doc.zip
DATA ../help/doc.zip:doc.zip handler_advanced.html
NAME webviewsample DEPENDS wxUSE_WEBVIEW)
if(TARGET webviewsample AND wxUSE_STC)
wx_exe_link_libraries(webviewsample wxstc)

View file

@ -0,0 +1,203 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/msw/private/comstream.h
// Purpose: COM Stream adapter to wxStreamBase based streams
// Created: 2021-01-15
// Copyright: (c) 2021 wxWidgets team
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef WX_COMSTREAM_H
#define WX_COMSTREAM_H
#include "wx/stream.h"
#include "wx/msw/ole/comimpl.h"
class wxCOMBaseStreamAdapter : public IStream
{
public:
wxCOMBaseStreamAdapter(wxStreamBase* stream):
m_stream(stream)
{ }
virtual ~wxCOMBaseStreamAdapter() { }
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppv) wxOVERRIDE
{
if (riid == IID_IUnknown ||
riid == IID_ISequentialStream ||
riid == IID_IStream)
{
*ppv = this;
AddRef();
return S_OK;
}
*ppv = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) AddRef() wxOVERRIDE
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) Release() wxOVERRIDE
{
if (--m_cRef == wxAutoULong(0))
{
delete this;
return 0;
}
else
return m_cRef;
}
// IStream
virtual HRESULT STDMETHODCALLTYPE Seek(
LARGE_INTEGER WXUNUSED(dlibMove), DWORD WXUNUSED(dwOrigin),
ULARGE_INTEGER *WXUNUSED(plibNewPosition)) wxOVERRIDE
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER WXUNUSED(libNewSize))
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream *WXUNUSED(pstm),
ULARGE_INTEGER WXUNUSED(cb), ULARGE_INTEGER *WXUNUSED(pcbRead),
ULARGE_INTEGER *WXUNUSED(pcbWritten)) wxOVERRIDE
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Commit(DWORD WXUNUSED(grfCommitFlags)) wxOVERRIDE
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Revert(void) wxOVERRIDE
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER WXUNUSED(libOffset),
ULARGE_INTEGER WXUNUSED(cb), DWORD WXUNUSED(dwLockType)) wxOVERRIDE
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER WXUNUSED(libOffset),
ULARGE_INTEGER WXUNUSED(cb), DWORD WXUNUSED(dwLockType)) wxOVERRIDE
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG *pstatstg, DWORD WXUNUSED(grfStatFlag)) wxOVERRIDE
{
pstatstg->type = STGTY_STREAM;
pstatstg->cbSize.QuadPart = m_stream->GetSize();
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE Clone(IStream **WXUNUSED(ppstm)) wxOVERRIDE
{
return E_NOTIMPL;
}
// ISequentialStream
virtual HRESULT STDMETHODCALLTYPE Read(void *WXUNUSED(pv),
ULONG WXUNUSED(cb), ULONG *WXUNUSED(pcbRead)) wxOVERRIDE
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Write(const void *WXUNUSED(pv),
ULONG WXUNUSED(cb), ULONG *WXUNUSED(pcbWritten)) wxOVERRIDE
{
return E_NOTIMPL;
}
protected:
wxStreamBase* m_stream;
static wxSeekMode OriginToSeekMode(DWORD origin)
{
switch (origin)
{
case STREAM_SEEK_SET:
return wxFromStart;
case STREAM_SEEK_CUR:
return wxFromCurrent;
case STREAM_SEEK_END:
return wxFromEnd;
}
return wxFromStart;
}
private:
wxAutoULong m_cRef;
};
class wxCOMInputStreamAdapter : public wxCOMBaseStreamAdapter
{
public:
wxCOMInputStreamAdapter(wxInputStream* stream):
wxCOMBaseStreamAdapter(stream)
{
}
virtual HRESULT STDMETHODCALLTYPE Seek(
LARGE_INTEGER dlibMove, DWORD dwOrigin,
ULARGE_INTEGER *plibNewPosition) wxOVERRIDE
{
wxFileOffset newOffset = reinterpret_cast<wxInputStream*>(m_stream)->SeekI(dlibMove.QuadPart, OriginToSeekMode(dwOrigin));
if (plibNewPosition)
plibNewPosition->QuadPart = newOffset;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE Read(void *pv, ULONG cb, ULONG *pcbRead) wxOVERRIDE
{
wxInputStream* inputStr = reinterpret_cast<wxInputStream*>(m_stream);
inputStr->Read(pv, cb);
*pcbRead = inputStr->LastRead();
return S_OK;
}
};
class wxCOMOutputStreamAdapter : public wxCOMBaseStreamAdapter
{
public:
wxCOMOutputStreamAdapter(wxOutputStream* stream) :
wxCOMBaseStreamAdapter(stream)
{ }
virtual HRESULT STDMETHODCALLTYPE Seek(
LARGE_INTEGER dlibMove, DWORD dwOrigin,
ULARGE_INTEGER *plibNewPosition) wxOVERRIDE
{
wxFileOffset newOffset = reinterpret_cast<wxOutputStream*>(m_stream)->SeekO(dlibMove.QuadPart, OriginToSeekMode(dwOrigin));
if (plibNewPosition)
plibNewPosition->QuadPart = newOffset;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE Write(const void *pv, ULONG cb, ULONG *pcbWritten) wxOVERRIDE
{
wxOutputStream* outputStr = reinterpret_cast<wxOutputStream*>(m_stream);
outputStr->Write(pv, cb);
*pcbWritten = outputStr->LastWrite();
return S_OK;
}
};
#endif // WX_COMSTREAM_H

View file

@ -14,6 +14,7 @@
#include "wx/dynlib.h"
#endif
#include "wx/msw/private/comptr.h"
#include "wx/hashmap.h"
#include <WebView2.h>
@ -40,6 +41,8 @@ __CRT_UUID_DECL(ICoreWebView2SourceChangedEventHandler, 0x3c067f9f, 0x5388, 0x47
__CRT_UUID_DECL(ICoreWebView2WebMessageReceivedEventHandler, 0x57213f19, 0x00e6, 0x49fa, 0x8e,0x07, 0x89,0x8e,0xa0,0x1e,0xcb,0xd2);
#endif
WX_DECLARE_STRING_HASH_MAP(wxSharedPtr<wxWebViewHandler>, wxStringToWebHandlerMap);
class wxWebViewEdgeImpl
{
public:
@ -65,6 +68,7 @@ public:
wxVector<wxString> m_userScriptIds;
wxString m_scriptMsgHandlerName;
wxString m_customUserAgent;
wxStringToWebHandlerMap m_handlers;
// WebView Events tokens
EventRegistrationToken m_navigationStartingToken = { };
@ -75,6 +79,7 @@ public:
EventRegistrationToken m_DOMContentLoadedToken = { };
EventRegistrationToken m_containsFullScreenElementChangedToken = { };
EventRegistrationToken m_webMessageReceivedToken = { };
EventRegistrationToken m_webResourceRequestedToken = { };
// WebView Event handlers
HRESULT OnNavigationStarting(ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args);
@ -85,6 +90,7 @@ public:
HRESULT OnDOMContentLoaded(ICoreWebView2* sender, ICoreWebView2DOMContentLoadedEventArgs* args);
HRESULT OnContainsFullScreenElementChanged(ICoreWebView2* sender, IUnknown* args);
HRESULT OnWebMessageReceived(ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args);
HRESULT OnWebResourceRequested(ICoreWebView2* sender, ICoreWebView2WebResourceRequestedEventArgs* args);
HRESULT OnAddScriptToExecuteOnDocumentedCreatedCompleted(HRESULT errorCode, LPCWSTR id);
HRESULT OnEnvironmentCreated(HRESULT result, ICoreWebView2Environment* environment);

View file

@ -92,6 +92,37 @@ enum wxWebViewUserScriptInjectionTime
wxWEBVIEW_INJECT_AT_DOCUMENT_END
};
class WXDLLIMPEXP_WEBVIEW wxWebViewHandlerRequest
{
public:
virtual ~wxWebViewHandlerRequest() { }
virtual wxString GetRawURI() const = 0;
virtual wxString GetURI() const { return GetRawURI(); }
virtual wxInputStream* GetData() const = 0;
virtual wxString GetDataString(const wxMBConv& conv = wxConvUTF8) const;
virtual wxString GetMethod() const = 0;
virtual wxString GetHeader(const wxString& name) const = 0;
};
class WXDLLIMPEXP_WEBVIEW wxWebViewHandlerResponseData
{
public:
virtual ~wxWebViewHandlerResponseData() { }
virtual wxInputStream* GetStream() = 0;
};
class WXDLLIMPEXP_WEBVIEW wxWebViewHandlerResponse
{
public:
virtual ~wxWebViewHandlerResponse() { }
virtual void SetStatus(int status) = 0;
virtual void SetContentType(const wxString& contentType) = 0;
virtual void SetHeader(const wxString& name, const wxString& value) = 0;
virtual void Finish(wxSharedPtr<wxWebViewHandlerResponseData> data) = 0;
virtual void Finish(const wxString& text, const wxMBConv& conv = wxConvUTF8);
virtual void FinishWithError() = 0;
};
//Base class for custom scheme handlers
class WXDLLIMPEXP_WEBVIEW wxWebViewHandler
{
@ -100,12 +131,17 @@ public:
: m_scheme(scheme), m_securityURL() {}
virtual ~wxWebViewHandler() {}
virtual wxString GetName() const { return m_scheme; }
virtual wxFSFile* GetFile(const wxString &uri) = 0;
virtual wxFSFile* GetFile(const wxString &uri);
virtual void SetSecurityURL(const wxString& url) { m_securityURL = url; }
virtual wxString GetSecurityURL() const { return m_securityURL; }
virtual void SetVirtualHost(const wxString& host) { m_virtualHost = host; }
virtual wxString GetVirtualHost() const;
virtual void StartRequest(const wxWebViewHandlerRequest& request,
wxSharedPtr<wxWebViewHandlerResponse> response);
private:
wxString m_scheme;
wxString m_securityURL;
wxString m_virtualHost;
};
extern WXDLLIMPEXP_DATA_WEBVIEW(const char) wxWebViewNameStr[];

View file

@ -161,6 +161,157 @@ enum wxWebViewIE_EmulationLevel
wxWEBVIEWIE_EMU_IE11_FORCE = 11001
};
/**
@class wxWebViewHandlerRequest
A class giving access to various parameters of a webview request.
@since 3.3.0
@library{wxwebview}
@category{webview}
@see wxWebViewHandler::StartRequest()
*/
class WXDLLIMPEXP_WEBVIEW wxWebViewHandlerRequest
{
public:
/**
@return The unmodified url of the request.
*/
virtual wxString GetRawURI() const = 0;
/**
@return The url of the request. Can be modified by the backend for
compatibility.
*/
virtual wxString GetURI() const { return GetRawURI(); }
/**
@return The body data of the request of @c null if nothing was posted.
*/
virtual wxInputStream* GetData() const = 0;
/**
@return The body data of the request as a string. The returned string
is empty if the supplied @c conv doesn't match the encoding.
*/
virtual wxString GetDataString(const wxMBConv& conv = wxConvUTF8) const;
/**
@return The requests HTTP method (e.g. POST, GET, OPTIONS).
*/
virtual wxString GetMethod() const = 0;
/**
Returns a header from the request or an empty string if the header
could not be found.
@param name Name of the header field
*/
virtual wxString GetHeader(const wxString& name) const = 0;
};
/**
@class wxWebViewHandlerResponseData
A class holding the response data. Stream must be available until
this object is destroyed.
@since 3.3.0
@library{wxwebview}
@category{webview}
@see wxWebViewHandlerResponse::Finish(wxSharedPtr<wxWebViewHandlerResponseData>)
*/
class WXDLLIMPEXP_WEBVIEW wxWebViewHandlerResponseData
{
public:
/**
@return returns pointer to the stream.
@see wxWebViewHandlerResponse::Finish()
*/
virtual wxInputStream* GetStream() = 0;
};
/**
@class wxWebViewHandlerResponse
A class giving access to various webview response parameters.
Usually a wxWebViewHandler() would set various parameters required
for the response like HTTP status, various headers and must then
call Finish() to complete the response or call FinishWithError() to
abort the request.
@since 3.3.0
@library{wxwebview}
@category{webview}
@see wxWebViewHandler::StartRequest()
*/
class WXDLLIMPEXP_WEBVIEW wxWebViewHandlerResponse
{
public:
/**
Sets the status code of the response.
@param status HTTP status code
*/
virtual void SetStatus(int status) = 0;
/**
Sets the MIME type of the response.
@param contentType MIME type of the response content
*/
virtual void SetContentType(const wxString& contentType) = 0;
/**
Sets a response header which will be sent to the web view.
The header will be added if it hasn't been set before or replaced
otherwise.
@param name
Name of the header
@param value
String value of the header
*/
virtual void SetHeader(const wxString& name, const wxString& value) = 0;
/**
Finishes the request with binary data.
@param data
The data object will be dereferenced when the request is completed
@see Finish(const wxString&, const wxMBConv&)
*/
virtual void Finish(wxSharedPtr<wxWebViewHandlerResponseData> data) = 0;
/**
Finishes the request with text data.
@param text
Text content of the response (can be empty)
@param conv
Conversion used when sending the text in the response
@see Finish(wxSharedPtr<wxWebViewHandlerResponseData>)
*/
virtual void Finish(const wxString& text, const wxMBConv& conv = wxConvUTF8);
/**
Finishes the request as an error.
This will notify that the request could not produce any data.
@see Finish()
*/
virtual void FinishWithError() = 0;
};
/**
@class wxWebViewHistoryItem
@ -259,6 +410,10 @@ public:
The base class for handling custom schemes in wxWebView, for example to
allow virtual file system support.
A new handler should either implement GetFile() or if a more detailed
request handling is required (access to headers, post data)
StartRequest() has to be implemented.
@since 2.9.3
@library{wxwebview}
@category{webview}
@ -277,7 +432,7 @@ public:
/**
@return A pointer to the file represented by @c uri.
*/
virtual wxFSFile* GetFile(const wxString &uri) = 0;
virtual wxFSFile* GetFile(const wxString &uri);
/**
@return The name of the scheme, as passed to the constructor.
@ -297,6 +452,81 @@ public:
@since 3.1.5
*/
virtual wxString GetSecurityURL() const;
/**
When using the edge backend handler urls are https urls with a
virtual host. As default @c name.wxsite is used as the virtual hostname.
If you customize this host, use a non existing site (ideally a reserved
subdomain of a domain you control). If @c localassests.domain.example is
used the handlers content will be available under
%https://localassets.domain.example/
This has to be set @b before registering the handler via
wxWebView::RegisterHandler().
@since 3.3.0
*/
virtual void SetVirtualHost(const wxString& host);
/**
@return The virtual host of this handler
@see SetVirtualHost()
@since 3.3.0
*/
virtual wxString GetVirtualHost() const;
/**
Implementing this method allows for more control over requests from
the backend then GetFile(). More details of the request are available
from the request object which allows access to URL, method, postdata
and headers.
A response can be send via the response object. The response does not
have to be finished from this method and it's possible to be finished
asynchronously via wxWebViewHandlerResponse::Finish().
The following pseudo code demonstrates a typical implementation:
@code
void StartRequest(const wxWebViewHandlerRequest& request,
wxSharedPtr<wxWebViewHandlerResponse> response) wxOVERRIDE
{
// Set common headers allowing access from XMLHTTPRequests()
response->SetHeader("Access-Control-Allow-Origin", "*");
response->SetHeader("Access-Control-Allow-Headers", "*");
// Handle options request
if (request.GetMethod().IsSameAs("options", false))
{
response->Finish("");
return;
}
// Check the post data type
if (!request.GetHeader("Content-Type").StartsWith("application/json"))
{
response->FinishWithError();
return;
}
// Process input data
wxString postData = request.GetDataString();
...
// Send result
response->SetContentType("application/json");
response->Finish("{ result: true }");
}
@endcode
@note This is only usesd by macOS and the Edge backend.
@see GetFile()
@since 3.3.0
*/
virtual void StartRequest(const wxWebViewHandlerRequest& request,
wxSharedPtr<wxWebViewHandlerResponse> response);
};
/**
@ -341,7 +571,9 @@ public:
<a href="https://docs.microsoft.com/en-us/microsoft-edge/hosting/webview2">Edge WebView2</a>.
It is available for Windows 7 and newer.
This backend does not support custom schemes and virtual file systems.
This backend does not support custom schemes. When using handlers see
wxWebViewHandler::SetVirtualHost() for more details on how to access
handler provided URLs.
This backend is not enabled by default, to build it follow these steps:
- Visual Studio 2015 or newer, or GCC/Clang with c++11 is required
@ -440,7 +672,8 @@ public:
wxWebView supports the registering of custom scheme handlers, for example
@c file or @c http. To do this create a new class which inherits from
wxWebViewHandler, where wxWebHandler::GetFile() returns a pointer to a
wxFSFile which represents the given url. You can then register your handler
wxFSFile which represents the given url or wxWebHandler::StartRequest() for
more complex requests. You can then register your handler
with RegisterHandler() it will be called for all pages and resources.
wxWebViewFSHandler is provided to access the virtual file system encapsulated by
@ -708,6 +941,12 @@ public:
@note On macOS in order to use handlers two-step creation has to be
used and RegisterHandler() has to be called before Create().
With the other backends it has to be called after Create().
@note The Edge backend does not support custom schemes, but the
handler is available as a virtual host under
%https://scheme.wxsite to customize this virtual host call
wxWebViewHandler::SetVirtualHost() before registering the
handler.
*/
virtual void RegisterHandler(wxSharedPtr<wxWebViewHandler> handler) = 0;

View file

@ -0,0 +1,55 @@
<head>
<title>Advanced Handler Sample</title>
<style>
.mono {
font-family: monospace;
}
</style>
<script>
function sendRequest() {
let url = document.getElementById("request_url").value;
let request_content_type = document.getElementById("request_content_type").value;
let request_data = document.getElementById("request_data").value;
console.log("clicked");
const req = new XMLHttpRequest();
req.addEventListener("load", (ev) => {
document.getElementById("response_response").value = req.responseText;
});
req.open("POST", url);
req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
req.send(request_data);
}
</script>
</head>
<body>
<p>
This sample demonstrates how to use <i>wxWebViewHandler::StartRequest()</i>
to enable advanced requests from html and javascript in the web view.
</p>
Request URL<br />
<input id="request_url" size="40" value=""><br />
Request content type<br />
<input id="request_content_type" size="40" value="application/json"><br />
Request Data<br />
<textarea class="mono" cols="60" rows="8" id="request_data">{ param1: "wxwidgets", param2: "webview" }</textarea>
<br />
<button onclick="sendRequest()">Start request</button><br />
Response Data<br />
<textarea class="mono" cols="60" rows="8" id="response_response"></textarea>
<script>
// init request_url
let postURL;
if (navigator.userAgent.indexOf("Edg") > 0)
postURL = "https://wxpost.wxsite//main/test/1";
else
postURL = "wxpost://main/test/1";
document.getElementById("request_url").value = postURL;
</script>
</body>

View file

@ -17,4 +17,10 @@
</if>
</exe>
<wx-data id="data">
<files>
handler_advanced.html
</files>
</wx-data>
</makefile>

View file

@ -164,6 +164,7 @@ public:
void OnSelectAll(wxCommandEvent& evt);
void OnLoadScheme(wxCommandEvent& evt);
void OnUseMemoryFS(wxCommandEvent& evt);
void OnLoadAdvancedHandler(wxCommandEvent& evt);
void OnFind(wxCommandEvent& evt);
void OnFindDone(wxCommandEvent& evt);
void OnFindText(wxCommandEvent& evt);
@ -257,6 +258,40 @@ public:
SourceViewDialog(wxWindow* parent, wxString source);
};
// AdvancedWebViewHandler is a sample handler used by handler_advanced.html
// to show a sample implementation of wxWebViewHandler::StartRequest().
// see the documentation for additional details.
class AdvancedWebViewHandler: public wxWebViewHandler
{
public:
AdvancedWebViewHandler():
wxWebViewHandler("wxpost")
{ }
virtual void StartRequest(const wxWebViewHandlerRequest& request,
wxSharedPtr<wxWebViewHandlerResponse> response) wxOVERRIDE
{
response->SetHeader("Access-Control-Allow-Origin", "*");
response->SetHeader("Access-Control-Allow-Headers", "*");
// Handle options request
if (request.GetMethod().IsSameAs("options", false))
{
response->Finish("");
return;
}
response->SetContentType("application/json");
response->Finish(
wxString::Format(
"{\n contentType: \"%s\",\n method: \"%s\",\n data: \"%s\"\n}",
request.GetHeader("Content-Type"),
request.GetMethod(),
request.GetDataString()
));
}
};
wxIMPLEMENT_APP(WebApp);
// ============================================================================
@ -383,6 +418,7 @@ WebFrame::WebFrame(const wxString& url) :
// With WKWebView handlers need to be registered before creation
m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new AdvancedWebViewHandler()));
#endif
m_browser->Create(this, wxID_ANY, url, wxDefaultPosition, wxDefaultSize);
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
@ -397,6 +433,7 @@ WebFrame::WebFrame(const wxString& url) :
m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
//And the memory: file system
m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new AdvancedWebViewHandler()));
#endif
if (!m_browser->AddScriptMessageHandler("wx"))
wxLogError("Could not add script message handler");
@ -492,8 +529,11 @@ WebFrame::WebFrame(const wxString& url) :
editmenu->AppendSubMenu(selection, "Selection");
wxMenuItem* loadscheme = m_tools_menu->Append(wxID_ANY, _("Custom Scheme Example"));
wxMenuItem* usememoryfs = m_tools_menu->Append(wxID_ANY, _("Memory File System Example"));
wxMenu* handlers = new wxMenu();
wxMenuItem* loadscheme = handlers->Append(wxID_ANY, _("Custom Scheme"));
wxMenuItem* usememoryfs = handlers->Append(wxID_ANY, _("Memory File System"));
wxMenuItem* advancedHandler = handlers->Append(wxID_ANY, _("Advanced Handler"));
m_tools_menu->AppendSubMenu(handlers, _("Handler Examples"));
m_context_menu = m_tools_menu->AppendCheckItem(wxID_ANY, _("Enable Context Menu"));
m_dev_tools = m_tools_menu->AppendCheckItem(wxID_ANY, _("Enable Dev Tools"));
@ -591,6 +631,7 @@ WebFrame::WebFrame(const wxString& url) :
Bind(wxEVT_MENU, &WebFrame::OnSelectAll, this, selectall->GetId());
Bind(wxEVT_MENU, &WebFrame::OnLoadScheme, this, loadscheme->GetId());
Bind(wxEVT_MENU, &WebFrame::OnUseMemoryFS, this, usememoryfs->GetId());
Bind(wxEVT_MENU, &WebFrame::OnLoadAdvancedHandler, this, advancedHandler->GetId());
Bind(wxEVT_MENU, &WebFrame::OnFind, this, m_find->GetId());
Bind(wxEVT_MENU, &WebFrame::OnEnableContextMenu, this, m_context_menu->GetId());
Bind(wxEVT_MENU, &WebFrame::OnEnableDevTools, this, m_dev_tools->GetId());
@ -748,6 +789,17 @@ void WebFrame::OnUseMemoryFS(wxCommandEvent& WXUNUSED(evt))
m_browser->LoadURL("memory:page1.htm");
}
void WebFrame::OnLoadAdvancedHandler(wxCommandEvent& WXUNUSED(evt))
{
wxPathList pathlist;
pathlist.Add(".");
pathlist.Add("..");
wxString path = wxFileName(pathlist.FindValidPath("handler_advanced.html")).GetAbsolutePath();
path = "file://" + path;
m_browser->LoadURL(path);
}
void WebFrame::OnEnableContextMenu(wxCommandEvent& evt)
{
m_browser->EnableContextMenu(evt.IsChecked());

View file

@ -13,6 +13,8 @@
#include "wx/webview.h"
#include "wx/filesys.h"
#include "wx/mstream.h"
#if defined(__WXOSX__)
#include "wx/osx/webview_webkit.h"
@ -52,6 +54,92 @@ wxDEFINE_EVENT( wxEVT_WEBVIEW_FULLSCREEN_CHANGED, wxWebViewEvent);
wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, wxWebViewEvent);
wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_RESULT, wxWebViewEvent);
// wxWebViewHandlerRequest
wxString wxWebViewHandlerRequest::GetDataString(const wxMBConv& conv) const
{
wxInputStream* data = GetData();
if (!data)
return wxString();
size_t length = data->GetLength();
wxMemoryBuffer buffer;
data->ReadAll(buffer.GetWriteBuf(length), length);
wxString dataStr(static_cast<const char*>(buffer.GetData()), conv, length);
return dataStr;
}
// wxWebViewHandlerResponseDataStream
class wxWebViewHandlerResponseDataString : public wxWebViewHandlerResponseData
{
public:
wxWebViewHandlerResponseDataString(const wxCharBuffer& data): m_data(data)
{
m_stream = new wxMemoryInputStream(m_data, m_data.length());
}
~wxWebViewHandlerResponseDataString() { delete m_stream; }
virtual wxInputStream* GetStream() wxOVERRIDE
{
return m_stream;
}
wxCharBuffer m_data;
wxInputStream* m_stream;
};
// wxWebViewHandlerResponse
void wxWebViewHandlerResponse::Finish(const wxString& text,
const wxMBConv& conv)
{
Finish(wxSharedPtr<wxWebViewHandlerResponseData>(
new wxWebViewHandlerResponseDataString(text.mb_str(conv))));
}
// wxWebViewHandlerResponseDataFile
class wxWebViewHandlerResponseDataFile : public wxWebViewHandlerResponseData
{
public:
wxWebViewHandlerResponseDataFile(wxFSFile* file): m_file(file) { }
~wxWebViewHandlerResponseDataFile() { delete m_file; }
virtual wxInputStream* GetStream() wxOVERRIDE
{ return m_file->GetStream(); }
wxFSFile* m_file;
};
// wxWebViewHandler
wxString wxWebViewHandler::GetVirtualHost() const
{
if (m_virtualHost.empty())
return GetName() + ".wxsite";
else
return m_virtualHost;
}
wxFSFile* wxWebViewHandler::GetFile(const wxString& WXUNUSED(uri))
{
return NULL;
}
void wxWebViewHandler::StartRequest(const wxWebViewHandlerRequest& request,
wxSharedPtr<wxWebViewHandlerResponse> response)
{
wxFSFile* file = GetFile(request.GetURI());
if (file)
{
response->SetContentType(file->GetMimeType());
response->Finish(wxSharedPtr<wxWebViewHandlerResponseData>(
new wxWebViewHandlerResponseDataFile(file)));
}
else
response->FinishWithError();
}
// wxWebView
wxStringWebViewFactoryMap wxWebView::m_factoryMap;
wxWebViewZoom wxWebView::GetZoom() const

View file

@ -16,15 +16,18 @@
#include "wx/filename.h"
#include "wx/module.h"
#include "wx/mstream.h"
#include "wx/log.h"
#include "wx/stdpaths.h"
#include "wx/thread.h"
#include "wx/tokenzr.h"
#include "wx/uri.h"
#include "wx/private/jsscriptwrapper.h"
#include "wx/private/json.h"
#include "wx/msw/private.h"
#include "wx/msw/private/cotaskmemptr.h"
#include "wx/msw/private/webview_edge.h"
#include "wx/msw/private/comstream.h"
#ifdef __VISUALC__
#include <wrl/event.h>
@ -64,6 +67,185 @@ GetAvailableCoreWebView2BrowserVersionString_t wxGetAvailableCoreWebView2Browser
wxDynamicLibrary wxWebViewEdgeImpl::ms_loaderDll;
#endif // wxUSE_WEBVIEW_EDGE_STATIC
class wxWebViewEdgeHandlerRequest : public wxWebViewHandlerRequest
{
public:
wxWebViewEdgeHandlerRequest(ICoreWebView2WebResourceRequest* request):
m_request(request),
m_handler(NULL),
m_dataStream(NULL)
{ }
~wxWebViewEdgeHandlerRequest()
{
wxDELETE(m_dataStream);
}
void SetHandler(wxWebViewHandler* handler) { m_handler = handler; }
virtual wxString GetRawURI() const wxOVERRIDE
{
wxCoTaskMemPtr<wchar_t> uri;
if (SUCCEEDED(m_request->get_Uri(&uri)))
return wxString(uri);
else
return wxString();
}
virtual wxString GetURI() const wxOVERRIDE
{
wxURI rawURI(GetRawURI());
wxString path = rawURI.GetPath();
if (!path.empty()) // Remove / in front
path.erase(0, 1);
wxString uri = wxString::Format("%s:%s", m_handler->GetName(), path);
return uri;
}
virtual wxInputStream* GetData() const wxOVERRIDE
{
if (!m_dataStream)
{
wxCOMPtr<IStream> dataStream;
if (SUCCEEDED(m_request->get_Content(&dataStream)) && dataStream)
{
ULARGE_INTEGER size;
LARGE_INTEGER pos;
pos.QuadPart = 0;
HRESULT hr = dataStream->Seek(pos, STREAM_SEEK_END, &size);
if (FAILED(hr))
return NULL;
hr = dataStream->Seek(pos, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
return NULL;
hr = dataStream->Read(m_data.GetWriteBuf(size.QuadPart), size.QuadPart, NULL);
if (FAILED(hr))
return NULL;
m_dataStream = new wxMemoryInputStream(m_data.GetData(), size.QuadPart);
}
}
return m_dataStream;
}
virtual wxString GetMethod() const wxOVERRIDE
{
wxCoTaskMemPtr<wchar_t> method;
if (SUCCEEDED(m_request->get_Method(&method)))
return wxString(method);
else
return wxString();
}
virtual wxString GetHeader(const wxString& name) const wxOVERRIDE
{
wxCOMPtr<ICoreWebView2HttpRequestHeaders> headers;
if (SUCCEEDED(m_request->get_Headers(&headers)))
{
wxCoTaskMemPtr<wchar_t> value;
if (SUCCEEDED(headers->GetHeader(name.wc_str(), &value)))
return wxString(value);
}
return wxString();
}
wxCOMPtr<ICoreWebView2WebResourceRequest> m_request;
wxWebViewHandler* m_handler;
mutable wxInputStream* m_dataStream;
mutable wxMemoryBuffer m_data;
};
class wxWebViewEdgeHandlerResponseStream : public wxCOMInputStreamAdapter
{
public:
wxWebViewEdgeHandlerResponseStream(wxSharedPtr<wxWebViewHandlerResponseData> data):
m_data(data),
wxCOMInputStreamAdapter(data->GetStream())
{ }
wxSharedPtr<wxWebViewHandlerResponseData> m_data;
};
class wxWebViewEdgeHandlerResponse : public wxWebViewHandlerResponse
{
public:
wxWebViewEdgeHandlerResponse(ICoreWebView2WebResourceRequestedEventArgs* args, ICoreWebView2Environment* env):
m_args(args),
m_env(env)
{
m_args->GetDeferral(&m_deferral);
// Create response
HRESULT hr = m_env->CreateWebResourceResponse(NULL, 200, NULL, NULL, &m_response);
if (FAILED(hr))
wxLogApiError("CreateWebResourceResponse", hr);
}
void SetReason(const wxString& reason)
{ m_response->put_ReasonPhrase(reason.wc_str()); }
virtual void SetStatus(int status) wxOVERRIDE
{ m_response->put_StatusCode(status); }
virtual void SetContentType(const wxString& contentType) wxOVERRIDE
{ SetHeader("Content-Type", contentType); }
virtual void SetHeader(const wxString& name, const wxString& value) wxOVERRIDE
{
wxCOMPtr<ICoreWebView2HttpResponseHeaders> headers;
if (SUCCEEDED(m_response->get_Headers(&headers)))
headers->AppendHeader(name.wc_str(), value.wc_str());
}
bool SendResponse()
{
// put response
HRESULT hr = m_args->put_Response(m_response);
if (FAILED(hr))
{
wxLogApiError("put_Response", hr);
return false;
}
// Mark event as completed
hr = m_deferral->Complete();
if (FAILED(hr))
{
wxLogApiError("deferral->Complete()", hr);
return false;
}
return true;
}
virtual void Finish(wxSharedPtr<wxWebViewHandlerResponseData> data) wxOVERRIDE
{
SetReason("OK");
// put content
if (data)
{
IStream* stream = new wxWebViewEdgeHandlerResponseStream(data);
HRESULT hr = m_response->put_Content(stream);
if (FAILED(hr))
wxLogApiError("put_Content", hr);
}
SendResponse();
}
virtual void FinishWithError() wxOVERRIDE
{
SetStatus(500);
SetReason("Error");
SendResponse();
}
int m_status;
wxCOMPtr<ICoreWebView2WebResourceResponse> m_response;
wxCOMPtr<ICoreWebView2Deferral> m_deferral;
wxCOMPtr<ICoreWebView2Environment> m_env;
wxCOMPtr<ICoreWebView2WebResourceRequestedEventArgs> m_args;
};
wxString wxWebViewEdgeImpl::ms_browserExecutableDir;
wxWebViewEdgeImpl::wxWebViewEdgeImpl(wxWebViewEdge* webview):
@ -86,6 +268,7 @@ wxWebViewEdgeImpl::~wxWebViewEdgeImpl()
m_webView->remove_DOMContentLoaded(m_DOMContentLoadedToken);
m_webView->remove_ContainsFullScreenElementChanged(m_containsFullScreenElementChangedToken);
m_webView->remove_WebMessageReceived(m_webMessageReceivedToken);
m_webView->remove_WebResourceRequested(m_webResourceRequestedToken);
}
}
@ -370,6 +553,28 @@ wxWebViewEdgeImpl::OnWebMessageReceived(ICoreWebView2* WXUNUSED(sender),
return S_OK;
}
HRESULT wxWebViewEdgeImpl::OnWebResourceRequested(ICoreWebView2* WXUNUSED(sender), ICoreWebView2WebResourceRequestedEventArgs* args)
{
wxCOMPtr<ICoreWebView2WebResourceRequest> apiRequest;
HRESULT hr = args->get_Request(&apiRequest);
if (FAILED(hr))
return hr;
wxWebViewEdgeHandlerRequest request(apiRequest);
// Find handler
wxURI uri(request.GetRawURI());
if (!uri.HasServer())
return E_INVALIDARG;
wxSharedPtr<wxWebViewHandler> handler = m_handlers[uri.GetServer()];
if (!handler)
return E_INVALIDARG;
request.SetHandler(handler.get());
wxSharedPtr<wxWebViewHandlerResponse> resp(new wxWebViewEdgeHandlerResponse(args, m_webViewEnvironment));
handler->StartRequest(request, resp);
return S_OK;
}
HRESULT wxWebViewEdgeImpl::OnWebViewCreated(HRESULT result, ICoreWebView2Controller* webViewController)
{
if (FAILED(result))
@ -431,6 +636,17 @@ HRESULT wxWebViewEdgeImpl::OnWebViewCreated(HRESULT result, ICoreWebView2Control
Callback<ICoreWebView2WebMessageReceivedEventHandler>(
this, &wxWebViewEdgeImpl::OnWebMessageReceived).Get(),
&m_webMessageReceivedToken);
m_webView->add_WebResourceRequested(
Callback<ICoreWebView2WebResourceRequestedEventHandler>(
this, &wxWebViewEdgeImpl::OnWebResourceRequested).Get(),
&m_webResourceRequestedToken);
// Register handlers
for (wxStringToWebHandlerMap::iterator it = m_handlers.begin(); it != m_handlers.end(); it++)
{
wxString filterURI = wxString::Format("*://%s/*", it->first);
m_webView->AddWebResourceRequestedFilter(filterURI.wc_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL);
}
if (m_pendingContextMenuEnabled != -1)
{
@ -591,7 +807,23 @@ void wxWebViewEdge::LoadURL(const wxString& url)
m_impl->m_pendingURL = url;
return;
}
HRESULT hr = m_impl->m_webView->Navigate(url.wc_str());
wxString navURL = url;
if (!m_impl->m_handlers.empty())
{
// Emulate custom protocol support for LoadURL()
for (wxStringToWebHandlerMap::iterator it = m_impl->m_handlers.begin();
it != m_impl->m_handlers.end(); it++)
{
wxString scheme = it->second->GetName() + ":";
if (navURL.StartsWith(scheme))
{
navURL.Remove(0, scheme.Length());
navURL.insert(0, "https://" + it->second->GetVirtualHost() + "/");
break;
}
}
}
HRESULT hr = m_impl->m_webView->Navigate(navURL.wc_str());
if (FAILED(hr))
wxLogApiError("WebView2::Navigate", hr);
}
@ -949,10 +1181,16 @@ void wxWebViewEdge::RemoveAllUserScripts()
m_impl->m_userScriptIds.clear();
}
void wxWebViewEdge::RegisterHandler(wxSharedPtr<wxWebViewHandler> WXUNUSED(handler))
void wxWebViewEdge::RegisterHandler(wxSharedPtr<wxWebViewHandler> handler)
{
// TODO: could maybe be implemented via IWebView2WebView5::add_WebResourceRequested
wxLogDebug("Registering handlers is not supported");
wxString handlerHost = handler->GetVirtualHost();
m_impl->m_handlers[handlerHost] = handler;
if (m_impl->m_webView)
{
wxString filterURI = wxString::Format("*://%s/*", handlerHost);
m_impl->m_webView->AddWebResourceRequestedFilter(filterURI.wc_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL);
}
}
void wxWebViewEdge::DoSetPage(const wxString& html, const wxString& WXUNUSED(baseUrl))

View file

@ -30,6 +30,7 @@
#include "wx/hashmap.h"
#include "wx/filesys.h"
#include "wx/msgdlg.h"
#include "wx/mstream.h"
#include "wx/textdlg.h"
#include "wx/filedlg.h"
@ -847,6 +848,107 @@ wxString nsErrorToWxHtmlError(NSError* error, wxWebViewNavigationError* out)
@end
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
class wxWebViewWebKitHandlerRequest: public wxWebViewHandlerRequest
{
public:
wxWebViewWebKitHandlerRequest(NSURLRequest* request):
m_data(NULL),
m_request(request)
{ }
~wxWebViewWebKitHandlerRequest()
{ wxDELETE(m_data); }
virtual wxString GetRawURI() const wxOVERRIDE
{ return wxCFStringRef::AsString(m_request.URL.absoluteString); }
virtual wxInputStream* GetData() const wxOVERRIDE
{
if (!m_data && m_request.HTTPBody)
m_data = new wxMemoryInputStream(m_request.HTTPBody.bytes, m_request.HTTPBody.length);
return m_data;
}
virtual wxString GetMethod() const wxOVERRIDE
{ return wxCFStringRef::AsString(m_request.HTTPMethod); }
virtual wxString GetHeader(const wxString& name) const wxOVERRIDE
{
return wxCFStringRef::AsString(
[m_request valueForHTTPHeaderField:wxCFStringRef(name).AsNSString()]);
}
mutable wxInputStream* m_data;
NSURLRequest* m_request;
};
class API_AVAILABLE(macos(10.13)) wxWebViewWebkitHandlerResponse: public wxWebViewHandlerResponse
{
public:
wxWebViewWebkitHandlerResponse(id<WKURLSchemeTask> task):
m_status(200),
m_task([task retain])
{
m_headers = [[NSMutableDictionary alloc] init];
}
~wxWebViewWebkitHandlerResponse()
{
[m_headers release];
[m_task release];
}
virtual void SetStatus(int status) wxOVERRIDE
{ m_status = status; }
virtual void SetContentType(const wxString& contentType) wxOVERRIDE
{ SetHeader("Content-Type", contentType); }
virtual void SetHeader(const wxString& name, const wxString& value) wxOVERRIDE
{
[m_headers setValue:wxCFStringRef(value).AsNSString()
forKey:wxCFStringRef(name).AsNSString()];
}
virtual void Finish(wxSharedPtr<wxWebViewHandlerResponseData> data) wxOVERRIDE
{
m_data = data;
wxInputStream* stream = data->GetStream();
size_t length = stream->GetLength();
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:m_task.request.URL
statusCode:m_status
HTTPVersion:nil
headerFields:m_headers];
[m_task didReceiveResponse:response];
[response release];
//Load the data, we malloc it so it is tidied up properly
void* buffer = malloc(length);
stream->Read(buffer, length);
NSData *taskData = [[NSData alloc] initWithBytesNoCopy:buffer length:length];
[m_task didReceiveData:taskData];
[taskData release];
[m_task didFinish];
}
virtual void FinishWithError() wxOVERRIDE
{
NSError *error = [[NSError alloc] initWithDomain:NSURLErrorDomain
code:NSURLErrorFileDoesNotExist
userInfo:nil];
[m_task didFailWithError:error];
[error release];
}
int m_status;
NSMutableDictionary* m_headers;
id<WKURLSchemeTask> m_task;
wxSharedPtr<wxWebViewHandlerResponseData> m_data;
};
@implementation WebViewCustomProtocol
- (id)initWithHandler:(wxWebViewHandler *)handler
@ -858,49 +960,9 @@ wxString nsErrorToWxHtmlError(NSError* error, wxWebViewNavigationError* out)
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask
WX_API_AVAILABLE_MACOS(10, 13)
{
NSURLRequest *request = urlSchemeTask.request;
NSString* path = [[request URL] absoluteString];
wxString wxpath = wxCFStringRef::AsString(path);
wxFSFile* file = m_handler->GetFile(wxpath);
if (!file)
{
NSError *error = [[NSError alloc] initWithDomain:NSURLErrorDomain
code:NSURLErrorFileDoesNotExist
userInfo:nil];
[urlSchemeTask didFailWithError:error];
[error release];
return;
}
size_t length = file->GetStream()->GetLength();
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[request URL]
MIMEType:wxCFStringRef(file->GetMimeType()).AsNSString()
expectedContentLength:length
textEncodingName:nil];
//Load the data, we malloc it so it is tidied up properly
void* buffer = malloc(length);
file->GetStream()->Read(buffer, length);
NSData *data = [[NSData alloc] initWithBytesNoCopy:buffer length:length];
//Set the data
[urlSchemeTask didReceiveResponse:response];
[urlSchemeTask didReceiveData:data];
//Notify that we have finished
[urlSchemeTask didFinish];
[data release];
[response release];
wxWebViewWebKitHandlerRequest request(urlSchemeTask.request);
wxSharedPtr<wxWebViewHandlerResponse> response(new wxWebViewWebkitHandlerResponse(urlSchemeTask));
m_handler->StartRequest(request, response);
}
- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask