WebKit:
[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.  Also, allow the DB
440     // to override this behavior and send full speed events to non key windows.
441     if ([[self window] isKeyWindow] || [[self webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendActiveNullEventsToPlugIns]) {
442         interval = NullEventIntervalActive;
443     } else {
444         interval = NullEventIntervalNotActive;
445     }
446     
447     nullEventTimer = [[NSTimer scheduledTimerWithTimeInterval:interval
448                                                        target:self
449                                                      selector:@selector(sendNullEvent)
450                                                      userInfo:nil
451                                                       repeats:YES] retain];
452 }
453
454 - (BOOL)acceptsFirstResponder
455 {
456     return YES;
457 }
458
459 - (void)installKeyEventHandler
460 {
461     static const EventTypeSpec sTSMEvents[] =
462     {
463     { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
464     };
465     
466     if (!keyEventHandler) {
467         InstallEventHandler(GetWindowEventTarget([[self window] windowRef]),
468                             NewEventHandlerUPP(TSMEventHandler),
469                             GetEventTypeCount(sTSMEvents),
470                             sTSMEvents,
471                             self,
472                             &keyEventHandler);
473     }
474 }
475
476 - (void)removeKeyEventHandler
477 {
478     if (keyEventHandler) {
479         RemoveEventHandler(keyEventHandler);
480         keyEventHandler = NULL;
481     }
482 }
483
484 - (BOOL)becomeFirstResponder
485 {
486     EventRecord event;
487     
488     [self getCarbonEvent:&event];
489     event.what = getFocusEvent;
490     
491     BOOL acceptedEvent;
492     acceptedEvent = [self sendEvent:&event]; 
493     
494     LOG(PluginEvents, "NPP_HandleEvent(getFocusEvent): %d", acceptedEvent);
495     
496     [self installKeyEventHandler];
497         
498     return YES;
499 }
500
501 - (BOOL)resignFirstResponder
502 {
503     EventRecord event;
504     
505     [self getCarbonEvent:&event];
506     event.what = loseFocusEvent;
507     
508     BOOL acceptedEvent;
509     acceptedEvent = [self sendEvent:&event]; 
510     
511     LOG(PluginEvents, "NPP_HandleEvent(loseFocusEvent): %d", acceptedEvent);
512     
513     [self removeKeyEventHandler];
514     
515     return YES;
516 }
517
518 // AppKit doesn't call mouseDown or mouseUp on right-click. Simulate control-click
519 // mouseDown and mouseUp so plug-ins get the right-click event as they do in Carbon (3125743).
520 - (void)rightMouseDown:(NSEvent *)theEvent
521 {
522     [self mouseDown:theEvent];
523 }
524
525 - (void)rightMouseUp:(NSEvent *)theEvent
526 {
527     [self mouseUp:theEvent];
528 }
529
530 - (void)mouseDown:(NSEvent *)theEvent
531 {
532     EventRecord event;
533
534     [self getCarbonEvent:&event withEvent:theEvent];
535     event.what = mouseDown;
536
537     BOOL acceptedEvent;
538     acceptedEvent = [self sendEvent:&event]; 
539     
540     LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
541 }
542
543 - (void)mouseUp:(NSEvent *)theEvent
544 {
545     EventRecord event;
546     
547     [self getCarbonEvent:&event withEvent:theEvent];
548     event.what = mouseUp;
549
550     BOOL acceptedEvent;
551     acceptedEvent = [self sendEvent:&event]; 
552     
553     LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
554 }
555
556 - (void)mouseEntered:(NSEvent *)theEvent
557 {
558     EventRecord event;
559     
560     [self getCarbonEvent:&event withEvent:theEvent];
561     event.what = adjustCursorEvent;
562
563     BOOL acceptedEvent;
564     acceptedEvent = [self sendEvent:&event]; 
565     
566     LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent);
567 }
568
569 - (void)mouseExited:(NSEvent *)theEvent
570 {
571     EventRecord event;
572         
573     [self getCarbonEvent:&event withEvent:theEvent];
574     event.what = adjustCursorEvent;
575
576     BOOL acceptedEvent;
577     acceptedEvent = [self sendEvent:&event]; 
578     
579     LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent);
580     
581     // Set cursor back to arrow cursor.
582     [[NSCursor arrowCursor] set];
583 }
584
585 - (void)mouseDragged:(NSEvent *)theEvent
586 {
587     // Do nothing so that other responders don't respond to the drag that initiated in this view.
588 }
589
590 - (UInt32)keyMessageForEvent:(NSEvent *)event
591 {
592     NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())];
593     if (!data) {
594         return 0;
595     }
596     UInt8 characterCode;
597     [data getBytes:&characterCode length:1];
598     UInt16 keyCode = [event keyCode];
599     return keyCode << 8 | characterCode;
600 }
601
602 - (void)keyUp:(NSEvent *)theEvent
603 {
604     TSMProcessRawKeyEvent([theEvent _eventRef]);
605     
606     // TSM won't send keyUp events so we have to send them ourselves.
607     // Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9.
608     if (!suspendKeyUpEvents) {
609         EventRecord event;
610         
611         [self getCarbonEvent:&event withEvent:theEvent];
612         event.what = keyUp;
613         
614         if (event.message == 0) {
615             event.message = [self keyMessageForEvent:theEvent];
616         }
617         
618         [self sendEvent:&event];
619     }
620 }
621
622 - (void)keyDown:(NSEvent *)theEvent
623 {
624     suspendKeyUpEvents = YES;
625     TSMProcessRawKeyEvent([theEvent _eventRef]);
626 }
627
628 static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *pluginView)
629 {    
630     EventRef rawKeyEventRef;
631     OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef);
632     if (status != noErr) {
633         ERROR("GetEventParameter failed with error: %d", status);
634         return noErr;
635     }
636     
637     // Two-pass read to allocate/extract Mac charCodes
638     UInt32 numBytes;    
639     status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL);
640     if (status != noErr) {
641         ERROR("GetEventParameter failed with error: %d", status);
642         return noErr;
643     }
644     char *buffer = malloc(numBytes);
645     status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer);
646     if (status != noErr) {
647         ERROR("GetEventParameter failed with error: %d", status);
648         free(buffer);
649         return noErr;
650     }
651     
652     EventRef cloneEvent = CopyEvent(rawKeyEventRef);
653     unsigned i;
654     for (i = 0; i < numBytes; i++) {
655         status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]);
656         if (status != noErr) {
657             ERROR("SetEventParameter failed with error: %d", status);
658             free(buffer);
659             return noErr;
660         }
661         
662         EventRecord eventRec;
663         if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
664             BOOL acceptedEvent;
665             acceptedEvent = [(WebBaseNetscapePluginView *)pluginView sendEvent:&eventRec];
666             
667             LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu",
668                 acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask));
669             
670             // We originally thought that if the plug-in didn't accept this event,
671             // we should pass it along so that keyboard scrolling, for example, will work.
672             // In practice, this is not a good idea, because plug-ins tend to eat the event but return false.
673             // MacIE handles each key event twice because of this, but we will emulate the other browsers instead.
674         }
675     }
676     ReleaseEvent(cloneEvent);
677     
678     free(buffer);
679     return noErr;
680 }
681
682 // Fake up command-modified events so cut, copy, paste and select all menus work.
683 - (void)sendModifierEventWithKeyCode:(int)keyCode character:(char)character
684 {
685     EventRecord event;
686     [self getCarbonEvent:&event];
687     event.what = keyDown;
688     event.modifiers |= cmdKey;
689     event.message = keyCode << 8 | character;
690     [self sendEvent:&event];
691 }
692
693 - (void)cut:(id)sender
694 {
695     [self sendModifierEventWithKeyCode:7 character:'x'];
696 }
697
698 - (void)copy:(id)sender
699 {
700     [self sendModifierEventWithKeyCode:8 character:'c'];
701 }
702
703 - (void)paste:(id)sender
704 {
705     [self sendModifierEventWithKeyCode:9 character:'v'];
706 }
707
708 - (void)selectAll:(id)sender
709 {
710     [self sendModifierEventWithKeyCode:0 character:'a'];
711 }
712
713 #pragma mark WEB_NETSCAPE_PLUGIN
714
715 - (BOOL)isNewWindowEqualToOldWindow
716 {
717     if (window.x != lastSetWindow.x) {
718         return NO;
719     }
720     if (window.y != lastSetWindow.y) {
721         return NO;
722     }
723     if (window.width != lastSetWindow.width) {
724         return NO;
725     }
726     if (window.height != lastSetWindow.height) {
727         return NO;
728     }
729     if (window.clipRect.top != lastSetWindow.clipRect.top) {
730         return NO;
731     }
732     if (window.clipRect.left != lastSetWindow.clipRect.left) {
733         return NO;
734     }
735     if (window.clipRect.bottom  != lastSetWindow.clipRect.bottom ) {
736         return NO;
737     }
738     if (window.clipRect.right != lastSetWindow.clipRect.right) {
739         return NO;
740     }
741     if (window.type != lastSetWindow.type) {
742         return NO;
743     }
744     if (nPort.portx != lastSetPort.portx) {
745         return NO;
746     }
747     if (nPort.porty != lastSetPort.porty) {
748         return NO;
749     }
750     if (nPort.port != lastSetPort.port) {
751         return NO;
752     }
753     
754     return YES;
755 }
756
757 - (void)setWindow
758 {
759     if (!isStarted) {
760         return;
761     }
762     
763     PortState portState = [self saveAndSetPortState];
764
765     if (![self isNewWindowEqualToOldWindow]) {        
766         // Make sure we don't call NPP_HandleEvent while we're inside NPP_SetWindow.
767         // We probably don't want more general reentrancy protection; we are really
768         // protecting only against this one case, which actually comes up when
769         // you first install the SVG viewer plug-in.
770         NPError npErr;
771         ASSERT(!inSetWindow);
772         
773         inSetWindow = YES;
774         npErr = NPP_SetWindow(instance, &window);
775         inSetWindow = NO;
776
777         LOG(Plugins, "NPP_SetWindow: %d, port=0x%08x, window.x:%d window.y:%d",
778             npErr, (int)nPort.port, (int)window.x, (int)window.y);
779
780         lastSetWindow = window;
781         lastSetPort = nPort;
782     }
783
784     [self restorePortState:portState];
785 }
786
787 - (void)removeTrackingRect
788 {
789     if (trackingTag) {
790         [self removeTrackingRect:trackingTag];
791         trackingTag = 0;
792
793         // Must release the window to balance the retain in resetTrackingRect.
794         // But must do it after setting trackingTag to 0 so we don't re-enter.
795         [[self window] release];
796     }
797 }
798
799 - (void)resetTrackingRect
800 {
801     [self removeTrackingRect];
802     if (isStarted) {
803         // Must retain the window so that removeTrackingRect can work after the window is closed.
804         [[self window] retain];
805         trackingTag = [self addTrackingRect:[self bounds] owner:self userData:nil assumeInside:NO];
806     }
807 }
808
809 + (void)setCurrentPluginView:(WebBaseNetscapePluginView *)view
810 {
811     currentPluginView = view;
812 }
813
814 + (WebBaseNetscapePluginView *)currentPluginView
815 {
816     return currentPluginView;
817 }
818
819 - (BOOL)canStart
820 {
821     return YES;
822 }
823
824 - (void)didStart
825 {
826     // Do nothing. Overridden by subclasses.
827 }
828
829 - (void)addWindowObservers
830 {
831     ASSERT([self window]);
832
833     NSWindow *theWindow = [self window];
834     
835     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
836     NSView *view;
837     for (view = self; view; view = [view superview]) {
838         [notificationCenter addObserver:self selector:@selector(viewHasMoved:)
839                                    name:NSViewFrameDidChangeNotification object:view];
840         [notificationCenter addObserver:self selector:@selector(viewHasMoved:)
841                                    name:NSViewBoundsDidChangeNotification object:view];
842     }
843     [notificationCenter addObserver:self selector:@selector(windowWillClose:)
844                                name:NSWindowWillCloseNotification object:theWindow];
845     [notificationCenter addObserver:self selector:@selector(windowBecameKey:)
846                                name:NSWindowDidBecomeKeyNotification object:theWindow];
847     [notificationCenter addObserver:self selector:@selector(windowResignedKey:)
848                                name:NSWindowDidResignKeyNotification object:theWindow];
849     [notificationCenter addObserver:self selector:@selector(windowDidMiniaturize:)
850                                name:NSWindowDidMiniaturizeNotification object:theWindow];
851     [notificationCenter addObserver:self selector:@selector(windowDidDeminiaturize:)
852                                name:NSWindowDidDeminiaturizeNotification object:theWindow];
853     
854     [notificationCenter addObserver:self selector:@selector(loginWindowDidSwitchFromUser:)
855                                name:LoginWindowDidSwitchFromUserNotification object:NSApp];
856     [notificationCenter addObserver:self selector:@selector(loginWindowDidSwitchToUser:)
857                                name:LoginWindowDidSwitchToUserNotification object:NSApp];
858 }
859
860 - (void)removeWindowObservers
861 {
862     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
863     [notificationCenter removeObserver:self name:NSViewFrameDidChangeNotification     object:nil];
864     [notificationCenter removeObserver:self name:NSViewBoundsDidChangeNotification    object:nil];
865     [notificationCenter removeObserver:self name:NSWindowWillCloseNotification        object:nil];
866     [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification     object:nil];
867     [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification     object:nil];
868     [notificationCenter removeObserver:self name:NSWindowDidMiniaturizeNotification   object:nil];
869     [notificationCenter removeObserver:self name:NSWindowDidDeminiaturizeNotification object:nil];
870     [notificationCenter removeObserver:self name:LoginWindowDidSwitchFromUserNotification   object:NSApp];
871     [notificationCenter removeObserver:self name:LoginWindowDidSwitchToUserNotification     object:NSApp];
872 }
873
874 - (BOOL)start
875 {
876     ASSERT([self currentWindow]);
877     
878     if (isStarted) {
879         return YES;
880     }
881
882     if (![[WebPreferences standardPreferences] arePlugInsEnabled] || ![self canStart]) {
883         return NO;
884     }
885
886     ASSERT(NPP_New);
887
888     [[self class] setCurrentPluginView:self];
889     NPError npErr = NPP_New((char *)[MIMEType cString], instance, mode, argsCount, cAttributes, cValues, NULL);
890     [[self class] setCurrentPluginView:nil];
891     
892     LOG(Plugins, "NPP_New: %d", npErr);
893     if (npErr != NPERR_NO_ERROR) {
894         ERROR("NPP_New failed with error: %d", npErr);
895         return NO;
896     }
897
898     isStarted = YES;
899         
900     [self setWindow];
901
902     if ([self window]) {
903         [self addWindowObservers];
904         if ([[self window] isKeyWindow]) {
905             [self sendActivateEvent:YES];
906         }
907         [self restartNullEvents];
908     }
909
910     [self resetTrackingRect];
911     
912     [self didStart];
913     
914     return YES;
915 }
916
917 - (void)stop
918 {
919     [self removeTrackingRect];
920
921     if (!isStarted) {
922         return;
923     }
924     
925     isStarted = NO;
926     
927     // Stop any active streams
928     [streams makeObjectsPerformSelector:@selector(stop)];
929     
930     // Stop the null events
931     [self stopNullEvents];
932
933     // Set cursor back to arrow cursor
934     [[NSCursor arrowCursor] set];
935     
936     // Stop notifications and callbacks.
937     [self removeWindowObservers];
938     [NSObject cancelPreviousPerformRequestsWithTarget:self];
939
940     // Setting the window type to 0 ensures that NPP_SetWindow will be called if the plug-in is restarted.
941     lastSetWindow.type = 0;
942     
943     NPError npErr;
944     npErr = NPP_Destroy(instance, NULL);
945     LOG(Plugins, "NPP_Destroy: %d", npErr);
946
947     instance->pdata = NULL;
948     
949     // We usually remove the key event handler in resignFirstResponder but it is possible that resignFirstResponder 
950     // may never get called so we can't completely rely on it.
951     [self removeKeyEventHandler];
952 }
953
954 - (BOOL)isStarted
955 {
956     return isStarted;
957 }
958
959 - (WebDataSource *)dataSource
960 {
961     // Do nothing. Overridden by subclasses.
962     return nil;
963 }
964
965 - (WebFrame *)webFrame
966 {
967     return [[self dataSource] webFrame];
968 }
969
970 - (WebView *)webView
971 {
972     return [[self webFrame] webView];
973 }
974
975 - (NSWindow *)currentWindow
976 {
977     return [self window] ? [self window] : [[self webView] hostWindow];
978 }
979
980 - (NPP)pluginPointer
981 {
982     return instance;
983 }
984
985 - (WebNetscapePluginPackage *)plugin
986 {
987     return plugin;
988 }
989
990 - (void)setPlugin:(WebNetscapePluginPackage *)thePlugin;
991 {
992     [thePlugin retain];
993     [plugin release];
994     plugin = thePlugin;
995
996     NPP_New =           [plugin NPP_New];
997     NPP_Destroy =       [plugin NPP_Destroy];
998     NPP_SetWindow =     [plugin NPP_SetWindow];
999     NPP_NewStream =     [plugin NPP_NewStream];
1000     NPP_WriteReady =    [plugin NPP_WriteReady];
1001     NPP_Write =         [plugin NPP_Write];
1002     NPP_StreamAsFile =  [plugin NPP_StreamAsFile];
1003     NPP_DestroyStream = [plugin NPP_DestroyStream];
1004     NPP_HandleEvent =   [plugin NPP_HandleEvent];
1005     NPP_URLNotify =     [plugin NPP_URLNotify];
1006     NPP_GetValue =      [plugin NPP_GetValue];
1007     NPP_SetValue =      [plugin NPP_SetValue];
1008     NPP_Print =         [plugin NPP_Print];
1009 }
1010
1011 - (void)setMIMEType:(NSString *)theMIMEType
1012 {
1013     NSString *type = [theMIMEType copy];
1014     [MIMEType release];
1015     MIMEType = type;
1016 }
1017
1018 - (void)setBaseURL:(NSURL *)theBaseURL
1019 {
1020     [theBaseURL retain];
1021     [baseURL release];
1022     baseURL = theBaseURL;
1023 }
1024
1025 - (void)setAttributeKeys:(NSArray *)keys andValues:(NSArray *)values;
1026 {
1027     ASSERT([keys count] == [values count]);
1028     
1029     // Convert the attributes to 2 C string arrays.
1030     // These arrays are passed to NPP_New, but the strings need to be
1031     // modifiable and live the entire life of the plugin.
1032
1033     // The Java plug-in requires the first argument to be the base URL
1034     if ([MIMEType isEqualToString:@"application/x-java-applet"]) {
1035         cAttributes = (char **)malloc(([keys count] + 1) * sizeof(char *));
1036         cValues = (char **)malloc(([values count] + 1) * sizeof(char *));
1037         cAttributes[0] = strdup("DOCBASE");
1038         cValues[0] = strdup([baseURL _web_URLCString]);
1039         argsCount++;
1040     } else {
1041         cAttributes = (char **)malloc([keys count] * sizeof(char *));
1042         cValues = (char **)malloc([values count] * sizeof(char *));
1043     }
1044
1045     BOOL isWMP = [[[plugin bundle] bundleIdentifier] isEqualToString:@"com.microsoft.WMP.defaultplugin"];
1046     
1047     unsigned i;
1048     unsigned count = [keys count];
1049     for (i = 0; i < count; i++) {
1050         NSString *key = [keys objectAtIndex:i];
1051         NSString *value = [values objectAtIndex:i];
1052         if ([key _web_isCaseInsensitiveEqualToString:@"height"]) {
1053             specifiedHeight = [value intValue];
1054         } else if ([key _web_isCaseInsensitiveEqualToString:@"width"]) {
1055             specifiedWidth = [value intValue];
1056         }
1057         // Avoid Window Media Player crash when these attributes are present.
1058         if (isWMP && ([key _web_isCaseInsensitiveEqualToString:@"SAMIStyle"] || [key _web_isCaseInsensitiveEqualToString:@"SAMILang"])) {
1059             continue;
1060         }
1061         cAttributes[argsCount] = strdup([key UTF8String]);
1062         cValues[argsCount] = strdup([value UTF8String]);
1063         LOG(Plugins, "%@ = %@", key, value);
1064         argsCount++;
1065     }
1066 }
1067
1068 - (void)setMode:(int)theMode
1069 {
1070     mode = theMode;
1071 }
1072
1073 #pragma mark NSVIEW
1074
1075 - initWithFrame:(NSRect)frame
1076 {
1077     [super initWithFrame:frame];
1078
1079     instance = &instanceStruct;
1080     instance->ndata = self;
1081
1082     streams = [[NSMutableArray alloc] init];
1083     streamNotifications = [[NSMutableDictionary alloc] init];
1084
1085     [[NSNotificationCenter defaultCenter] addObserver:self
1086                                              selector:@selector(preferencesHaveChanged:)
1087                                                  name:WebPreferencesChangedNotification
1088                                                object:nil];
1089
1090     return self;
1091 }
1092
1093 - (void)freeAttributeKeysAndValues
1094 {
1095     unsigned i;
1096     for (i = 0; i < argsCount; i++) {
1097         free(cAttributes[i]);
1098         free(cValues[i]);
1099     }
1100     free(cAttributes);
1101     free(cValues);
1102 }
1103
1104 - (void)dealloc
1105 {
1106     [[NSNotificationCenter defaultCenter] removeObserver:self];
1107     
1108     [self stop];
1109
1110     [plugin release];
1111     [streams release];
1112     [MIMEType release];
1113     [baseURL release];
1114     [streamNotifications release];
1115
1116     [self freeAttributeKeysAndValues];
1117
1118     [super dealloc];
1119 }
1120
1121 - (void)finalize
1122 {
1123     [[NSNotificationCenter defaultCenter] removeObserver:self];
1124
1125     // FIXME: Bad to stop at finalize time. Need to restructure code
1126     // so that we're already stopped before we get to this point.
1127     [self stop];
1128
1129     [self freeAttributeKeysAndValues];
1130
1131     [super finalize];
1132 }
1133
1134 - (void)drawRect:(NSRect)rect
1135 {
1136     if (!isStarted) {
1137         return;
1138     }
1139     
1140     if ([NSGraphicsContext currentContextDrawingToScreen]) {
1141         [self sendUpdateEvent];
1142     } else {
1143         // Printing 2862383
1144     }
1145 }
1146
1147 - (BOOL)isFlipped
1148 {
1149     return YES;
1150 }
1151
1152 -(void)tellQuickTimeToChill
1153 {
1154     // Make a call to the secret QuickDraw API that makes QuickTime calm down.
1155     WindowRef windowRef = [[self window] windowRef];
1156     if (!windowRef) {
1157         return;
1158     }
1159     CGrafPtr port = GetWindowPort(windowRef);
1160     Rect bounds;
1161     GetPortBounds(port, &bounds);
1162     CallDrawingNotifications(port, &bounds, kBitsProc);
1163 }
1164
1165 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
1166 {
1167     [self tellQuickTimeToChill];
1168
1169     // We must remove the tracking rect before we move to the new window.
1170     // Once we move to the new window, it will be too late.
1171     [self removeTrackingRect];
1172     [self removeWindowObservers];
1173
1174     if (!newWindow) {
1175         if ([[self webView] hostWindow]) {
1176             // View will be moved out of the actual window but it still has a host window.
1177             [self stopNullEvents];
1178         } else {
1179             // View will have no associated windows.
1180             [self stop];
1181         }
1182     }
1183 }
1184
1185 - (void)viewDidMoveToWindow
1186 {
1187     [self resetTrackingRect];
1188     
1189     if ([self window]) {
1190         // View moved to an actual window. Start it if not already started.
1191         [self start];
1192         [self restartNullEvents];
1193         [self addWindowObservers];
1194     } else if ([[self webView] hostWindow]) {
1195         // View moved out of an actual window, but still has a host window.
1196         // Call setWindow to explicitly "clip out" the plug-in from sight.
1197         // FIXME: It would be nice to do this where we call stopNullEvents in viewWillMoveToWindow.
1198         [self setWindow];
1199     }
1200 }
1201
1202 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
1203 {
1204     if (!hostWindow && ![self window]) {
1205         // View will have no associated windows.
1206         [self stop];
1207     }
1208 }
1209
1210 - (void)viewDidMoveToHostWindow
1211 {
1212     if ([[self webView] hostWindow]) {
1213         // View now has an associated window. Start it if not already started.
1214         [self start];
1215     }
1216 }
1217
1218 #pragma mark NOTIFICATIONS
1219
1220 - (void)viewHasMoved:(NSNotification *)notification
1221 {
1222     [self tellQuickTimeToChill];
1223     [self setWindow];
1224     [self resetTrackingRect];
1225 }
1226
1227 - (void)windowWillClose:(NSNotification *)notification
1228 {
1229     [self stop];
1230 }
1231
1232 - (void)windowBecameKey:(NSNotification *)notification
1233 {
1234     [self sendActivateEvent:YES];
1235     [self setNeedsDisplay:YES];
1236     [self restartNullEvents];
1237     SetUserFocusWindow([[self window] windowRef]);
1238 }
1239
1240 - (void)windowResignedKey:(NSNotification *)notification
1241 {
1242     [self sendActivateEvent:NO];
1243     [self setNeedsDisplay:YES];
1244     [self restartNullEvents];
1245 }
1246
1247 - (void)windowDidMiniaturize:(NSNotification *)notification
1248 {
1249     [self stopNullEvents];
1250 }
1251
1252 - (void)windowDidDeminiaturize:(NSNotification *)notification
1253 {
1254     [self restartNullEvents];
1255 }
1256
1257 - (void)loginWindowDidSwitchFromUser:(NSNotification *)notification
1258 {
1259     [self stopNullEvents];
1260 }
1261
1262 -(void)loginWindowDidSwitchToUser:(NSNotification *)notification
1263 {
1264     [self restartNullEvents];
1265 }
1266
1267 - (void)preferencesHaveChanged:(NSNotification *)notification
1268 {
1269     WebPreferences *preferences = [[self webView] preferences];
1270     BOOL arePlugInsEnabled = [preferences arePlugInsEnabled];
1271     
1272     if ([notification object] == preferences && isStarted != arePlugInsEnabled) {
1273         if (arePlugInsEnabled) {
1274             if ([self currentWindow]) {
1275                 [self start];
1276             }
1277         } else {
1278             [self stop];
1279             [self setNeedsDisplay:YES];
1280         }
1281     }
1282 }
1283
1284 - (void)frameStateChanged:(NSNotification *)notification
1285 {
1286     WebFrame *frame = [notification object];
1287     NSURL *URL = [[[frame dataSource] request] URL];
1288     NSValue *notifyDataValue = [streamNotifications objectForKey:URL];
1289     if (!notifyDataValue) {
1290         return;
1291     }
1292     
1293     void *notifyData = [notifyDataValue pointerValue];
1294     WebFrameState frameState = [[[notification userInfo] objectForKey:WebCurrentFrameState] intValue];
1295     if (frameState == WebFrameStateComplete) {
1296         if (isStarted) {
1297             NPP_URLNotify(instance, [URL _web_URLCString], NPRES_DONE, notifyData);
1298         }
1299         [streamNotifications removeObjectForKey:URL];
1300     }
1301
1302     //FIXME: Need to send other NPReasons
1303 }
1304
1305 - (void *)pluginScriptableObject
1306 {
1307     if (NPP_GetValue) {
1308         void *value = 0;
1309         NPError npErr = NPP_GetValue (instance, NPPVpluginScriptableNPObject, (void *)&value);
1310         if (npErr == NPERR_NO_ERROR) {
1311             return value;
1312         }
1313     }
1314     return (void *)0;
1315 }
1316
1317
1318 @end
1319
1320 @implementation WebBaseNetscapePluginView (WebNPPCallbacks)
1321
1322 - (NSMutableURLRequest *)requestWithURLCString:(const char *)URLCString
1323 {
1324     if (!URLCString) {
1325         return nil;
1326     }
1327     
1328     CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, URLCString, kCFStringEncodingWindowsLatin1);
1329     NSString *URLString = [(NSString *)string _web_stringByStrippingReturnCharacters];
1330     NSURL *URL = [NSURL _web_URLWithDataAsString:URLString relativeToURL:baseURL];
1331     CFRelease(string);
1332     if (!URL) {
1333         return nil;
1334     }
1335     
1336     return [NSMutableURLRequest requestWithURL:URL];
1337 }
1338
1339 - (void)evaluateJavaScriptPluginRequest:(WebPluginRequest *)JSPluginRequest
1340 {
1341     // FIXME: Is this isStarted check needed here? evaluateJavaScriptPluginRequest should not be called
1342     // if we are stopped since this method is called after a delay and we call 
1343     // cancelPreviousPerformRequestsWithTarget inside of stop.
1344     if (!isStarted) {
1345         return;
1346     }
1347     
1348     NSURL *URL = [[JSPluginRequest request] URL];
1349     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1350     ASSERT(JSString);
1351     
1352     NSString *result = [[[self webFrame] _bridge] stringByEvaluatingJavaScriptFromString:JSString];
1353     
1354     // Don't continue if stringByEvaluatingJavaScriptFromString caused the plug-in to stop.
1355     if (!isStarted) {
1356         return;
1357     }
1358     
1359     void *notifyData = [JSPluginRequest notifyData];
1360     
1361     if ([JSPluginRequest frameName] != nil) {
1362         // FIXME: If the result is a string, we probably want to put that string into the frame, just
1363         // like we do in KHTMLPartBrowserExtension::openURLRequest.
1364         if (notifyData) {
1365             NPP_URLNotify(instance, [URL _web_URLCString], NPRES_DONE, notifyData);
1366         }
1367     } else if ([result length] > 0) {
1368         // Don't call NPP_NewStream and other stream methods if there is no JS result to deliver. This is what Mozilla does.
1369         NSData *JSData = [result dataUsingEncoding:NSUTF8StringEncoding];
1370         WebBaseNetscapePluginStream *stream = [[WebBaseNetscapePluginStream alloc] init];
1371         [stream setPluginPointer:instance];
1372         [stream setNotifyData:notifyData];
1373         [stream startStreamWithURL:URL
1374              expectedContentLength:[JSData length]
1375                   lastModifiedDate:nil
1376                           MIMEType:@"text/plain"];
1377         [stream receivedData:JSData];
1378         [stream finishedLoadingWithData:JSData];
1379         [stream release];
1380     }
1381 }
1382
1383 - (void)loadPluginRequest:(WebPluginRequest *)pluginRequest
1384 {
1385     NSURLRequest *request = [pluginRequest request];
1386     NSString *frameName = [pluginRequest frameName];
1387     void *notifyData = [pluginRequest notifyData];
1388     WebFrame *frame = nil;
1389     
1390     NSURL *URL = [request URL];
1391     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1392     
1393     ASSERT(frameName || JSString);
1394     
1395     if (frameName) {
1396         // FIXME - need to get rid of this window creation which
1397         // bypasses normal targeted link handling
1398         frame = [[self webFrame] findFrameNamed:frameName];
1399     
1400         if (frame == nil) {
1401             WebView *newWebView = nil;
1402             WebView *currentWebView = [self webView];
1403             id wd = [currentWebView UIDelegate];
1404             if ([wd respondsToSelector:@selector(webView:createWebViewWithRequest:)]) {
1405                 newWebView = [wd webView:currentWebView createWebViewWithRequest:nil];
1406             } else {
1407                 newWebView = [[WebDefaultUIDelegate sharedUIDelegate] webView:currentWebView createWebViewWithRequest:nil];
1408             }
1409             
1410             [newWebView _setTopLevelFrameName:frameName];
1411             [[newWebView _UIDelegateForwarder] webViewShow:newWebView];
1412             frame = [newWebView mainFrame];
1413         }
1414     }
1415
1416     if (JSString) {
1417         ASSERT(frame == nil || [self webFrame] == frame);
1418         [self evaluateJavaScriptPluginRequest:pluginRequest];
1419     } else {
1420         [frame loadRequest:request];
1421         if (notifyData) {
1422             // FIXME: How do we notify about failures? It seems this will only notify about success.
1423         
1424             // FIXME: This will overwrite any previous notification for the same URL.
1425             // It might be better to keep track of these per frame.
1426             [streamNotifications setObject:[NSValue valueWithPointer:notifyData] forKey:URL];
1427             
1428             // FIXME: We add this same observer to a frame multiple times. Is that OK?
1429             // FIXME: This observer doesn't get removed until the plugin stops, so we could
1430             // end up with lots and lots of these.
1431             [[NSNotificationCenter defaultCenter] addObserver:self
1432                                                      selector:@selector(frameStateChanged:)
1433                                                          name:WebFrameStateChangedNotification
1434                                                        object:frame];
1435         }
1436     }
1437 }
1438
1439 - (NPError)loadRequest:(NSMutableURLRequest *)request inTarget:(const char *)cTarget withNotifyData:(void *)notifyData
1440 {
1441     NSURL *URL = [request URL];
1442
1443     if (!URL) {
1444         return NPERR_INVALID_URL;
1445     }
1446     
1447     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1448     if (JSString != nil) {
1449         if (![[[self webView] preferences] isJavaScriptEnabled]) {
1450             // Return NPERR_GENERIC_ERROR if JS is disabled. This is what Mozilla does.
1451             return NPERR_GENERIC_ERROR;
1452         } else if (cTarget == NULL && mode == NP_FULL) {
1453             // Don't allow a JavaScript request from a standalone plug-in that is self-targetted
1454             // because this can cause the user to be redirected to a blank page (3424039).
1455             return NPERR_INVALID_PARAM;
1456         }
1457     }
1458         
1459     if (cTarget || JSString) {
1460         // Make when targetting a frame or evaluating a JS string, perform the request after a delay because we don't
1461         // want to potentially kill the plug-in inside of its URL request.
1462         NSString *target = nil;
1463         if (cTarget) {
1464             // Find the frame given the target string.
1465             target = (NSString *)CFStringCreateWithCString(kCFAllocatorDefault, cTarget, kCFStringEncodingWindowsLatin1);
1466         }
1467         
1468         WebFrame *frame = [self webFrame];
1469         if (JSString != nil && target != nil && [frame findFrameNamed:target] != frame) {
1470             // For security reasons, only allow JS requests to be made on the frame that contains the plug-in.
1471             CFRelease(target);
1472             return NPERR_INVALID_PARAM;
1473         }
1474         
1475         [request setHTTPReferrer:[[[[[self webFrame] dataSource] request] URL] _web_originalDataAsString]];
1476         WebPluginRequest *pluginRequest = [[WebPluginRequest alloc] initWithRequest:request frameName:target notifyData:notifyData];
1477         [self performSelector:@selector(loadPluginRequest:) withObject:pluginRequest afterDelay:0];
1478         [pluginRequest release];
1479         if (target) {
1480             CFRelease(target);
1481         }
1482     } else {
1483         WebNetscapePluginStream *stream = [[WebNetscapePluginStream alloc]
1484             initWithRequest:request pluginPointer:instance notifyData:notifyData];
1485         if (!stream) {
1486             return NPERR_INVALID_URL;
1487         }
1488         [streams addObject:stream];
1489         [stream start];
1490         [stream release];
1491     }
1492     
1493     return NPERR_NO_ERROR;
1494 }
1495
1496 -(NPError)getURLNotify:(const char *)URLCString target:(const char *)cTarget notifyData:(void *)notifyData
1497 {
1498     LOG(Plugins, "NPN_GetURLNotify: %s target: %s", URLCString, cTarget);
1499
1500     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1501     return [self loadRequest:request inTarget:cTarget withNotifyData:notifyData];
1502 }
1503
1504 -(NPError)getURL:(const char *)URLCString target:(const char *)cTarget
1505 {
1506     LOG(Plugins, "NPN_GetURL: %s target: %s", URLCString, cTarget);
1507
1508     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1509     return [self loadRequest:request inTarget:cTarget withNotifyData:NULL];
1510 }
1511
1512 - (NPError)_postURLNotify:(const char *)URLCString
1513                    target:(const char *)target
1514                       len:(UInt32)len
1515                       buf:(const char *)buf
1516                      file:(NPBool)file
1517                notifyData:(void *)notifyData
1518              allowHeaders:(BOOL)allowHeaders
1519 {
1520     if (!URLCString || !len || !buf) {
1521         return NPERR_INVALID_PARAM;
1522     }
1523     
1524     NSData *postData = nil;
1525
1526     if (file) {
1527         // If we're posting a file, buf is either a file URL or a path to the file.
1528         NSString *bufString = (NSString *)CFStringCreateWithCString(kCFAllocatorDefault, buf, kCFStringEncodingWindowsLatin1);
1529         if (!bufString) {
1530             return NPERR_INVALID_PARAM;
1531         }
1532         NSURL *fileURL = [NSURL _web_URLWithDataAsString:bufString];
1533         NSString *path;
1534         if ([fileURL isFileURL]) {
1535             path = [fileURL path];
1536         } else {
1537             path = bufString;
1538         }
1539         postData = [NSData dataWithContentsOfFile:[path _web_fixedCarbonPOSIXPath]];
1540         CFRelease(bufString);
1541         if (!postData) {
1542             return NPERR_FILE_NOT_FOUND;
1543         }
1544     } else {
1545         postData = [NSData dataWithBytes:buf length:len];
1546     }
1547
1548     if ([postData length] == 0) {
1549         return NPERR_INVALID_PARAM;
1550     }
1551
1552     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1553     [request setHTTPMethod:@"POST"];
1554     
1555     if (allowHeaders) {
1556         if ([postData _web_startsWithBlankLine]) {
1557             postData = [postData subdataWithRange:NSMakeRange(1, [postData length] - 1)];
1558         } else {
1559             unsigned location = [postData _web_locationAfterFirstBlankLine];
1560             if (location != NSNotFound) {
1561                 // If the blank line is somewhere in the middle of postData, everything before is the header.
1562                 NSData *headerData = [postData subdataWithRange:NSMakeRange(0, location)];
1563                 NSMutableDictionary *header = [headerData _web_parseRFC822HeaderFields];
1564                 unsigned dataLength = [postData length] - location;
1565
1566                 // Sometimes plugins like to set Content-Length themselves when they post,
1567                 // but WebFoundation does not like that. So we will remove the header
1568                 // and instead truncate the data to the requested length.
1569                 NSString *contentLength = [header objectForKey:@"Content-Length"];
1570
1571                 if (contentLength != nil) {
1572                     dataLength = MIN((unsigned)[contentLength intValue], dataLength);
1573                 }
1574                 [header removeObjectForKey:@"Content-Length"];
1575
1576                 if ([header count] > 0) {
1577                     [request setAllHTTPHeaderFields:header];
1578                 }
1579                 // Everything after the blank line is the actual content of the POST.
1580                 postData = [postData subdataWithRange:NSMakeRange(location, dataLength)];
1581
1582             }
1583         }
1584         if ([postData length] == 0) {
1585             return NPERR_INVALID_PARAM;
1586         }
1587     }
1588
1589     // Plug-ins expect to receive uncached data when doing a POST (3347134).
1590     [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
1591     [request setHTTPBody:postData];
1592     
1593     return [self loadRequest:request inTarget:target withNotifyData:notifyData];
1594 }
1595
1596 - (NPError)postURLNotify:(const char *)URLCString
1597                   target:(const char *)target
1598                      len:(UInt32)len
1599                      buf:(const char *)buf
1600                     file:(NPBool)file
1601               notifyData:(void *)notifyData
1602 {
1603     LOG(Plugins, "NPN_PostURLNotify: %s", URLCString);
1604     return [self _postURLNotify:URLCString target:target len:len buf:buf file:file notifyData:notifyData allowHeaders:YES];
1605 }
1606
1607 -(NPError)postURL:(const char *)URLCString
1608            target:(const char *)target
1609               len:(UInt32)len
1610               buf:(const char *)buf
1611              file:(NPBool)file
1612 {
1613     LOG(Plugins, "NPN_PostURL: %s", URLCString);        
1614     // As documented, only allow headers to be specified via NPP_PostURL when using a file.
1615     return [self _postURLNotify:URLCString target:target len:len buf:buf file:file notifyData:NULL allowHeaders:file];
1616 }
1617
1618 -(NPError)newStream:(NPMIMEType)type target:(const char *)target stream:(NPStream**)stream
1619 {
1620     LOG(Plugins, "NPN_NewStream");
1621     return NPERR_GENERIC_ERROR;
1622 }
1623
1624 -(NPError)write:(NPStream*)stream len:(SInt32)len buffer:(void *)buffer
1625 {
1626     LOG(Plugins, "NPN_Write");
1627     return NPERR_GENERIC_ERROR;
1628 }
1629
1630 -(NPError)destroyStream:(NPStream*)stream reason:(NPReason)reason
1631 {
1632     LOG(Plugins, "NPN_DestroyStream");
1633     if (!stream->ndata) {
1634         return NPERR_INVALID_INSTANCE_ERROR;
1635     }
1636     [(WebBaseNetscapePluginStream *)stream->ndata cancelWithReason:reason];
1637     return NPERR_NO_ERROR;
1638 }
1639
1640 - (const char *)userAgent
1641 {
1642     return [[[self webView] userAgentForURL:baseURL] lossyCString];
1643 }
1644
1645 -(void)status:(const char *)message
1646 {    
1647     if (!message) {
1648         ERROR("NPN_Status passed a NULL status message");
1649         return;
1650     }
1651
1652     CFStringRef status = CFStringCreateWithCString(NULL, message, kCFStringEncodingWindowsLatin1);
1653     LOG(Plugins, "NPN_Status: %@", status);
1654     WebView *wv = [self webView];
1655     [[wv _UIDelegateForwarder] webView:wv setStatusText:(NSString *)status];
1656     CFRelease(status);
1657 }
1658
1659 -(void)invalidateRect:(NPRect *)invalidRect
1660 {
1661     LOG(Plugins, "NPN_InvalidateRect");
1662     [self setNeedsDisplayInRect:NSMakeRect(invalidRect->left, invalidRect->top,
1663         (float)invalidRect->right - invalidRect->left, (float)invalidRect->bottom - invalidRect->top)];
1664 }
1665
1666 -(void)invalidateRegion:(NPRegion)invalidRegion
1667 {
1668     LOG(Plugins, "NPN_InvalidateRegion");
1669     Rect invalidRect;
1670     GetRegionBounds(invalidRegion, &invalidRect);
1671     [self setNeedsDisplayInRect:NSMakeRect(invalidRect.left, invalidRect.top,
1672         (float)invalidRect.right - invalidRect.left, (float)invalidRect.bottom - invalidRect.top)];
1673 }
1674
1675 -(void)forceRedraw
1676 {
1677     LOG(Plugins, "forceRedraw");
1678     [self setNeedsDisplay:YES];
1679     [[self window] displayIfNeeded];
1680 }
1681
1682 - (NPError)getVariable:(NPNVariable)variable value:(void *)value
1683 {
1684     if (variable == NPNVWindowNPObject) {
1685         void **v = (void **)value;
1686         *v = [[[self webFrame] _bridge] windowScriptNPObject];
1687         return NPERR_NO_ERROR;
1688     }
1689     return NPERR_GENERIC_ERROR;
1690 }
1691
1692 @end
1693
1694 @implementation WebPluginRequest
1695
1696 - (id)initWithRequest:(NSURLRequest *)request frameName:(NSString *)frameName notifyData:(void *)notifyData
1697 {
1698     [super init];
1699     _request = [request retain];
1700     _frameName = [frameName retain];
1701     _notifyData = notifyData;
1702     return self;
1703 }
1704
1705 - (void)dealloc
1706 {
1707     [_request release];
1708     [_frameName release];
1709     [super dealloc];
1710 }
1711
1712 - (NSURLRequest *)request
1713 {
1714     return _request;
1715 }
1716
1717 - (NSString *)frameName
1718 {
1719     return _frameName;
1720 }
1721
1722 - (void *)notifyData
1723 {
1724     return _notifyData;
1725 }
1726
1727 @end
1728
1729 @implementation NSData (PluginExtras)
1730
1731 - (BOOL)_web_startsWithBlankLine
1732 {
1733     return [self length] > 0 && ((const char *)[self bytes])[0] == '\n';
1734 }
1735
1736 - (unsigned)_web_locationAfterFirstBlankLine
1737 {
1738     const char *bytes = (const char *)[self bytes];
1739     unsigned length = [self length];
1740
1741     unsigned i;
1742     for (i = 0; i < length - 2; i++) {
1743         if (bytes[i] == '\n' && (i == 0 || bytes[i+1] == '\n')) {
1744             i++;
1745             while (i < length - 2 && bytes[i] == '\n') {
1746                 i++;
1747             }
1748             return i;
1749         }
1750     }
1751     
1752     return NSNotFound;
1753 }
1754
1755 @end
1756
1757 void ConsoleConnectionChangeNotifyProc(CGSNotificationType type, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg)
1758 {
1759     NSString *notificationName = nil;
1760     if (type == kCGSessionConsoleConnect) {
1761         notificationName = LoginWindowDidSwitchToUserNotification;
1762     } else if (type == kCGSessionConsoleDisconnect) {
1763         notificationName = LoginWindowDidSwitchFromUserNotification;
1764     } else {
1765         ASSERT_NOT_REACHED();
1766     }
1767     [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:NSApp];
1768 }