带有 WebView 的 Mac 屏幕保护程序崩溃

发布于 2024-08-20 18:54:54 字数 4180 浏览 6 评论 0原文

大家好,

我有一个用 obj-c 和 cocoa 制作的屏幕保护程序。除以下情况外,在 OsX 10.6.2 下一切正常。 在我的屏幕保护程序中,我有一个正在运行某些应用程序的 WebView。当我尝试通过 javascript 调用 Objective-C 应用程序(屏幕保护程序)时,出现错误,屏幕保护程序和系统首选项面板崩溃。

系统偏好设置[86666] *** 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序

原因:'-[NSCFArray排出]:无法识别的选择器发送到实例0x20049b1e0'

*** 第一次抛出时调用堆栈:(
0 CoreFoundation 0x00007fff8123a444 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x00007fff81f130f3 objc_exception_throw + 45
2 CoreFoundation 0x00007fff812931c0 +[NSObject(NSObject) doesNotRecognizeSelector:] + 0
3 CoreFoundation 0x00007fff8120d08f 转发 + 751
4 核心基础 0x00007fff812091d8 _CF_forwarding_prep_0 + 232 5 WebCore 0x00007fff847adee0 _ZN3JSC8Bindings12ObjcInstance10virtualEndEv + 48
6 WebCore 0x00007fff8470d71d _ZN3JSC16RuntimeObjectImp18getOwnPropertySlotEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE + 397
7 JavaScriptCore 0x00007fff80862b66 NK3JSC7JSValue3getEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE + 486

我知道这看起来像是一些内存泄漏,但正如您将在代码中看到的那样,我实际上几乎没有分配任何对象。

仅当我使用屏幕保护程序系统首选项中的“测试”按钮启动屏幕保护程序时,才会发生这种情况。 当我通过终端启动屏幕保护程序或者如果它自动启动时,相同的操作(从 javascript 调用 obj-c)工作正常。

也许有人知道错误可能来自哪里。这是实现中的一些代码:

@implementation ScreensaverView

- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {

    self = [super initWithFrame:frame isPreview:isPreview];

    if (self) {

        [self setAnimationTimeInterval:-1];
        [self setAutoresizesSubviews:YES];

        // ::::::::::::::::::::::: Init stuff ::::::::::::::::::    

        // init 
        quitFlag = false;
        previewMode = isPreview;

        // find out the path the screensaver bundle
        pMainBundle = [NSBundle bundleForClass:[self class]];
        pBundlePath = [pMainBundle bundlePath];

        // read Info.plist
        infoDict = [pMainBundle infoDictionary];
    }

    return self;
}

- (void)startAnimation
{   
    [super startAnimation];

    // combine: bundle path + filename for screensaver file 
    NSString *pathToScreensaver = [NSString stringWithString:pBundlePath];
    NSString *valueScreensaverFile;

    if(!previewMode)
    {
        valueScreensaverFile = [infoDict objectForKey:@"ScreensaverFile"];
    }
    else 
    {
        valueScreensaverFile = [infoDict objectForKey:@"PreviewFile"];
    }

    // add filename to bundle path
    pathToScreensaver = [pathToScreensaver stringByAppendingString:valueScreensaverFile];

    // complete NSURL to the screensaver file
    NSURL *screensaverUrl = [NSURL fileURLWithPath: pathToScreensaver];

    webView = [WebView alloc];
    [webView initWithFrame:[self frame]];
    [webView setDrawsBackground:NO];

    // delegation policy for interactive mode
    [webView setPolicyDelegate: self];
    [webView setUIDelegate:self];

    // load screensaver
    [[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:screensaverUrl]];

    scriptObject = [webView windowScriptObject];
    [scriptObject setValue:self forKey:@"screensaver"];

    [self addSubview:webView];
}

- (void)stopAnimation
{   
    [[webView mainFrame] stopLoading];
    [webView removeFromSuperview];
    [webView release];
    [super stopAnimation];
}

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector 
{       
    if (selector == @selector(quitScreenSaver)) {
        return NO;
    }

    if(selector == @selector(gotoUrl:) ){
        return NO;
    }

    return YES;
}

+(NSString *)webScriptNameForSelector:(SEL)selector
{   
    if(selector == @selector(quitScreenSaver))
    {
        return @"quitNoOpen";
    }

    if(selector == @selector(gotoUrl:))
    {
        return @"openAndQuit";
    }

    return nil;
}

- (void) quitScreenSaver
{
    quitFlag = true;
    [super stopAnimation];
}

- (void) gotoUrl:(NSString *) destinationURL 
{   
    if(destinationURL == NULL)
    {
        return;
    }

    NSString * path    = destinationURL;
    NSURL    * fileURL = [NSURL URLWithString:path];
    [[ NSWorkspace sharedWorkspace ] openURL:fileURL];
    [self quitScreenSaver];
}

@end

我希望这些代码足以让您看到一些问题/解决方案。 我真的很感激任何答案。

Hy everybody,

I have a screensaver made with obj-c and cocoa. Everything works fine under OsX 10.6.2 except the following.
Within my screensaver I have a WebView with some application running. When I try to call my objective-c app (the screensaver) via javascript, I get an error and the screensaver and the system preferences panel crash.

System Preferences[86666]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException'

reason: '-[NSCFArray drain]: unrecognized selector sent to instance 0x20049b1e0'

*** Call stack at first throw:(
0 CoreFoundation 0x00007fff8123a444 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x00007fff81f130f3 objc_exception_throw + 45
2 CoreFoundation 0x00007fff812931c0 +[NSObject(NSObject) doesNotRecognizeSelector:] + 0
3 CoreFoundation 0x00007fff8120d08f forwarding + 751
4 CoreFoundation 0x00007fff812091d8 _CF_forwarding_prep_0 + 232
5 WebCore 0x00007fff847adee0 _ZN3JSC8Bindings12ObjcInstance10virtualEndEv + 48
6 WebCore 0x00007fff8470d71d _ZN3JSC16RuntimeObjectImp18getOwnPropertySlotEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE + 397
7 JavaScriptCore 0x00007fff80862b66 NK3JSC7JSValue3getEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE + 486
)

I know this looks like some memory leak, but as you will see in the code, I really have nearly no objects allocated.

This only happens, when I start the screensaver with the "Test" button from the screensaver system prefs.
When I start the screensaver via terminal or if it starts automatically, the same action (calling obj-c from javascript) works fine.

Maybe someone has any idea, where the error could come from. Here is some code from the implementation:

@implementation ScreensaverView

- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {

    self = [super initWithFrame:frame isPreview:isPreview];

    if (self) {

        [self setAnimationTimeInterval:-1];
        [self setAutoresizesSubviews:YES];

        // ::::::::::::::::::::::: Init stuff ::::::::::::::::::    

        // init 
        quitFlag = false;
        previewMode = isPreview;

        // find out the path the screensaver bundle
        pMainBundle = [NSBundle bundleForClass:[self class]];
        pBundlePath = [pMainBundle bundlePath];

        // read Info.plist
        infoDict = [pMainBundle infoDictionary];
    }

    return self;
}

- (void)startAnimation
{   
    [super startAnimation];

    // combine: bundle path + filename for screensaver file 
    NSString *pathToScreensaver = [NSString stringWithString:pBundlePath];
    NSString *valueScreensaverFile;

    if(!previewMode)
    {
        valueScreensaverFile = [infoDict objectForKey:@"ScreensaverFile"];
    }
    else 
    {
        valueScreensaverFile = [infoDict objectForKey:@"PreviewFile"];
    }

    // add filename to bundle path
    pathToScreensaver = [pathToScreensaver stringByAppendingString:valueScreensaverFile];

    // complete NSURL to the screensaver file
    NSURL *screensaverUrl = [NSURL fileURLWithPath: pathToScreensaver];

    webView = [WebView alloc];
    [webView initWithFrame:[self frame]];
    [webView setDrawsBackground:NO];

    // delegation policy for interactive mode
    [webView setPolicyDelegate: self];
    [webView setUIDelegate:self];

    // load screensaver
    [[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:screensaverUrl]];

    scriptObject = [webView windowScriptObject];
    [scriptObject setValue:self forKey:@"screensaver"];

    [self addSubview:webView];
}

- (void)stopAnimation
{   
    [[webView mainFrame] stopLoading];
    [webView removeFromSuperview];
    [webView release];
    [super stopAnimation];
}

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector 
{       
    if (selector == @selector(quitScreenSaver)) {
        return NO;
    }

    if(selector == @selector(gotoUrl:) ){
        return NO;
    }

    return YES;
}

+(NSString *)webScriptNameForSelector:(SEL)selector
{   
    if(selector == @selector(quitScreenSaver))
    {
        return @"quitNoOpen";
    }

    if(selector == @selector(gotoUrl:))
    {
        return @"openAndQuit";
    }

    return nil;
}

- (void) quitScreenSaver
{
    quitFlag = true;
    [super stopAnimation];
}

- (void) gotoUrl:(NSString *) destinationURL 
{   
    if(destinationURL == NULL)
    {
        return;
    }

    NSString * path    = destinationURL;
    NSURL    * fileURL = [NSURL URLWithString:path];
    [[ NSWorkspace sharedWorkspace ] openURL:fileURL];
    [self quitScreenSaver];
}

@end

I hope that's enough code for you to see some problems / solutions.
I would really appreciaty any answers.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

天暗了我发光 2024-08-27 18:54:54

不知何故,一个 NSCFArray (NSMutableArray) 被发送了一条“drain”消息,该消息是针对 NSAutoreleasePool 的。

通过实现 NSMutableArray 的排出方法,您也许能够获得有关数组的更多信息,这样您就可以捕获现在识别的选择器并打印出数组对象的内容。尝试将其添加到代码中的某个位置:

@interface NSMutableArray (drain)

- (void) drain;

@end

@implementation NSMutableArray (drain)

- (void) drain
{
   NSLog(@"drain message received by object: %@", self);
}

@end

如果您没有看到控制台中显示任何消息,请尝试将上述代码中的“NSMutableArray”更改为“NSObject”。

Somehow an NSCFArray (NSMutableArray) is being sent a "drain" message that's meant for an NSAutoreleasePool.

You might be able to get a bit more info on what the array is by implementing the drain method for NSMutableArray, so you can trap the now-recognized selector and print out the contents of the array object. Try adding this somewhere in your code:

@interface NSMutableArray (drain)

- (void) drain;

@end

@implementation NSMutableArray (drain)

- (void) drain
{
   NSLog(@"drain message received by object: %@", self);
}

@end

If you don't see any messages show up in the Console, try changing the "NSMutableArray" in the above code to "NSObject".

终止放荡 2024-08-27 18:54:54

需要注意的一件事是,当您通过系统首选项中的“测试”按钮启动屏幕保护程序时,实际上有两个屏幕保护程序视图实例在不同线程的同一进程的地址空间中运行。一个(isPreview==YES)是 SysPrefs 窗口中的小预览(即使全屏版本启动,它也会继续运行),另一个是全屏版本。它们都在 SysPrefs.app 进程中运行。因此,您必须小心检查所有通知/等。查看它们是否来自您期望的视图实例。

快速浏览一下您发布的代码,我没有发现任何明显的问题,但它可能在其他地方。您在任何地方使用通知吗?

我在 github 上放置了一个类似的 webview-in-a-screensaver 项目 http://github.com/kelan/WikiWalker ,我最初遇到了一些类似的问题(尽管我没有使用任何 javascript 的东西)。这不是完美的代码,但可能会有所帮助。我还使用了一些技巧将通知转发到 .请参阅 WWScreenSaverView.{h,m} 的“线程通知支持”部分。

One thing to be aware of is that when you start the screensaver via the "Test" button in System Prefs, you actually have 2 instances of your screensaver view running in the same process' address space on different threads. One (with isPreview==YES) is the little preview in the SysPrefs window (which continues running even when the full-screen version is started), and the other one is the full-screen version. They are both running in the SysPrefs.app process. So, you have to be careful to check all Notifications/etc. to see if they are coming from the view instance you expect.

I don't see any obvious problems from a quick glance at the code you posted, but it may be somewhere else. Do you use Notifications anywhere?

I put a similar webview-in-a-screensaver project on github at http://github.com/kelan/WikiWalker, where I initially had some similar problems (though I wasn't using any javascript stuff). It's not perfect code, but might help. I also did some tricks to forward notifications to the main thread (for drawing) in a . See the "Threaded Notification Support" parts of WWScreenSaverView.{h,m}.

半世晨晓 2024-08-27 18:54:54

可以尝试的方法:

  • 打开终端窗口并输入以下行以运行系统偏好设置 NSZombieEnabled:

env NSZombieEnabled=YES "/Applications/System Preferences.app/Contents/MacOS/System Preferences"

  • 执行导致崩溃的步骤。

  • 运行控制台应用程序,将右上角的过滤器设置为“系统偏好设置”,然后查找 NSZombie 消息。

    运行控制台应用程序,将右上角的过滤

希望这有帮助!

Something to try:

  • Open up a terminal window and enter the following line to run System Preferences with NSZombieEnabled:

env NSZombieEnabled=YES "/Applications/System Preferences.app/Contents/MacOS/System Preferences"

  • Perform the steps that lead to the crash.

  • Run the Console app, set the filter in the upper right to "System Preferences", and look for NSZombie messages.

Hope this helps!

水晶透心 2024-08-27 18:54:54

只是为了排除故障,您是否尝试不释放WebView?

另外,也许在首先释放 WebView 之前将其委托设置为 nil ?

Just to troubleshoot, did you try not releasing the WebView?

Also, maybe set the WebView's delegates to nil before releasing it first?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文