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