Plugin objects in WebView getting destroyed? - objective-c

Here's my situation:
I've written a simple web browser. Tabbed browsing was easy enough to get working once I wrapped my head around the concepts and figured how to perform operations on specific tabs. It works well and is pretty reliable under most circumstances.
A problem has plagued me, however, and I cannot figure out its cause.
Let's say I open a new tab and navigate to YouTube. I click on a video and the flash player loads. The video plays and all works fine. I now create another new tab and navigate to some site. The audio from the youtube player stops completely.
When I switch back to the youtube tab, the page will all still be there just as it was except the player has to reload completely, as if I had just reloaded the page. This seems to apply to other plugin types as well.
What's causing this?
Garbage collection is not enabled and as far as I know I'm creating the web views properly. Is there some silly, simple little thing that I missed somewhere along the line?

The - (void)setHostWindow:(NSWindow *)hostWindow method on WebView is probably what you're looking for.
I had the same problem with the flash in a WebView reloading whenever the syle mask of the enclosing window was changed. Wrapping the call to setStyleMask fixed the problem, as follows:
NSWindow *hostWindow = [[NSWindow alloc] init];
[self.webView setHostWindow:hostWindow];
[[self windowForSheet] setStyleMask:styleMask];
[self.webView setHostWindow:nil];
[hostWindow release];
The documentation for the method isn't stellar, but it does explicitly state a having a WebView inside a NSTabView as one of the use cases:
This method sets the receiver’s host window to hostWindow. Your application should only use this method if a web view is going to be removed from its window temporarily, and you want the web view to continue operating (for example, you don’t want to interrupt a load in progress). Since the receiver retains hostWindow, it is your responsibility to set the host window to nil before closing the window to avoid a retain loop.
For example, you might invoke this method if you attach a web view to an NSTabView object (as in a tabbed browser implementation). The NSTabView object takes views out of the window when they are not in the active tab, so you need to invoke this method before the web view is removed from its window. If you don't invoke this method, plug-ins will stop operating when the web view is removed from its window.

I had a similar problem but with a window with a webView that is closed and restored. Unfortunately the solution by #mlwelles did not solve the problem alone.
What did solve however is removing the webView from the window before it closes (proper "timing" is important). What I came up with is something like this:
id contentView;
id tmpHostWindow;
[window setDelegate:self];
- (BOOL)windowShouldClose:(NSNotification *)notification
{
// set temporary hostWindow on WebView and remove it from
// the closed window to prevent stopping flash plugin
// (windowWillClose would be better but that doesn't always work)
tmpHostWindow = [[NSWindow alloc] init];
[webView setHostWindow:tmpHostWindow];
[window setContentView:nil];
[contentView removeFromSuperview];
return TRUE;
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
// restore "hidden" webview
// (would be better to do it in applicationShouldHandleReopen
// but that seems to be too early (has no effect)
if ([window contentView] != contentView) {
[window setContentView:contentView];
[webView setHostWindow:nil];
tmpHostWindow = nil;
}
}

Related

Why does my app crash when an NSPanel is closed by pressing Escape?

I have an NSPanel in my app that is crashing the app (EXC_BAD_ACCESS in main()) whenever it is closed by pressing the Escape key. Closing it any other way (File > Close, Cmd-W, clicking the red close button) works fine.
The panel is shown by instantiating its window controller from the storyboard. A reference to the window controller is stored in a dictionary. In the view controller's -viewWillDisappear the window controller is removed from the dictionary.
wc = [[NSStoryboard storyboardWithName:#"Main" bundle:nil] instantiateControllerWithIdentifier:kSBIDPreviewWindowController];
// ...
[windowsOfType addObject:wc];
// ...
[wc showWindow:self];
On close:
-(void)viewDidDisappear {
// ...
if ([windowsOfType containsObject:winController]) {
[windowsOfType removeObject:winController];
}
}
The crash is apparently triggered by removing the window controller from the dictionary. If I keep it, it won't crash.
Some further info:
The panel contains (only) a WebView.
The panel's Release When Closed checkbox is off.
All other options are default (except Hide on Deactivate which is on.)
I can't find the problem with the debugger as the crash happens in main(), ie. after the window has been closed.
Any idea what could be causing this?
And, more generally, which technique should I use to debug such a situation?
Updated 2018-02-19:
Enabling Zombies got me the message -[NSWindowController setNextResponder:]: message sent to deallocated instance 0x60000028a9b0. So something is talking to the window controller after it has been released. Since that isn't my code directly (I'm not even subclassing NSWindowController, and not making any calls to the window controller anywhere), the question remains: What is different when the window is closed by hitting Escape as compared to other methods (like Cmd-W)?
Updated 2018-02-19 #2:
Instruments gave an interesting result. Apparently the WebView which I have on the panel tries to process the pressing of the Escape key after the panel has been closed/deallocated (triggered in my case by the view controller's -viewDidDisappear.)
The Zombie source is -[WebResponderChainSink detach], called (via some intermediate calls) by -[WebHTMLView keyDown:].
It looks like the WebView is not correctly passing on the keypress or something...? Could this mean that a WebView on a panel is a bad idea?
I found the solution to avoid this type of crash; you have to delay the release of the last reference to the window controller until after the current event processing is done (by using dispatch_async(), for example.)
That allows the WebView to do its thing with the keypress.

Cocoa window dragging

I'm using addGlobalMonitorForEventsMatchingMask to listen to events in Cocoa:
[NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDraggedMask
handler:^(NSEvent *event) {
NSLog(#"Dragged...");
}];
Though I would like to know if I'm dragging/moving a window (and which window that is, I can find the focused window though when holding command and dragging a window it doesn't get focus as far as I know.)
So, can I detect whether or not I'm dragging a window?
Update:
I now have a class: "SATest : NSObject <NSWindowDelegate>" in which I implement the windowDidMove method (and later perhaps windowWillMove too.) Though, now the next step would be attaching this to a window, right? So my question now is: How do I attach the delegate to all windows of all apps?
Update 2:
I now can find a list of all open windows on the screen:
AXUIElementRef _systemWideElement;
_systemWideElement = AXUIElementCreateSystemWide();
CFArrayRef _windows;
AXUIElementCopyAttributeValues(_systemWideElement, kAXWindowsAttribute, 0, 100, &_windows);
Now I have to iterate over the windows, and of each get it's NSWindow so I can add my delegate to it: [window setDelegate:self];
Update 3: To be clear, this question is about detecting the dragging of ALL windows of ALL apps. Not only the windows of my own app.
Also, I'm very new to this event and window management stuff, so no need to keep your answer short I'm happy to read a lot :P
Thanks!
-P
To find out if a window is being dragged you need to have an object that acts as the delegate of the window by responding to the following messages of the NSWindowDelegate protocol:
windowWillMove - this tells the delegate that the window is about to move.
windowDidMove - this tells the delegate that the window has moved.
You can retrieve the NSWindow object in question by sending object to the notification parameter sent to these methods:
e.g.
NSWindow draggedWindow = [notification object];
More information can be found here.
Update:
In response to your request about getting this information for all windows the NSApplication class provides a method which returns an array of all windows owned by the application. The typical way of getting this information is to use one of the NSApplicationDelegate methods to get a reference to your application object.
For example, in your app delegate (pseudocode):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSApplication * app = [aNotification object];
// you now have a reference to your application.
// and can iterate over the collection of windows and call
// [window setDelegate:self]; for each window.
}
Note that you will need to add / remove you delegates as windows are added and removed. The best method for doing this is – applicationDidUpdate:.
This should be enough to get you started solving your problem.
As suggested by Benjamin the answer lies in the accessibility API. I was looking around in this for a while, even before I asked this question, but never got it to do what I wanted. I now have found a pretty nice solution.
At a high-level I do the following:
Listen to the mouse down event and remember both on which window you clicked and it's location.
Listen to the mouse up event and check if the location has changed, if so you know you moved a window
You can do something similar for the size if you also want to know if you resized. There might be a better solution, but after days of trying stuff this is the only way I got it to work the way I wanted.
Hope this helps anyone who was looking for something similar.
-Pablo

I added a WebView to my Cocoa app and it's generating an error

I added a WebView to my app and I'm loading a page into it using this code:
-(void)awakeFromNib{
NSString *resourcesPath = [[NSBundle mainBundle] resourcePath];
NSString *htmlPath = [resourcesPath stringByAppendingString:#"/calendarHTML/test.html"];
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:htmlPath]]];
}
However, when I run the app I receive the following error:
Layout still needs update after calling -[WebHTMLView layout].
WebHTMLView or one of its superclasses may have overridden
-layout without calling super. Or, something may have dirtied
layout in the middle of updating it. Both are programming errors in
Cocoa Autolayout. The former is pretty likely to arise if some
pre-Cocoa Autolayout class had a method called layout, but it should be fixed.!
What is causing this problem?
This is caused by the fact that the nib containing the window that you have placed the WebView into is using the new Auto Layout feature, introduced in Lion.
When a nib file has auto layout enabled, the window will call the -layout method on all NSView objects in the window.
This causes a problem with WebView because it had a method named -layout before it the method was added to the NSView API in Lion, and WebView's layout method does not understand auto layout.
Probably the best fix for the time being is to use the old autoresizing mask method of laying out the views in your window. As Xcode now creates nib files with autolayout enabled, you need to disable it yourself.
You can do that in the File inspector for your nib file, by disabling the Use Auto Layout checkbox.
After you do this, you'll need to make sure that all the views in the nib have the correct autoresizing settings in the Size tab of the view inspector.
Note that you can safely ignore that log message for WebViews.
I am not sure about the case of WebView but If you are using any custom class (like subclass of UILabel) and you are using the method :
- (void)updateConstraints
{
}
then definitely It will crash your app. Best solution is to remove this method and write your required changes in 'awakeFromNib' method. Hope this help someone else who is getting this crash in custom class.

prevent windows from opening (windowless application)

I want to make a windowless application. The problem is that I'm using a framework that shows some alert boxes sometimes, and I'm not able to suppress them (at least not without a crash).
I've set LSBackgroundOnly to 1 (in the info.plist app file), but this doesn't seem to work. Any chance I'm missing something here (maybe some other settings that I need to set)?
I've also tried creating a category for NSWindow:
Overwritten alloc and allocWithZone: so that no window gets created. This works on some situations, but starting a modal session throws exception (because sending a nil parameter to beginModalSessionForWindow:).
Tried overwriting NSApplication's beginModalSessionForWindow: and returned nil everytime, but this leads to EXC_BADACCESS somewhere in the framework (runModalSession: and endModalSession: were also overwritten, but the crash is before any of them being called).
Any other ideas will be welcome.
In case it helps, the framework that I'm using is Twain.Framework, so if instead anyone knows a way to block all UI from twain (this includes error alerts shown when a device is not connected) it would be great if you share.
It's not clear what you're hoping for. Are you hoping for an app that doesn't normally display windows but does allow the third-party framework to show its alerts? Or are you hoping to find a way to prevent the framework from showing its alerts so your app absolutely never has any windows?
If it's the former, you're probably looking for LSUIElement. That's an app which can have some UI elements, but doesn't show in the Dock or the Command-Tab application switcher and doesn't have a menu bar.
I managed to make it 'windowless' (some windows are still shown, but at least they're not visible, and they're automatically closed). I only handled modal windows, since these where shown in my situation.
I made a category for NSWindow, so that all windows are invisible, and can't become key or main windows:
-(BOOL)canBecomeKeyWindow{
[self setOpaque:NO];
self.alphaValue = 0;
return NO;
}
-(BOOL)canBecomeMainWindow{
[self setOpaque:NO];
self.alphaValue = 0;
return NO;
}
Subclassed NSApplication:
-(NSInteger)runModalForWindow:(NSWindow *)aWindow{
return NSCancelButton;
}
-(NSModalSession)beginModalSessionForWindow:(NSWindow *)aWindow{
NSModalSession session = [super beginModalSessionForWindow:aWindow];
[aWindow performSelector:#selector(close) withObject:nil afterDelay:1];
return session;
}
However, this didn't help much. In case of runModalForWindow: everything was good, since the owner of the window received a response. In case of beginModalSessionForWindow: the application got stuck, most probably because the window's owner was waiting for a specific event (e.g. button click) which it never received.

How to activate a custom screensaver preview in Cocoa/Obj-C?

I have created a fairly simple screensaver that runs on Mac OS 10.6.5 without issue.
The configuration screen has accumulated quite a few different options and I'm trying to implement my own preview on the configureSheet window so the user (just me, currently) can immediately see the effect of a change without having to OK and Test each change.
I've added an NSView to the configureSheet and set the custom class in Interface Builder to my ScreenSaverView subclass. I know that drawRect: is firing, because I can remove the condition for clearing the view to black, and my custom preview no longer appears with the black background.
Here is that function (based on several fine tutorials on the Internet):
- (void)drawRect:(NSRect)rect
{
if ( shouldDrawBackground )
{
[super drawRect:rect];
shouldDrawBackground = NO;
}
if (pausing == NO)
[spiroForm drawForm];
}
The spiroForm class simply draws itself into the ScreenSaverView frame using NSBezierPath and, as mentioned, is not problematical for the actual screensaver or the built-in System Preferences preview. The custom preview (configureView) frame is passed into the init method for, um, itself (since its custom class is my ScreenSaverView subclass.) The -initWithFrame method is called in configureSheet before returning the configureSheet object to the OS:
[configureView initWithFrame:[configureView bounds] isPreview:YES];
Maybe I don't have to do that? It was just something I tried to see if it was required for drawing.
I eventually added a delegate to the configureSheet to try triggering the startAnimation and stopAnimation functions of my preview via windowWillBeginSheet and windowWillEndSheet notifications, but those don't appear to be getting called for some reason. The delegate is declared as NSObject <NSWindowDelegate> and I set the delegate in the configureSheet method before returning the configureSheet object.
I've been working on this for days, but haven't been able to find anything about how the OS manages the ScreenSaverView objects (which I think is what I'm trying to emulate by running my own copy.)
Does anybody have any suggestions on how to manage this or if Apple documents it somewhere that I haven't found? This isn't really required for the screensaver to work, I just think it would be fun (I also looked for a way to use the OS preview, but it's blocked while the configureSheet is activated.)
OK, there are a couple of 'duh' moments involved with the solution:
First of all, I was setting the delegate for the sheet notifications to the sheet itself. The window that the sheet belongs to gets the notifications.
Secondly, that very window that the sheet belongs to is owned by System Preferences, I don't see any way to set my delegate class as a delegate to that window, so the whole delegate thing doesn't appear to be a viable solution.
I ended up subclassing NSWindow for the configureSheet so that I could start and stop animation on my preview by over-riding the makeKeyWindow and close methods.
- (void) makeKeyWindow
{
if (myPreview != nil)
if ( ! [myPreview isAnimating])
{
[myPreview startAnimation];
}
[super makeKeyWindow];
}
I also had to add an IBOutlet for my preview object itself and connect it in Interface Builder.
Still working out a couple of issues, but now when I click on my screensaver Options button, my configureSheet drops down and displays its own preview while you set options. Sheesh. The hoops I jump through for these little niceties. Anyway, I like it. Onward and upward.