2008-11-06 14 views
7

Je construis une application Cocoa vraiment basique en utilisant WebKit, pour y afficher une application Flash/Silverlight. Très basique, aucune intention pour que ce soit un navigateur lui-même.Cocoa/WebKit, ayant "windows.open()" liens JavaScript ouverture dans une instance de Safari

Jusqu'à présent, j'ai pu l'obtenir pour ouvrir les liens html de base (<a href="..." />) dans une nouvelle instance de Safari en utilisant

[[NSWorkspace sharedWorkspace] openURL:[request URL]]; 

Maintenant, ma difficulté est l'ouverture d'un lien dans une nouvelle instance de Safari lorsque window.open() est utilisé en JavaScript. Je « pense » (et par cela, je suis bidouiller le code et je suis pas sûr si je réellement fait ou non) Je suis arrivé ce genre de travail en fixant policyDelegate de la WebView et mettre en œuvre son

-webView:decidePolicyForNavigationAction:request:frame:decisionListener: 

méthode déléguée . Cependant, cela a conduit à un comportement erratique.

Donc la simple question, que dois-je faire pour que lorsque window.open() est appelée, le lien est ouvert dans une nouvelle instance de Safari.

Merci

Point Big, je suis normalement un développeur .NET, et ont seulement travaillé avec Cocoa/WebKit pendant quelques jours.

+0

J'ai exactement le même problème. Cela semble être un bug que 'webView: decisionPolicyForNewWindowAction: request: newFrameName: decisionListener' n'est pas appelé. –

Répondre

2

Vous ne mentionnez pas le type de comportement erratique que vous observez. Une possibilité rapide, est que lors de l'implémentation de la méthode déléguée vous avez oublié de dire à la vue Web que vous ignorez le clic en appelant la méthode ignorée du WebPolicyDecisionListener qui a été transmise à votre délégué, ce qui peut avoir mis les choses dans un état étrange.

Si ce n'est pas le problème, alors quel contrôle avez-vous sur le contenu que vous affichez? Le délégué de stratégie vous fournit des mécanismes simples pour filtrer tous les chargements de ressources (comme vous l'avez découvert) et toute nouvelle fenêtre s'ouvre via webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:. Tous les appels window.open doivent passer par là, tout comme toute autre chose qui déclenche une nouvelle fenêtre.

S'il y a d'autres ouvertures de fenêtre que vous voulez garder dans votre application, vous allez faire un peu plus de travail. Un des arguments transmis au délégué est un dictionnaire contenant information à propos de l'événement. Insie ce dictionnaire le WebActionElementKey aura un dictionnaire contenant un nombre de details, y compris le contenu dom d'origine du lien. Si vous voulez fouiller là-dedans, vous pouvez récupérer l'élément DOM réel, et vérifier le texte de l'href pour voir s'il commence par window.open. C'est un peu lourd, mais si vous voulez un contrôle précis, il vous le donnera.

+0

Merci pour votre avis Louis, j'ai vérifié dans les deux derniers liens, et je les ai examinés, malheureusement je ne pense pas que cela fonctionnera parce que window.open n'est pas en ligne dans une balise d'ancrage. Il est invoqué via une méthode Javascript qui est appelée depuis Silverlight. – FireWire

9

J'ai fait des progrès la nuit dernière et épinglé une partie de mon problème.

J'utilise déjà webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener: et je l'ai fait fonctionner avec des balises d'ancrage, mais la méthode ne semble jamais être appelée lorsque JavaScript est invoqué.

Cependant quand window.open() est appelé webView:createWebViewWithRequest:request est appelée, j'ai essayé de forcer la fenêtre à s'ouvrir dans Safari ici, cependant la demande est toujours nulle. Donc, je ne peux jamais lire l'URL.

J'ai fait quelques recherches autour, et cela semble être un "misfeature" mais je n'ai pas été en mesure de trouver un moyen de contourner ce problème.D'après ce que je comprends createWebViewWithRequest vous donne la possibilité de créer le nouveau webview, l'URL demandée est alors envoyée à la nouvelle webView à charger. This is the best explanation I have been able to find so far.

Alors que beaucoup de gens ont souligné ce problème, je n'ai pas encore trouvé de solution qui correspond à mes besoins. Je vais essayer de plonger un peu plus loin dans le decidePolicyForNewWindowAction encore.

Merci!

+0

Ceci est très utile. Bon travail, mec! :) – Kimpoy

6

Eh bien, je manipuler en créant un webView factice, il est délégué la mise frameLoad à une classe personnalisée qui gère

- (void)webView:decidePolicyForNavigationAction:actionInformation :request:frame:decisionListener: 

et ouvre une nouvelle fenêtre il.

code:

- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request { 
    //this is a hack because request URL is null here due to a bug in webkit   
    return [newWindowHandler webView]; 
} 

et NewWindowHandler:

@implementation NewWindowHandler 

-(NewWindowHandler*)initWithWebView:(WebView*)newWebView { 
    webView = newWebView; 

    [webView setUIDelegate:self]; 
    [webView setPolicyDelegate:self]; 
    [webView setResourceLoadDelegate:self]; 

    return self; 
} 

- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener { 
    [[NSWorkspace sharedWorkspace] openURL:[actionInformation objectForKey:WebActionOriginalURLKey]]; 
} 

-(WebView*)webView { 
    return webView; 
} 
4

Il semble y avoir un bug avec webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener: en ce que la demande est toujours nil, mais il y a une solution robuste qui fonctionne à la fois normale target="_blank" liens ainsi que ceux de javascript.

Fondamentalement, j'utilise un autre WebView éphémère pour gérer le chargement de la nouvelle page. Similaire à Yoni Shalom mais avec un peu plus de sucre syntaxique.

Pour l'utiliser d'abord définir un objet délégué pour votre WebView, dans ce cas, je me mettre en tant que délégué:

webView.UIDelegate = self; 

Ensuite, mettre en œuvre juste la méthode déléguée webView:createWebViewWithRequest: et utiliser mon bloc API basée à faire quelque chose quand une nouvelle page est chargée, dans ce cas, j'ouvre la page dans un navigateur externe:

-(WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request { 
    return [GBWebViewExternalLinkHandler riggedWebViewWithLoadHandler:^(NSURL *url) { 
     [[NSWorkspace sharedWorkspace] openURL:url]; 
    }]; 
} 

C'est à peu près tout. Voici le code pour ma classe. En-tête:

// GBWebViewExternalLinkHandler.h 
// TabApp2 
// 
// Created by Luka Mirosevic on 13/03/2013. 
// Copyright (c) 2013 Goonbee. All rights reserved. 
// 

#import <Foundation/Foundation.h> 

@class WebView; 

typedef void(^NewWindowCallback)(NSURL *url); 

@interface GBWebViewExternalLinkHandler : NSObject 

+(WebView *)riggedWebViewWithLoadHandler:(NewWindowCallback)handler; 

@end 

Implémentation:

// GBWebViewExternalLinkHandler.m 
// TabApp2 
// 
// Created by Luka Mirosevic on 13/03/2013. 
// Copyright (c) 2013 Goonbee. All rights reserved. 
// 

#import "GBWebViewExternalLinkHandler.h" 

#import <WebKit/WebKit.h> 

@interface GBWebViewExternalLinkHandler() 

@property (strong, nonatomic) WebView       *attachedWebView; 
@property (strong, nonatomic) GBWebViewExternalLinkHandler  *retainedSelf; 
@property (copy, nonatomic) NewWindowCallback     handler; 

@end 

@implementation GBWebViewExternalLinkHandler 

-(id)init { 
    if (self = [super init]) { 
     //create a new webview with self as the policyDelegate, and keep a ref to it 
     self.attachedWebView = [WebView new]; 
     self.attachedWebView.policyDelegate = self; 
    } 

    return self; 
} 

-(void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener { 
    //execute handler 
    if (self.handler) { 
     self.handler(actionInformation[WebActionOriginalURLKey]); 
    } 

    //our job is done so safe to unretain yourself 
    self.retainedSelf = nil; 
} 

+(WebView *)riggedWebViewWithLoadHandler:(NewWindowCallback)handler { 
    //create a new handler 
    GBWebViewExternalLinkHandler *newWindowHandler = [GBWebViewExternalLinkHandler new]; 

    //store the block 
    newWindowHandler.handler = handler; 

    //retain yourself so that we persist until the webView:decidePolicyForNavigationAction:request:frame:decisionListener: method has been called 
    newWindowHandler.retainedSelf = newWindowHandler; 

    //return the attached webview 
    return newWindowHandler.attachedWebView; 
} 

@end 

sous licence Apache 2.

+0

Vieux mais génial, ce bug semble toujours être présent et cette réponse était exactement ce dont j'avais besoin. –

+0

Encore une bonne solution. –

2

En lisant tous les messages, je suis venu avec ma solution simple, tous les funcs sont dans la même classe, voilà est, ouvre un lien avec le navigateur.

- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request { 

    return [self externalWebView:sender]; 
} 




- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener 
{ 
    [[NSWorkspace sharedWorkspace] openURL:[actionInformation objectForKey:WebActionOriginalURLKey]]; 
} 

-(WebView*)externalWebView:(WebView*)newWebView 
{ 
    WebView *webView = newWebView; 

    [webView setUIDelegate:self]; 
    [webView setPolicyDelegate:self]; 
    [webView setResourceLoadDelegate:self]; 
    return webView; 
} 
0

Explication:

fenêtres créées à partir de JavaScript via window.open passer par createWebViewWithRequest. Tous les appels window.open aboutissent à un createWebViewWithRequest: avec une demande nulle, puis un changement d'emplacement sur ce WebView.

Pour plus d'informations, see this old post sur la liste de diffusion WebKit.

0

Une alternative à retourner une nouvelle WebView et d'attendre sa méthode loadRequest: à appeler, j'ai fini par écraser la fonction window.open dans JSContext du WebView:

D'abord, je mets mon contrôleur à la WebFrameLoadDelegate du WebView :

myWebView.frameLoadDelegate = self; 

Ensuite, dans la méthode déléguée, je réenregistrés la fonction window.open, et je peux traiter l'URL là à la place. Cela m'a permis de gérer la requête, mais je devais sans le besoin maladroit de créer des WebViews supplémentaires.

Questions connexes