Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion include/wx/osx/webview_webkit.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView
public:
wxDECLARE_DYNAMIC_CLASS(wxWebViewWebKit);

wxWebViewWebKit() {}
wxWebViewWebKit() : m_nonPersistentWebsiteDataStore(false) {}
wxWebViewWebKit(wxWindow *parent,
wxWindowID winID = wxID_ANY,
const wxString& strURL = wxASCII_STR(wxWebViewDefaultURLStr),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize, long style = 0,
const wxString& name = wxASCII_STR(wxWebViewNameStr))
: m_nonPersistentWebsiteDataStore(false)
{
Create(parent, winID, strURL, pos, size, style, name);
}
Expand Down Expand Up @@ -75,6 +76,18 @@ class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView
virtual void EnableAccessToDevTools(bool enable = true) wxOVERRIDE;
virtual bool SetUserAgent(const wxString& userAgent) wxOVERRIDE;

// Per-instance website data isolation for dual Studio processes.
// On macOS this maps to WKWebsiteDataStore (identifier on 14+,
// non-persistent fallback for secondary instances on older OS), not a
// custom filesystem path like WebView2's UserDataFolder.
virtual void SetUserDataPathOption(const wxString& path) wxOVERRIDE;

// Force WKWebsiteDataStore.nonPersistentDataStore (in-memory, not shared
// with the default store). Takes precedence over SetUserDataPathOption.
// Must be called before Create().
virtual void SetNonPersistentWebsiteDataStore(bool enable = true) wxOVERRIDE;
virtual bool IsNonPersistentWebsiteDataStore() const wxOVERRIDE;

//History functions
virtual void ClearHistory() wxOVERRIDE;
virtual void EnableHistory(bool enable = true) wxOVERRIDE;
Expand All @@ -96,6 +109,7 @@ class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView

bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE;
virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool AddScriptMessageHandler(const wxString& name, bool runScriptSync) wxOVERRIDE;
virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool AddUserScript(const wxString& javascript,
wxWebViewUserScriptInjectionTime injectionTime = wxWEBVIEW_INJECT_AT_DOCUMENT_START) wxOVERRIDE;
Expand All @@ -115,6 +129,8 @@ class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView
OSXWebViewPtr m_webView;
wxStringToWebHandlerMap m_handlers;
wxString m_customUserAgent;
wxString m_customUserDataPath;
bool m_nonPersistentWebsiteDataStore;

WX_NSObject m_navigationDelegate;
WX_NSObject m_UIDelegate;
Expand Down
18 changes: 18 additions & 0 deletions include/wx/webview.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ class WXDLLIMPEXP_WEBVIEW wxWebView : public wxControl
// User data path for storing cookies and other data for wxWebViewEdge
virtual void SetUserDataPathOption(const wxString&) {};

// Request a non-persistent website data store (macOS WKWebView:
// WKWebsiteDataStore.nonPersistentDataStore). No-op on backends that
// do not support it. Must be called before Create().
virtual void SetNonPersistentWebsiteDataStore(bool WXUNUSED(enable) = true) {}
virtual bool IsNonPersistentWebsiteDataStore() const { return false; }

// General methods
virtual void EnableContextMenu(bool enable = true)
{
Expand All @@ -197,6 +203,18 @@ class WXDLLIMPEXP_WEBVIEW wxWebView : public wxControl
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const = 0;
virtual bool AddScriptMessageHandler(const wxString& name)
{ wxUnusedVar(name); return false; }
// Optional second argument: when false, backends that support it inject the
// handler's bootstrap script asynchronously. Currently only the macOS/WebKit
// backend overrides this; the default implementation ignores the flag and
// falls back to the single-argument overload (GTK/Edge unchanged).
// Use async mode on macOS to avoid a hang when RunScriptSync() spins
// wxYield() waiting for evaluateJavaScript while the WKWebView is
// off-screen / in a throttled WebContent process.
virtual bool AddScriptMessageHandler(const wxString& name, bool runScriptSync)
{
wxUnusedVar(runScriptSync);
return AddScriptMessageHandler(name);
}
virtual bool RemoveScriptMessageHandler(const wxString& name)
{ wxUnusedVar(name); return false; }
virtual bool AddUserScript(const wxString& javascript,
Expand Down
22 changes: 22 additions & 0 deletions interface/wx/webview.h
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,28 @@ class wxWebView : public wxControl
*/
virtual bool AddScriptMessageHandler(const wxString& name);

/**
Same as AddScriptMessageHandler(const wxString& name) with an extra flag
controlling how the handler bootstrap script is injected into the currently
loaded document.

@param name Name of the message handler that can be used from javascript
@param runScriptSync If @true the alias is injected with a synchronous
RunScript(). If @false it is injected asynchronously. Currently only
the macOS/WebKit backend honours @false; other backends ignore the flag
and behave like the single-argument overload. Use @false on macOS to
avoid a main-thread hang when the view is off-screen and its WebContent
process is throttled, which makes the synchronous RunScriptSync()
busy-wait never complete.

@return @true if the handler could be added, @false if it could not be added.

@see AddScriptMessageHandler(const wxString& name)

@since 3.1.5
*/
virtual bool AddScriptMessageHandler(const wxString& name, bool runScriptSync);

/**
Remove a script message handler with the given name that was previously added via
AddScriptMessageHandler().
Expand Down
126 changes: 124 additions & 2 deletions src/osx/webview_webkit.mm
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@

#include "wx/hashmap.h"
#include "wx/filesys.h"
#include "wx/filename.h"
#include "wx/msgdlg.h"
#include "wx/textdlg.h"
#include "wx/filedlg.h"
#include "wx/log.h"

#include <WebKit/WebKit.h>
#include <Foundation/NSURLError.h>
Expand All @@ -49,6 +51,83 @@
wxBEGIN_EVENT_TABLE(wxWebViewWebKit, wxControl)
wxEND_EVENT_TABLE()

namespace
{

// Last path component is the instance slot created by Studio
// (…/WebViewCache/0, …/WebViewCache/1, …).
long wxWebViewWebKitSlotFromPath(const wxString& path)
{
if (path.empty())
return 0;

wxFileName fn(path);
long slot = 0;
if (!fn.GetFullName().ToLong(&slot))
slot = 0;
return slot;
}

WKWebsiteDataStore* wxWebViewWebKitDataStoreForPath(const wxString& path,
bool forceNonPersistent)
{
if (forceNonPersistent)
{
wxLogMessage("wxWebViewWebKit: using nonPersistentDataStore (explicit request)");
return [WKWebsiteDataStore nonPersistentDataStore];
}

if (path.empty())
return [WKWebsiteDataStore defaultDataStore];

const long slot = wxWebViewWebKitSlotFromPath(path);

#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
if (WX_IS_MACOS_AVAILABLE(14, 0))
{
NSString* dir = wxCFStringRef(path).AsNSString();
NSString* uuidPath = [dir stringByAppendingPathComponent:@"webview_store.uuid"];
NSString* uuidStr = [NSString stringWithContentsOfFile:uuidPath
encoding:NSUTF8StringEncoding
error:nil];
NSUUID* uuid = nil;
if (uuidStr.length > 0)
uuid = [[NSUUID alloc] initWithUUIDString:uuidStr];
if (!uuid)
{
uuid = [NSUUID UUID];
[[NSFileManager defaultManager] createDirectoryAtPath:dir
withIntermediateDirectories:YES
attributes:nil
error:nil];
[[uuid UUIDString] writeToFile:uuidPath
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
wxLogMessage("wxWebViewWebKit: using persistent data store slot=%ld uuid=%s",
slot,
(const char*)wxCFStringRef([uuid UUIDString]).AsString().utf8_str());
return [WKWebsiteDataStore dataStoreForIdentifier:uuid];
}
#endif // macOS 14 SDK

// macOS < 14: only one persistent store exists. Keep slot 0 on the default
// store for single-instance cookie continuity; secondary instances use a
// non-persistent store so they do not fight over defaultDataStore.
if (slot == 0)
{
wxLogMessage("wxWebViewWebKit: macOS < 14 slot 0 using defaultDataStore");
return [WKWebsiteDataStore defaultDataStore];
}

wxLogMessage("wxWebViewWebKit: macOS < 14 slot=%ld using nonPersistentDataStore",
slot);
return [WKWebsiteDataStore nonPersistentDataStore];
}

} // namespace

@interface WXWKWebView: WKWebView
{
wxWebViewWebKit* m_webView;
Expand Down Expand Up @@ -124,6 +203,10 @@ - (id)initWithWxWindow: (wxWebViewWebKit*)inWindow;
NSRect r = wxOSXGetFrameForControl( this, pos , size ) ;
WKWebViewConfiguration* webViewConfig = [[WKWebViewConfiguration alloc] init];

webViewConfig.websiteDataStore =
wxWebViewWebKitDataStoreForPath(m_customUserDataPath,
m_nonPersistentWebsiteDataStore);

// WebKit API available since macOS 10.11 and iOS 9.0
SEL fullScreenSelector = @selector(_setFullScreenEnabled:);
if ([webViewConfig.preferences respondsToSelector:fullScreenSelector])
Expand Down Expand Up @@ -346,6 +429,24 @@ - (id)initWithWxWindow: (wxWebViewWebKit*)inWindow;
return false;
}

void wxWebViewWebKit::SetUserDataPathOption(const wxString& path)
{
// Must be called before Create(); Create() consumes m_customUserDataPath
// when building WKWebViewConfiguration.websiteDataStore.
m_customUserDataPath = path;
}

void wxWebViewWebKit::SetNonPersistentWebsiteDataStore(bool enable)
{
// Must be called before Create(); takes precedence over SetUserDataPathOption.
m_nonPersistentWebsiteDataStore = enable;
}

bool wxWebViewWebKit::IsNonPersistentWebsiteDataStore() const
{
return m_nonPersistentWebsiteDataStore;
}

void wxWebViewWebKit::SetZoomType(wxWebViewZoomType zoomType)
{
// there is only one supported zoom type at the moment so this setter
Expand Down Expand Up @@ -444,14 +545,35 @@ - (id)initWithWxWindow: (wxWebViewWebKit*)inWindow;
}

bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)
{
return AddScriptMessageHandler(name, true);
}

bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name, bool runScriptSync)
{
[m_webView.configuration.userContentController addScriptMessageHandler:
[[WebViewScriptMessageHandler alloc] initWithWxWindow:this] name:wxCFStringRef(name).AsNSString()];
// Make webkit message handler available under common name
wxString js = wxString::Format("window.%s = window.webkit.messageHandlers.%s;",
name, name);
name, name);
// AddUserScript() injects the alias into every *future* document load. The
// call below is only needed to expose it in the document that is already
// loaded (if any).
AddUserScript(js);
RunScript(js);
if (runScriptSync)
{
RunScript(js);
}
else
{
// Asynchronous injection: RunScript() -> RunScriptSync() busy-waits with
// while(!done) wxYield() for evaluateJavaScript's completion handler. When
// this view is off-screen / in a background tab, macOS can throttle or
// suspend its WebContent process so that handler never fires, hanging the
// main thread. Inject without blocking instead.
[m_webView evaluateJavaScript:wxCFStringRef(js).AsNSString()
completionHandler:nil];
}
return true;
}

Expand Down
Loading