104be55b56d041a021cbe0f27096fb4c37693931
[WebKit-https.git] / WebKit / Plugins.subproj / WebBaseNetscapePluginView.m
1 /*      
2         WebBaseNetscapePluginView.m
3         Copyright 2002, Apple, Inc. All rights reserved.
4 */
5
6 #import <WebKit/WebBaseNetscapePluginView.h>
7
8 #import <WebKit/WebAssertions.h>
9 #import <WebKit/WebBridge.h>
10 #import <WebKit/WebDataSource.h>
11 #import <WebKit/WebDefaultUIDelegate.h>
12 #import <WebKit/WebFrame.h>
13 #import <WebKit/WebFramePrivate.h> 
14 #import <WebKit/WebFrameView.h>
15 #import <WebKit/WebKitLogging.h>
16 #import <WebKit/WebKitNSStringExtras.h>
17 #import <WebKit/WebNetscapePluginStream.h>
18 #import <WebKit/WebNullPluginView.h>
19 #import <WebKit/WebNSObjectExtras.h>
20 #import <WebKit/WebNSURLExtras.h>
21 #import <WebKit/WebNSViewExtras.h>
22 #import <WebKit/WebNetscapePluginPackage.h>
23 #import <WebKit/WebPreferences.h>
24 #import <WebKit/WebViewPrivate.h>
25 #import <WebKit/WebUIDelegate.h>
26
27 #import <Foundation/NSData_NSURLExtras.h>
28 #import <Foundation/NSString_NSURLExtras.h>
29 #import <Foundation/NSURL_NSURLExtras.h>
30 #import <Foundation/NSURLRequestPrivate.h>
31
32 #import <AppKit/NSEvent_Private.h>
33 #import <Carbon/Carbon.h>
34 #import <CoreGraphics/CoreGraphicsPrivate.h>
35 #import <HIToolbox/TextServicesPriv.h>
36 #import <QD/QuickdrawPriv.h>
37
38 // This is not yet in QuickdrawPriv.h, although it's supposed to be.
39 void CallDrawingNotifications(CGrafPtr port, Rect *mayDrawIntoThisRect, int drawingType);
40
41 // Send null events 100 times a second when active, so plug-ins like Flash get high frame rates.
42 #define NullEventIntervalActive         0.01
43 #define NullEventIntervalNotActive      0.25
44
45 #define LoginWindowDidSwitchFromUserNotification    @"LoginWindowDidSwitchFromUserNotification"
46 #define LoginWindowDidSwitchToUserNotification      @"LoginWindowDidSwitchToUserNotification"
47
48
49 static WebBaseNetscapePluginView *currentPluginView = nil;
50
51 typedef struct {
52     GrafPtr oldPort;
53     Point oldOrigin;
54     RgnHandle oldClipRegion;
55     RgnHandle oldVisibleRegion;
56     RgnHandle clipRegion;
57     BOOL forUpdate;
58 } PortState;
59
60 @interface WebPluginRequest : NSObject
61 {
62     NSURLRequest *_request;
63     NSString *_frameName;
64     void *_notifyData;
65 }
66
67 - (id)initWithRequest:(NSURLRequest *)request frameName:(NSString *)frameName notifyData:(void *)notifyData;
68
69 - (NSURLRequest *)request;
70 - (NSString *)frameName;
71 - (void *)notifyData;
72
73 @end
74
75 @interface NSData (WebPluginDataExtras)
76 - (BOOL)_web_startsWithBlankLine;
77 - (unsigned)_web_locationAfterFirstBlankLine;
78 @end
79
80 static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *pluginView);
81 void ConsoleConnectionChangeNotifyProc(CGSNotificationType type, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg);
82
83 @implementation WebBaseNetscapePluginView
84
85 + (void)initialize
86 {
87     CGSRegisterNotifyProc(ConsoleConnectionChangeNotifyProc, kCGSessionConsoleConnect, NULL);
88     CGSRegisterNotifyProc(ConsoleConnectionChangeNotifyProc, kCGSessionConsoleDisconnect, NULL);
89 }
90
91 #pragma mark EVENTS
92
93 + (void)getCarbonEvent:(EventRecord *)carbonEvent
94 {
95     carbonEvent->what = nullEvent;
96     carbonEvent->message = 0;
97     carbonEvent->when = TickCount();
98     GetGlobalMouse(&carbonEvent->where);
99     carbonEvent->modifiers = GetCurrentKeyModifiers();
100     if (!Button())
101         carbonEvent->modifiers |= btnState;
102 }
103
104 - (void)getCarbonEvent:(EventRecord *)carbonEvent
105 {
106     [[self class] getCarbonEvent:carbonEvent];
107 }
108
109 - (EventModifiers)modifiersForEvent:(NSEvent *)event
110 {
111     EventModifiers modifiers;
112     unsigned int modifierFlags = [event modifierFlags];
113     NSEventType eventType = [event type];
114     
115     modifiers = 0;
116     
117     if (eventType != NSLeftMouseDown && eventType != NSRightMouseDown)
118         modifiers |= btnState;
119     
120     if (modifierFlags & NSCommandKeyMask)
121         modifiers |= cmdKey;
122     
123     if (modifierFlags & NSShiftKeyMask)
124         modifiers |= shiftKey;
125
126     if (modifierFlags & NSAlphaShiftKeyMask)
127         modifiers |= alphaLock;
128
129     if (modifierFlags & NSAlternateKeyMask)
130         modifiers |= optionKey;
131
132     if (modifierFlags & NSControlKeyMask || eventType == NSRightMouseDown)
133         modifiers |= controlKey;
134     
135     return modifiers;
136 }
137
138 - (void)getCarbonEvent:(EventRecord *)carbonEvent withEvent:(NSEvent *)cocoaEvent
139 {
140     if ([cocoaEvent _eventRef] && ConvertEventRefToEventRecord([cocoaEvent _eventRef], carbonEvent)) {
141         return;
142     }
143     
144     NSPoint where = [[cocoaEvent window] convertBaseToScreen:[cocoaEvent locationInWindow]];
145         
146     carbonEvent->what = nullEvent;
147     carbonEvent->message = 0;
148     carbonEvent->when = (UInt32)([cocoaEvent timestamp] * 60); // seconds to ticks
149     carbonEvent->where.h = (short)where.x;
150     carbonEvent->where.v = (short)(NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - where.y);
151     carbonEvent->modifiers = [self modifiersForEvent:cocoaEvent];
152 }
153
154 - (PortState)saveAndSetPortStateForUpdate:(BOOL)forUpdate
155 {
156     ASSERT([self currentWindow]);
157     
158     WindowRef windowRef = [[self currentWindow] windowRef];
159     CGrafPtr port = GetWindowPort(windowRef);
160
161     Rect portBounds;
162     GetPortBounds(port, &portBounds);
163
164     // Use AppKit to convert view coordinates to NSWindow coordinates.
165
166     NSRect boundsInWindow = [self convertRect:[self bounds] toView:nil];
167     NSRect visibleRectInWindow = [self convertRect:[self visibleRect] toView:nil];
168     
169     // Flip Y to convert NSWindow coordinates to top-left-based window coordinates.
170
171     float borderViewHeight = [[self currentWindow] frame].size.height;
172     boundsInWindow.origin.y = borderViewHeight - NSMaxY(boundsInWindow);
173     visibleRectInWindow.origin.y = borderViewHeight - NSMaxY(visibleRectInWindow);
174     
175     // Look at the Carbon port to convert top-left-based window coordinates into top-left-based content coordinates.
176
177     PixMap *pix = *GetPortPixMap(port);
178     boundsInWindow.origin.x += pix->bounds.left - portBounds.left;
179     boundsInWindow.origin.y += pix->bounds.top - portBounds.top;
180     visibleRectInWindow.origin.x += pix->bounds.left - portBounds.left;
181     visibleRectInWindow.origin.y += pix->bounds.top - portBounds.top;
182     
183     // Set up NS_Port.
184     
185     nPort.port = port;
186     nPort.portx = (int32)-boundsInWindow.origin.x;
187     nPort.porty = (int32)-boundsInWindow.origin.y;
188     
189     // Set up NPWindow.
190     
191     window.window = &nPort;
192     
193     window.x = (int32)boundsInWindow.origin.x; 
194     window.y = (int32)boundsInWindow.origin.y;
195     window.width = NSWidth(boundsInWindow);
196     window.height = NSHeight(boundsInWindow);
197
198     window.clipRect.top = (uint16)visibleRectInWindow.origin.y;
199     window.clipRect.left = (uint16)visibleRectInWindow.origin.x;
200     window.clipRect.bottom = (uint16)(visibleRectInWindow.origin.y + visibleRectInWindow.size.height);
201     window.clipRect.right = (uint16)(visibleRectInWindow.origin.x + visibleRectInWindow.size.width);
202
203     // Clip out the plug-in when it's not really in a window or off screen or has no height or width.
204     // The "big negative number" technique is how WebCore expresses off-screen widgets.
205     NSWindow *realWindow = [self window];
206     if (window.width <= 0 || window.height <= 0 || window.x < -100000 || realWindow == nil || [realWindow isMiniaturized] || [NSApp isHidden]) {
207         // The following code tries to give plug-ins the same size they will eventually have.
208         // The specifiedWidth and specifiedHeight variables are used to predict the size that
209         // WebCore will eventually resize us to.
210
211         // The QuickTime plug-in has problems if you give it a width or height of 0.
212         // Since other plug-ins also might have the same sort of trouble, we make sure
213         // to always give plug-ins a size other than 0,0.
214
215         if (window.width <= 0) {
216             window.width = specifiedWidth > 0 ? specifiedWidth : 100;
217         }
218         if (window.height <= 0) {
219             window.height = specifiedHeight > 0 ? specifiedHeight : 100;
220         }
221
222         window.clipRect.bottom = window.clipRect.top;
223         window.clipRect.left = window.clipRect.right;
224     }
225
226     window.type = NPWindowTypeWindow;
227     
228     // Save the port state.
229
230     PortState portState;
231     
232     GetPort(&portState.oldPort);    
233
234     portState.oldOrigin.h = portBounds.left;
235     portState.oldOrigin.v = portBounds.top;
236
237     portState.oldClipRegion = NewRgn();
238     GetPortClipRegion(port, portState.oldClipRegion);
239     
240     portState.oldVisibleRegion = NewRgn();
241     GetPortVisibleRegion(port, portState.oldVisibleRegion);
242     
243     RgnHandle clipRegion = NewRgn();
244     portState.clipRegion = clipRegion;
245     
246     MacSetRectRgn(clipRegion,
247         window.clipRect.left + nPort.portx, window.clipRect.top + nPort.porty,
248         window.clipRect.right + nPort.portx, window.clipRect.bottom + nPort.porty);
249     
250     portState.forUpdate = forUpdate;
251     
252     // Switch to the port and set it up.
253
254     SetPort(port);
255
256     PenNormal();
257     ForeColor(blackColor);
258     BackColor(whiteColor);
259     
260     SetOrigin(nPort.portx, nPort.porty);
261
262     SetPortClipRegion(nPort.port, clipRegion);
263
264     if (forUpdate) {
265         // AppKit may have tried to help us by doing a BeginUpdate.
266         // But the invalid region at that level didn't include AppKit's notion of what was not valid.
267         // We reset the port's visible region to counteract what BeginUpdate did.
268         SetPortVisibleRegion(nPort.port, clipRegion);
269
270         // Some plugins do their own BeginUpdate/EndUpdate.
271         // For those, we must make sure that the update region contains the area we want to draw.
272         InvalWindowRgn(windowRef, clipRegion);
273     }
274     
275     return portState;
276 }
277
278 - (PortState)saveAndSetPortState
279 {
280     return [self saveAndSetPortStateForUpdate:NO];
281 }
282
283 - (void)restorePortState:(PortState)portState
284 {
285     ASSERT([self currentWindow]);
286     
287     WindowRef windowRef = [[self currentWindow] windowRef];
288     CGrafPtr port = GetWindowPort(windowRef);
289
290     if (portState.forUpdate) {
291         ValidWindowRgn(windowRef, portState.clipRegion);
292     }
293     
294     SetOrigin(portState.oldOrigin.h, portState.oldOrigin.v);
295
296     SetPortClipRegion(port, portState.oldClipRegion);
297     if (portState.forUpdate) {
298         SetPortVisibleRegion(port, portState.oldVisibleRegion);
299     }
300
301     DisposeRgn(portState.oldClipRegion);
302     DisposeRgn(portState.oldVisibleRegion);
303     DisposeRgn(portState.clipRegion);
304
305     SetPort(portState.oldPort);
306 }
307
308 - (BOOL)sendEvent:(EventRecord *)event
309 {
310     ASSERT([self window]);
311
312     suspendKeyUpEvents = NO;
313     
314     if (!isStarted) {
315         return NO;
316     }
317
318     ASSERT(NPP_HandleEvent);
319     
320     // Make sure we don't call NPP_HandleEvent while we're inside NPP_SetWindow.
321     // We probably don't want more general reentrancy protection; we are really
322     // protecting only against this one case, which actually comes up when
323     // you first install the SVG viewer plug-in.
324     if (inSetWindow) {
325         return NO;
326     }
327
328     BOOL defers = [[self webView] defersCallbacks];
329     if (!defers) {
330         [[self webView] setDefersCallbacks:YES];
331     }
332
333     PortState portState = [self saveAndSetPortStateForUpdate:event->what == updateEvt];
334
335 #ifndef NDEBUG
336     // Draw green to help debug.
337     // If we see any green we know something's wrong.
338     if (event->what == updateEvt) {
339         ForeColor(greenColor);
340         const Rect bigRect = { -10000, -10000, 10000, 10000 };
341         PaintRect(&bigRect);
342         ForeColor(blackColor);
343     }
344 #endif
345     
346     // Temporarily retain self in case the plug-in view is released while sending an event. 
347     [self retain];
348
349     BOOL acceptedEvent = NPP_HandleEvent(instance, event);
350
351     if ([self currentWindow]) {
352         [self restorePortState:portState];
353     }
354
355     if (!defers) {
356         [[self webView] setDefersCallbacks:NO];
357     }
358     
359     [self release];
360     
361     return acceptedEvent;
362 }
363
364 - (void)sendActivateEvent:(BOOL)activate
365 {
366     EventRecord event;
367     
368     [self getCarbonEvent:&event];
369     event.what = activateEvt;
370     WindowRef windowRef = [[self window] windowRef];
371     event.message = (UInt32)windowRef;
372     if (activate) {
373         event.modifiers |= activeFlag;
374     }
375     
376     BOOL acceptedEvent;
377     acceptedEvent = [self sendEvent:&event]; 
378     
379     LOG(PluginEvents, "NPP_HandleEvent(activateEvent): %d  isActive: %d", acceptedEvent, activate);
380 }
381
382 - (BOOL)sendUpdateEvent
383 {
384     EventRecord event;
385     
386     [self getCarbonEvent:&event];
387     event.what = updateEvt;
388     WindowRef windowRef = [[self window] windowRef];
389     event.message = (UInt32)windowRef;
390
391     BOOL acceptedEvent = [self sendEvent:&event];
392
393     LOG(PluginEvents, "NPP_HandleEvent(updateEvt): %d", acceptedEvent);
394
395     return acceptedEvent;
396 }
397
398 -(void)sendNullEvent
399 {
400     EventRecord event;
401
402     [self getCarbonEvent:&event];
403
404     // Plug-in should not react to cursor position when not active or when a menu is down.
405     MenuTrackingData trackingData;
406     OSStatus error = GetMenuTrackingData(NULL, &trackingData);
407
408     // Plug-in should not react to cursor position when the actual window is not key.
409     if (![[self window] isKeyWindow] || (error == noErr && trackingData.menu)) {
410         // FIXME: Does passing a v and h of -1 really prevent it from reacting to the cursor position?
411         event.where.v = -1;
412         event.where.h = -1;
413     }
414
415     [self sendEvent:&event];
416 }
417
418 - (void)stopNullEvents
419 {
420     [nullEventTimer invalidate];
421     [nullEventTimer release];
422     nullEventTimer = nil;
423 }
424
425 - (void)restartNullEvents
426 {
427     ASSERT([self window]);
428     
429     if (nullEventTimer) {
430         [self stopNullEvents];
431     }
432     
433     if ([[self window] isMiniaturized]) {
434         return;
435     }
436
437     NSTimeInterval interval;
438
439     // Send null events less frequently when the actual window is not key.
440     if ([[self window] isKeyWindow]) {
441         interval = NullEventIntervalActive;
442     } else {
443         interval = NullEventIntervalNotActive;
444     }
445     
446     nullEventTimer = [[NSTimer scheduledTimerWithTimeInterval:interval
447                                                        target:self
448                                                      selector:@selector(sendNullEvent)
449                                                      userInfo:nil
450                                                       repeats:YES] retain];
451 }
452
453 - (BOOL)acceptsFirstResponder
454 {
455     return YES;
456 }
457
458 - (void)installKeyEventHandler
459 {
460     static const EventTypeSpec sTSMEvents[] =
461     {
462     { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
463     };
464     
465     if (!keyEventHandler) {
466         InstallEventHandler(GetWindowEventTarget([[self window] windowRef]),
467                             NewEventHandlerUPP(TSMEventHandler),
468                             GetEventTypeCount(sTSMEvents),
469                             sTSMEvents,
470                             self,
471                             &keyEventHandler);
472     }
473 }
474
475 - (void)removeKeyEventHandler
476 {
477     if (keyEventHandler) {
478         RemoveEventHandler(keyEventHandler);
479         keyEventHandler = NULL;
480     }
481 }
482
483 - (BOOL)becomeFirstResponder
484 {
485     EventRecord event;
486     
487     [self getCarbonEvent:&event];
488     event.what = getFocusEvent;
489     
490     BOOL acceptedEvent;
491     acceptedEvent = [self sendEvent:&event]; 
492     
493     LOG(PluginEvents, "NPP_HandleEvent(getFocusEvent): %d", acceptedEvent);
494     
495     [self installKeyEventHandler];
496         
497     return YES;
498 }
499
500 - (BOOL)resignFirstResponder
501 {
502     EventRecord event;
503     
504     [self getCarbonEvent:&event];
505     event.what = loseFocusEvent;
506     
507     BOOL acceptedEvent;
508     acceptedEvent = [self sendEvent:&event]; 
509     
510     LOG(PluginEvents, "NPP_HandleEvent(loseFocusEvent): %d", acceptedEvent);
511     
512     [self removeKeyEventHandler];
513     
514     return YES;
515 }
516
517 // AppKit doesn't call mouseDown or mouseUp on right-click. Simulate control-click
518 // mouseDown and mouseUp so plug-ins get the right-click event as they do in Carbon (3125743).
519 - (void)rightMouseDown:(NSEvent *)theEvent
520 {
521     [self mouseDown:theEvent];
522 }
523
524 - (void)rightMouseUp:(NSEvent *)theEvent
525 {
526     [self mouseUp:theEvent];
527 }
528
529 - (void)mouseDown:(NSEvent *)theEvent
530 {
531     EventRecord event;
532
533     [self getCarbonEvent:&event withEvent:theEvent];
534     event.what = mouseDown;
535
536     BOOL acceptedEvent;
537     acceptedEvent = [self sendEvent:&event]; 
538     
539     LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
540 }
541
542 - (void)mouseUp:(NSEvent *)theEvent
543 {
544     EventRecord event;
545     
546     [self getCarbonEvent:&event withEvent:theEvent];
547     event.what = mouseUp;
548
549     BOOL acceptedEvent;
550     acceptedEvent = [self sendEvent:&event]; 
551     
552     LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
553 }
554
555 - (void)mouseEntered:(NSEvent *)theEvent
556 {
557     EventRecord event;
558     
559     [self getCarbonEvent:&event withEvent:theEvent];
560     event.what = adjustCursorEvent;
561
562     BOOL acceptedEvent;
563     acceptedEvent = [self sendEvent:&event]; 
564     
565     LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent);
566 }
567
568 - (void)mouseExited:(NSEvent *)theEvent
569 {
570     EventRecord event;
571         
572     [self getCarbonEvent:&event withEvent:theEvent];
573     event.what = adjustCursorEvent;
574
575     BOOL acceptedEvent;
576     acceptedEvent = [self sendEvent:&event]; 
577     
578     LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent);
579     
580     // Set cursor back to arrow cursor.
581     [[NSCursor arrowCursor] set];
582 }
583
584 - (void)mouseDragged:(NSEvent *)theEvent
585 {
586     // Do nothing so that other responders don't respond to the drag that initiated in this view.
587 }
588
589 - (UInt32)keyMessageForEvent:(NSEvent *)event
590 {
591     NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())];
592     if (!data) {
593         return 0;
594     }
595     UInt8 characterCode;
596     [data getBytes:&characterCode length:1];
597     UInt16 keyCode = [event keyCode];
598     return keyCode << 8 | characterCode;
599 }
600
601 - (void)keyUp:(NSEvent *)theEvent
602 {
603     TSMProcessRawKeyEvent([theEvent _eventRef]);
604     
605     // TSM won't send keyUp events so we have to send them ourselves.
606     // Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9.
607     if (!suspendKeyUpEvents) {
608         EventRecord event;
609         
610         [self getCarbonEvent:&event withEvent:theEvent];
611         event.what = keyUp;
612         
613         if (event.message == 0) {
614             event.message = [self keyMessageForEvent:theEvent];
615         }
616         
617         [self sendEvent:&event];
618     }
619 }
620
621 - (void)keyDown:(NSEvent *)theEvent
622 {
623     suspendKeyUpEvents = YES;
624     TSMProcessRawKeyEvent([theEvent _eventRef]);
625 }
626
627 static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *pluginView)
628 {    
629     EventRef rawKeyEventRef;
630     OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef);
631     if (status != noErr) {
632         ERROR("GetEventParameter failed with error: %d", status);
633         return noErr;
634     }
635     
636     // Two-pass read to allocate/extract Mac charCodes
637     UInt32 numBytes;    
638     status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL);
639     if (status != noErr) {
640         ERROR("GetEventParameter failed with error: %d", status);
641         return noErr;
642     }
643     char *buffer = malloc(numBytes);
644     status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer);
645     if (status != noErr) {
646         ERROR("GetEventParameter failed with error: %d", status);
647         free(buffer);
648         return noErr;
649     }
650     
651     EventRef cloneEvent = CopyEvent(rawKeyEventRef);
652     unsigned i;
653     for (i = 0; i < numBytes; i++) {
654         status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]);
655         if (status != noErr) {
656             ERROR("SetEventParameter failed with error: %d", status);
657             free(buffer);
658             return noErr;
659         }
660         
661         EventRecord eventRec;
662         if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
663             BOOL acceptedEvent;
664             acceptedEvent = [(WebBaseNetscapePluginView *)pluginView sendEvent:&eventRec];
665             
666             LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu",
667                 acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask));
668             
669             // We originally thought that if the plug-in didn't accept this event,
670             // we should pass it along so that keyboard scrolling, for example, will work.
671             // In practice, this is not a good idea, because plug-ins tend to eat the event but return false.
672             // MacIE handles each key event twice because of this, but we will emulate the other browsers instead.
673         }
674     }
675     ReleaseEvent(cloneEvent);
676     
677     free(buffer);
678     return noErr;
679 }
680
681 // Fake up command-modified events so cut, copy, paste and select all menus work.
682 - (void)sendModifierEventWithKeyCode:(int)keyCode character:(char)character
683 {
684     EventRecord event;
685     [self getCarbonEvent:&event];
686     event.what = keyDown;
687     event.modifiers |= cmdKey;
688     event.message = keyCode << 8 | character;
689     [self sendEvent:&event];
690 }
691
692 - (void)cut:(id)sender
693 {
694     [self sendModifierEventWithKeyCode:7 character:'x'];
695 }
696
697 - (void)copy:(id)sender
698 {
699     [self sendModifierEventWithKeyCode:8 character:'c'];
700 }
701
702 - (void)paste:(id)sender
703 {
704     [self sendModifierEventWithKeyCode:9 character:'v'];
705 }
706
707 - (void)selectAll:(id)sender
708 {
709     [self sendModifierEventWithKeyCode:0 character:'a'];
710 }
711
712 #pragma mark WEB_NETSCAPE_PLUGIN
713
714 - (BOOL)isNewWindowEqualToOldWindow
715 {
716     if (window.x != lastSetWindow.x) {
717         return NO;
718     }
719     if (window.y != lastSetWindow.y) {
720         return NO;
721     }
722     if (window.width != lastSetWindow.width) {
723         return NO;
724     }
725     if (window.height != lastSetWindow.height) {
726         return NO;
727     }
728     if (window.clipRect.top != lastSetWindow.clipRect.top) {
729         return NO;
730     }
731     if (window.clipRect.left != lastSetWindow.clipRect.left) {
732         return NO;
733     }
734     if (window.clipRect.bottom  != lastSetWindow.clipRect.bottom ) {
735         return NO;
736     }
737     if (window.clipRect.right != lastSetWindow.clipRect.right) {
738         return NO;
739     }
740     if (window.type != lastSetWindow.type) {
741         return NO;
742     }
743     if (nPort.portx != lastSetPort.portx) {
744         return NO;
745     }
746     if (nPort.porty != lastSetPort.porty) {
747         return NO;
748     }
749     if (nPort.port != lastSetPort.port) {
750         return NO;
751     }
752     
753     return YES;
754 }
755
756 - (void)setWindow
757 {
758     if (!isStarted) {
759         return;
760     }
761     
762     PortState portState = [self saveAndSetPortState];
763
764     if (![self isNewWindowEqualToOldWindow]) {        
765         // Make sure we don't call NPP_HandleEvent while we're inside NPP_SetWindow.
766         // We probably don't want more general reentrancy protection; we are really
767         // protecting only against this one case, which actually comes up when
768         // you first install the SVG viewer plug-in.
769         NPError npErr;
770         ASSERT(!inSetWindow);
771         
772         inSetWindow = YES;
773         npErr = NPP_SetWindow(instance, &window);
774         inSetWindow = NO;
775
776         LOG(Plugins, "NPP_SetWindow: %d, port=0x%08x, window.x:%d window.y:%d",
777             npErr, (int)nPort.port, (int)window.x, (int)window.y);
778
779         lastSetWindow = window;
780         lastSetPort = nPort;
781     }
782
783     [self restorePortState:portState];
784 }
785
786 - (void)removeTrackingRect
787 {
788     if (trackingTag) {
789         [self removeTrackingRect:trackingTag];
790         trackingTag = 0;
791
792         // Must release the window to balance the retain in resetTrackingRect.
793         // But must do it after setting trackingTag to 0 so we don't re-enter.
794         [[self window] release];
795     }
796 }
797
798 - (void)resetTrackingRect
799 {
800     [self removeTrackingRect];
801     if (isStarted) {
802         // Must retain the window so that removeTrackingRect can work after the window is closed.
803         [[self window] retain];
804         trackingTag = [self addTrackingRect:[self bounds] owner:self userData:nil assumeInside:NO];
805     }
806 }
807
808 + (void)setCurrentPluginView:(WebBaseNetscapePluginView *)view
809 {
810     currentPluginView = view;
811 }
812
813 + (WebBaseNetscapePluginView *)currentPluginView
814 {
815     return currentPluginView;
816 }
817
818 - (BOOL)canStart
819 {
820     return YES;
821 }
822
823 - (void)didStart
824 {
825     // Do nothing. Overridden by subclasses.
826 }
827
828 - (void)addWindowObservers
829 {
830     ASSERT([self window]);
831
832     NSWindow *theWindow = [self window];
833     
834     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
835     NSView *view;
836     for (view = self; view; view = [view superview]) {
837         [notificationCenter addObserver:self selector:@selector(viewHasMoved:)
838                                    name:NSViewFrameDidChangeNotification object:view];
839         [notificationCenter addObserver:self selector:@selector(viewHasMoved:)
840                                    name:NSViewBoundsDidChangeNotification object:view];
841     }
842     [notificationCenter addObserver:self selector:@selector(windowWillClose:)
843                                name:NSWindowWillCloseNotification object:theWindow];
844     [notificationCenter addObserver:self selector:@selector(windowBecameKey:)
845                                name:NSWindowDidBecomeKeyNotification object:theWindow];
846     [notificationCenter addObserver:self selector:@selector(windowResignedKey:)
847                                name:NSWindowDidResignKeyNotification object:theWindow];
848     [notificationCenter addObserver:self selector:@selector(windowDidMiniaturize:)
849                                name:NSWindowDidMiniaturizeNotification object:theWindow];
850     [notificationCenter addObserver:self selector:@selector(windowDidDeminiaturize:)
851                                name:NSWindowDidDeminiaturizeNotification object:theWindow];
852     
853     [notificationCenter addObserver:self selector:@selector(loginWindowDidSwitchFromUser:)
854                                name:LoginWindowDidSwitchFromUserNotification object:NSApp];
855     [notificationCenter addObserver:self selector:@selector(loginWindowDidSwitchToUser:)
856                                name:LoginWindowDidSwitchToUserNotification object:NSApp];
857 }
858
859 - (void)removeWindowObservers
860 {
861     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
862     [notificationCenter removeObserver:self name:NSViewFrameDidChangeNotification     object:nil];
863     [notificationCenter removeObserver:self name:NSViewBoundsDidChangeNotification    object:nil];
864     [notificationCenter removeObserver:self name:NSWindowWillCloseNotification        object:nil];
865     [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification     object:nil];
866     [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification     object:nil];
867     [notificationCenter removeObserver:self name:NSWindowDidMiniaturizeNotification   object:nil];
868     [notificationCenter removeObserver:self name:NSWindowDidDeminiaturizeNotification object:nil];
869     [notificationCenter removeObserver:self name:LoginWindowDidSwitchFromUserNotification   object:NSApp];
870     [notificationCenter removeObserver:self name:LoginWindowDidSwitchToUserNotification     object:NSApp];
871 }
872
873 - (BOOL)start
874 {
875     ASSERT([self currentWindow]);
876     
877     if (isStarted) {
878         return YES;
879     }
880
881     if (![[WebPreferences standardPreferences] arePlugInsEnabled] || ![self canStart]) {
882         return NO;
883     }
884
885     ASSERT(NPP_New);
886
887     [[self class] setCurrentPluginView:self];
888     NPError npErr = NPP_New((char *)[MIMEType cString], instance, mode, argsCount, cAttributes, cValues, NULL);
889     [[self class] setCurrentPluginView:nil];
890     
891     LOG(Plugins, "NPP_New: %d", npErr);
892     if (npErr != NPERR_NO_ERROR) {
893         ERROR("NPP_New failed with error: %d", npErr);
894         return NO;
895     }
896
897     isStarted = YES;
898         
899     [self setWindow];
900
901     if ([self window]) {
902         [self addWindowObservers];
903         if ([[self window] isKeyWindow]) {
904             [self sendActivateEvent:YES];
905         }
906         [self restartNullEvents];
907     }
908
909     [self resetTrackingRect];
910     
911     [self didStart];
912     
913     return YES;
914 }
915
916 - (void)stop
917 {
918     [self removeTrackingRect];
919
920     if (!isStarted) {
921         return;
922     }
923     
924     isStarted = NO;
925     
926     // Stop any active streams
927     [streams makeObjectsPerformSelector:@selector(stop)];
928     
929     // Stop the null events
930     [self stopNullEvents];
931
932     // Set cursor back to arrow cursor
933     [[NSCursor arrowCursor] set];
934     
935     // Stop notifications and callbacks.
936     [self removeWindowObservers];
937     [NSObject cancelPreviousPerformRequestsWithTarget:self];
938
939     // Setting the window type to 0 ensures that NPP_SetWindow will be called if the plug-in is restarted.
940     lastSetWindow.type = 0;
941     
942     NPError npErr;
943     npErr = NPP_Destroy(instance, NULL);
944     LOG(Plugins, "NPP_Destroy: %d", npErr);
945
946     instance->pdata = NULL;
947     
948     // We usually remove the key event handler in resignFirstResponder but it is possible that resignFirstResponder 
949     // may never get called so we can't completely rely on it.
950     [self removeKeyEventHandler];
951 }
952
953 - (BOOL)isStarted
954 {
955     return isStarted;
956 }
957
958 - (WebDataSource *)dataSource
959 {
960     // Do nothing. Overridden by subclasses.
961     return nil;
962 }
963
964 - (WebFrame *)webFrame
965 {
966     return [[self dataSource] webFrame];
967 }
968
969 - (WebView *)webView
970 {
971     return [[self webFrame] webView];
972 }
973
974 - (NSWindow *)currentWindow
975 {
976     return [self window] ? [self window] : [[self webView] hostWindow];
977 }
978
979 - (NPP)pluginPointer
980 {
981     return instance;
982 }
983
984 - (WebNetscapePluginPackage *)plugin
985 {
986     return plugin;
987 }
988
989 - (void)setPlugin:(WebNetscapePluginPackage *)thePlugin;
990 {
991     [thePlugin retain];
992     [plugin release];
993     plugin = thePlugin;
994
995     NPP_New =           [plugin NPP_New];
996     NPP_Destroy =       [plugin NPP_Destroy];
997     NPP_SetWindow =     [plugin NPP_SetWindow];
998     NPP_NewStream =     [plugin NPP_NewStream];
999     NPP_WriteReady =    [plugin NPP_WriteReady];
1000     NPP_Write =         [plugin NPP_Write];
1001     NPP_StreamAsFile =  [plugin NPP_StreamAsFile];
1002     NPP_DestroyStream = [plugin NPP_DestroyStream];
1003     NPP_HandleEvent =   [plugin NPP_HandleEvent];
1004     NPP_URLNotify =     [plugin NPP_URLNotify];
1005     NPP_GetValue =      [plugin NPP_GetValue];
1006     NPP_SetValue =      [plugin NPP_SetValue];
1007     NPP_Print =         [plugin NPP_Print];
1008 }
1009
1010 - (void)setMIMEType:(NSString *)theMIMEType
1011 {
1012     NSString *type = [theMIMEType copy];
1013     [MIMEType release];
1014     MIMEType = type;
1015 }
1016
1017 - (void)setBaseURL:(NSURL *)theBaseURL
1018 {
1019     [theBaseURL retain];
1020     [baseURL release];
1021     baseURL = theBaseURL;
1022 }
1023
1024 - (void)setAttributeKeys:(NSArray *)keys andValues:(NSArray *)values;
1025 {
1026     ASSERT([keys count] == [values count]);
1027     
1028     // Convert the attributes to 2 C string arrays.
1029     // These arrays are passed to NPP_New, but the strings need to be
1030     // modifiable and live the entire life of the plugin.
1031
1032     // The Java plug-in requires the first argument to be the base URL
1033     if ([MIMEType isEqualToString:@"application/x-java-applet"]) {
1034         cAttributes = (char **)malloc(([keys count] + 1) * sizeof(char *));
1035         cValues = (char **)malloc(([values count] + 1) * sizeof(char *));
1036         cAttributes[0] = strdup("DOCBASE");
1037         cValues[0] = strdup([baseURL _web_URLCString]);
1038         argsCount++;
1039     } else {
1040         cAttributes = (char **)malloc([keys count] * sizeof(char *));
1041         cValues = (char **)malloc([values count] * sizeof(char *));
1042     }
1043
1044     BOOL isWMP = [[[plugin bundle] bundleIdentifier] isEqualToString:@"com.microsoft.WMP.defaultplugin"];
1045     
1046     unsigned i;
1047     unsigned count = [keys count];
1048     for (i = 0; i < count; i++) {
1049         NSString *key = [keys objectAtIndex:i];
1050         NSString *value = [values objectAtIndex:i];
1051         if ([key _web_isCaseInsensitiveEqualToString:@"height"]) {
1052             specifiedHeight = [value intValue];
1053         } else if ([key _web_isCaseInsensitiveEqualToString:@"width"]) {
1054             specifiedWidth = [value intValue];
1055         }
1056         // Avoid Window Media Player crash when these attributes are present.
1057         if (isWMP && ([key _web_isCaseInsensitiveEqualToString:@"SAMIStyle"] || [key _web_isCaseInsensitiveEqualToString:@"SAMILang"])) {
1058             continue;
1059         }
1060         cAttributes[argsCount] = strdup([key UTF8String]);
1061         cValues[argsCount] = strdup([value UTF8String]);
1062         LOG(Plugins, "%@ = %@", key, value);
1063         argsCount++;
1064     }
1065 }
1066
1067 - (void)setMode:(int)theMode
1068 {
1069     mode = theMode;
1070 }
1071
1072 #pragma mark NSVIEW
1073
1074 - initWithFrame:(NSRect)frame
1075 {
1076     [super initWithFrame:frame];
1077
1078     instance = &instanceStruct;
1079     instance->ndata = self;
1080
1081     streams = [[NSMutableArray alloc] init];
1082     streamNotifications = [[NSMutableDictionary alloc] init];
1083
1084     [[NSNotificationCenter defaultCenter] addObserver:self
1085                                              selector:@selector(preferencesHaveChanged:)
1086                                                  name:WebPreferencesChangedNotification
1087                                                object:nil];
1088
1089     return self;
1090 }
1091
1092 - (void)freeAttributeKeysAndValues
1093 {
1094     unsigned i;
1095     for (i = 0; i < argsCount; i++) {
1096         free(cAttributes[i]);
1097         free(cValues[i]);
1098     }
1099     free(cAttributes);
1100     free(cValues);
1101 }
1102
1103 - (void)dealloc
1104 {
1105     [[NSNotificationCenter defaultCenter] removeObserver:self];
1106     
1107     [self stop];
1108
1109     [plugin release];
1110     [streams release];
1111     [MIMEType release];
1112     [baseURL release];
1113     [streamNotifications release];
1114
1115     [self freeAttributeKeysAndValues];
1116
1117     [super dealloc];
1118 }
1119
1120 - (void)finalize
1121 {
1122     [[NSNotificationCenter defaultCenter] removeObserver:self];
1123
1124     // FIXME: Bad to stop at finalize time. Need to restructure code
1125     // so that we're already stopped before we get to this point.
1126     [self stop];
1127
1128     [self freeAttributeKeysAndValues];
1129
1130     [super finalize];
1131 }
1132
1133 - (void)drawRect:(NSRect)rect
1134 {
1135     if (!isStarted) {
1136         return;
1137     }
1138     
1139     if ([NSGraphicsContext currentContextDrawingToScreen]) {
1140         [self sendUpdateEvent];
1141     } else {
1142         // Printing 2862383
1143     }
1144 }
1145
1146 - (BOOL)isFlipped
1147 {
1148     return YES;
1149 }
1150
1151 -(void)tellQuickTimeToChill
1152 {
1153     // Make a call to the secret QuickDraw API that makes QuickTime calm down.
1154     WindowRef windowRef = [[self window] windowRef];
1155     if (!windowRef) {
1156         return;
1157     }
1158     CGrafPtr port = GetWindowPort(windowRef);
1159     Rect bounds;
1160     GetPortBounds(port, &bounds);
1161     CallDrawingNotifications(port, &bounds, kBitsProc);
1162 }
1163
1164 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
1165 {
1166     [self tellQuickTimeToChill];
1167
1168     // We must remove the tracking rect before we move to the new window.
1169     // Once we move to the new window, it will be too late.
1170     [self removeTrackingRect];
1171     [self removeWindowObservers];
1172
1173     if (!newWindow) {
1174         if ([[self webView] hostWindow]) {
1175             // View will be moved out of the actual window but it still has a host window.
1176             [self stopNullEvents];
1177         } else {
1178             // View will have no associated windows.
1179             [self stop];
1180         }
1181     }
1182 }
1183
1184 - (void)viewDidMoveToWindow
1185 {
1186     [self resetTrackingRect];
1187     
1188     if ([self window]) {
1189         // View moved to an actual window. Start it if not already started.
1190         [self start];
1191         [self restartNullEvents];
1192         [self addWindowObservers];
1193     } else if ([[self webView] hostWindow]) {
1194         // View moved out of an actual window, but still has a host window.
1195         // Call setWindow to explicitly "clip out" the plug-in from sight.
1196         // FIXME: It would be nice to do this where we call stopNullEvents in viewWillMoveToWindow.
1197         [self setWindow];
1198     }
1199 }
1200
1201 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
1202 {
1203     if (!hostWindow && ![self window]) {
1204         // View will have no associated windows.
1205         [self stop];
1206     }
1207 }
1208
1209 - (void)viewDidMoveToHostWindow
1210 {
1211     if ([[self webView] hostWindow]) {
1212         // View now has an associated window. Start it if not already started.
1213         [self start];
1214     }
1215 }
1216
1217 #pragma mark NOTIFICATIONS
1218
1219 - (void)viewHasMoved:(NSNotification *)notification
1220 {
1221     [self tellQuickTimeToChill];
1222     [self setWindow];
1223     [self resetTrackingRect];
1224 }
1225
1226 - (void)windowWillClose:(NSNotification *)notification
1227 {
1228     [self stop];
1229 }
1230
1231 - (void)windowBecameKey:(NSNotification *)notification
1232 {
1233     [self sendActivateEvent:YES];
1234     [self setNeedsDisplay:YES];
1235     [self restartNullEvents];
1236     SetUserFocusWindow([[self window] windowRef]);
1237 }
1238
1239 - (void)windowResignedKey:(NSNotification *)notification
1240 {
1241     [self sendActivateEvent:NO];
1242     [self setNeedsDisplay:YES];
1243     [self restartNullEvents];
1244 }
1245
1246 - (void)windowDidMiniaturize:(NSNotification *)notification
1247 {
1248     [self stopNullEvents];
1249 }
1250
1251 - (void)windowDidDeminiaturize:(NSNotification *)notification
1252 {
1253     [self restartNullEvents];
1254 }
1255
1256 - (void)loginWindowDidSwitchFromUser:(NSNotification *)notification
1257 {
1258     [self stopNullEvents];
1259 }
1260
1261 -(void)loginWindowDidSwitchToUser:(NSNotification *)notification
1262 {
1263     [self restartNullEvents];
1264 }
1265
1266 - (void)preferencesHaveChanged:(NSNotification *)notification
1267 {
1268     WebPreferences *preferences = [[self webView] preferences];
1269     BOOL arePlugInsEnabled = [preferences arePlugInsEnabled];
1270     
1271     if ([notification object] == preferences && isStarted != arePlugInsEnabled) {
1272         if (arePlugInsEnabled) {
1273             if ([self currentWindow]) {
1274                 [self start];
1275             }
1276         } else {
1277             [self stop];
1278             [self setNeedsDisplay:YES];
1279         }
1280     }
1281 }
1282
1283 - (void)frameStateChanged:(NSNotification *)notification
1284 {
1285     WebFrame *frame = [notification object];
1286     NSURL *URL = [[[frame dataSource] request] URL];
1287     NSValue *notifyDataValue = [streamNotifications objectForKey:URL];
1288     if (!notifyDataValue) {
1289         return;
1290     }
1291     
1292     void *notifyData = [notifyDataValue pointerValue];
1293     WebFrameState frameState = [[[notification userInfo] objectForKey:WebCurrentFrameState] intValue];
1294     if (frameState == WebFrameStateComplete) {
1295         if (isStarted) {
1296             NPP_URLNotify(instance, [URL _web_URLCString], NPRES_DONE, notifyData);
1297         }
1298         [streamNotifications removeObjectForKey:URL];
1299     }
1300
1301     //FIXME: Need to send other NPReasons
1302 }
1303
1304 - (void *)pluginScriptableObject
1305 {
1306     if (NPP_GetValue) {
1307         void *value = 0;
1308         NPError npErr = NPP_GetValue (instance, NPPVpluginScriptableNPObject, (void *)&value);
1309         if (npErr == NPERR_NO_ERROR) {
1310             return value;
1311         }
1312     }
1313     return (void *)0;
1314 }
1315
1316
1317 @end
1318
1319 @implementation WebBaseNetscapePluginView (WebNPPCallbacks)
1320
1321 - (NSMutableURLRequest *)requestWithURLCString:(const char *)URLCString
1322 {
1323     if (!URLCString) {
1324         return nil;
1325     }
1326     
1327     CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, URLCString, kCFStringEncodingWindowsLatin1);
1328     NSString *URLString = [(NSString *)string _web_stringByStrippingReturnCharacters];
1329     NSURL *URL = [NSURL _web_URLWithDataAsString:URLString relativeToURL:baseURL];
1330     CFRelease(string);
1331     if (!URL) {
1332         return nil;
1333     }
1334     
1335     return [NSMutableURLRequest requestWithURL:URL];
1336 }
1337
1338 - (void)evaluateJavaScriptPluginRequest:(WebPluginRequest *)JSPluginRequest
1339 {
1340     // FIXME: Is this isStarted check needed here? evaluateJavaScriptPluginRequest should not be called
1341     // if we are stopped since this method is called after a delay and we call 
1342     // cancelPreviousPerformRequestsWithTarget inside of stop.
1343     if (!isStarted) {
1344         return;
1345     }
1346     
1347     NSURL *URL = [[JSPluginRequest request] URL];
1348     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1349     ASSERT(JSString);
1350     
1351     NSString *result = [[[self webFrame] _bridge] stringByEvaluatingJavaScriptFromString:JSString];
1352     
1353     // Don't continue if stringByEvaluatingJavaScriptFromString caused the plug-in to stop.
1354     if (!isStarted) {
1355         return;
1356     }
1357     
1358     void *notifyData = [JSPluginRequest notifyData];
1359     
1360     if ([JSPluginRequest frameName] != nil) {
1361         // FIXME: If the result is a string, we probably want to put that string into the frame, just
1362         // like we do in KHTMLPartBrowserExtension::openURLRequest.
1363         if (notifyData) {
1364             NPP_URLNotify(instance, [URL _web_URLCString], NPRES_DONE, notifyData);
1365         }
1366     } else if ([result length] > 0) {
1367         // Don't call NPP_NewStream and other stream methods if there is no JS result to deliver. This is what Mozilla does.
1368         NSData *JSData = [result dataUsingEncoding:NSUTF8StringEncoding];
1369         WebBaseNetscapePluginStream *stream = [[WebBaseNetscapePluginStream alloc] init];
1370         [stream setPluginPointer:instance];
1371         [stream setNotifyData:notifyData];
1372         [stream startStreamWithURL:URL
1373              expectedContentLength:[JSData length]
1374                   lastModifiedDate:nil
1375                           MIMEType:@"text/plain"];
1376         [stream receivedData:JSData];
1377         [stream finishedLoadingWithData:JSData];
1378         [stream release];
1379     }
1380 }
1381
1382 - (void)loadPluginRequest:(WebPluginRequest *)pluginRequest
1383 {
1384     NSURLRequest *request = [pluginRequest request];
1385     NSString *frameName = [pluginRequest frameName];
1386     void *notifyData = [pluginRequest notifyData];
1387     WebFrame *frame = nil;
1388     
1389     NSURL *URL = [request URL];
1390     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1391     
1392     ASSERT(frameName || JSString);
1393     
1394     if (frameName) {
1395         // FIXME - need to get rid of this window creation which
1396         // bypasses normal targeted link handling
1397         frame = [[self webFrame] findFrameNamed:frameName];
1398     
1399         if (frame == nil) {
1400             WebView *newWebView = nil;
1401             WebView *currentWebView = [self webView];
1402             id wd = [currentWebView UIDelegate];
1403             if ([wd respondsToSelector:@selector(webView:createWebViewWithRequest:)]) {
1404                 newWebView = [wd webView:currentWebView createWebViewWithRequest:nil];
1405             } else {
1406                 newWebView = [[WebDefaultUIDelegate sharedUIDelegate] webView:currentWebView createWebViewWithRequest:nil];
1407             }
1408             
1409             [newWebView _setTopLevelFrameName:frameName];
1410             [[newWebView _UIDelegateForwarder] webViewShow:newWebView];
1411             frame = [newWebView mainFrame];
1412         }
1413     }
1414
1415     if (JSString) {
1416         ASSERT(frame == nil || [self webFrame] == frame);
1417         [self evaluateJavaScriptPluginRequest:pluginRequest];
1418     } else {
1419         [frame loadRequest:request];
1420         if (notifyData) {
1421             // FIXME: How do we notify about failures? It seems this will only notify about success.
1422         
1423             // FIXME: This will overwrite any previous notification for the same URL.
1424             // It might be better to keep track of these per frame.
1425             [streamNotifications setObject:[NSValue valueWithPointer:notifyData] forKey:URL];
1426             
1427             // FIXME: We add this same observer to a frame multiple times. Is that OK?
1428             // FIXME: This observer doesn't get removed until the plugin stops, so we could
1429             // end up with lots and lots of these.
1430             [[NSNotificationCenter defaultCenter] addObserver:self
1431                                                      selector:@selector(frameStateChanged:)
1432                                                          name:WebFrameStateChangedNotification
1433                                                        object:frame];
1434         }
1435     }
1436 }
1437
1438 - (NPError)loadRequest:(NSMutableURLRequest *)request inTarget:(const char *)cTarget withNotifyData:(void *)notifyData
1439 {
1440     NSURL *URL = [request URL];
1441
1442     if (!URL) {
1443         return NPERR_INVALID_URL;
1444     }
1445     
1446     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1447     if (JSString != nil) {
1448         if (![[[self webView] preferences] isJavaScriptEnabled]) {
1449             // Return NPERR_GENERIC_ERROR if JS is disabled. This is what Mozilla does.
1450             return NPERR_GENERIC_ERROR;
1451         } else if (cTarget == NULL && mode == NP_FULL) {
1452             // Don't allow a JavaScript request from a standalone plug-in that is self-targetted
1453             // because this can cause the user to be redirected to a blank page (3424039).
1454             return NPERR_INVALID_PARAM;
1455         }
1456     }
1457         
1458     if (cTarget || JSString) {
1459         // Make when targetting a frame or evaluating a JS string, perform the request after a delay because we don't
1460         // want to potentially kill the plug-in inside of its URL request.
1461         NSString *target = nil;
1462         if (cTarget) {
1463             // Find the frame given the target string.
1464             target = (NSString *)CFStringCreateWithCString(kCFAllocatorDefault, cTarget, kCFStringEncodingWindowsLatin1);
1465         }
1466         
1467         WebFrame *frame = [self webFrame];
1468         if (JSString != nil && target != nil && [frame findFrameNamed:target] != frame) {
1469             // For security reasons, only allow JS requests to be made on the frame that contains the plug-in.
1470             CFRelease(target);
1471             return NPERR_INVALID_PARAM;
1472         }
1473         
1474         [request setHTTPReferrer:[[[[[self webFrame] dataSource] request] URL] _web_originalDataAsString]];
1475         WebPluginRequest *pluginRequest = [[WebPluginRequest alloc] initWithRequest:request frameName:target notifyData:notifyData];
1476         [self performSelector:@selector(loadPluginRequest:) withObject:pluginRequest afterDelay:0];
1477         [pluginRequest release];
1478         if (target) {
1479             CFRelease(target);
1480         }
1481     } else {
1482         WebNetscapePluginStream *stream = [[WebNetscapePluginStream alloc]
1483             initWithRequest:request pluginPointer:instance notifyData:notifyData];
1484         if (!stream) {
1485             return NPERR_INVALID_URL;
1486         }
1487         [streams addObject:stream];
1488         [stream start];
1489         [stream release];
1490     }
1491     
1492     return NPERR_NO_ERROR;
1493 }
1494
1495 -(NPError)getURLNotify:(const char *)URLCString target:(const char *)cTarget notifyData:(void *)notifyData
1496 {
1497     LOG(Plugins, "NPN_GetURLNotify: %s target: %s", URLCString, cTarget);
1498
1499     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1500     return [self loadRequest:request inTarget:cTarget withNotifyData:notifyData];
1501 }
1502
1503 -(NPError)getURL:(const char *)URLCString target:(const char *)cTarget
1504 {
1505     LOG(Plugins, "NPN_GetURL: %s target: %s", URLCString, cTarget);
1506
1507     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1508     return [self loadRequest:request inTarget:cTarget withNotifyData:NULL];
1509 }
1510
1511 - (NPError)_postURLNotify:(const char *)URLCString
1512                    target:(const char *)target
1513                       len:(UInt32)len
1514                       buf:(const char *)buf
1515                      file:(NPBool)file
1516                notifyData:(void *)notifyData
1517              allowHeaders:(BOOL)allowHeaders
1518 {
1519     if (!URLCString || !len || !buf) {
1520         return NPERR_INVALID_PARAM;
1521     }
1522     
1523     NSData *postData = nil;
1524
1525     if (file) {
1526         // If we're posting a file, buf is either a file URL or a path to the file.
1527         NSString *bufString = (NSString *)CFStringCreateWithCString(kCFAllocatorDefault, buf, kCFStringEncodingWindowsLatin1);
1528         if (!bufString) {
1529             return NPERR_INVALID_PARAM;
1530         }
1531         NSURL *fileURL = [NSURL _web_URLWithDataAsString:bufString];
1532         NSString *path;
1533         if ([fileURL isFileURL]) {
1534             path = [fileURL path];
1535         } else {
1536             path = bufString;
1537         }
1538         postData = [NSData dataWithContentsOfFile:[path _web_fixedCarbonPOSIXPath]];
1539         CFRelease(bufString);
1540         if (!postData) {
1541             return NPERR_FILE_NOT_FOUND;
1542         }
1543     } else {
1544         postData = [NSData dataWithBytes:buf length:len];
1545     }
1546
1547     if ([postData length] == 0) {
1548         return NPERR_INVALID_PARAM;
1549     }
1550
1551     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1552     [request setHTTPMethod:@"POST"];
1553     
1554     if (allowHeaders) {
1555         if ([postData _web_startsWithBlankLine]) {
1556             postData = [postData subdataWithRange:NSMakeRange(1, [postData length] - 1)];
1557         } else {
1558             unsigned location = [postData _web_locationAfterFirstBlankLine];
1559             if (location != NSNotFound) {
1560                 // If the blank line is somewhere in the middle of postData, everything before is the header.
1561                 NSData *headerData = [postData subdataWithRange:NSMakeRange(0, location)];
1562                 NSMutableDictionary *header = [headerData _web_parseRFC822HeaderFields];
1563                 unsigned dataLength = [postData length] - location;
1564
1565                 // Sometimes plugins like to set Content-Length themselves when they post,
1566                 // but WebFoundation does not like that. So we will remove the header
1567                 // and instead truncate the data to the requested length.
1568                 NSString *contentLength = [header objectForKey:@"Content-Length"];
1569
1570                 if (contentLength != nil) {
1571                     dataLength = MIN((unsigned)[contentLength intValue], dataLength);
1572                 }
1573                 [header removeObjectForKey:@"Content-Length"];
1574
1575                 if ([header count] > 0) {
1576                     [request setAllHTTPHeaderFields:header];
1577                 }
1578                 // Everything after the blank line is the actual content of the POST.
1579                 postData = [postData subdataWithRange:NSMakeRange(location, dataLength)];
1580
1581             }
1582         }
1583         if ([postData length] == 0) {
1584             return NPERR_INVALID_PARAM;
1585         }
1586     }
1587
1588     // Plug-ins expect to receive uncached data when doing a POST (3347134).
1589     [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
1590     [request setHTTPBody:postData];
1591     
1592     return [self loadRequest:request inTarget:target withNotifyData:notifyData];
1593 }
1594
1595 - (NPError)postURLNotify:(const char *)URLCString
1596                   target:(const char *)target
1597                      len:(UInt32)len
1598                      buf:(const char *)buf
1599                     file:(NPBool)file
1600               notifyData:(void *)notifyData
1601 {
1602     LOG(Plugins, "NPN_PostURLNotify: %s", URLCString);
1603     return [self _postURLNotify:URLCString target:target len:len buf:buf file:file notifyData:notifyData allowHeaders:YES];
1604 }
1605
1606 -(NPError)postURL:(const char *)URLCString
1607            target:(const char *)target
1608               len:(UInt32)len
1609               buf:(const char *)buf
1610              file:(NPBool)file
1611 {
1612     LOG(Plugins, "NPN_PostURL: %s", URLCString);        
1613     // As documented, only allow headers to be specified via NPP_PostURL when using a file.
1614     return [self _postURLNotify:URLCString target:target len:len buf:buf file:file notifyData:NULL allowHeaders:file];
1615 }
1616
1617 -(NPError)newStream:(NPMIMEType)type target:(const char *)target stream:(NPStream**)stream
1618 {
1619     LOG(Plugins, "NPN_NewStream");
1620     return NPERR_GENERIC_ERROR;
1621 }
1622
1623 -(NPError)write:(NPStream*)stream len:(SInt32)len buffer:(void *)buffer
1624 {
1625     LOG(Plugins, "NPN_Write");
1626     return NPERR_GENERIC_ERROR;
1627 }
1628
1629 -(NPError)destroyStream:(NPStream*)stream reason:(NPReason)reason
1630 {
1631     LOG(Plugins, "NPN_DestroyStream");
1632     if (!stream->ndata) {
1633         return NPERR_INVALID_INSTANCE_ERROR;
1634     }
1635     [(WebBaseNetscapePluginStream *)stream->ndata cancelWithReason:reason];
1636     return NPERR_NO_ERROR;
1637 }
1638
1639 - (const char *)userAgent
1640 {
1641     return [[[self webView] userAgentForURL:baseURL] lossyCString];
1642 }
1643
1644 -(void)status:(const char *)message
1645 {    
1646     if (!message) {
1647         ERROR("NPN_Status passed a NULL status message");
1648         return;
1649     }
1650
1651     CFStringRef status = CFStringCreateWithCString(NULL, message, kCFStringEncodingWindowsLatin1);
1652     LOG(Plugins, "NPN_Status: %@", status);
1653     WebView *wv = [self webView];
1654     [[wv _UIDelegateForwarder] webView:wv setStatusText:(NSString *)status];
1655     CFRelease(status);
1656 }
1657
1658 -(void)invalidateRect:(NPRect *)invalidRect
1659 {
1660     LOG(Plugins, "NPN_InvalidateRect");
1661     [self setNeedsDisplayInRect:NSMakeRect(invalidRect->left, invalidRect->top,
1662         (float)invalidRect->right - invalidRect->left, (float)invalidRect->bottom - invalidRect->top)];
1663 }
1664
1665 -(void)invalidateRegion:(NPRegion)invalidRegion
1666 {
1667     LOG(Plugins, "NPN_InvalidateRegion");
1668     Rect invalidRect;
1669     GetRegionBounds(invalidRegion, &invalidRect);
1670     [self setNeedsDisplayInRect:NSMakeRect(invalidRect.left, invalidRect.top,
1671         (float)invalidRect.right - invalidRect.left, (float)invalidRect.bottom - invalidRect.top)];
1672 }
1673
1674 -(void)forceRedraw
1675 {
1676     LOG(Plugins, "forceRedraw");
1677     [self setNeedsDisplay:YES];
1678     [[self window] displayIfNeeded];
1679 }
1680
1681 - (NPError)getVariable:(NPNVariable)variable value:(void *)value
1682 {
1683     if (variable == NPNVWindowNPObject) {
1684         void **v = (void **)value;
1685         *v = [[[self webFrame] _bridge] windowScriptNPObject];
1686         return NPERR_NO_ERROR;
1687     }
1688     return NPERR_GENERIC_ERROR;
1689 }
1690
1691 @end
1692
1693 @implementation WebPluginRequest
1694
1695 - (id)initWithRequest:(NSURLRequest *)request frameName:(NSString *)frameName notifyData:(void *)notifyData
1696 {
1697     [super init];
1698     _request = [request retain];
1699     _frameName = [frameName retain];
1700     _notifyData = notifyData;
1701     return self;
1702 }
1703
1704 - (void)dealloc
1705 {
1706     [_request release];
1707     [_frameName release];
1708     [super dealloc];
1709 }
1710
1711 - (NSURLRequest *)request
1712 {
1713     return _request;
1714 }
1715
1716 - (NSString *)frameName
1717 {
1718     return _frameName;
1719 }
1720
1721 - (void *)notifyData
1722 {
1723     return _notifyData;
1724 }
1725
1726 @end
1727
1728 @implementation NSData (PluginExtras)
1729
1730 - (BOOL)_web_startsWithBlankLine
1731 {
1732     return [self length] > 0 && ((const char *)[self bytes])[0] == '\n';
1733 }
1734
1735 - (unsigned)_web_locationAfterFirstBlankLine
1736 {
1737     const char *bytes = (const char *)[self bytes];
1738     unsigned length = [self length];
1739
1740     unsigned i;
1741     for (i = 0; i < length - 2; i++) {
1742         if (bytes[i] == '\n' && (i == 0 || bytes[i+1] == '\n')) {
1743             i++;
1744             while (i < length - 2 && bytes[i] == '\n') {
1745                 i++;
1746             }
1747             return i;
1748         }
1749     }
1750     
1751     return NSNotFound;
1752 }
1753
1754 @end
1755
1756 void ConsoleConnectionChangeNotifyProc(CGSNotificationType type, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg)
1757 {
1758     NSString *notificationName = nil;
1759     if (type == kCGSessionConsoleConnect) {
1760         notificationName = LoginWindowDidSwitchToUserNotification;
1761     } else if (type == kCGSessionConsoleDisconnect) {
1762         notificationName = LoginWindowDidSwitchFromUserNotification;
1763     } else {
1764         ASSERT_NOT_REACHED();
1765     }
1766     [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:NSApp];
1767 }