how objective-c cocoa osx webkit

objective-c - how - webview xcode 9



Cocoa/WebKit, con enlaces de JavaScript "window.open()" abriéndose en una instancia de Safari (7)

Estoy construyendo una aplicación Cocoa realmente básica usando WebKit, para mostrar una aplicación Flash / Silverlight dentro de ella. Muy básico, sin intenciones de que sea un navegador en sí mismo.

Hasta ahora he podido hacer que abra enlaces html básicos ( <a href="..." /> ) en una nueva instancia de Safari usando

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

Ahora mi dificultad es abrir un enlace en una nueva instancia de Safari cuando se usa window.open() en JavaScript. Yo "pienso" (y por esto, he estado pirateando el código y no estoy seguro de si realmente lo hice o no) policyDelegate este tipo de trabajo configurando la policyDelegate de WebView e implementando su

-webView:decidePolicyForNavigationAction:request:frame:decisionListener:

método delegado. Sin embargo, esto condujo a un comportamiento errático.

Entonces, la pregunta simple, ¿qué debo hacer para que cuando se llame a window.open() , el enlace se abra en una nueva instancia de Safari?

Gracias

Gran punto, normalmente soy desarrollador de .NET y solo he estado trabajando con Cocoa / WebKit por unos días.


No mencionas qué tipo de comportamiento errático estás viendo. Una posibilidad rápida es que al implementar el método de delegado se olvidó de decirle a la vista web que está ignorando el clic llamando al método de ignorar del WebPolicyDecisionListener que se pasó a su delegado, lo que puede haber puesto las cosas en un estado extraño.

Si ese no es el problema, ¿cuánto control tiene sobre el contenido que está mostrando? El delegado de políticas le brinda mecanismos fáciles para filtrar todas las cargas de recursos (como ha descubierto), y se abre toda la nueva ventana a través de webView: decidePolicyForNewWindowAction: request: newFrameName: decisionListener:. Todas las llamadas a window.open deberían canalizarse a través de eso, al igual que cualquier otra cosa que active una nueva ventana.

Si hay otras ventanas abiertas que desea mantener dentro de su aplicación, deberá hacer un poco más de trabajo. Uno de los argumentos pasados ​​al delegado es un diccionario que contiene información sobre el evento. Si se incluye ese diccionario, WebActionElementKey tendrá un diccionario que contiene una serie de detalles , incluido el contenido original del enlace. Si quiere hurgar, puede tomar el elemento DOM real y verificar el texto del href para ver si comienza con window.open. Eso es un poco pesado, pero si quieres control de grano fino te lo dará.


Bueno, lo estoy manejando creando un webView falso, configurando su delegado frameLoad en una clase personalizada que maneja

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

y abre una nueva ventana allí.

código:

- (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]; }

y 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; }


Parece que hay un error con webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener: en que la solicitud es siempre nil , pero hay una solución robusta que funciona tanto con los enlaces normales target="_blank" como con javascript.

Básicamente utilizo otra WebView efímera para manejar la carga de la nueva página. Similar a Yoni Shalom pero con un poco más de azúcar sintáctico.

Para utilizarlo primero configure un objeto delegado para su WebView, en este caso me estoy configurando como el delegado:

webView.UIDelegate = self;

Luego simplemente implemente el webView:createWebViewWithRequest: delegate y use mi API basada en bloques para hacer algo cuando se cargue una nueva página, en este caso estoy abriendo la página en un navegador externo:

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

Eso es practicamente todo. Aquí está el código para mi clase. Encabezamiento:

// 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

Implementación:

// 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

Licenciado como Apache 2.


Al leer todas las publicaciones, he encontrado mi solución simple, todos los funcs están en la misma clase, aquí está, abre un enlace con el navegador.

- (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; }


Explicación:

Windows creado desde JavaScript a través de window.open vaya a createWebViewWithRequest. Todas las llamadas de window.open dan como resultado un createWebViewWithRequest: con una solicitud nula, luego un cambio de ubicación en ese WebView.

Para obtener más información, consulte esta publicación anterior en la lista de correo de WebKit.


Una alternativa para devolver un nuevo WebView y esperar a que se llame su método loadRequest: terminé sobrescribiendo la función window.open en el JSContext de WebView:

Primero, configuré mi controlador como WebFrameLoadDelegate de WebView:

myWebView.frameLoadDelegate = self;

Luego, en el método de delegado, sobrescribí la función window.open y, en su lugar, puedo procesar la URL allí.

- (void)webView:(WebView *)webView didCreateJavaScriptContext:(JSContext *)context forFrame:(WebFrame *)frame{ context[@"window"][@"open"] = ^(id url){ NSLog(@"url to load: %@", url); }; }

Esto me permitió manejar la solicitud como lo necesitaba sin la incómoda necesidad de crear WebViews adicionales.


Hice el progreso anoche y anoté parte de mi problema.

Ya estoy usando webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener: y he conseguido que funcione con etiquetas de anclaje; sin embargo, parece que nunca se llama al método cuando se invoca JavaScript.

Sin embargo, cuando window.open() se llama webView:createWebViewWithRequest:request , he intentado forzar que la ventana se abra en Safari aquí, sin embargo la solicitud siempre es nula. Entonces nunca puedo leer la URL.

He hecho algunas búsquedas alrededor, y esto parece ser un " error " conocido, sin embargo, no he podido encontrar una manera de evitarlo .

Por lo que entiendo createWebViewWithRequest le da la posibilidad de crear la nueva vista web, la url solicitada se envía a la nueva webView para ser cargada. Esta es la mejor explicación que he podido encontrar hasta ahora.

Entonces, aunque muchas personas han señalado este problema, aún no he visto ninguna solución que se ajuste a mis necesidades. Trataré de profundizar un poco más en decidePolicyForNewWindowAction nuevamente.

¡Gracias!