fit-webview-bridge 0.2.2a5__tar.gz → 0.2.3a2__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fit-webview-bridge
3
- Version: 0.2.2a5
3
+ Version: 0.2.3a2
4
4
  Summary: Qt native WebView bridge with PySide6 bindings
5
5
  Author: FIT Project
6
6
  License: LGPL-3.0-or-later
@@ -0,0 +1,131 @@
1
+ import os
2
+ import sys
3
+
4
+ ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
5
+ sys.path[:0] = [
6
+ os.path.join(ROOT, "build"),
7
+ os.path.join(ROOT, "build", "bindings", "shiboken_out"),
8
+ ]
9
+
10
+ from PySide6.QtCore import QUrl
11
+ from PySide6.QtWidgets import (
12
+ QApplication,
13
+ QHBoxLayout,
14
+ QLineEdit,
15
+ QMainWindow,
16
+ QPushButton,
17
+ QVBoxLayout,
18
+ QWidget,
19
+ )
20
+
21
+ # tentativo 1: pacchetto generato da shiboken (wkwebview)
22
+ try:
23
+ import wkwebview
24
+
25
+ WKWebViewWidget = wkwebview.WKWebViewWidget
26
+ except Exception:
27
+ # tentativo 2: modulo nativo diretto
28
+ from _wkwebview import WKWebViewWidget
29
+
30
+
31
+ HOME_URL = "https://github.com/fit-project"
32
+
33
+
34
+ class Main(QMainWindow):
35
+ def __init__(self):
36
+ super().__init__()
37
+
38
+ central = QWidget(self)
39
+ root = QVBoxLayout(central)
40
+ self.setCentralWidget(central)
41
+
42
+ # --- toolbar: back/forward/home + address bar + go ---
43
+ bar = QHBoxLayout()
44
+ self.btnBack = QPushButton("◀︎ Back")
45
+ self.btnFwd = QPushButton("Forward ▶︎")
46
+ self.btnHome = QPushButton("🏠 Home")
47
+
48
+ self.address = QLineEdit() # ← barra indirizzi
49
+ self.address.setPlaceholderText("Digita un URL o una ricerca…")
50
+ self.btnGo = QPushButton("Go")
51
+
52
+ bar.addWidget(self.btnBack)
53
+ bar.addWidget(self.btnFwd)
54
+ bar.addWidget(self.btnHome)
55
+ bar.addWidget(self.address, 1) # ← occupa spazio elastico
56
+ bar.addWidget(self.btnGo)
57
+ root.addLayout(bar)
58
+
59
+ # --- webview ---
60
+ self.view = WKWebViewWidget()
61
+ root.addWidget(self.view)
62
+
63
+ # segnali base
64
+ self.view.titleChanged.connect(self.setWindowTitle)
65
+ self.view.loadProgress.connect(lambda p: print("progress:", p))
66
+
67
+ # abilita/disabilita i bottoni in base alla navigazione
68
+ self.btnBack.setEnabled(False)
69
+ self.btnFwd.setEnabled(False)
70
+ self.view.canGoBackChanged.connect(self.btnBack.setEnabled)
71
+ self.view.canGoForwardChanged.connect(self.btnFwd.setEnabled)
72
+
73
+ # azioni bottoni
74
+ self.btnBack.clicked.connect(self.view.back)
75
+ self.btnFwd.clicked.connect(self.view.forward)
76
+ self.btnHome.clicked.connect(lambda: self.view.setUrl(QUrl(HOME_URL)))
77
+
78
+ # --- address bar: invio / bottone Go ---
79
+ def navigate_from_address():
80
+ text = (self.address.text() or "").strip()
81
+ if not text:
82
+ return
83
+ url = QUrl.fromUserInput(text) # gestisce http/https, domini, file, ecc.
84
+ self.view.setUrl(url)
85
+
86
+ self.address.returnPressed.connect(navigate_from_address)
87
+ self.btnGo.clicked.connect(navigate_from_address)
88
+
89
+ # mantieni sincronizzata la barra con la URL corrente
90
+ self.view.urlChanged.connect(lambda u: self.address.setText(u.toString()))
91
+
92
+ # --- eventi download: print semplici ---
93
+ self.view.downloadStarted.connect(
94
+ lambda name, path: print(f"[download] started: name='{name}' path='{path}'")
95
+ )
96
+ self.view.downloadProgress.connect(
97
+ lambda done, total: print(
98
+ f"[download] progress: {done}/{total if total >= 0 else '?'}"
99
+ )
100
+ )
101
+ self.view.downloadFailed.connect(
102
+ lambda path, err: print(f"[download] FAILED: path='{path}' err='{err}'")
103
+ )
104
+
105
+ def on_finished(info):
106
+ try:
107
+ fname = info.fileName() if hasattr(info, "fileName") else None
108
+ directory = info.directory() if hasattr(info, "directory") else None
109
+ url = info.url().toString() if hasattr(info, "url") else None
110
+ if fname or directory or url:
111
+ print(
112
+ f"[download] finished: file='{fname}' dir='{directory}' url='{url}'"
113
+ )
114
+ else:
115
+ print(f"[download] finished: {info}")
116
+ except Exception as e:
117
+ print(f"[download] finished (inspect error: {e}): {info}")
118
+
119
+ self.view.downloadFinished.connect(on_finished)
120
+
121
+ # carica home e imposta barra
122
+ self.view.setUrl(QUrl(HOME_URL))
123
+ self.address.setText(HOME_URL)
124
+
125
+
126
+ if __name__ == "__main__":
127
+ app = QApplication([])
128
+ m = Main()
129
+ m.resize(1200, 800)
130
+ m.show()
131
+ app.exec()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fit-webview-bridge"
3
- version = "0.2.2a5"
3
+ version = "0.2.3a2"
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"]
@@ -6,6 +6,36 @@
6
6
  #include "DownloadInfo.h"
7
7
 
8
8
 
9
+ static inline void fit_emit_downloadStarted(WKWebViewWidget* owner,
10
+ const QString& name,
11
+ const QString& path) {
12
+ if (!owner) return;
13
+ QMetaObject::invokeMethod(owner, [owner, name, path]{
14
+ emit owner->downloadStarted(name, path);
15
+ }, Qt::QueuedConnection);
16
+ }
17
+
18
+ static inline void fit_emit_downloadFailed(WKWebViewWidget* owner,
19
+ const QString& path,
20
+ const QString& err) {
21
+ if (!owner) return;
22
+ QMetaObject::invokeMethod(owner, [owner, path, err]{
23
+ emit owner->downloadFailed(path, err);
24
+ }, Qt::QueuedConnection);
25
+ }
26
+
27
+ static inline void fit_emit_downloadFinished(WKWebViewWidget* owner,
28
+ const QString& fileName,
29
+ const QString& dir,
30
+ const QUrl& src) {
31
+ if (!owner) return;
32
+ QMetaObject::invokeMethod(owner, [owner, fileName, dir, src]{
33
+ auto *info = new DownloadInfo(fileName, dir, src, owner);
34
+ emit owner->downloadFinished(info);
35
+ }, Qt::QueuedConnection);
36
+ }
37
+
38
+
9
39
  #include <QtWidgets>
10
40
  #include <QString>
11
41
  #include <QUrl>
@@ -35,19 +65,232 @@ static NSURL* toNSURL(QUrl u);
35
65
  // =======================
36
66
  @interface FitUrlMsgHandler : NSObject <WKScriptMessageHandler>
37
67
  @property(nonatomic, assign) WKWebViewWidget* owner;
68
+ @property(nonatomic, assign) WKWebView* webView;
69
+
70
+ - (void)_fitShowContextMenuFromPayload:(NSDictionary*)payload;
71
+ - (void)_fitOpenLink:(NSMenuItem*)item;
72
+ - (void)_fitCopyURL:(NSMenuItem*)item;
38
73
  @end
39
74
 
75
+
76
+ static inline NSString* FITURLStr(NSURL *u) { return u ? u.absoluteString : @"(nil)"; }
77
+
78
+ static NSString* FIT_CurrentLang(void) {
79
+ NSString *lang = NSLocale.preferredLanguages.firstObject ?: @"en";
80
+ // normalizza es. "it-IT" -> "it"
81
+ NSRange dash = [lang rangeOfString:@"-"];
82
+ return (dash.location != NSNotFound) ? [lang substringToIndex:dash.location] : lang;
83
+ }
84
+
85
+ static NSString* FIT_T(NSString* key) {
86
+ static NSDictionary *en, *it;
87
+ static dispatch_once_t once;
88
+ dispatch_once(&once, ^{
89
+ en = @{
90
+ @"menu.openLink": @"Open link",
91
+ @"menu.copyLink": @"Copy link address",
92
+ @"menu.openImage": @"Open image",
93
+ @"menu.copyImageURL": @"Copy image URL",
94
+ @"menu.downloadImage":@"Download image…",
95
+ };
96
+ it = @{
97
+ @"menu.openLink": @"Apri link",
98
+ @"menu.copyLink": @"Copia indirizzo link",
99
+ @"menu.openImage": @"Apri immagine",
100
+ @"menu.copyImageURL": @"Copia URL immagine",
101
+ @"menu.downloadImage":@"Scarica immagine…",
102
+ };
103
+ });
104
+ NSString *lang = FIT_CurrentLang();
105
+ NSDictionary *tbl = [lang isEqualToString:@"it"] ? it : en;
106
+ return tbl[key] ?: en[key] ?: key;
107
+ }
108
+
40
109
  @implementation FitUrlMsgHandler
110
+
111
+ // Helpers per sanity-check su tipi da payload
112
+ static inline NSString* FITStringOrNil(id obj) {
113
+ return [obj isKindOfClass:NSString.class] ? (NSString*)obj : nil;
114
+ }
115
+ static inline NSNumber* FITNumberOrNil(id obj) {
116
+ return [obj isKindOfClass:NSNumber.class] ? (NSNumber*)obj : nil;
117
+ }
118
+
41
119
  - (void)userContentController:(WKUserContentController *)userContentController
42
- didReceiveScriptMessage:(WKScriptMessage *)message {
120
+ didReceiveScriptMessage:(WKScriptMessage *)message
121
+ {
43
122
  if (!self.owner) return;
44
- if (![message.name isEqualToString:@"fitUrlChanged"]) return;
45
- if (![message.body isKindOfClass:[NSString class]]) return;
46
- QString s = QString::fromUtf8([(NSString*)message.body UTF8String]);
47
- emit self.owner->urlChanged(QUrl::fromEncoded(s.toUtf8()));
123
+
124
+ if ([message.name isEqualToString:@"fitUrlChanged"]) {
125
+ if (![message.body isKindOfClass:[NSString class]]) return;
126
+ QString s = QString::fromUtf8([(NSString*)message.body UTF8String]);
127
+ emit self.owner->urlChanged(QUrl::fromEncoded(s.toUtf8()));
128
+ return;
129
+ }
130
+
131
+ if ([message.name isEqualToString:@"fitContextMenu"]) {
132
+ if (![message.body isKindOfClass:[NSDictionary class]]) return;
133
+ dispatch_async(dispatch_get_main_queue(), ^{
134
+ [self _fitShowContextMenuFromPayload:(NSDictionary*)message.body];
135
+ });
136
+ return;
137
+ }
138
+ }
139
+
140
+ - (void)_fitShowContextMenuFromPayload:(NSDictionary*)payload
141
+ {
142
+ WKWebView* wv = self.webView;
143
+ if (!wv || !wv.window) return;
144
+
145
+ NSString *linkStr = FITStringOrNil(payload[@"link"]);
146
+ NSString *imgStr = FITStringOrNil(payload[@"image"]);
147
+ NSURL *linkURL = (linkStr.length ? [NSURL URLWithString:linkStr] : nil);
148
+ NSURL *imgURL = (imgStr.length ? [NSURL URLWithString:imgStr] : nil);
149
+ if (!linkURL && !imgURL) return;
150
+
151
+ NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
152
+
153
+ if (linkURL) {
154
+ NSMenuItem *open = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.openLink")
155
+ action:@selector(_fitOpenLink:)
156
+ keyEquivalent:@""];
157
+ open.target = self; open.representedObject = @{@"url": linkURL};
158
+ [menu addItem:open];
159
+
160
+ NSMenuItem *copy = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.copyLink")
161
+ action:@selector(_fitCopyURL:)
162
+ keyEquivalent:@""];
163
+ copy.target = self; copy.representedObject = @{@"url": linkURL};
164
+ [menu addItem:copy];
165
+ }
166
+
167
+ if (imgURL) {
168
+ NSMenuItem *openImg = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.openImage")
169
+ action:@selector(_fitOpenLink:)
170
+ keyEquivalent:@""];
171
+ openImg.target = self; openImg.representedObject = @{@"url": imgURL};
172
+ [menu addItem:openImg];
173
+
174
+ NSMenuItem *copyImg = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.copyImageURL")
175
+ action:@selector(_fitCopyURL:)
176
+ keyEquivalent:@""];
177
+ copyImg.target = self; copyImg.representedObject = @{@"url": imgURL};
178
+ [menu addItem:copyImg];
179
+
180
+ NSMenuItem *dlImg = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.downloadImage")
181
+ action:@selector(_fitDownloadImage:)
182
+ keyEquivalent:@""];
183
+ dlImg.target = self;
184
+ dlImg.representedObject = @{@"url": imgURL};
185
+ [menu addItem:[NSMenuItem separatorItem]];
186
+ [menu addItem:dlImg];
187
+ }
188
+
189
+ NSPoint mouseOnScreen = [NSEvent mouseLocation];
190
+ NSPoint inWindow = [wv.window convertPointFromScreen:mouseOnScreen];
191
+ NSPoint inView = [wv convertPoint:inWindow fromView:nil];
192
+
193
+ [menu popUpMenuPositioningItem:nil atLocation:inView inView:wv];
194
+ }
195
+
196
+ - (void)_fitOpenLink:(NSMenuItem*)item {
197
+ NSURL *url = ((NSDictionary*)item.representedObject)[@"url"];
198
+ if (!url || !self.webView) return;
199
+ [self.webView loadRequest:[NSURLRequest requestWithURL:url]];
200
+ }
201
+
202
+ - (void)_fitCopyURL:(NSMenuItem*)item {
203
+ NSURL *url = ((NSDictionary*)item.representedObject)[@"url"];
204
+ if (!url) return;
205
+ NSPasteboard *pb = [NSPasteboard generalPasteboard];
206
+ [pb clearContents];
207
+ [pb setString:url.absoluteString forType:NSPasteboardTypeString];
208
+ }
209
+
210
+ // Utility: crea nome unico in una cartella
211
+ static NSString* fit_uniquePath(NSString* baseDir, NSString* filename) {
212
+ NSString* fname = filename.length ? filename : @"download";
213
+ NSString* path = [baseDir stringByAppendingPathComponent:fname];
214
+ NSFileManager* fm = [NSFileManager defaultManager];
215
+ if (![fm fileExistsAtPath:path]) return path;
216
+
217
+ NSString* name = [fname stringByDeletingPathExtension];
218
+ NSString* ext = [fname pathExtension];
219
+ for (NSUInteger i = 1; i < 10000; ++i) {
220
+ NSString* cand = ext.length
221
+ ? [NSString stringWithFormat:@"%@ (%lu).%@", name, (unsigned long)i, ext]
222
+ : [NSString stringWithFormat:@"%@ (%lu)", name, (unsigned long)i];
223
+ NSString* candPath = [baseDir stringByAppendingPathComponent:cand];
224
+ if (![fm fileExistsAtPath:candPath]) return candPath;
225
+ }
226
+ return path;
227
+ }
228
+
229
+ // Scarica un URL (usato dall’azione immagine)
230
+ - (void)_fitDownloadURL:(NSURL *)url suggestedName:(NSString *)suggestedName {
231
+ if (!url || !self.owner) return;
232
+
233
+ // cartella destinazione da Qt
234
+ QString qdir = self.owner->downloadDirectory();
235
+ NSString *destDir = [NSString stringWithUTF8String:qdir.toUtf8().constData()];
236
+ if (!destDir.length) destDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Downloads"];
237
+ [[NSFileManager defaultManager] createDirectoryAtPath:destDir
238
+ withIntermediateDirectories:YES
239
+ attributes:nil error:nil];
240
+
241
+ // nome iniziale
242
+ NSString *fname = suggestedName.length ? suggestedName : (url.lastPathComponent.length ? url.lastPathComponent : @"download");
243
+ NSString *tmpTarget = fit_uniquePath(destDir, fname);
244
+
245
+ // segnala start (nome provvisorio)
246
+ fit_emit_downloadStarted(self.owner,
247
+ QString::fromUtf8([tmpTarget lastPathComponent].UTF8String),
248
+ QString::fromUtf8(tmpTarget.UTF8String));
249
+
250
+ NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration];
251
+ NSURLSession *session = [NSURLSession sessionWithConfiguration:cfg];
252
+ NSURLSessionDownloadTask *task =
253
+ [session downloadTaskWithURL:url
254
+ completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
255
+ {
256
+ if (error) {
257
+ fit_emit_downloadFailed(self.owner,
258
+ QString::fromUtf8(tmpTarget.UTF8String),
259
+ QString::fromUtf8(error.localizedDescription.UTF8String));
260
+ return;
261
+ }
262
+
263
+ // usa il suggerimento del server se c’è
264
+ NSString *serverName = response.suggestedFilename.length ? response.suggestedFilename : [tmpTarget lastPathComponent];
265
+ NSString *finalPath = fit_uniquePath(destDir, serverName);
266
+
267
+ NSError *mvErr = nil;
268
+ [[NSFileManager defaultManager] moveItemAtURL:location
269
+ toURL:[NSURL fileURLWithPath:finalPath]
270
+ error:&mvErr];
271
+ if (mvErr) {
272
+ fit_emit_downloadFailed(self.owner,
273
+ QString::fromUtf8(finalPath.UTF8String),
274
+ QString::fromUtf8(mvErr.localizedDescription.UTF8String));
275
+ return;
276
+ }
277
+
278
+ QUrl qsrc = QUrl::fromEncoded(QByteArray(url.absoluteString.UTF8String));
279
+ fit_emit_downloadFinished(self.owner,
280
+ QString::fromUtf8([finalPath lastPathComponent].UTF8String),
281
+ QString::fromUtf8([finalPath stringByDeletingLastPathComponent].UTF8String),
282
+ qsrc);
283
+ }];
284
+ [task resume];
285
+ }
286
+
287
+ - (void)_fitDownloadImage:(NSMenuItem*)item {
288
+ NSURL *url = ((NSDictionary*)item.representedObject)[@"url"];
289
+ [self _fitDownloadURL:url suggestedName:nil];
48
290
  }
49
291
  @end
50
292
 
293
+
51
294
  // ===== WKNavDelegate =====
52
295
  @interface WKNavDelegate : NSObject <WKNavigationDelegate, WKDownloadDelegate, WKUIDelegate>
53
296
  @property(nonatomic, assign) WKWebViewWidget* owner;
@@ -58,6 +301,9 @@ static NSURL* toNSURL(QUrl u);
58
301
  @property(nonatomic, strong) NSMapTable<WKDownload*, NSNumber*>* expectedTotals; // weak->strong
59
302
  @property(nonatomic, strong) NSMapTable<WKDownload*, NSURL*>* sourceURLs; // weak->strong
60
303
  @property(nonatomic, strong) NSMapTable<WKDownload*, NSString*>* suggestedNames; // weak->strong
304
+ @property(nonatomic, strong) NSURL* pendingPopupParentURL;
305
+ @property(nonatomic, strong) NSURL* pendingPopupChildURL;
306
+ @property(nonatomic, assign) WKWebView* webView;
61
307
  @end
62
308
 
63
309
  @implementation WKNavDelegate
@@ -75,17 +321,15 @@ static NSURL* toNSURL(QUrl u);
75
321
  }
76
322
 
77
323
  #pragma mark - Navigazione
78
-
79
-
80
324
  // 1a) Navigation: intercetta click con targetFrame == nil (tipico di _blank)
81
325
  - (void)webView:(WKWebView *)webView
82
326
  decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
83
327
  decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
84
328
  {
85
- // Se è un _blank, no-op qui: ci pensa createWebView... (sopra)
86
329
  decisionHandler(WKNavigationActionPolicyAllow);
87
330
  }
88
331
 
332
+
89
333
  // 1b) UI: invocato quando la pagina chiede esplicitamente una nuova webview
90
334
  - (WKWebView *)webView:(WKWebView *)webView
91
335
  createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
@@ -93,8 +337,18 @@ createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
93
337
  windowFeatures:(WKWindowFeatures *)windowFeatures
94
338
  {
95
339
  if (navigationAction.targetFrame == nil || !navigationAction.targetFrame.isMainFrame) {
96
- [webView loadRequest:navigationAction.request]; // apri nella stessa webview
340
+ NSURL *parent = webView.URL;
341
+ NSURL *child = navigationAction.request.URL;
342
+
343
+ // salva coppia padre/figlio per il “ritorno” post-download
344
+ self.pendingPopupParentURL = parent;
345
+ self.pendingPopupChildURL = child;
346
+
347
+ if (child) {
348
+ [webView loadRequest:navigationAction.request]; // apri nella stessa webview
349
+ }
97
350
  }
351
+
98
352
  return nil; // restituisci nil per NON creare una nuova finestra
99
353
  }
100
354
 
@@ -149,10 +403,27 @@ createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
149
403
  decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
150
404
  decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
151
405
  {
406
+ NSURLResponse *resp = navigationResponse.response;
407
+ NSURL *url = resp.URL;
408
+
409
+ BOOL isAttachment = NO;
410
+ if ([resp isKindOfClass:NSHTTPURLResponse.class]) {
411
+ NSHTTPURLResponse *http = (NSHTTPURLResponse *)resp;
412
+ NSString *cd = http.allHeaderFields[@"Content-Disposition"];
413
+ if (cd && [[cd lowercaseString] containsString:@"attachment"]) {
414
+ isAttachment = YES;
415
+ }
416
+ }
417
+
418
+ if (isAttachment) {
419
+ decisionHandler(WKNavigationResponsePolicyDownload);
420
+ return;
421
+ }
422
+
152
423
  if (navigationResponse.canShowMIMEType) {
153
424
  decisionHandler(WKNavigationResponsePolicyAllow);
154
425
  } else {
155
- decisionHandler(WKNavigationResponsePolicyDownload); // API moderna
426
+ decisionHandler(WKNavigationResponsePolicyDownload);
156
427
  }
157
428
  }
158
429
 
@@ -341,6 +612,42 @@ completionHandler:(void (^)(NSURL * _Nullable destination))completionHandler
341
612
  DownloadInfo* info = new DownloadInfo(qFileName, qDir, qUrl, self.owner);
342
613
  emit self.owner->downloadFinished(info);
343
614
 
615
+
616
+
617
+ WKWebView *webView = self.webView;
618
+ NSURL *srcURL = [self.sourceURLs objectForKey:download];
619
+
620
+ if (webView && self.pendingPopupChildURL && srcURL &&
621
+ [srcURL isEqual:self.pendingPopupChildURL]) {
622
+
623
+ WKBackForwardList *bf = webView.backForwardList;
624
+ NSURL *current = webView.URL;
625
+ NSURL *backURL = bf.backItem.URL;
626
+
627
+ // CASI:
628
+ // A) Sei sul FIGLIO → torna indietro alla PARENT
629
+ if (current && [current isEqual:self.pendingPopupChildURL]) {
630
+ [webView goBack];
631
+ }
632
+ // B) Sei già sulla PARENT → non fare nulla
633
+ else if (current && [current isEqual:self.pendingPopupParentURL]) {
634
+ // niente
635
+ }
636
+ // C) Non sei sul child, ma l’item precedente è la PARENT → goBack
637
+ else if (backURL && [backURL isEqual:self.pendingPopupParentURL]) {
638
+ [webView goBack];
639
+ }
640
+ // D) Fallback: carica esplicitamente la PARENT
641
+ else if (self.pendingPopupParentURL) {
642
+ [webView loadRequest:[NSURLRequest requestWithURL:self.pendingPopupParentURL]];
643
+ } else {
644
+ //Niente
645
+ }
646
+
647
+ // pulizia stato
648
+ self.pendingPopupChildURL = nil;
649
+ self.pendingPopupParentURL = nil;
650
+ }
344
651
  // 5) cleanup mappe
345
652
  if (finalPath) [self.downloadPaths removeObjectForKey:download];
346
653
  [self.progressToDownload removeObjectForKey:download.progress];
@@ -368,6 +675,20 @@ completionHandler:(void (^)(NSURL * _Nullable destination))completionHandler
368
675
  QString::fromUtf8(error.localizedDescription.UTF8String)
369
676
  );
370
677
 
678
+ // 🔙 Se il download proviene dal "figlio", torna alla "pagina padre"
679
+ WKWebView *webView = self.webView;
680
+ NSURL *src = [self.sourceURLs objectForKey:download];
681
+ if (webView && self.pendingPopupChildURL && src && [src isEqual:self.pendingPopupChildURL]) {
682
+ if (webView.canGoBack) {
683
+ [webView goBack];
684
+ } else if (self.pendingPopupParentURL) {
685
+ [webView loadRequest:[NSURLRequest requestWithURL:self.pendingPopupParentURL]];
686
+ }
687
+ // ripulisci lo stato
688
+ self.pendingPopupChildURL = nil;
689
+ self.pendingPopupParentURL = nil;
690
+ }
691
+
371
692
  // cleanup mappe
372
693
  if (finalPath) [self.downloadPaths removeObjectForKey:download];
373
694
  [self.progressToDownload removeObjectForKey:download.progress];
@@ -415,6 +736,11 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
415
736
  cfg.defaultWebpagePreferences.allowsContentJavaScript = YES;
416
737
  }
417
738
 
739
+ // ✅ Consenti window.open() senza creare una nuova finestra UI
740
+ @try {
741
+ cfg.preferences.javaScriptCanOpenWindowsAutomatically = YES;
742
+ } @catch (...) {}
743
+
418
744
  // --- Fullscreen HTML5 (via KVC tollerante) ---
419
745
  @try {
420
746
  [cfg.preferences setValue:@YES forKey:@"fullScreenEnabled"];
@@ -435,6 +761,7 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
435
761
  d->msg = [FitUrlMsgHandler new];
436
762
  d->msg.owner = this;
437
763
  [d->ucc addScriptMessageHandler:d->msg name:@"fitUrlChanged"];
764
+ [d->ucc addScriptMessageHandler:d->msg name:@"fitContextMenu"];
438
765
 
439
766
  NSString* js =
440
767
  @"(function(){"
@@ -447,8 +774,25 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
447
774
  @" if (!a) return; if (a.target === '_blank' || a.hasAttribute('download')) return;"
448
775
  @" setTimeout(emit, 0);"
449
776
  @" }, true);"
777
+ @"})();"
778
+ @"(function(){"
779
+ @" document.addEventListener('contextmenu', function(ev){"
780
+ @" var el = ev.target;"
781
+ @" var a = el && el.closest ? el.closest('a[href]') : null;"
782
+ @" var img = el && el.closest ? el.closest('img[src]') : null;"
783
+ @" if (!a && !img) return;" // lascia il menu nativo altrove
784
+ @" ev.preventDefault();"
785
+ @" try {"
786
+ @" window.webkit.messageHandlers.fitContextMenu.postMessage({"
787
+ @" x: ev.clientX, y: ev.clientY,"
788
+ @" link: a ? a.href : null,"
789
+ @" image: img ? img.src : null"
790
+ @" });"
791
+ @" } catch(e){}"
792
+ @" }, true);"
450
793
  @"})();";
451
794
 
795
+
452
796
  WKUserScript* us = [[WKUserScript alloc]
453
797
  initWithSource:js
454
798
  injectionTime:WKUserScriptInjectionTimeAtDocumentStart
@@ -458,12 +802,13 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
458
802
 
459
803
  d->wk = [[WKWebView alloc] initWithFrame:nsParent.bounds configuration:cfg];
460
804
  d->wk.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
805
+ [d->msg setWebView:d->wk];
461
806
  [nsParent addSubview:d->wk];
462
807
 
463
808
  d->delegate = [WKNavDelegate new];
464
809
  d->delegate.owner = this;
810
+ d->delegate.webView = d->wk;
465
811
  [d->wk setNavigationDelegate:d->delegate];
466
-
467
812
  [d->wk setUIDelegate:d->delegate];
468
813
  }
469
814
 
@@ -472,6 +817,7 @@ WKWebViewWidget::~WKWebViewWidget() {
472
817
 
473
818
  if (d->ucc && d->msg) {
474
819
  @try { [d->ucc removeScriptMessageHandlerForName:@"fitUrlChanged"]; } @catch (...) {}
820
+ @try { [d->ucc removeScriptMessageHandlerForName:@"fitContextMenu"]; } @catch (...) {}
475
821
  }
476
822
  d->msg = nil;
477
823
 
@@ -1,43 +0,0 @@
1
- import os
2
- import sys
3
-
4
- ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
5
- sys.path[:0] = [
6
- os.path.join(ROOT, "build"),
7
- os.path.join(ROOT, "build", "bindings", "shiboken_out"),
8
- ]
9
-
10
- from PySide6.QtCore import QUrl
11
- from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
12
-
13
- # tentativo 1: pacchetto generato da shiboken (wkwebview)
14
- try:
15
- import wkwebview
16
-
17
- WKWebViewWidget = wkwebview.WKWebViewWidget # accesso via attributo
18
- except Exception:
19
- # tentativo 2: modulo nativo diretto
20
- from _wkwebview import WKWebViewWidget
21
-
22
-
23
- class Main(QMainWindow):
24
- def __init__(self):
25
- super().__init__()
26
- central = QWidget(self)
27
- lay = QVBoxLayout(central)
28
- self.view = WKWebViewWidget()
29
- lay.addWidget(self.view)
30
- self.setCentralWidget(central)
31
-
32
- # segnali utili
33
- self.view.titleChanged.connect(self.setWindowTitle)
34
- self.view.loadProgress.connect(lambda p: print("progress:", p))
35
-
36
- self.view.setUrl(QUrl("https://web.whatsapp.com/"))
37
-
38
-
39
- app = QApplication([])
40
- m = Main()
41
- m.resize(1200, 800)
42
- m.show()
43
- app.exec()