macos Cocoa/WebKit,在 Safari 实例中打开“window.open()”JavaScript 链接

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/270458/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 05:57:47  来源:igfitidea点击:

Cocoa/WebKit, having "window.open()" JavaScript links opening in an instance of Safari

objective-ccocoamacoswebkit

提问by FireWire

I am building a really basic Cocoa application using WebKit, to display a Flash/Silverlight application within it. Very basic, no intentions for it to be a browser itself.

我正在使用 WebKit 构建一个非常基本的 Cocoa 应用程序,以在其中显示 Flash/Silverlight 应用程序。非常基本,无意让它成为浏览器本身。

So far I have been able to get it to open basic html links (<a href="..." />) in a new instance of Safari using

到目前为止,我已经能够使用它<a href="..." />在 Safari 的新实例中打开基本的 html 链接 ( )

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

Now my difficulty is opening a link in a new instance of Safari when window.open()is used in JavaScript. I "think" (and by this, I have been hacking away at the code and am unsure if i actually did or not) I got this kind of working by setting the WebView's policyDelegateand implementing its

现在我的困难是window.open()在 JavaScript 中使用时在Safari 的新实例中打开链接。我“认为”(因此,我一直在破解代码,不确定我是否真的这样做了)我通过设置 WebViewpolicyDelegate并实现它来实现这种工作

-webView:decidePolicyForNavigationAction:request:frame:decisionListener:

delegate method. However this led to some erratic behavior.

委托方法。然而,这导致了一些不稳定的行为。

So the simple question, what do I need to do so that when window.open()is called, the link is opened in a new instance of Safari.

所以这个简单的问题,我需要做什么才能在window.open()调用时链接在 Safari 的新实例中打开。

Thanks

谢谢

Big point, I am normally a .NET developer, and have only been working with Cocoa/WebKit for a few days.

重要的是,我通常是 .NET 开发人员,并且只使用 Cocoa/WebKit 几天。

回答by FireWire

I made from progress last night and pinned down part of my problem.

我昨晚取得了进步,并解决了我的部分问题。

I am already using webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:and I have gotten it to work with anchor tags, however the method never seems to get called when JavaScript is invoked.

我已经在使用webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:并且我已经让它与锚标记一起工作,但是在调用 JavaScript 时似乎永远不会调用该方法。

However when window.open()is called webView:createWebViewWithRequest:requestis called, I have tried to force the window to open in Safari here, however request is always null. So I can never read the URL out.

但是,当window.open()调用webView:createWebViewWithRequest:request被调用时,我试图在这里强制在 Safari 中打开窗口,但是请求始终为空。所以我永远无法读取 URL。

I have done some searching around, and this seems to be a known "misfeature" however I have not been able to find a way to work around it.

我已经进行了一些搜索,这似乎是一个已知的“错误特征”,但是我一直无法找到解决它的方法。

From what I understand createWebViewWithRequestgives you the ability to create the new webview, the the requested url is then sent to the new webView to be loaded. This is the best explanation I have been able to find so far.

据我了解createWebViewWithRequest,您可以创建新的 webview,然后将请求的 url 发送到要加载的新 webview。 这是迄今为止我能找到的最好的解释。

So while many people have pointed out this problem, I have yet to see any solution which fits my needs. I will try to delve a little deeper into the decidePolicyForNewWindowActionagain.

因此,虽然很多人指出了这个问题,但我还没有看到任何适合我需求的解决方案。我将尝试更深入地研究decidePolicyForNewWindowAction

Thanks!

谢谢!

回答by Yoni Shalom

Well, I'm handling it by creating a dummy webView, setting it's frameLoad delegate to a custom class that handles

好吧,我通过创建一个虚拟的 webView 来处理它,将它的 frameLoad 委托设置为一个自定义类来处理

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

and opens a new window there.

并在那里打开一个新窗口。

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

and NewWindowHandler :

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

回答by lms

There seems to be a bug with webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:in that the request is always nil, but there is a robust solution that works with both normal target="_blank"links as well as javascript ones.

似乎有一个错误webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:,因为请求总是nil,但是有一个强大的解决方案,可以同时使用普通target="_blank"链接和 javascript 链接。

Basically I use another ephemeral WebView to handle the new page load in. Similar to Yoni Shalom but with a little more syntactic sugar.

基本上我使用另一个短暂的 WebView 来处理新页面的加载。类似于 Yoni Shalom,但有更多的语法糖。

To use it first set a delegate object for your WebView, in this case I'm setting myself as the delegate:

要使用它,首先为您的 WebView 设置一个委托对象,在这种情况下,我将自己设置为委托:

webView.UIDelegate = self;

Then just implement the webView:createWebViewWithRequest:delegate method and use my block based API to do something when a new page is loaded, in this case I'm opening the page in an external browser:

然后只需实现webView:createWebViewWithRequest:委托方法并使用我的基于块的 API 在加载新页面时执行某些操作,在这种情况下,我将在外部浏览器中打开页面:

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

That's pretty much it. Here's the code for my class. Header:

差不多就是这样。这是我班级的代码。标题:

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

Implemetation:

实现:

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

Licensed as Apache 2.

获得 Apache 2 许可。

回答by KeranMarinov

By reading all posts, i have come up with my simple solution, all funcs are in same class,here it is, opens a link with browser.

通过阅读所有帖子,我想出了我的简单解决方案,所有函数都在同一个类中,在这里,用浏览器打开一个链接。

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

回答by Louis Gerbarg

You don't mention what kind of erratic behaviour you are seeing. A quick possibility, is that when implementing the delegate method you forgot to tell the webview you are ignoring the click by calling the ignore method of the WebPolicyDecisionListenerthat was passed to your delegate, which may have put things into a weird state.

你没有提到你所看到的那种古怪的行为。一个快速的可能性是,在实现委托方法时,您忘记通过调用传递给您的委托的WebPolicyDecisionListener的 ignore 方法告诉 webview 您忽略了单击,这可能会使事情进入一个奇怪的状态。

If that is not the issue, then how much control do you have over the content you are displaying? The policy delegate gives you easy mechanisms to filter all resource loads (as you have discovered), and all new window opens via webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:. All window.open calls should funnel through that, as will anything else that triggers a new window.

如果这不是问题,那么您对显示的内容有多少控制权?策略委托为您提供了简单的机制来过滤所有资源负载(如您所见),并且所有新窗口都通过webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:打开。所有 window.open 调用都应该通过它,以及触发新窗口的任何其他调用。

If there are other window opens you want to keep inside your app, you will to do a little more work. One of the arguments passed into the delegate is a dictionary containing informationabout the event. Insie that dictionary the WebActionElementKey will have a dictionary containing a number of details, including the original dom content of the link. If you want to poke around in there you can grab the actual DOM element, and check the text of the href to see if it starts with window.open. That is a bit heavy weight, but if you want fine grained control it will give it to you.

如果还有其他打开的窗口您想保留在您的应用程序中,您需要做更多的工作。传递给委托的参数之一是包含有关事件信息的字典。Insie 那个字典 WebActionElementKey 将有一个包含许多详细信息的字典,包括链接的原始 dom 内容。如果您想在那里闲逛,您可以获取实际的 DOM 元素,并检查 href 的文本以查看它是否以 window.open 开头。这有点重,但如果你想要细粒度的控制,它会给你。

回答by Louis Gerbarg

Explanation:

解释:

Windows created from JavaScript via window.open go through createWebViewWithRequest. All window.open calls result in a createWebViewWithRequest: with a null request, then later a location change on that WebView.

通过 window.open 从 JavaScript 创建的 Windows 通过 createWebViewWithRequest。所有 window.open 调用都会导致 createWebViewWithRequest: 请求为空,然后在该 WebView 上发生位置更改。

For further information, see this old poston the WebKit mailing list.

有关更多信息,请参阅WebKit 邮件列表上的这篇旧帖子

回答by adam.wulf

An alternative to returning a new WebView and waiting for its loadRequest:method to be called, I ended up overwriting the window.openfunction in the WebView's JSContext:

返回一个新的 WebView 并等待其loadRequest:方法被调用的替代方法,我最终覆盖window.open了 WebView 的 JSContext 中的函数:

First, I set my controller to be the WebFrameLoadDelegate of the WebView:

首先,我将控制器设置为 WebView 的 WebFrameLoadDelegate:

myWebView.frameLoadDelegate = self;

Then, in the delegate method, I overwrote the window.openfunction, and I can process the URL there instead.

然后,在委托方法中,我覆盖了该window.open函数,并且可以在那里处理 URL。

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

This let me handle the request however I needed to without the awkward need to create additional WebViews.

这让我可以处理请求,但是我不需要创建额外的 WebViews。