fit-webview-bridge 0.2.0a4__tar.gz → 0.2.1a3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fit-webview-bridge might be problematic. Click here for more details.
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/PKG-INFO +1 -1
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/pyproject.toml +1 -1
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/src/macos/WKWebViewWidget.h +9 -0
- fit_webview_bridge-0.2.1a3/src/macos/WKWebViewWidget.mm +409 -0
- fit_webview_bridge-0.2.0a4/src/macos/WKWebViewWidget.mm +0 -195
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/.github/workflows/wheels-macos.yml +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/.gitignore +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/.vscode/settings.json +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/README.md +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/bindings/pyside6/macos/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/bindings/pyside6/macos/typesystem_wkwebview.xml +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/examples/macos/wkwebview_demo.py +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/fit_webview_bridge/__init__.py +0 -0
- {fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/src/macos/CMakeLists.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fit-webview-bridge"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1a3"
|
|
4
4
|
description = "Qt native WebView bridge with PySide6 bindings"
|
|
5
5
|
requires-python = ">=3.11,<3.14"
|
|
6
6
|
dependencies = ["PySide6==6.9.0", "shiboken6==6.9.0", "shiboken6-generator==6.9.0"]
|
|
@@ -22,6 +22,9 @@ public:
|
|
|
22
22
|
Q_INVOKABLE void reload();
|
|
23
23
|
Q_INVOKABLE void evaluateJavaScript(const QString& script);
|
|
24
24
|
|
|
25
|
+
Q_INVOKABLE void setDownloadDirectory(const QString& dirPath);
|
|
26
|
+
Q_INVOKABLE QString downloadDirectory() const;
|
|
27
|
+
|
|
25
28
|
signals:
|
|
26
29
|
void loadFinished(bool ok);
|
|
27
30
|
void urlChanged(const QUrl& url);
|
|
@@ -30,6 +33,12 @@ signals:
|
|
|
30
33
|
void canGoBackChanged(bool);
|
|
31
34
|
void canGoForwardChanged(bool);
|
|
32
35
|
|
|
36
|
+
|
|
37
|
+
void downloadStarted(const QString& suggestedFilename, const QString& destinationPath);
|
|
38
|
+
void downloadProgress(qint64 bytesReceived, qint64 totalBytes);
|
|
39
|
+
void downloadFinished(const QString& filePath);
|
|
40
|
+
void downloadFailed(const QString& filePath, const QString& error);
|
|
41
|
+
|
|
33
42
|
protected:
|
|
34
43
|
void showEvent(QShowEvent*) override;
|
|
35
44
|
void resizeEvent(QResizeEvent*) override;
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
#import <Cocoa/Cocoa.h>
|
|
2
|
+
#import <WebKit/WebKit.h>
|
|
3
|
+
|
|
4
|
+
#include "WKWebViewWidget.h"
|
|
5
|
+
|
|
6
|
+
#include <QtWidgets>
|
|
7
|
+
#include <QString>
|
|
8
|
+
#include <QUrl>
|
|
9
|
+
#include <QDir>
|
|
10
|
+
|
|
11
|
+
@class WKNavDelegate;
|
|
12
|
+
@class FitUrlMsgHandler;
|
|
13
|
+
|
|
14
|
+
// =======================
|
|
15
|
+
// Impl
|
|
16
|
+
// =======================
|
|
17
|
+
struct WKWebViewWidget::Impl {
|
|
18
|
+
WKWebView* wk = nil;
|
|
19
|
+
WKNavDelegate* delegate = nil;
|
|
20
|
+
WKUserContentController* ucc = nil;
|
|
21
|
+
FitUrlMsgHandler* msg = nil;
|
|
22
|
+
QString downloadDir; // es. ~/Downloads
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// =======================
|
|
26
|
+
// Helpers forward
|
|
27
|
+
// =======================
|
|
28
|
+
static NSURL* toNSURL(QUrl u);
|
|
29
|
+
|
|
30
|
+
// =======================
|
|
31
|
+
// SPA message handler
|
|
32
|
+
// =======================
|
|
33
|
+
@interface FitUrlMsgHandler : NSObject <WKScriptMessageHandler>
|
|
34
|
+
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
@implementation FitUrlMsgHandler
|
|
38
|
+
- (void)userContentController:(WKUserContentController *)userContentController
|
|
39
|
+
didReceiveScriptMessage:(WKScriptMessage *)message {
|
|
40
|
+
if (!self.owner) return;
|
|
41
|
+
if (![message.name isEqualToString:@"fitUrlChanged"]) return;
|
|
42
|
+
if (![message.body isKindOfClass:[NSString class]]) return;
|
|
43
|
+
QString s = QString::fromUtf8([(NSString*)message.body UTF8String]);
|
|
44
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(s.toUtf8()));
|
|
45
|
+
}
|
|
46
|
+
@end
|
|
47
|
+
|
|
48
|
+
// =======================
|
|
49
|
+
// Navigation + Download delegate
|
|
50
|
+
// =======================
|
|
51
|
+
@interface WKNavDelegate : NSObject <WKNavigationDelegate, WKDownloadDelegate>
|
|
52
|
+
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
53
|
+
// stato download (nel delegate, non toccare i privati del widget)
|
|
54
|
+
@property(nonatomic, strong) NSMapTable<WKDownload*, NSString*>* downloadPaths;
|
|
55
|
+
@end
|
|
56
|
+
|
|
57
|
+
@implementation WKNavDelegate
|
|
58
|
+
|
|
59
|
+
- (instancetype)init {
|
|
60
|
+
if ((self = [super init])) {
|
|
61
|
+
_downloadPaths = [NSMapTable weakToStrongObjectsMapTable];
|
|
62
|
+
}
|
|
63
|
+
return self;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#pragma mark - Navigazione
|
|
67
|
+
|
|
68
|
+
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
|
|
69
|
+
if (!self.owner) return;
|
|
70
|
+
if (webView.URL)
|
|
71
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
72
|
+
emit self.owner->loadProgress(5);
|
|
73
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
74
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
|
|
78
|
+
if (!self.owner) return;
|
|
79
|
+
if (webView.URL)
|
|
80
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
81
|
+
emit self.owner->loadProgress(50);
|
|
82
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
83
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
|
|
87
|
+
if (!self.owner) return;
|
|
88
|
+
if (webView.URL)
|
|
89
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
|
93
|
+
if (!self.owner) return;
|
|
94
|
+
emit self.owner->loadFinished(true);
|
|
95
|
+
if (webView.URL)
|
|
96
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
97
|
+
if (webView.title)
|
|
98
|
+
emit self.owner->titleChanged(QString::fromUtf8(webView.title.UTF8String));
|
|
99
|
+
emit self.owner->loadProgress(100);
|
|
100
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
101
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
|
105
|
+
if (!self.owner) return;
|
|
106
|
+
emit self.owner->loadFinished(false);
|
|
107
|
+
emit self.owner->loadProgress(0);
|
|
108
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
109
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#pragma mark - Decide download vs render
|
|
113
|
+
|
|
114
|
+
- (void)webView:(WKWebView *)webView
|
|
115
|
+
decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
|
|
116
|
+
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
|
|
117
|
+
{
|
|
118
|
+
if (navigationResponse.canShowMIMEType) {
|
|
119
|
+
decisionHandler(WKNavigationResponsePolicyAllow);
|
|
120
|
+
} else {
|
|
121
|
+
// API moderna: "Download" (non BecomeDownload)
|
|
122
|
+
decisionHandler(WKNavigationResponsePolicyDownload);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#pragma mark - Diventare download
|
|
127
|
+
|
|
128
|
+
- (void)webView:(WKWebView *)webView
|
|
129
|
+
navigationAction:(WKNavigationAction *)navigationAction
|
|
130
|
+
didBecomeDownload:(WKDownload *)download
|
|
131
|
+
{
|
|
132
|
+
download.delegate = self;
|
|
133
|
+
if (self.owner) {
|
|
134
|
+
emit self.owner->downloadStarted(QString(), QString());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[download.progress addObserver:self forKeyPath:@"fractionCompleted"
|
|
138
|
+
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial)
|
|
139
|
+
context:NULL];
|
|
140
|
+
[download.progress addObserver:self forKeyPath:@"completedUnitCount"
|
|
141
|
+
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial)
|
|
142
|
+
context:NULL];
|
|
143
|
+
[download.progress addObserver:self forKeyPath:@"totalUnitCount"
|
|
144
|
+
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial)
|
|
145
|
+
context:NULL];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
- (void)webView:(WKWebView *)webView
|
|
149
|
+
navigationResponse:(WKNavigationResponse *)navigationResponse
|
|
150
|
+
didBecomeDownload:(WKDownload *)download
|
|
151
|
+
{
|
|
152
|
+
download.delegate = self;
|
|
153
|
+
|
|
154
|
+
NSString* suggested = navigationResponse.response.suggestedFilename ?: @"download";
|
|
155
|
+
if (self.owner) {
|
|
156
|
+
QString destDir = self.owner->downloadDirectory();
|
|
157
|
+
QString destPath = destDir + "/" + QString::fromUtf8(suggested.UTF8String);
|
|
158
|
+
emit self.owner->downloadStarted(QString::fromUtf8(suggested.UTF8String),
|
|
159
|
+
destPath);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
[download.progress addObserver:self forKeyPath:@"fractionCompleted"
|
|
163
|
+
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial)
|
|
164
|
+
context:NULL];
|
|
165
|
+
[download.progress addObserver:self forKeyPath:@"completedUnitCount"
|
|
166
|
+
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial)
|
|
167
|
+
context:NULL];
|
|
168
|
+
[download.progress addObserver:self forKeyPath:@"totalUnitCount"
|
|
169
|
+
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial)
|
|
170
|
+
context:NULL];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#pragma mark - Scegli destinazione
|
|
174
|
+
|
|
175
|
+
static NSString* uniquePath(NSString* baseDir, NSString* filename) {
|
|
176
|
+
NSString* fname = filename ?: @"download";
|
|
177
|
+
NSString* path = [baseDir stringByAppendingPathComponent:fname];
|
|
178
|
+
NSFileManager* fm = [NSFileManager defaultManager];
|
|
179
|
+
if (![fm fileExistsAtPath:path]) return path;
|
|
180
|
+
|
|
181
|
+
NSString* name = [fname stringByDeletingPathExtension];
|
|
182
|
+
NSString* ext = [fname pathExtension];
|
|
183
|
+
for (NSUInteger i = 1; i < 10000; ++i) {
|
|
184
|
+
NSString* cand = ext.length
|
|
185
|
+
? [NSString stringWithFormat:@"%@ (%lu).%@", name, (unsigned long)i, ext]
|
|
186
|
+
: [NSString stringWithFormat:@"%@ (%lu)", name, (unsigned long)i];
|
|
187
|
+
NSString* candPath = [baseDir stringByAppendingPathComponent:cand];
|
|
188
|
+
if (![fm fileExistsAtPath:candPath]) return candPath;
|
|
189
|
+
}
|
|
190
|
+
return path;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
- (void)download:(WKDownload *)download
|
|
194
|
+
decideDestinationUsingResponse:(NSURLResponse *)response
|
|
195
|
+
suggestedFilename:(NSString *)suggestedFilename
|
|
196
|
+
completionHandler:(void (^)(NSURL * _Nullable destination))completionHandler
|
|
197
|
+
{
|
|
198
|
+
if (!self.owner) { completionHandler(nil); return; }
|
|
199
|
+
|
|
200
|
+
QString qdir = self.owner->downloadDirectory();
|
|
201
|
+
NSString* dir = [NSString stringWithUTF8String:qdir.toUtf8().constData()];
|
|
202
|
+
if (!dir.length) {
|
|
203
|
+
dir = [NSHomeDirectory() stringByAppendingPathComponent:@"Downloads"];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
[[NSFileManager defaultManager] createDirectoryAtPath:dir
|
|
207
|
+
withIntermediateDirectories:YES
|
|
208
|
+
attributes:nil error:nil];
|
|
209
|
+
|
|
210
|
+
NSString* finalPath = uniquePath(dir, suggestedFilename ?: @"download");
|
|
211
|
+
[self.downloadPaths setObject:finalPath forKey:download];
|
|
212
|
+
|
|
213
|
+
emit self.owner->downloadStarted(
|
|
214
|
+
QString::fromUtf8((suggestedFilename ?: @"download").UTF8String),
|
|
215
|
+
QString::fromUtf8(finalPath.UTF8String)
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
completionHandler([NSURL fileURLWithPath:finalPath]);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#pragma mark - Progress / Fine / Errore
|
|
222
|
+
|
|
223
|
+
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)obj
|
|
224
|
+
change:(NSDictionary *)change context:(void *)ctx
|
|
225
|
+
{
|
|
226
|
+
if (![obj isKindOfClass:[NSProgress class]] || !self.owner) {
|
|
227
|
+
[super observeValueForKeyPath:keyPath ofObject:obj change:change context:ctx];
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
NSProgress* prog = (NSProgress*)obj;
|
|
232
|
+
int64_t total = prog.totalUnitCount; // può essere -1 (sconosciuto)
|
|
233
|
+
int64_t done = prog.completedUnitCount;
|
|
234
|
+
|
|
235
|
+
// Emetti SEMPRE su main (thread-safety Qt/UI)
|
|
236
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
237
|
+
emit self.owner->downloadProgress(done, (total >= 0 ? total : -1));
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
- (void)downloadDidFinish:(WKDownload *)download {
|
|
243
|
+
if (!self.owner) return;
|
|
244
|
+
|
|
245
|
+
@try {
|
|
246
|
+
[download.progress removeObserver:self forKeyPath:@"fractionCompleted"];
|
|
247
|
+
[download.progress removeObserver:self forKeyPath:@"completedUnitCount"];
|
|
248
|
+
[download.progress removeObserver:self forKeyPath:@"totalUnitCount"];
|
|
249
|
+
} @catch (...) {}
|
|
250
|
+
|
|
251
|
+
NSString* finalPath = [self.downloadPaths objectForKey:download];
|
|
252
|
+
if (finalPath) {
|
|
253
|
+
emit self.owner->downloadFinished(QString::fromUtf8(finalPath.UTF8String));
|
|
254
|
+
[self.downloadPaths removeObjectForKey:download];
|
|
255
|
+
} else {
|
|
256
|
+
emit self.owner->downloadFinished(QString());
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
- (void)download:(WKDownload *)download didFailWithError:(NSError *)error resumeData:(NSData *)resumeData {
|
|
261
|
+
if (!self.owner) return;
|
|
262
|
+
|
|
263
|
+
@try {
|
|
264
|
+
[download.progress removeObserver:self forKeyPath:@"fractionCompleted"];
|
|
265
|
+
[download.progress removeObserver:self forKeyPath:@"completedUnitCount"];
|
|
266
|
+
[download.progress removeObserver:self forKeyPath:@"totalUnitCount"];
|
|
267
|
+
} @catch (...) {}
|
|
268
|
+
|
|
269
|
+
NSString* finalPath = [self.downloadPaths objectForKey:download];
|
|
270
|
+
QString qpath = finalPath ? QString::fromUtf8(finalPath.UTF8String) : QString();
|
|
271
|
+
emit self.owner->downloadFailed(qpath, QString::fromUtf8(error.localizedDescription.UTF8String));
|
|
272
|
+
if (finalPath) [self.downloadPaths removeObjectForKey:download];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@end
|
|
276
|
+
|
|
277
|
+
// =======================
|
|
278
|
+
// QUrl -> NSURL (normalizza e forza https)
|
|
279
|
+
// =======================
|
|
280
|
+
static NSURL* toNSURL(QUrl u) {
|
|
281
|
+
if (!u.isValid()) return nil;
|
|
282
|
+
|
|
283
|
+
if (u.scheme().isEmpty())
|
|
284
|
+
u = QUrl::fromUserInput(u.toString());
|
|
285
|
+
|
|
286
|
+
// Forza sempre http -> https (nessuna eccezione)
|
|
287
|
+
if (u.scheme() == "http")
|
|
288
|
+
u.setScheme("https");
|
|
289
|
+
|
|
290
|
+
if (u.isLocalFile())
|
|
291
|
+
return [NSURL fileURLWithPath:[NSString stringWithUTF8String:u.toLocalFile().toUtf8().constData()]];
|
|
292
|
+
|
|
293
|
+
const QByteArray enc = u.toString(QUrl::FullyEncoded).toUtf8();
|
|
294
|
+
return [NSURL URLWithString:[NSString stringWithUTF8String:enc.constData()]];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// =======================
|
|
298
|
+
// WKWebViewWidget
|
|
299
|
+
// =======================
|
|
300
|
+
WKWebViewWidget::WKWebViewWidget(QWidget* parent)
|
|
301
|
+
: QWidget(parent), d(new Impl) {
|
|
302
|
+
setAttribute(Qt::WA_NativeWindow, true);
|
|
303
|
+
(void)winId();
|
|
304
|
+
|
|
305
|
+
d->downloadDir = QDir::homePath() + "/Downloads";
|
|
306
|
+
|
|
307
|
+
NSView* nsParent = (__bridge NSView*)reinterpret_cast<void*>(winId());
|
|
308
|
+
WKWebViewConfiguration* cfg = [[WKWebViewConfiguration alloc] init];
|
|
309
|
+
if ([cfg respondsToSelector:@selector(defaultWebpagePreferences)]) {
|
|
310
|
+
cfg.defaultWebpagePreferences.allowsContentJavaScript = YES;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// SPA: intercetta pushState/replaceState/popstate/click
|
|
314
|
+
d->ucc = [WKUserContentController new];
|
|
315
|
+
d->msg = [FitUrlMsgHandler new];
|
|
316
|
+
d->msg.owner = this;
|
|
317
|
+
[d->ucc addScriptMessageHandler:d->msg name:@"fitUrlChanged"];
|
|
318
|
+
|
|
319
|
+
NSString* js =
|
|
320
|
+
@"(function(){"
|
|
321
|
+
@" function emit(){ try{ window.webkit.messageHandlers.fitUrlChanged.postMessage(location.href); }catch(e){} }"
|
|
322
|
+
@" var _ps = history.pushState; history.pushState = function(){ _ps.apply(this, arguments); emit(); };"
|
|
323
|
+
@" var _rs = history.replaceState; history.replaceState = function(){ _rs.apply(this, arguments); emit(); };"
|
|
324
|
+
@" window.addEventListener('popstate', emit, true);"
|
|
325
|
+
@" document.addEventListener('click', function(ev){"
|
|
326
|
+
@" var a = ev.target && ev.target.closest ? ev.target.closest('a[href]') : null;"
|
|
327
|
+
@" if (!a) return; if (a.target === '_blank' || a.hasAttribute('download')) return;"
|
|
328
|
+
@" setTimeout(emit, 0);"
|
|
329
|
+
@" }, true);"
|
|
330
|
+
@"})();";
|
|
331
|
+
|
|
332
|
+
WKUserScript* us = [[WKUserScript alloc]
|
|
333
|
+
initWithSource:js
|
|
334
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
|
335
|
+
forMainFrameOnly:YES];
|
|
336
|
+
[d->ucc addUserScript:us];
|
|
337
|
+
cfg.userContentController = d->ucc;
|
|
338
|
+
|
|
339
|
+
d->wk = [[WKWebView alloc] initWithFrame:nsParent.bounds configuration:cfg];
|
|
340
|
+
d->wk.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
341
|
+
[nsParent addSubview:d->wk];
|
|
342
|
+
|
|
343
|
+
d->delegate = [WKNavDelegate new];
|
|
344
|
+
d->delegate.owner = this;
|
|
345
|
+
[d->wk setNavigationDelegate:d->delegate];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
WKWebViewWidget::~WKWebViewWidget() {
|
|
349
|
+
if (!d) return;
|
|
350
|
+
|
|
351
|
+
if (d->ucc && d->msg) {
|
|
352
|
+
@try { [d->ucc removeScriptMessageHandlerForName:@"fitUrlChanged"]; } @catch (...) {}
|
|
353
|
+
}
|
|
354
|
+
d->msg = nil;
|
|
355
|
+
|
|
356
|
+
if (d->wk) { [d->wk removeFromSuperview]; d->wk = nil; }
|
|
357
|
+
d->delegate = nil;
|
|
358
|
+
d->ucc = nil;
|
|
359
|
+
|
|
360
|
+
delete d; d = nullptr;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
void WKWebViewWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); }
|
|
364
|
+
void WKWebViewWidget::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); }
|
|
365
|
+
|
|
366
|
+
QUrl WKWebViewWidget::url() const {
|
|
367
|
+
if (!(d && d->wk)) return QUrl();
|
|
368
|
+
NSURL* nsurl = d->wk.URL;
|
|
369
|
+
if (!nsurl) return QUrl();
|
|
370
|
+
const char* utf8 = nsurl.absoluteString.UTF8String;
|
|
371
|
+
if (!utf8) return QUrl();
|
|
372
|
+
return QUrl::fromEncoded(QByteArray(utf8));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
void WKWebViewWidget::setUrl(const QUrl& u) {
|
|
376
|
+
if (!(d && d->wk)) return;
|
|
377
|
+
NSURL* nsurl = toNSURL(u);
|
|
378
|
+
if (!nsurl) return;
|
|
379
|
+
[d->wk loadRequest:[NSURLRequest requestWithURL:nsurl]];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
void WKWebViewWidget::back() { if (d && d->wk && d->wk.canGoBack) [d->wk goBack]; }
|
|
383
|
+
void WKWebViewWidget::forward() { if (d && d->wk && d->wk.canGoForward) [d->wk goForward]; }
|
|
384
|
+
void WKWebViewWidget::stop() { if (d && d->wk) [d->wk stopLoading:nil]; }
|
|
385
|
+
void WKWebViewWidget::reload() { if (d && d->wk) [d->wk reload]; }
|
|
386
|
+
|
|
387
|
+
void WKWebViewWidget::evaluateJavaScript(const QString& script) {
|
|
388
|
+
if (!d || !d->wk) return;
|
|
389
|
+
NSString* s = [NSString stringWithUTF8String:script.toUtf8().constData()];
|
|
390
|
+
[d->wk evaluateJavaScript:s completionHandler:^(id result, NSError* error){
|
|
391
|
+
Q_UNUSED(result); Q_UNUSED(error);
|
|
392
|
+
}];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// =======================
|
|
396
|
+
// Download directory API
|
|
397
|
+
// =======================
|
|
398
|
+
QString WKWebViewWidget::downloadDirectory() const {
|
|
399
|
+
return d ? d->downloadDir : QString();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
void WKWebViewWidget::setDownloadDirectory(const QString& dirPath) {
|
|
403
|
+
if (!d) return;
|
|
404
|
+
QString p = QDir::fromNativeSeparators(dirPath);
|
|
405
|
+
if (p.endsWith('/')) p.chop(1);
|
|
406
|
+
if (p.isEmpty()) return;
|
|
407
|
+
QDir().mkpath(p);
|
|
408
|
+
d->downloadDir = p;
|
|
409
|
+
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
#import <Cocoa/Cocoa.h>
|
|
2
|
-
#import <WebKit/WebKit.h>
|
|
3
|
-
|
|
4
|
-
#include "WKWebViewWidget.h"
|
|
5
|
-
|
|
6
|
-
#include <QtWidgets>
|
|
7
|
-
#include <QString>
|
|
8
|
-
#include <QUrl>
|
|
9
|
-
|
|
10
|
-
@class WKNavDelegate;
|
|
11
|
-
@class FitUrlMsgHandler;
|
|
12
|
-
|
|
13
|
-
struct WKWebViewWidget::Impl {
|
|
14
|
-
WKWebView* wk = nil;
|
|
15
|
-
WKNavDelegate* delegate = nil;
|
|
16
|
-
WKUserContentController* ucc = nil;
|
|
17
|
-
FitUrlMsgHandler* msg = nil;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
@interface WKNavDelegate : NSObject <WKNavigationDelegate>
|
|
21
|
-
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
22
|
-
@end
|
|
23
|
-
|
|
24
|
-
@implementation WKNavDelegate
|
|
25
|
-
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
|
|
26
|
-
if (!self.owner) return;
|
|
27
|
-
if (webView.URL)
|
|
28
|
-
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
29
|
-
emit self.owner->loadProgress(5);
|
|
30
|
-
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
31
|
-
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
32
|
-
}
|
|
33
|
-
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
|
|
34
|
-
if (!self.owner) return;
|
|
35
|
-
if (webView.URL)
|
|
36
|
-
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
37
|
-
emit self.owner->loadProgress(50);
|
|
38
|
-
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
39
|
-
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
40
|
-
}
|
|
41
|
-
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
|
|
42
|
-
if (!self.owner) return;
|
|
43
|
-
if (webView.URL)
|
|
44
|
-
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
45
|
-
}
|
|
46
|
-
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
|
47
|
-
if (!self.owner) return;
|
|
48
|
-
emit self.owner->loadFinished(true);
|
|
49
|
-
if (webView.URL)
|
|
50
|
-
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
51
|
-
if (webView.title)
|
|
52
|
-
emit self.owner->titleChanged(QString::fromUtf8(webView.title.UTF8String));
|
|
53
|
-
emit self.owner->loadProgress(100);
|
|
54
|
-
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
55
|
-
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
56
|
-
}
|
|
57
|
-
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
|
58
|
-
if (!self.owner) return;
|
|
59
|
-
emit self.owner->loadFinished(false);
|
|
60
|
-
emit self.owner->loadProgress(0);
|
|
61
|
-
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
62
|
-
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
63
|
-
}
|
|
64
|
-
@end
|
|
65
|
-
|
|
66
|
-
// Handler per messaggi JS (SPA: pushState/replaceState/popstate/click)
|
|
67
|
-
@interface FitUrlMsgHandler : NSObject <WKScriptMessageHandler>
|
|
68
|
-
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
69
|
-
@end
|
|
70
|
-
|
|
71
|
-
@implementation FitUrlMsgHandler
|
|
72
|
-
- (void)userContentController:(WKUserContentController *)userContentController
|
|
73
|
-
didReceiveScriptMessage:(WKScriptMessage *)message {
|
|
74
|
-
if (!self.owner) return;
|
|
75
|
-
if (![message.name isEqualToString:@"fitUrlChanged"]) return;
|
|
76
|
-
if (![message.body isKindOfClass:[NSString class]]) return;
|
|
77
|
-
QString s = QString::fromUtf8([(NSString*)message.body UTF8String]);
|
|
78
|
-
emit self.owner->urlChanged(QUrl::fromEncoded(s.toUtf8()));
|
|
79
|
-
}
|
|
80
|
-
@end
|
|
81
|
-
|
|
82
|
-
// Conversione QUrl -> NSURL con forzatura http->https e normalizzazione user-input
|
|
83
|
-
static NSURL* toNSURL(QUrl u) {
|
|
84
|
-
if (!u.isValid()) return nil;
|
|
85
|
-
|
|
86
|
-
if (u.scheme().isEmpty())
|
|
87
|
-
u = QUrl::fromUserInput(u.toString());
|
|
88
|
-
|
|
89
|
-
// Forza sempre http -> https (nessuna eccezione)
|
|
90
|
-
if (u.scheme() == "http")
|
|
91
|
-
u.setScheme("https");
|
|
92
|
-
|
|
93
|
-
if (u.isLocalFile())
|
|
94
|
-
return [NSURL fileURLWithPath:[NSString stringWithUTF8String:u.toLocalFile().toUtf8().constData()]];
|
|
95
|
-
|
|
96
|
-
const QByteArray enc = u.toString(QUrl::FullyEncoded).toUtf8();
|
|
97
|
-
return [NSURL URLWithString:[NSString stringWithUTF8String:enc.constData()]];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
WKWebViewWidget::WKWebViewWidget(QWidget* parent)
|
|
101
|
-
: QWidget(parent), d(new Impl) {
|
|
102
|
-
setAttribute(Qt::WA_NativeWindow, true);
|
|
103
|
-
(void)winId();
|
|
104
|
-
|
|
105
|
-
NSView* nsParent = (__bridge NSView*)reinterpret_cast<void*>(winId());
|
|
106
|
-
WKWebViewConfiguration* cfg = [[WKWebViewConfiguration alloc] init];
|
|
107
|
-
if ([cfg respondsToSelector:@selector(defaultWebpagePreferences)]) {
|
|
108
|
-
cfg.defaultWebpagePreferences.allowsContentJavaScript = YES;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// UserContentController + script per intercettare navigazioni SPA
|
|
112
|
-
d->ucc = [WKUserContentController new];
|
|
113
|
-
d->msg = [FitUrlMsgHandler new];
|
|
114
|
-
d->msg.owner = this;
|
|
115
|
-
[d->ucc addScriptMessageHandler:d->msg name:@"fitUrlChanged"];
|
|
116
|
-
|
|
117
|
-
NSString* js =
|
|
118
|
-
@"(function(){"
|
|
119
|
-
@" function emit(){"
|
|
120
|
-
@" try{ window.webkit.messageHandlers.fitUrlChanged.postMessage(location.href); }catch(e){}"
|
|
121
|
-
@" }"
|
|
122
|
-
@" var _ps = history.pushState;"
|
|
123
|
-
@" history.pushState = function(){ _ps.apply(this, arguments); emit(); };"
|
|
124
|
-
@" var _rs = history.replaceState;"
|
|
125
|
-
@" history.replaceState = function(){ _rs.apply(this, arguments); emit(); };"
|
|
126
|
-
@" window.addEventListener('popstate', emit, true);"
|
|
127
|
-
@" document.addEventListener('click', function(ev){"
|
|
128
|
-
@" var a = ev.target && ev.target.closest ? ev.target.closest('a[href]') : null;"
|
|
129
|
-
@" if (!a) return;"
|
|
130
|
-
@" if (a.target === '_blank' || a.hasAttribute('download')) return;"
|
|
131
|
-
@" setTimeout(emit, 0);" // lascia tempo alla SPA di aggiornare l’URL
|
|
132
|
-
@" }, true);"
|
|
133
|
-
@"})();";
|
|
134
|
-
|
|
135
|
-
WKUserScript* us = [[WKUserScript alloc]
|
|
136
|
-
initWithSource:js
|
|
137
|
-
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
|
138
|
-
forMainFrameOnly:YES];
|
|
139
|
-
|
|
140
|
-
[d->ucc addUserScript:us];
|
|
141
|
-
cfg.userContentController = d->ucc;
|
|
142
|
-
|
|
143
|
-
d->wk = [[WKWebView alloc] initWithFrame:nsParent.bounds configuration:cfg];
|
|
144
|
-
d->wk.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
145
|
-
[nsParent addSubview:d->wk];
|
|
146
|
-
|
|
147
|
-
d->delegate = [WKNavDelegate new];
|
|
148
|
-
d->delegate.owner = this;
|
|
149
|
-
[d->wk setNavigationDelegate:d->delegate];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
WKWebViewWidget::~WKWebViewWidget() {
|
|
153
|
-
if (!d) return;
|
|
154
|
-
if (d->ucc && d->msg) {
|
|
155
|
-
@try { [d->ucc removeScriptMessageHandlerForName:@"fitUrlChanged"]; } @catch (...) {}
|
|
156
|
-
}
|
|
157
|
-
d->msg = nil;
|
|
158
|
-
|
|
159
|
-
if (d->wk) { [d->wk removeFromSuperview]; d->wk = nil; }
|
|
160
|
-
d->delegate = nil;
|
|
161
|
-
d->ucc = nil;
|
|
162
|
-
|
|
163
|
-
delete d; d = nullptr;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
void WKWebViewWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); }
|
|
167
|
-
void WKWebViewWidget::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); }
|
|
168
|
-
|
|
169
|
-
QUrl WKWebViewWidget::url() const {
|
|
170
|
-
if (!(d && d->wk)) return QUrl();
|
|
171
|
-
NSURL* nsurl = d->wk.URL;
|
|
172
|
-
if (!nsurl) return QUrl();
|
|
173
|
-
const char* utf8 = nsurl.absoluteString.UTF8String;
|
|
174
|
-
if (!utf8) return QUrl();
|
|
175
|
-
return QUrl::fromEncoded(QByteArray(utf8));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
void WKWebViewWidget::setUrl(const QUrl& u) {
|
|
179
|
-
if (!(d && d->wk)) return;
|
|
180
|
-
NSURL* nsurl = toNSURL(u);
|
|
181
|
-
if (!nsurl) return;
|
|
182
|
-
[d->wk loadRequest:[NSURLRequest requestWithURL:nsurl]];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
void WKWebViewWidget::back() { if (d && d->wk && d->wk.canGoBack) [d->wk goBack]; }
|
|
186
|
-
void WKWebViewWidget::forward() { if (d && d->wk && d->wk.canGoForward) [d->wk goForward]; }
|
|
187
|
-
void WKWebViewWidget::stop() { if (d && d->wk) [d->wk stopLoading:nil]; }
|
|
188
|
-
void WKWebViewWidget::reload() { if (d && d->wk) [d->wk reload]; }
|
|
189
|
-
void WKWebViewWidget::evaluateJavaScript(const QString& script) {
|
|
190
|
-
if (!d || !d->wk) return;
|
|
191
|
-
NSString* s = [NSString stringWithUTF8String:script.toUtf8().constData()];
|
|
192
|
-
[d->wk evaluateJavaScript:s completionHandler:^(id result, NSError* error){
|
|
193
|
-
Q_UNUSED(result); Q_UNUSED(error);
|
|
194
|
-
}];
|
|
195
|
-
}
|
{fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/.github/workflows/wheels-macos.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fit_webview_bridge-0.2.0a4 → fit_webview_bridge-0.2.1a3}/bindings/pyside6/macos/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|