fit-webview-bridge 0.2.3a1__tar.gz → 0.2.4a1__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.3a1
3
+ Version: 0.2.4a1
4
4
  Summary: Qt native WebView bridge with PySide6 bindings
5
5
  Author: FIT Project
6
6
  License: LGPL-3.0-or-later
@@ -11,6 +11,7 @@ from PySide6.QtCore import QUrl
11
11
  from PySide6.QtWidgets import (
12
12
  QApplication,
13
13
  QHBoxLayout,
14
+ QLineEdit,
14
15
  QMainWindow,
15
16
  QPushButton,
16
17
  QVBoxLayout,
@@ -27,7 +28,7 @@ except Exception:
27
28
  from _wkwebview import WKWebViewWidget
28
29
 
29
30
 
30
- HOME_URL = "https://google.it"
31
+ HOME_URL = "https://github.com/fit-project"
31
32
 
32
33
 
33
34
  class Main(QMainWindow):
@@ -38,15 +39,21 @@ class Main(QMainWindow):
38
39
  root = QVBoxLayout(central)
39
40
  self.setCentralWidget(central)
40
41
 
41
- # --- toolbar semplice ---
42
+ # --- toolbar: back/forward/home + address bar + go ---
42
43
  bar = QHBoxLayout()
43
44
  self.btnBack = QPushButton("◀︎ Back")
44
45
  self.btnFwd = QPushButton("Forward ▶︎")
45
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
+
46
52
  bar.addWidget(self.btnBack)
47
53
  bar.addWidget(self.btnFwd)
48
54
  bar.addWidget(self.btnHome)
49
- bar.addStretch(1)
55
+ bar.addWidget(self.address, 1) # ← occupa spazio elastico
56
+ bar.addWidget(self.btnGo)
50
57
  root.addLayout(bar)
51
58
 
52
59
  # --- webview ---
@@ -68,6 +75,20 @@ class Main(QMainWindow):
68
75
  self.btnFwd.clicked.connect(self.view.forward)
69
76
  self.btnHome.clicked.connect(lambda: self.view.setUrl(QUrl(HOME_URL)))
70
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
+
71
92
  # --- eventi download: print semplici ---
72
93
  self.view.downloadStarted.connect(
73
94
  lambda name, path: print(f"[download] started: name='{name}' path='{path}'")
@@ -82,7 +103,6 @@ class Main(QMainWindow):
82
103
  )
83
104
 
84
105
  def on_finished(info):
85
- # Proviamo a leggere i getter se disponibili; fallback a repr
86
106
  try:
87
107
  fname = info.fileName() if hasattr(info, "fileName") else None
88
108
  directory = info.directory() if hasattr(info, "directory") else None
@@ -98,8 +118,9 @@ class Main(QMainWindow):
98
118
 
99
119
  self.view.downloadFinished.connect(on_finished)
100
120
 
101
- # carica home
121
+ # carica home e imposta barra
102
122
  self.view.setUrl(QUrl(HOME_URL))
123
+ self.address.setText(HOME_URL)
103
124
 
104
125
 
105
126
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fit-webview-bridge"
3
- version = "0.2.3a1"
3
+ version = "0.2.4a1"
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"]
@@ -26,6 +26,7 @@ public:
26
26
 
27
27
  Q_INVOKABLE void setDownloadDirectory(const QString& dirPath);
28
28
  Q_INVOKABLE QString downloadDirectory() const;
29
+ void renderErrorPage(const QUrl& url, const QString& reason, int httpStatus);
29
30
 
30
31
  signals:
31
32
  void loadFinished(bool ok);
@@ -34,6 +35,7 @@ signals:
34
35
  void loadProgress(int percent);
35
36
  void canGoBackChanged(bool);
36
37
  void canGoForwardChanged(bool);
38
+
37
39
 
38
40
 
39
41
  void downloadStarted(const QString& suggestedFilename, const QString& destinationPath);
@@ -73,6 +73,8 @@ static NSURL* toNSURL(QUrl u);
73
73
  @end
74
74
 
75
75
 
76
+ static inline NSString* FITURLStr(NSURL *u) { return u ? u.absoluteString : @"(nil)"; }
77
+
76
78
  static NSString* FIT_CurrentLang(void) {
77
79
  NSString *lang = NSLocale.preferredLanguages.firstObject ?: @"en";
78
80
  // normalizza es. "it-IT" -> "it"
@@ -90,6 +92,10 @@ static NSString* FIT_T(NSString* key) {
90
92
  @"menu.openImage": @"Open image",
91
93
  @"menu.copyImageURL": @"Copy image URL",
92
94
  @"menu.downloadImage":@"Download image…",
95
+ @"error.title": @"Can’t load this page",
96
+ @"error.reason": @"The site may not exist or be temporarily unreachable.",
97
+ @"error.retry": @"Retry",
98
+ @"error.close": @"Close",
93
99
  };
94
100
  it = @{
95
101
  @"menu.openLink": @"Apri link",
@@ -97,6 +103,10 @@ static NSString* FIT_T(NSString* key) {
97
103
  @"menu.openImage": @"Apri immagine",
98
104
  @"menu.copyImageURL": @"Copia URL immagine",
99
105
  @"menu.downloadImage":@"Scarica immagine…",
106
+ @"error.title": @"Impossibile caricare la pagina",
107
+ @"error.reason": @"Il sito potrebbe essere inesistente o momentaneamente non raggiungibile.",
108
+ @"error.retry": @"Riprova",
109
+ @"error.close": @"Chiudi",
100
110
  };
101
111
  });
102
112
  NSString *lang = FIT_CurrentLang();
@@ -299,6 +309,9 @@ static NSString* fit_uniquePath(NSString* baseDir, NSString* filename) {
299
309
  @property(nonatomic, strong) NSMapTable<WKDownload*, NSNumber*>* expectedTotals; // weak->strong
300
310
  @property(nonatomic, strong) NSMapTable<WKDownload*, NSURL*>* sourceURLs; // weak->strong
301
311
  @property(nonatomic, strong) NSMapTable<WKDownload*, NSString*>* suggestedNames; // weak->strong
312
+ @property(nonatomic, strong) NSURL* pendingPopupParentURL;
313
+ @property(nonatomic, strong) NSURL* pendingPopupChildURL;
314
+ @property(nonatomic, assign) WKWebView* webView;
302
315
  @end
303
316
 
304
317
  @implementation WKNavDelegate
@@ -316,17 +329,15 @@ static NSString* fit_uniquePath(NSString* baseDir, NSString* filename) {
316
329
  }
317
330
 
318
331
  #pragma mark - Navigazione
319
-
320
-
321
332
  // 1a) Navigation: intercetta click con targetFrame == nil (tipico di _blank)
322
333
  - (void)webView:(WKWebView *)webView
323
334
  decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
324
335
  decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
325
336
  {
326
- // Se è un _blank, no-op qui: ci pensa createWebView... (sopra)
327
337
  decisionHandler(WKNavigationActionPolicyAllow);
328
338
  }
329
339
 
340
+
330
341
  // 1b) UI: invocato quando la pagina chiede esplicitamente una nuova webview
331
342
  - (WKWebView *)webView:(WKWebView *)webView
332
343
  createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
@@ -334,12 +345,42 @@ createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
334
345
  windowFeatures:(WKWindowFeatures *)windowFeatures
335
346
  {
336
347
  if (navigationAction.targetFrame == nil || !navigationAction.targetFrame.isMainFrame) {
337
- [webView loadRequest:navigationAction.request]; // apri nella stessa webview
348
+ NSURL *parent = webView.URL;
349
+ NSURL *child = navigationAction.request.URL;
350
+
351
+ // salva coppia padre/figlio per il “ritorno” post-download
352
+ self.pendingPopupParentURL = parent;
353
+ self.pendingPopupChildURL = child;
354
+
355
+ if (child) {
356
+ [webView loadRequest:navigationAction.request]; // apri nella stessa webview
357
+ }
338
358
  }
359
+
339
360
  return nil; // restituisci nil per NON creare una nuova finestra
340
361
  }
341
362
 
342
363
 
364
+ - (void)webView:(WKWebView *)webView
365
+ didFailProvisionalNavigation:(WKNavigation *)navigation
366
+ withError:(NSError *)error
367
+ {
368
+ if (!self.owner) return;
369
+ emit self.owner->loadFinished(false);
370
+ emit self.owner->loadProgress(0);
371
+ emit self.owner->canGoBackChanged(webView.canGoBack);
372
+ emit self.owner->canGoForwardChanged(webView.canGoForward);
373
+
374
+ QUrl qurl = webView.URL
375
+ ? QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String))
376
+ : QUrl();
377
+ self.owner->renderErrorPage(qurl,
378
+ QString::fromUtf8(error.localizedDescription.UTF8String),
379
+ /*httpStatus*/ 0);
380
+ }
381
+
382
+
383
+
343
384
  - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
344
385
  if (!self.owner) return;
345
386
  if (webView.URL)
@@ -382,6 +423,13 @@ createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
382
423
  emit self.owner->loadProgress(0);
383
424
  emit self.owner->canGoBackChanged(webView.canGoBack);
384
425
  emit self.owner->canGoForwardChanged(webView.canGoForward);
426
+ // Mostra pagina d'errore interna
427
+ QUrl qurl = webView.URL
428
+ ? QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String))
429
+ : QUrl();
430
+ self.owner->renderErrorPage(qurl,
431
+ QString::fromUtf8(error.localizedDescription.UTF8String),
432
+ /*httpStatus*/ 0);
385
433
  }
386
434
 
387
435
  #pragma mark - Decide download vs render
@@ -390,10 +438,36 @@ createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
390
438
  decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
391
439
  decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
392
440
  {
441
+ NSURLResponse *resp = navigationResponse.response;
442
+ NSURL *url = resp.URL;
443
+
444
+ BOOL isAttachment = NO;
445
+ if ([resp isKindOfClass:NSHTTPURLResponse.class]) {
446
+ NSHTTPURLResponse *http = (NSHTTPURLResponse *)resp;
447
+ NSString *cd = http.allHeaderFields[@"Content-Disposition"];
448
+ if (cd && [[cd lowercaseString] containsString:@"attachment"]) {
449
+ isAttachment = YES;
450
+ }
451
+ // 🔎 Se è main frame e status HTTP >= 400, mostra pagina d'errore custom
452
+ if (navigationResponse.isForMainFrame && http.statusCode >= 400 && self.owner) {
453
+ QUrl qurl = QUrl::fromEncoded(QByteArray(url.absoluteString.UTF8String));
454
+ self.owner->renderErrorPage(qurl,
455
+ QStringLiteral(""), // reason generica localizzata dal template
456
+ (int)http.statusCode);
457
+ decisionHandler(WKNavigationResponsePolicyCancel);
458
+ return;
459
+ }
460
+ }
461
+
462
+ if (isAttachment) {
463
+ decisionHandler(WKNavigationResponsePolicyDownload);
464
+ return;
465
+ }
466
+
393
467
  if (navigationResponse.canShowMIMEType) {
394
468
  decisionHandler(WKNavigationResponsePolicyAllow);
395
469
  } else {
396
- decisionHandler(WKNavigationResponsePolicyDownload); // API moderna
470
+ decisionHandler(WKNavigationResponsePolicyDownload);
397
471
  }
398
472
  }
399
473
 
@@ -582,6 +656,42 @@ completionHandler:(void (^)(NSURL * _Nullable destination))completionHandler
582
656
  DownloadInfo* info = new DownloadInfo(qFileName, qDir, qUrl, self.owner);
583
657
  emit self.owner->downloadFinished(info);
584
658
 
659
+
660
+
661
+ WKWebView *webView = self.webView;
662
+ NSURL *srcURL = [self.sourceURLs objectForKey:download];
663
+
664
+ if (webView && self.pendingPopupChildURL && srcURL &&
665
+ [srcURL isEqual:self.pendingPopupChildURL]) {
666
+
667
+ WKBackForwardList *bf = webView.backForwardList;
668
+ NSURL *current = webView.URL;
669
+ NSURL *backURL = bf.backItem.URL;
670
+
671
+ // CASI:
672
+ // A) Sei sul FIGLIO → torna indietro alla PARENT
673
+ if (current && [current isEqual:self.pendingPopupChildURL]) {
674
+ [webView goBack];
675
+ }
676
+ // B) Sei già sulla PARENT → non fare nulla
677
+ else if (current && [current isEqual:self.pendingPopupParentURL]) {
678
+ // niente
679
+ }
680
+ // C) Non sei sul child, ma l’item precedente è la PARENT → goBack
681
+ else if (backURL && [backURL isEqual:self.pendingPopupParentURL]) {
682
+ [webView goBack];
683
+ }
684
+ // D) Fallback: carica esplicitamente la PARENT
685
+ else if (self.pendingPopupParentURL) {
686
+ [webView loadRequest:[NSURLRequest requestWithURL:self.pendingPopupParentURL]];
687
+ } else {
688
+ //Niente
689
+ }
690
+
691
+ // pulizia stato
692
+ self.pendingPopupChildURL = nil;
693
+ self.pendingPopupParentURL = nil;
694
+ }
585
695
  // 5) cleanup mappe
586
696
  if (finalPath) [self.downloadPaths removeObjectForKey:download];
587
697
  [self.progressToDownload removeObjectForKey:download.progress];
@@ -609,6 +719,20 @@ completionHandler:(void (^)(NSURL * _Nullable destination))completionHandler
609
719
  QString::fromUtf8(error.localizedDescription.UTF8String)
610
720
  );
611
721
 
722
+ // 🔙 Se il download proviene dal "figlio", torna alla "pagina padre"
723
+ WKWebView *webView = self.webView;
724
+ NSURL *src = [self.sourceURLs objectForKey:download];
725
+ if (webView && self.pendingPopupChildURL && src && [src isEqual:self.pendingPopupChildURL]) {
726
+ if (webView.canGoBack) {
727
+ [webView goBack];
728
+ } else if (self.pendingPopupParentURL) {
729
+ [webView loadRequest:[NSURLRequest requestWithURL:self.pendingPopupParentURL]];
730
+ }
731
+ // ripulisci lo stato
732
+ self.pendingPopupChildURL = nil;
733
+ self.pendingPopupParentURL = nil;
734
+ }
735
+
612
736
  // cleanup mappe
613
737
  if (finalPath) [self.downloadPaths removeObjectForKey:download];
614
738
  [self.progressToDownload removeObjectForKey:download.progress];
@@ -656,6 +780,11 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
656
780
  cfg.defaultWebpagePreferences.allowsContentJavaScript = YES;
657
781
  }
658
782
 
783
+ // ✅ Consenti window.open() senza creare una nuova finestra UI
784
+ @try {
785
+ cfg.preferences.javaScriptCanOpenWindowsAutomatically = YES;
786
+ } @catch (...) {}
787
+
659
788
  // --- Fullscreen HTML5 (via KVC tollerante) ---
660
789
  @try {
661
790
  [cfg.preferences setValue:@YES forKey:@"fullScreenEnabled"];
@@ -722,8 +851,8 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
722
851
 
723
852
  d->delegate = [WKNavDelegate new];
724
853
  d->delegate.owner = this;
854
+ d->delegate.webView = d->wk;
725
855
  [d->wk setNavigationDelegate:d->delegate];
726
-
727
856
  [d->wk setUIDelegate:d->delegate];
728
857
  }
729
858
 
@@ -789,4 +918,87 @@ void WKWebViewWidget::setDownloadDirectory(const QString& dirPath) {
789
918
  if (p.isEmpty()) return;
790
919
  QDir().mkpath(p);
791
920
  d->downloadDir = p;
921
+ }
922
+
923
+ void WKWebViewWidget::renderErrorPage(const QUrl& url,
924
+ const QString& reason,
925
+ int httpStatus)
926
+ {
927
+ if (!(d && d->wk)) return;
928
+
929
+ // Template HTML minimale, con testo bilingue (IT/EN tramite FIT_T).
930
+ // Segnaposti: {url}, {reason}, {status}, {title}, {retry}, {close}
931
+ QString html = QString::fromUtf8(
932
+ R"FWB(<!doctype html><html lang="it"><meta charset="utf-8">
933
+ <meta name="viewport" content="width=device-width,initial-scale=1">
934
+ <title>{title}</title>
935
+ <style>
936
+ :root { color-scheme: light dark; }
937
+ html,body{height:100%}
938
+ body{
939
+ font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;
940
+ margin:0;background:#fff;color:#000
941
+ }
942
+ @media (prefers-color-scheme: dark){
943
+ body{background:#000;color:#fff}
944
+ }
945
+ .card{
946
+ max-width:720px;margin:8vh auto;padding:28px;border-radius:16px;
947
+ background:#fff;color:#000;box-shadow:0 6px 24px rgba(0,0,0,.18)
948
+ }
949
+ @media (prefers-color-scheme: dark){
950
+ .card{background:#111;color:#eee;box-shadow:0 6px 24px rgba(255,255,255,.05)}
951
+ }
952
+ h1{font-size:22px;margin:0 0 6px}
953
+ p{line-height:1.5}
954
+ code{
955
+ background:#eee;color:#000;padding:2px 6px;border-radius:6px
956
+ }
957
+ @media (prefers-color-scheme: dark){
958
+ code{background:#222;color:#fff}
959
+ }
960
+ .actions{margin-top:18px;display:flex;gap:10px;flex-wrap:wrap}
961
+ button,a{
962
+ padding:10px 14px;border-radius:10px;border:1px solid currentColor;
963
+ cursor:pointer;text-decoration:none;background:transparent;color:inherit
964
+ }
965
+ button.primary{
966
+ background:#000;color:#fff;border-color:#000
967
+ }
968
+ @media (prefers-color-scheme: dark){
969
+ button.primary{background:#fff;color:#000;border-color:#fff}
970
+ }
971
+ small{opacity:.7}
972
+ </style>
973
+ <div class="card">
974
+ <h1>{title}</h1>
975
+ <p>URL: <code>{url}</code></p>
976
+ <p>{reason} <small>{status}</small></p>
977
+ <div class="actions">
978
+ <button class="primary" onclick="location.reload()">{retry}</button>
979
+ <a class="ghost" href="about:blank">{close}</a>
980
+ </div>
981
+ </div>)FWB"
982
+ );
983
+
984
+
985
+
986
+ // Localizza con FIT_T
987
+ QString title = QString::fromUtf8([FIT_T(@"error.title") UTF8String]);
988
+ QString reasonText = reason.isEmpty()
989
+ ? QString::fromUtf8([FIT_T(@"error.reason") UTF8String])
990
+ : reason;
991
+ QString retry = QString::fromUtf8([FIT_T(@"error.retry") UTF8String]);
992
+ QString close = QString::fromUtf8([FIT_T(@"error.close") UTF8String]);
993
+
994
+ html.replace("{title}", title);
995
+ html.replace("{url}", url.toString());
996
+ html.replace("{reason}", reasonText);
997
+ html.replace("{status}", httpStatus > 0 ? QString("HTTP %1").arg(httpStatus) : QString());
998
+ html.replace("{retry}", retry);
999
+ html.replace("{close}", close);
1000
+
1001
+ // Carica l'HTML direttamente nella webview
1002
+ [d->wk loadHTMLString:[NSString stringWithUTF8String:html.toUtf8().constData()]
1003
+ baseURL:[NSURL URLWithString:@"about:blank"]];
792
1004
  }