1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#if USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
27
28#import "WebHostedNetscapePluginView.h"
29
30#import "HostedNetscapePluginStream.h"
31#import "NetscapePluginInstanceProxy.h"
32#import "NetscapePluginHostManager.h"
33#import "NetscapePluginHostProxy.h"
34#import "WebTextInputWindowController.h"
35#import "WebFrameInternal.h"
36#import "WebView.h"
37#import "WebViewInternal.h"
38#import "WebUIDelegate.h"
39
40#import <CoreFoundation/CoreFoundation.h>
41#import <WebCore/BridgeJSC.h>
42#import <WebCore/Frame.h>
43#import <WebCore/FrameLoaderTypes.h>
44#import <WebCore/FrameView.h>
45#import <WebCore/HTMLPlugInElement.h>
46#import <WebCore/RenderEmbeddedObject.h>
47#import <WebCore/ResourceError.h>
48#import <WebCore/WebCoreObjCExtras.h>
49#import <WebCore/RunLoop.h>
50#import <WebCore/runtime_root.h>
51#import <runtime/InitializeThreading.h>
52#import <wtf/Assertions.h>
53#import <wtf/MainThread.h>
54#import <wtf/ObjcRuntimeExtras.h>
55
56using namespace WebCore;
57using namespace WebKit;
58
59extern "C" {
60#include "WebKitPluginClientServer.h"
61#include "WebKitPluginHost.h"
62}
63
64#if HAVE(LAYER_HOSTING_IN_WINDOW_SERVER)
65@interface NSWindow (Details)
66- (BOOL)_hostsLayersInWindowServer;
67@end
68#endif
69
70@implementation WebHostedNetscapePluginView
71
72+ (void)initialize
73{
74    JSC::initializeThreading();
75    WTF::initializeMainThreadToProcessMainThread();
76    WebCore::RunLoop::initializeMainRunLoop();
77    WebCoreObjCFinalizeOnMainThread(self);
78    WKSendUserChangeNotifications();
79}
80
81- (id)initWithFrame:(NSRect)frame
82      pluginPackage:(WebNetscapePluginPackage *)pluginPackage
83                URL:(NSURL *)URL
84            baseURL:(NSURL *)baseURL
85           MIMEType:(NSString *)MIME
86      attributeKeys:(NSArray *)keys
87    attributeValues:(NSArray *)values
88       loadManually:(BOOL)loadManually
89            element:(PassRefPtr<WebCore::HTMLPlugInElement>)element
90{
91    self = [super initWithFrame:frame pluginPackage:pluginPackage URL:URL baseURL:baseURL MIMEType:MIME attributeKeys:keys attributeValues:values loadManually:loadManually element:element];
92    if (!self)
93        return nil;
94
95    return self;
96}
97
98- (void)handleMouseMoved:(NSEvent *)event
99{
100    if (_isStarted && _proxy)
101        _proxy->mouseEvent(self, event, NPCocoaEventMouseMoved);
102}
103
104- (void)setAttributeKeys:(NSArray *)keys andValues:(NSArray *)values
105{
106    ASSERT(!_attributeKeys);
107    ASSERT(!_attributeValues);
108
109    _attributeKeys = adoptNS([keys copy]);
110    _attributeValues = adoptNS([values copy]);
111}
112
113- (BOOL)windowHostsLayersInWindowServer
114{
115#if HAVE(LAYER_HOSTING_IN_WINDOW_SERVER)
116    return [[[self webView] window] _hostsLayersInWindowServer];
117#else
118    return false;
119#endif
120}
121
122- (BOOL)createPlugin
123{
124    ASSERT(!_proxy);
125
126    NSString *userAgent = [[self webView] userAgentForURL:_baseURL.get()];
127    BOOL acceleratedCompositingEnabled = false;
128#if USE(ACCELERATED_COMPOSITING)
129    acceleratedCompositingEnabled = [[[self webView] preferences] acceleratedCompositingEnabled];
130#endif
131    _hostsLayersInWindowServer = [self windowHostsLayersInWindowServer];
132
133    _proxy = NetscapePluginHostManager::shared().instantiatePlugin([_pluginPackage.get() path], [_pluginPackage.get() pluginHostArchitecture], [_pluginPackage.get() bundleIdentifier], self, _MIMEType.get(), _attributeKeys.get(), _attributeValues.get(), userAgent, _sourceURL.get(),
134                                                                   _mode == NP_FULL, _isPrivateBrowsingEnabled, acceleratedCompositingEnabled, _hostsLayersInWindowServer);
135    if (!_proxy)
136        return NO;
137
138    if (_proxy->rendererType() == UseSoftwareRenderer)
139        _softwareRenderer = WKSoftwareCARendererCreate(_proxy->renderContextID());
140    else
141        [self createPluginLayer];
142
143    // Update the window frame.
144    _proxy->windowFrameChanged([[self window] frame]);
145
146    return YES;
147}
148
149- (void)createPluginLayer
150{
151    BOOL acceleratedCompositingEnabled = false;
152#if USE(ACCELERATED_COMPOSITING)
153    acceleratedCompositingEnabled = [[[self webView] preferences] acceleratedCompositingEnabled];
154#endif
155
156    _pluginLayer = WKMakeRenderLayer(_proxy->renderContextID());
157
158    if (acceleratedCompositingEnabled && _proxy->rendererType() == UseAcceleratedCompositing) {
159        // FIXME: This code can be shared between WebHostedNetscapePluginView and WebNetscapePluginView.
160        // Since this layer isn't going to be inserted into a view, we need to create another layer and flip its geometry
161        // in order to get the coordinate system right.
162        RetainPtr<CALayer> realPluginLayer = adoptNS(_pluginLayer.leakRef());
163
164        _pluginLayer = adoptNS([[CALayer alloc] init]);
165        _pluginLayer.get().bounds = realPluginLayer.get().bounds;
166        _pluginLayer.get().geometryFlipped = YES;
167
168        _pluginLayer.get().backgroundColor = adoptCF(CGColorCreateGenericRGB(1, 0, 1, 1)).get();
169
170        realPluginLayer.get().autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
171        [_pluginLayer.get() addSublayer:realPluginLayer.get()];
172
173        // Eagerly enter compositing mode, since we know we'll need it. This avoids firing setNeedsStyleRecalc()
174        // for iframes that contain composited plugins at bad times. https://bugs.webkit.org/show_bug.cgi?id=39033
175        core([self webFrame])->view()->enterCompositingMode();
176        [self element]->setNeedsStyleRecalc(SyntheticStyleChange);
177    } else
178        self.wantsLayer = YES;
179}
180
181- (void)setHostsLayersInWindowServer:(bool)hostsLayersInWindowServer
182{
183    if (_proxy->rendererType() == UseSoftwareRenderer)
184        return;
185
186    RetainPtr<CALayer> currentSuperlayer = [_pluginLayer superlayer];
187
188    _hostsLayersInWindowServer = hostsLayersInWindowServer;
189
190    [self createPluginLayer];
191    [self setLayer:currentSuperlayer.get()];
192}
193
194// FIXME: This method is an ideal candidate to move up to the base class
195- (CALayer *)pluginLayer
196{
197    return _pluginLayer.get();
198}
199
200- (BOOL)getFormValue:(NSString **)value
201{
202    // FIXME: We cannot implement this method for now because NPP_GetValue
203    // is not currently exposed by the plugin host. Once we have an IPC routine
204    // which allows us to invoke NPP_GetValue, we could implement this method.
205    return false;
206}
207
208- (void)setLayer:(CALayer *)newLayer
209{
210    // FIXME: This should use the same implementation as WebNetscapePluginView (and move to the base class).
211    [super setLayer:newLayer];
212
213    if (_pluginLayer)
214        [newLayer addSublayer:_pluginLayer.get()];
215}
216
217- (void)privateBrowsingModeDidChange
218{
219    if (_proxy)
220        _proxy->privateBrowsingModeDidChange(_isPrivateBrowsingEnabled);
221}
222
223- (void)loadStream
224{
225}
226
227- (void)updateAndSetWindow
228{
229    if (!_proxy)
230        return;
231
232    // The base coordinates of a window and it's contentView happen to be the equal at a userSpaceScaleFactor
233    // of 1. For non-1.0 scale factors this assumption is false.
234    NSView *windowContentView = [[self window] contentView];
235    NSRect boundsInWindow = [self convertRect:[self bounds] toView:windowContentView];
236
237    NSRect visibleRectInWindow;
238
239    // Core Animation plug-ins need to be updated (with a 0,0,0,0 clipRect) when
240    // moved to a background tab. We don't do this for Core Graphics plug-ins as
241    // older versions of Flash have historical WebKit-specific code that isn't
242    // compatible with this behavior.
243    BOOL shouldClipOutPlugin = _pluginLayer && [self shouldClipOutPlugin];
244    if (!shouldClipOutPlugin)
245        visibleRectInWindow = [self actualVisibleRectInWindow];
246    else
247        visibleRectInWindow = NSZeroRect;
248
249    // Flip Y to convert NSWindow coordinates to top-left-based window coordinates.
250    float borderViewHeight = [[self currentWindow] frame].size.height;
251    boundsInWindow.origin.y = borderViewHeight - NSMaxY(boundsInWindow);
252
253    if (!shouldClipOutPlugin)
254        visibleRectInWindow.origin.y = borderViewHeight - NSMaxY(visibleRectInWindow);
255
256    _previousSize = boundsInWindow.size;
257
258    _proxy->resize(boundsInWindow, visibleRectInWindow);
259
260    bool shouldHostLayersInWindowServer = [self windowHostsLayersInWindowServer];
261    if (_hostsLayersInWindowServer != shouldHostLayersInWindowServer)
262        _proxy->setShouldHostLayersInWindowServer(shouldHostLayersInWindowServer);
263}
264
265- (void)windowFocusChanged:(BOOL)hasFocus
266{
267    if (_proxy)
268        _proxy->windowFocusChanged(hasFocus);
269}
270
271- (BOOL)shouldStop
272{
273    if (!_proxy)
274        return YES;
275
276    return _proxy->shouldStop();
277}
278
279- (void)destroyPlugin
280{
281    if (_proxy) {
282        if (_softwareRenderer) {
283            WKSoftwareCARendererDestroy(_softwareRenderer);
284            _softwareRenderer = 0;
285        }
286
287        _proxy->destroy();
288        _proxy = 0;
289    }
290
291    _pluginLayer = 0;
292}
293
294- (void)startTimers
295{
296    if (_proxy)
297        _proxy->startTimers(_isCompletelyObscured);
298}
299
300- (void)stopTimers
301{
302    if (_proxy)
303        _proxy->stopTimers();
304}
305
306- (void)focusChanged
307{
308    if (_proxy)
309        _proxy->focusChanged(_hasFocus);
310}
311
312- (void)windowFrameDidChange:(NSNotification *)notification
313{
314    if (_proxy && [self window])
315        _proxy->windowFrameChanged([[self window] frame]);
316}
317
318- (void)addWindowObservers
319{
320    [super addWindowObservers];
321
322    ASSERT([self window]);
323
324    NSWindow *window = [self window];
325
326    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
327    [notificationCenter addObserver:self selector:@selector(windowFrameDidChange:)
328                               name:NSWindowDidMoveNotification object:window];
329    [notificationCenter addObserver:self selector:@selector(windowFrameDidChange:)
330                               name:NSWindowDidResizeNotification object:window];
331
332    if (_proxy)
333        _proxy->windowFrameChanged([window frame]);
334    [self updateAndSetWindow];
335}
336
337- (void)removeWindowObservers
338{
339    [super removeWindowObservers];
340
341    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
342    [notificationCenter removeObserver:self name:NSWindowDidMoveNotification object:nil];
343    [notificationCenter removeObserver:self name:NSWindowDidResizeNotification object:nil];
344}
345
346- (void)mouseDown:(NSEvent *)event
347{
348    if (_isStarted && _proxy)
349        _proxy->mouseEvent(self, event, NPCocoaEventMouseDown);
350}
351
352- (void)mouseUp:(NSEvent *)event
353{
354    if (_isStarted && _proxy)
355        _proxy->mouseEvent(self, event, NPCocoaEventMouseUp);
356}
357
358- (void)mouseDragged:(NSEvent *)event
359{
360    if (_isStarted && _proxy)
361        _proxy->mouseEvent(self, event, NPCocoaEventMouseDragged);
362}
363
364- (void)handleMouseEntered:(NSEvent *)event
365{
366    // Set cursor to arrow. Plugins often handle cursor internally, but those that don't will just get this default one.
367    [[NSCursor arrowCursor] set];
368
369    if (_isStarted && _proxy)
370        _proxy->mouseEvent(self, event, NPCocoaEventMouseEntered);
371}
372
373- (void)handleMouseExited:(NSEvent *)event
374{
375    if (_isStarted && _proxy)
376        _proxy->mouseEvent(self, event, NPCocoaEventMouseExited);
377
378    // Set cursor back to arrow cursor.  Because NSCursor doesn't know about changes that the plugin made, we could get confused about what we think the
379    // current cursor is otherwise.  Therefore we have no choice but to unconditionally reset the cursor when the mouse exits the plugin.
380    // FIXME: This should be job of plugin host, see <rdar://problem/7654434>.
381    [[NSCursor arrowCursor] set];
382}
383
384- (void)scrollWheel:(NSEvent *)event
385{
386    bool processedEvent = false;
387
388    if (_isStarted && _proxy)
389        processedEvent = _proxy->wheelEvent(self, event);
390
391    if (!processedEvent)
392        [super scrollWheel:event];
393}
394
395- (NSTextInputContext *)inputContext
396{
397    return [[WebTextInputWindowController sharedTextInputWindowController] inputContext];
398}
399
400- (void)keyDown:(NSEvent *)event
401{
402    if (!_isStarted || !_proxy)
403        return;
404
405    NSString *string = nil;
406    if ([[WebTextInputWindowController sharedTextInputWindowController] interpretKeyEvent:event string:&string]) {
407        if (string)
408            _proxy->insertText(string);
409        return;
410    }
411
412    _proxy->keyEvent(self, event, NPCocoaEventKeyDown);
413}
414
415- (void)keyUp:(NSEvent *)event
416{
417    if (_isStarted && _proxy)
418        _proxy->keyEvent(self, event, NPCocoaEventKeyUp);
419}
420
421- (void)flagsChanged:(NSEvent *)event
422{
423    if (_isStarted && _proxy)
424        _proxy->flagsChanged(event);
425}
426
427- (void)sendModifierEventWithKeyCode:(int)keyCode character:(char)character
428{
429    if (_isStarted && _proxy)
430        _proxy->syntheticKeyDownWithCommandModifier(keyCode, character);
431}
432
433- (void)pluginHostDied
434{
435    if (_element->renderer() && _element->renderer()->isEmbeddedObject()) {
436        // FIXME: The renderer could also be a RenderApplet, we should handle that.
437        RenderEmbeddedObject* renderer = toRenderEmbeddedObject(_element->renderer());
438        renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginCrashed);
439    }
440
441    _pluginLayer = nil;
442    _proxy = 0;
443
444    // No need for us to be layer backed anymore
445    self.wantsLayer = NO;
446
447    [self invalidatePluginContentRect:[self bounds]];
448}
449
450- (void)visibleRectDidChange
451{
452    [super visibleRectDidChange];
453    WKSyncSurfaceToView(self);
454}
455
456- (void)drawRect:(NSRect)rect
457{
458    if (_cachedSnapshot) {
459        NSRect sourceRect = { NSZeroPoint, [_cachedSnapshot.get() size] };
460        [_cachedSnapshot.get() drawInRect:[self bounds] fromRect:sourceRect operation:NSCompositeSourceOver fraction:1];
461        return;
462    }
463
464    if (_proxy) {
465        if (_softwareRenderer) {
466            if ([NSGraphicsContext currentContextDrawingToScreen]) {
467                WKSoftwareCARendererRender(_softwareRenderer, (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort], NSRectToCGRect(rect));
468                _proxy->didDraw();
469            } else
470                _proxy->print(reinterpret_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]), [self bounds].size.width, [self bounds].size.height);
471        } else if (_snapshotting && [self supportsSnapshotting]) {
472            _proxy->snapshot(reinterpret_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]), [self bounds].size.width, [self bounds].size.height);
473        }
474
475        return;
476    }
477}
478
479- (PassRefPtr<JSC::Bindings::Instance>)createPluginBindingsInstance:(PassRefPtr<JSC::Bindings::RootObject>)rootObject
480{
481    if (!_proxy)
482        return 0;
483
484    return _proxy->createBindingsInstance(rootObject);
485}
486
487- (void)pluginView:(NSView *)pluginView receivedResponse:(NSURLResponse *)response
488{
489    ASSERT(_loadManually);
490    if (!_proxy)
491        return;
492
493    ASSERT(!_proxy->manualStream());
494
495    _proxy->setManualStream(HostedNetscapePluginStream::create(_proxy.get(), core([self webFrame])->loader()));
496    _proxy->manualStream()->startStreamWithResponse(response);
497}
498
499- (void)pluginView:(NSView *)pluginView receivedData:(NSData *)data
500{
501    ASSERT(_loadManually);
502    if (!_proxy)
503        return;
504
505    if (HostedNetscapePluginStream* manualStream = _proxy->manualStream())
506        manualStream->didReceiveData(0, static_cast<const char*>([data bytes]), [data length]);
507}
508
509- (void)pluginView:(NSView *)pluginView receivedError:(NSError *)error
510{
511    ASSERT(_loadManually);
512    if (!_proxy)
513        return;
514
515    if (HostedNetscapePluginStream* manualStream = _proxy->manualStream())
516        manualStream->didFail(0, error);
517}
518
519- (void)pluginViewFinishedLoading:(NSView *)pluginView
520{
521    ASSERT(_loadManually);
522    if (!_proxy)
523        return;
524
525    if (HostedNetscapePluginStream* manualStream = _proxy->manualStream())
526        manualStream->didFinishLoading(0);
527}
528
529- (void)_webPluginContainerCancelCheckIfAllowedToLoadRequest:(id)webPluginContainerCheck
530{
531    ASSERT([webPluginContainerCheck isKindOfClass:[WebPluginContainerCheck class]]);
532
533    id contextInfo = [webPluginContainerCheck contextInfo];
534    ASSERT([contextInfo isKindOfClass:[NSNumber class]]);
535
536    if (!_proxy)
537        return;
538
539    uint32_t checkID = [(NSNumber *)contextInfo unsignedIntValue];
540    _proxy->cancelCheckIfAllowedToLoadURL(checkID);
541}
542
543- (void)_containerCheckResult:(PolicyAction)policy contextInfo:(id)contextInfo
544{
545    ASSERT([contextInfo isKindOfClass:[NSNumber class]]);
546    if (!_proxy)
547        return;
548
549    uint32_t checkID = [(NSNumber *)contextInfo unsignedIntValue];
550    _proxy->checkIfAllowedToLoadURLResult(checkID, (policy == PolicyUse));
551}
552
553- (void)webFrame:(WebFrame *)webFrame didFinishLoadWithReason:(NPReason)reason
554{
555    if (_isStarted && _proxy)
556        _proxy->webFrameDidFinishLoadWithReason(webFrame, reason);
557}
558
559- (void)webFrame:(WebFrame *)webFrame didFinishLoadWithError:(NSError *)error
560{
561    NPReason reason = NPRES_DONE;
562    if (error)
563        reason = HostedNetscapePluginStream::reasonForError(error);
564    [self webFrame:webFrame didFinishLoadWithReason:reason];
565}
566
567@end
568
569#endif // USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
570