Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / widget / src / cocoa / nsWindowMap.mm
blob955a16c3b7988e123fa2da2500a8601f14507443
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License
6  * Version 1.1 (the "License"); you may not use this file except in
7  * compliance with the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is mozilla.org code.
16  *
17  * The Initial Developer of the Original Code is 
18  * Netscape Communications Corporation.
19  * Portions created by the Initial Developer are Copyright (C) 1998
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *    Simon Fraser <smfr@smfr.org>
24  *    Josh Aas <josh@mozilla.com>
25  *
26  * Alternatively, the contents of this file may be used under the terms of
27  * either the GNU General Public License Version 2 or later (the "GPL"), or 
28  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29  * in which case the provisions of the GPL or the LGPL are applicable instead
30  * of those above. If you wish to allow use of your version of this file only
31  * under the terms of either the GPL or the LGPL, and not to allow others to
32  * use your version of this file under the terms of the MPL, indicate your
33  * decision by deleting the provisions above and replace them with the notice
34  * and other provisions required by the GPL or the LGPL. If you do not delete
35  * the provisions above, a recipient may use your version of this file under
36  * the terms of any one of the MPL, the GPL or the LGPL.
37  *
38  * ***** END LICENSE BLOCK ***** */
40 #include "nsWindowMap.h"
41 #include "nsObjCExceptions.h"
42 #include "nsChildView.h"
43 #include "nsCocoaWindow.h"
45 @interface WindowDataMap(Private)
47 - (NSString*)keyForWindow:(NSWindow*)inWindow;
49 @end
51 @interface TopLevelWindowData(Private)
53 - (void)windowResignedKey:(NSNotification*)inNotification;
54 - (void)windowBecameKey:(NSNotification*)inNotification;
55 - (void)windowWillClose:(NSNotification*)inNotification;
57 @end
59 #pragma mark -
61 @implementation WindowDataMap
63 + (WindowDataMap*)sharedWindowDataMap
65   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
67   static WindowDataMap* sWindowMap = nil;
68   if (!sWindowMap)
69     sWindowMap = [[WindowDataMap alloc] init];
71   return sWindowMap;
73   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
76 - (id)init
78   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
80   if ((self = [super init])) {
81     mWindowMap = [[NSMutableDictionary alloc] initWithCapacity:10];
82   }
83   return self;
85   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
88 - (void)dealloc
90   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
92   [mWindowMap release];
93   [super dealloc];
95   NS_OBJC_END_TRY_ABORT_BLOCK;
98 - (id)dataForWindow:(NSWindow*)inWindow
100   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
102   return [mWindowMap objectForKey:[self keyForWindow:inWindow]];
104   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
107 - (void)setData:(id)inData forWindow:(NSWindow*)inWindow
109   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
111   [mWindowMap setObject:inData forKey:[self keyForWindow:inWindow]];
113   NS_OBJC_END_TRY_ABORT_BLOCK;
116 - (void)removeDataForWindow:(NSWindow*)inWindow
118   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
120   [mWindowMap removeObjectForKey:[self keyForWindow:inWindow]];
122   NS_OBJC_END_TRY_ABORT_BLOCK;
125 - (NSString*)keyForWindow:(NSWindow*)inWindow
127   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
129   return [NSString stringWithFormat:@"%p", inWindow];
131   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
134 @end
137 //  TopLevelWindowData
138 // 
139 //  This class holds data about top-level windows. We can't use a window
140 //  delegate, because an embedder may already have one.
142 @implementation TopLevelWindowData
144 - (id)initWithWindow:(NSWindow*)inWindow
146   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
148   if ((self = [super init])) {
149     mShouldFocusView = nil;
150     [[NSNotificationCenter defaultCenter] addObserver:self
151                                              selector:@selector(windowBecameKey:)
152                                                  name:NSWindowDidBecomeKeyNotification
153                                                object:inWindow];
155     [[NSNotificationCenter defaultCenter] addObserver:self
156                                              selector:@selector(windowResignedKey:)
157                                                  name:NSWindowDidResignKeyNotification
158                                                object:inWindow];
160     [[NSNotificationCenter defaultCenter] addObserver:self
161                                              selector:@selector(windowBecameMain:)
162                                                  name:NSWindowDidBecomeMainNotification
163                                                object:inWindow];
165     [[NSNotificationCenter defaultCenter] addObserver:self
166                                              selector:@selector(windowResignedMain:)
167                                                  name:NSWindowDidResignMainNotification
168                                                object:inWindow];
170     [[NSNotificationCenter defaultCenter] addObserver:self
171                                              selector:@selector(windowWillClose:)
172                                                  name:NSWindowWillCloseNotification
173                                                object:inWindow];
174   }
175   return self;
177   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
180 - (void)dealloc
182   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
184   if (mShouldFocusView)
185     [mShouldFocusView release];
187   [[NSNotificationCenter defaultCenter] removeObserver:self];
188   [super dealloc];
190   NS_OBJC_END_TRY_ABORT_BLOCK;
193 // mShouldFocusView (if non-nil) is the ChildView object that *should* be
194 // focused in our NSWindow -- even if it isn't the one that's actually
195 // currently focused.  (By "focused" I mean "is our NSWindow's first
196 // responder, which takes keyboard input".)  We assume that [ChildView
197 // sendFocusEvent:] is sent (to a ChildView object) if and only if that
198 // ChildView object is about to be (or has just been) appropriately focused
199 // or unfocused.  And mShouldFocusView keeps track of the results of calls
200 // to [ChildView sendFocusEvent:] on ChildView objects in our NSWindow.
201 - (ChildView *)getShouldFocusView
203   // A ChildView that's been detached from its window should never be made
204   // first responder (it should have been unfocused, but wasn't).
205   if (![mShouldFocusView window]) {
206     [mShouldFocusView release];
207     mShouldFocusView = nil;
208   }
209   return mShouldFocusView;
212 - (void)markShouldFocus:(ChildView *)aView
214   if (aView == mShouldFocusView)
215     return;
216   if (mShouldFocusView)
217     [mShouldFocusView release];
218   mShouldFocusView = [aView retain];
221 - (void)markShouldUnfocus:(ChildView *)aView
223   if (aView == mShouldFocusView) {
224     [mShouldFocusView release];
225     mShouldFocusView = nil;
226   }
229 // As best I can tell, if the notification's object has a corresponding
230 // top-level widget (an nsCocoaWindow object), it has a delegate (set in
231 // nsCocoaWindow::StandardCreate()) of class WindowDelegate, and otherwise
232 // not (Camino doesn't use top-level widgets (nsCocoaWindow objects) --
233 // only child widgets (nsChildView objects)).  (The notification is sent
234 // to windowBecameKey: or windowBecameMain: below.)
236 // If we're using top-level widgets, we need to send them both kinds of
237 // focus event (NS_GOTFOCUS and NS_ACTIVATE, which by convention are sent in
238 // that order) -- otherwise text input can (under unusual circumstances) stop
239 // working in the currently focused child widget (see bmo bug 354768).
241 // When we send focus events to a top-level widget, they get propagated
242 // (via nsWebShellWindow::HandleEvent(), indirectly) to a child widget (an
243 // nsChildView object) -- so in principle we shouldn't have to send them to
244 // child widgets here.  But I've found that, unless I also send at least an
245 // NS_GOTFOCUS event directly to the currently focused child widget, it's
246 // easy to get blinking I-bar cursors in multiple text input fields
247 // (particularly if one of them is the Google search box).  On other platforms
248 // (e.g. Windows and GTK2), NS_ACTIVATE events are only sent (directly) to
249 // top-level widgets -- so we do the same here.  Not sending them directly
250 // to child widgets also avoids "win is null" assertions on debug builds
251 // (see bug 354768 comments 55 and 58).
253 // For use with clients that (like Firefox) do use top-level widgets (and
254 // have NSWindow delegates of class WindowDelegate).
255 + (void)activateInWindow:(NSWindow*)aWindow
257   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
259   WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
260   if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
261     return;
263   if ([delegate toplevelActiveState])
264     return;
265   [delegate sendToplevelActivateEvents];
267   id firstResponder = [aWindow firstResponder];
268   if ([firstResponder isKindOfClass:[ChildView class]]) {
269     BOOL isMozWindow = [aWindow respondsToSelector:@selector(setSuppressMakeKeyFront:)];
270     if (isMozWindow)
271       [aWindow setSuppressMakeKeyFront:YES];
272     [firstResponder sendFocusEvent:NS_GOTFOCUS];
273     if (isMozWindow)
274       [aWindow setSuppressMakeKeyFront:NO];
275   }
277   NS_OBJC_END_TRY_ABORT_BLOCK;
280 // See comments above activateInWindow:
282 // If we're using top-level widgets (nsCocoaWindow objects), we send them
283 // NS_DEACTIVATE events (which propagate to child widgets (nsChildView
284 // objects) via nsWebShellWindow::HandleEvent()).  Sending NS_LOSTFOCUS
285 // events to top-level widgets currently has no effect (nsWebShellWindow::
286 // HandleEvent(), which processes focus events sent to top-level widgets,
287 // doesn't have a section for NS_LOSTFOCUS).  But on general principles we
288 // send them anyway.
290 // On other platforms (e.g. Windows and GTK2), NS_DEACTIVATE events are only
291 // sent (directly) to top-level widgets.  And (as noted above) these events
292 // propagate to child widgets when they're sent to top-level widgets.  But if
293 // we don't send them again, blinking I-bar cursors can appear in multiple
294 // text input fields.  Since we also need to send NS_LOSTFOCUS events and
295 // call nsTSMManager::CommitIME(), we just always call through to ChildView
296 // viewsWindowDidResignKey (whether or not we're using top-level widgets).
298 // For use with clients that (like Firefox) do use top-level widgets (and
299 // have NSWindow delegates of class WindowDelegate).
300 + (void)deactivateInWindow:(NSWindow*)aWindow
302   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
304   WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
305   if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
306     return;
308   if (![delegate toplevelActiveState])
309     return;
310   [delegate sendToplevelDeactivateEvents];
312   id firstResponder = [aWindow firstResponder];
313   if ([firstResponder isKindOfClass:[ChildView class]])
314     [firstResponder viewsWindowDidResignKey];
316   NS_OBJC_END_TRY_ABORT_BLOCK;
319 // For use with clients that (like Camino) don't use top-level widgets (and
320 // don't have NSWindow delegates of class WindowDelegate).
321 + (void)activateInWindowViews:(NSWindow*)aWindow
323   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
325   id firstResponder = [aWindow firstResponder];
326   if ([firstResponder isKindOfClass:[ChildView class]])
327     [firstResponder viewsWindowDidBecomeKey];
329   NS_OBJC_END_TRY_ABORT_BLOCK;
332 // For use with clients that (like Camino) don't use top-level widgets (and
333 // don't have NSWindow delegates of class WindowDelegate).
334 + (void)deactivateInWindowViews:(NSWindow*)aWindow
336   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
338   id firstResponder = [aWindow firstResponder];
339   if ([firstResponder isKindOfClass:[ChildView class]])
340     [firstResponder viewsWindowDidResignKey];
342   NS_OBJC_END_TRY_ABORT_BLOCK;
345 // We make certain exceptions for top-level windows in non-embedders (see
346 // comment above windowBecameMain below).  And we need (elsewhere) to guard
347 // against sending duplicate events.  But in general NS_ACTIVATE and
348 // NS_GOTFOCUS events should be sent when a native window becomes key, and
349 // NS_LOSTFOCUS and NS_DEACTIVATE events should be sent when it resignes key.
350 - (void)windowBecameKey:(NSNotification*)inNotification
352   NSWindow* window = (NSWindow*)[inNotification object];
354   id delegate = [window delegate];
355   if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
356     [TopLevelWindowData activateInWindowViews:window];
357   } else if ([window isSheet]) {
358     [TopLevelWindowData activateInWindow:window];
359   }
361   [[window contentView] setNeedsDisplay:YES];
364 - (void)windowResignedKey:(NSNotification*)inNotification
366   NSWindow* window = (NSWindow*)[inNotification object];
368   id delegate = [window delegate];
369   if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
370     [TopLevelWindowData deactivateInWindowViews:window];
371   } else if ([window isSheet]) {
372     [TopLevelWindowData deactivateInWindow:window];
373   }
375   [[window contentView] setNeedsDisplay:YES];
378 // The appearance of a top-level window depends on its main state (not its key
379 // state).  So (for non-embedders) we need to ensure that a top-level window
380 // is main when an NS_ACTIVATE event is sent to Gecko for it.
381 - (void)windowBecameMain:(NSNotification*)inNotification
383   NSWindow* window = (NSWindow*)[inNotification object];
385   id delegate = [window delegate];
386   // Don't send events to a top-level window that has a sheet open above it --
387   // as far as Gecko is concerned, it's inactive, and stays so until the sheet
388   // closes.
389   if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
390     [TopLevelWindowData activateInWindow:window];
393 - (void)windowResignedMain:(NSNotification*)inNotification
395   NSWindow* window = (NSWindow*)[inNotification object];
397   id delegate = [window delegate];
398   if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
399     [TopLevelWindowData deactivateInWindow:window];
402 - (void)windowWillClose:(NSNotification*)inNotification
404   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
406   // postpone our destruction
407   [[self retain] autorelease];
409   // remove ourselves from the window map (which owns us)
410   [[WindowDataMap sharedWindowDataMap] removeDataForWindow:[inNotification object]];
412   NS_OBJC_END_TRY_ABORT_BLOCK;
415 @end