OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "webkit/activex_shim/activex_plugin.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/fix_wp64.h" | |
10 #include "base/scoped_comptr_win.h" | |
11 #include "base/string_util.h" | |
12 #include "googleurl/src/gurl.h" | |
13 #include "webkit/activex_shim/activex_shared.h" | |
14 #include "webkit/activex_shim/activex_util.h" | |
15 #include "webkit/activex_shim/npp_impl.h" | |
16 #include "webkit/activex_shim/npn_scripting.h" | |
17 #include "webkit/activex_shim/web_activex_site.h" | |
18 | |
19 using std::string; | |
20 using std::wstring; | |
21 | |
22 namespace activex_shim { | |
23 | |
24 // Constant strings used in SetProp. | |
25 const wchar_t WNDPROP_ORIGINAL_WNDPROC[] = L"activexshim_orgwndproc"; | |
26 | |
27 ActiveXPlugin::ActiveXPlugin(NPP instance) | |
28 : DispatchObject(NULL), | |
29 npp_(instance), | |
30 container_(NULL), | |
31 windowless_(false), | |
32 tried_activation_(false), | |
33 control_activated_(false), | |
34 activex_type_(ACTIVEX_GENERIC) { | |
35 rect_.left = 0; | |
36 rect_.top = 0; | |
37 rect_.right = 0; | |
38 rect_.bottom = 0; | |
39 } | |
40 | |
41 ActiveXPlugin::~ActiveXPlugin() { | |
42 // Releases all spawned Dispatch objects so that we won't have dangling | |
43 // references. | |
44 ReleaseSpawned(); | |
45 } | |
46 | |
47 // Firefox makes it pretty easy to distiguish between attrs and real params. | |
48 // it always places attrs first, then a pair with name "PARAM" and empty value. | |
49 // However, Chrome always put params first, then attrs. Need to figure out | |
50 // a way to handle them nicely. | |
51 void ActiveXPlugin::ProcessParams(int16 argc, char* argn[], char* argv[]) { | |
52 // TODO(ruijiang): This list is not exhaustive yet. Add all possible | |
53 // commmon attributes. | |
54 static const char* const excluded_param_names[] = { | |
55 "id", "name", "type", "class", "classid", | |
56 "codebase", "width", "height" }; | |
57 | |
58 // Handle parameters. | |
59 for (int i = 0; i < argc; i++) { | |
60 if (argn[i] == NULL) | |
61 continue; | |
62 | |
63 ControlParam param; | |
64 param.name = UTF8ToWide(argn[i]); | |
65 // Sometimes browser will pass NULL when no value is present. | |
66 if (argv[i] != NULL) { | |
67 param.value = UTF8ToWide(argv[i]); | |
68 } | |
69 if (LowerCaseEqualsASCII(param.name, "classid")) { | |
70 std::string clsid_ascii; | |
71 if (GetClsidFromClassidAttribute(argv[i], &clsid_ascii)) { | |
72 std::wstring raw_clsid = UTF8ToWide(clsid_ascii); | |
73 clsid_ = wstring(L"{") + raw_clsid + L"}"; | |
74 activex_type_ = MapClassIdToType(clsid_ascii); | |
75 } | |
76 } | |
77 if (LowerCaseEqualsASCII(param.name, "codebase")) { | |
78 codebase_ = param.value; | |
79 } | |
80 bool ignore = false; | |
81 for (int i = 0; i < arraysize(excluded_param_names); i++) { | |
82 if (LowerCaseEqualsASCII(param.name, excluded_param_names[i])) { | |
83 ignore = true; | |
84 break; | |
85 } | |
86 } | |
87 if (!ignore) | |
88 params_.push_back(param); | |
89 } | |
90 } | |
91 | |
92 void ActiveXPlugin::ConvertForEmbeddedWmp() { | |
93 clsid_ = L"{6bf52a52-394a-11d3-b153-00c04f79faa6}"; | |
94 ControlParam* existing_url_param = NULL; | |
95 std::wstring src; | |
96 // Find the src parameter and use it to add a new url parameter. | |
97 // Find the volume parameter and setup defaults which make sense in | |
98 // the Activex media player world. | |
99 for (unsigned int i = 0; i < params_.size(); i++) { | |
100 if (LowerCaseEqualsASCII(params_[i].name, "src")) { | |
101 src = params_[i].value; | |
102 } else if (LowerCaseEqualsASCII(params_[i].name, "url")) { | |
103 existing_url_param = ¶ms_[i]; | |
104 } else if (LowerCaseEqualsASCII(params_[i].name, "volume")) { | |
105 // In the NPAPI media player world a volume value lesser than | |
106 // -3000 turns off the volume. A volume value of 0 indicates | |
107 // full volume. Translate these to their Activex counterparts. | |
108 int specified_volume = 0; | |
109 if (StringToInt(params_[i].value, &specified_volume)) { | |
110 // Valid volume lies between 0 and -3000 | |
111 specified_volume = std::min (0, std::max(-3000, specified_volume)); | |
112 // Translate to a value between 0 and 100. | |
113 int activex_volume = specified_volume / 30 + 100; | |
114 params_[i].value = IntToWString(activex_volume); | |
115 } | |
116 } | |
117 } | |
118 | |
119 if (!src.empty()) { | |
120 if (existing_url_param == NULL) | |
121 params_.push_back(ControlParam(L"url", src)); | |
122 else | |
123 existing_url_param->value = src; | |
124 } | |
125 } | |
126 | |
127 NPError ActiveXPlugin::NPP_New(NPMIMEType plugin_type, int16 argc, char* argn[], | |
128 char* argv[], NPSavedData* saved) { | |
129 ProcessParams(argc, argn, argv); | |
130 | |
131 // If mimetype is not activex, it must be windows media type. Do necessary | |
132 // param conversion. | |
133 if (!IsMimeTypeActiveX(plugin_type)) | |
134 ConvertForEmbeddedWmp(); | |
135 | |
136 DCHECK(container_ == NULL); | |
137 container_.reset(new NoRefIUnknownImpl<WebActiveXContainer>); | |
138 // At this time we don't know the browser window yet. | |
139 container_->Init(this); | |
140 HRESULT hr = container_->CreateControlWithSite(clsid_.c_str()); | |
141 // TODO(ruijiang): We may still return OK, then show error inside the control | |
142 // so that user may get a chance to install it. | |
143 if (FAILED(hr)) | |
144 return NPERR_GENERIC_ERROR; | |
145 | |
146 // Does the control support windowless activation? | |
147 // TODO(ruijiang): temporary disable windowless plugin cause it's not fully | |
148 // working yet. | |
149 if (false && container_->GetFirstSite()->inplace_object_windowless_ != NULL) { | |
150 // TODO(ruijiang): Fix this. Right now Chrome will never return browser | |
151 // window when plugin hasn't set NPPVpluginWindowBool to false yet. | |
152 // Fix Chrome then we could remove this line. | |
153 g_browser->setvalue(npp_, NPPVpluginWindowBool, false); | |
154 // If we could get the container window successfully, we could go | |
155 // windowless. | |
156 HWND hwnd = NULL; | |
157 g_browser->getvalue(npp_, NPNVnetscapeWindow, &hwnd); | |
158 if (hwnd) { | |
159 container_->set_container_wnd(hwnd); | |
160 g_browser->setvalue(npp_, NPPVpluginWindowBool, false); | |
161 windowless_ = true; | |
162 } else { | |
163 g_browser->setvalue(npp_, NPPVpluginWindowBool, | |
164 reinterpret_cast<void*>(true)); | |
165 } | |
166 } | |
167 | |
168 // TODO(ruijiang): It is very common that controls query for the current url | |
169 // during activation. In the current Chrome multi-process structure this | |
170 // often causes deadlock (e.g. realplayer). Let's cache the url first while | |
171 // looking for ways to solve deadlock. | |
172 GetCurrentURL(); | |
173 return 0; | |
174 } | |
175 | |
176 void SubclassWindow(HWND hwnd, WNDPROC wndproc) { | |
177 LONG_PTR org_wndproc = SetWindowLongPtr( | |
178 hwnd, GWL_WNDPROC, reinterpret_cast<LONG_PTR>(wndproc)); | |
179 SetProp(hwnd, WNDPROP_ORIGINAL_WNDPROC, | |
180 reinterpret_cast<HANDLE>(org_wndproc)); | |
181 } | |
182 | |
183 // Unsubclass a window that has been subclassed by us (has the property | |
184 // WNDPROP_ORIGINAL_WNDPROC) | |
185 void UnsubclassWindow(HWND hwnd) { | |
186 WNDPROC org_wndproc = static_cast<WNDPROC>( | |
187 GetProp(hwnd, WNDPROP_ORIGINAL_WNDPROC)); | |
188 // Either this window has already been unsubclassed or it is not subclassed | |
189 // by us. | |
190 if (org_wndproc == NULL) | |
191 return; | |
192 SetWindowLongPtr(hwnd, | |
193 GWL_WNDPROC, | |
194 reinterpret_cast<LONG_PTR>(org_wndproc)); | |
195 RemoveProp(hwnd, WNDPROP_ORIGINAL_WNDPROC); | |
196 } | |
197 | |
198 // Window procedure to subclass Window created by control. | |
199 LRESULT CALLBACK ControlWindowProc(HWND hwnd, UINT msg, | |
200 WPARAM wparam, LPARAM lparam) { | |
201 WNDPROC org_wndproc = static_cast<WNDPROC>( | |
202 GetProp(hwnd, WNDPROP_ORIGINAL_WNDPROC)); | |
203 switch(msg) { | |
204 case WM_KEYDOWN: | |
205 if (wparam == VK_TAB) { | |
206 // TODO(ruijiang): Handle the tab key to transfer focus back to browser. | |
207 // HWND hparent = GetParent(hwnd); | |
208 // HWND hparent2 = GetParent(hparent); | |
209 // PostMessage(hparent2, WM_KEYDOWN, wparam, lparam); | |
210 // return 0; | |
211 } | |
212 break; | |
213 case WM_DESTROY: | |
214 // Unsubclass myself. | |
215 UnsubclassWindow(hwnd); | |
216 break; | |
217 } | |
218 if (org_wndproc != NULL) | |
219 return CallWindowProc(org_wndproc, hwnd, msg, wparam, lparam); | |
220 else | |
221 return 0; | |
222 } | |
223 | |
224 // NPP API Processing. | |
225 NPError ActiveXPlugin::NPP_SetWindow(NPWindow* window) { | |
226 if (window->type != NPWindowTypeWindow && | |
227 window->type != NPWindowTypeDrawable) | |
228 return NPERR_GENERIC_ERROR; | |
229 | |
230 // Remember the window position. This position is relative to the browser. | |
231 rect_.left = window->x; | |
232 rect_.top = window->y; | |
233 rect_.right = rect_.left + window->width; | |
234 rect_.bottom = rect_.top + window->height; | |
235 | |
236 RECT client; | |
237 client.left = 0; | |
238 client.top = 0; | |
239 client.right = window->width; | |
240 client.bottom = window->height; | |
241 | |
242 // This happens when we did not create the container because we do not | |
243 // allow initialization of certain ActiveX objects. | |
244 if (container_ == NULL) | |
245 return NPERR_GENERIC_ERROR; | |
246 | |
247 if (!tried_activation_) { | |
248 // Do not try activation again. | |
249 tried_activation_ = true; | |
250 | |
251 // For windowed controls we need to get the plugin window. | |
252 if (window->type == NPWindowTypeWindow) | |
253 container_->set_container_wnd(static_cast<HWND>(window->window)); | |
254 WebActiveXSite* site = container_->GetFirstSite(); | |
255 if (site == NULL) | |
256 return NPERR_GENERIC_ERROR; | |
257 POINT pos; | |
258 if (windowless()) { | |
259 pos.x = window->x; | |
260 pos.y = window->y; | |
261 } else { | |
262 pos.x = 0; | |
263 pos.y = 0; | |
264 } | |
265 HRESULT hr = site->ActivateControl(pos.x, pos.y, window->width, | |
266 window->height, params_); | |
267 if (FAILED(hr)) | |
268 return NPERR_GENERIC_ERROR; | |
269 | |
270 // We are done with activation. | |
271 control_activated_ = true; | |
272 | |
273 if (window->type == NPWindowTypeWindow) { | |
274 HWND hwnd = static_cast<HWND>(window->window); | |
275 // The window some browser (FF) created does not clip children. It will | |
276 // cause blinking of the control area during resizing, clicking etc. | |
277 SetWindowLong(hwnd, GWL_STYLE, | |
278 GetWindowLong(hwnd, GWL_STYLE) | WS_CLIPCHILDREN | | |
279 WS_CLIPSIBLINGS); | |
280 // If the control has a window, we need to subclass it. | |
281 IUnknown* control = container_->GetFirstControl(); | |
282 if (control) { | |
283 ScopedComPtr<IOleWindow> ole_window; | |
284 ole_window.QueryFrom(control); | |
285 if (ole_window != NULL) { | |
286 HWND control_wnd = NULL; | |
287 hr = ole_window->GetWindow(&control_wnd); | |
288 if (SUCCEEDED(hr)) { | |
289 SubclassWindow(control_wnd, ControlWindowProc); | |
290 } | |
291 } | |
292 } | |
293 } | |
294 return 0; | |
295 } else if (control_activated_) { | |
296 WebActiveXSite* site = container_->GetFirstSite(); | |
297 DCHECK(site != NULL); | |
298 if (window->type == NPWindowTypeWindow) { | |
299 site->SetRect(&client); | |
300 } else { | |
301 site->SetRect(&rect_); | |
302 } | |
303 return 0; | |
304 } else { | |
305 return NPERR_GENERIC_ERROR; | |
306 } | |
307 } | |
308 | |
309 NPError ActiveXPlugin::NPP_NewStream(NPMIMEType type, NPStream* stream, | |
310 NPBool seekable, uint16* stype) { | |
311 return 0; | |
312 } | |
313 | |
314 NPError ActiveXPlugin::NPP_DestroyStream(NPStream* stream, NPReason reason) { | |
315 return 0; | |
316 } | |
317 | |
318 int32 ActiveXPlugin::NPP_WriteReady(NPStream* stream) { | |
319 // TODO(ruijiang): Now returns an arbitary value. Will handle it later. | |
320 return 65536; | |
321 } | |
322 | |
323 int32 ActiveXPlugin::NPP_Write(NPStream* stream, int32 offset, int32 len, | |
324 void* buffer) { | |
325 // TODO(ruijiang): Pretend we have processed it. Otherwise FireFox will | |
326 // pretty much deadlock. | |
327 return len; | |
328 } | |
329 | |
330 void ActiveXPlugin::NPP_StreamAsFile(NPStream* stream, const char* fname) { | |
331 } | |
332 | |
333 void ActiveXPlugin::NPP_Print(NPPrint* platformPrint) { | |
334 } | |
335 | |
336 int16 ActiveXPlugin::NPP_HandleEvent(void* event) { | |
337 if (!control_activated_) | |
338 return NPERR_GENERIC_ERROR; | |
339 | |
340 NPEvent* evt = static_cast<NPEvent*>(event); | |
341 // TODO(ruijiang): Handle various events here for windowless control. | |
342 switch (evt->event) { | |
343 case WM_PAINT: | |
344 return HandlePaintEvent( | |
345 reinterpret_cast<HDC>(static_cast<LONG_PTR>(evt->wParam)), | |
346 reinterpret_cast<NPRect*>(static_cast<LONG_PTR>(evt->lParam))); | |
347 case WM_LBUTTONDOWN: | |
348 case WM_MBUTTONDOWN: | |
349 case WM_RBUTTONDOWN: | |
350 case WM_LBUTTONUP: | |
351 case WM_MBUTTONUP: | |
352 case WM_RBUTTONUP: | |
353 case WM_LBUTTONDBLCLK: | |
354 case WM_MBUTTONDBLCLK: | |
355 case WM_RBUTTONDBLCLK: | |
356 case WM_MOUSEMOVE: | |
357 case WM_KEYUP: | |
358 case WM_KEYDOWN: | |
359 case WM_SETFOCUS: | |
360 return HandleInputEvent(evt->event, evt->wParam, evt->lParam); | |
361 case WM_SETCURSOR: | |
362 // TODO(ruijiang): seems we are not getting this message | |
363 break; | |
364 case WM_KILLFOCUS: | |
365 // TODO(ruijiang): We are not getting this message yet. | |
366 break; | |
367 default: | |
368 break; | |
369 } | |
370 | |
371 return 0; | |
372 } | |
373 | |
374 int16 ActiveXPlugin::HandlePaintEvent(HDC dc, NPRect* invalid_area) { | |
375 // Chrome sets world transform by a certain offset in some cases, e.g., | |
376 // clicking on the control. This will cause unfortunate effect on ActiveX | |
377 // control, because some will try to adjust the drawing rect and reset the | |
378 // window/view point origin to 0. However, they are not aware of the new | |
379 // SetWolrTransform feature. Thus causing drawing off the real control area | |
380 // (see atlctl.h: CComControlBase::OnDrawAdvanced) | |
381 // On the other hand, FireFox never changes the origins. I've spent hours | |
382 // figuring out what went wrong... | |
383 int saved = SaveDC(dc); | |
384 | |
385 POINT offset; | |
386 offset.x = offset.y = 0; | |
387 // Easy way to figure out the difference between world and device. | |
388 LPtoDP(dc, &offset, 1); | |
389 RECT rc = rect_; | |
390 OffsetRect(&rc, offset.x, offset.y); | |
391 | |
392 // Reset everything so that device page has the same origin as the world. | |
393 SetWindowOrgEx(dc, 0, 0, NULL); | |
394 SetViewportOrgEx(dc, 0, 0, NULL); | |
395 if (GetGraphicsMode(dc) == GM_ADVANCED) { | |
396 XFORM f; | |
397 if (GetWorldTransform(dc, &f)) { | |
398 f.eDx = 0; | |
399 f.eDy = 0; | |
400 SetWorldTransform(dc, &f); | |
401 } | |
402 } | |
403 | |
404 WebActiveXSite* site = container_->GetFirstSite(); | |
405 if (site->view_object_ != NULL) | |
406 site->view_object_->Draw(DVASPECT_CONTENT, -1, NULL, NULL, NULL, dc, | |
407 reinterpret_cast<RECTL*>(&rc), NULL, NULL, 0); | |
408 | |
409 RestoreDC(dc, saved); | |
410 return 0; | |
411 } | |
412 | |
413 int16 ActiveXPlugin::HandleInputEvent(uint32 msg, uint32 wparam, | |
414 uint32 lparam) { | |
415 WebActiveXSite* site = container_->GetFirstSite(); | |
416 if (site->inplace_object_windowless_ == NULL) | |
417 return 0; | |
418 LRESULT result; | |
419 HRESULT hr = site->inplace_object_windowless_->OnWindowMessage( | |
420 msg, wparam, lparam, &result); | |
421 return 0; | |
422 } | |
423 | |
424 void ActiveXPlugin::NPP_URLNotify(const char* url, NPReason reason, | |
425 void* notifyData) { | |
426 } | |
427 | |
428 NPError ActiveXPlugin::NPP_GetValue(NPPVariable variable, void* value) { | |
429 if (variable == NPPVpluginScriptableNPObject) { | |
430 *(static_cast<void**>(value)) = GetScriptableNPObject(); | |
431 return 0; | |
432 } | |
433 return NPERR_GENERIC_ERROR; | |
434 } | |
435 | |
436 NPError ActiveXPlugin::NPP_SetValue(NPNVariable variable, void* value) { | |
437 // No setable value yet. | |
438 return NPERR_GENERIC_ERROR; | |
439 } | |
440 | |
441 void ActiveXPlugin::Draw(HDC dc, RECT* lprc, RECT* lpclip) { | |
442 // TODO(ruijiang): Temporary. Fix this later. | |
443 int ret = FillRect(dc, lprc, | |
444 static_cast<HBRUSH>(GetStockObject(DKGRAY_BRUSH))); | |
445 TextOut(dc, lprc->left, lprc->top, _T("HelloWorld"), 5); | |
446 } | |
447 | |
448 IDispatch* ActiveXPlugin::GetDispatch() { | |
449 if (container_ == NULL || container_->GetFirstControl() == NULL) | |
450 return NULL; | |
451 ScopedComPtr<IDispatch> disp; | |
452 disp.QueryFrom(container_->GetFirstControl()); | |
453 if (!disp) | |
454 return NULL; | |
455 IDispatch* res = disp.Detach(); | |
456 res->Release(); | |
457 return res; | |
458 } | |
459 | |
460 NPNScriptableObject ActiveXPlugin::GetWindow() { | |
461 if (!window_.IsValid()) { | |
462 NPObject* object = NULL; | |
463 g_browser->getvalue(npp_, NPNVWindowNPObject, &object); | |
464 window_ = NPNScriptableObject(npp_, object); | |
465 } | |
466 return window_; | |
467 } | |
468 | |
469 std::wstring ActiveXPlugin::GetCurrentURL() { | |
470 if (url_.size()) | |
471 return url_; | |
472 url_ = GetWindow().GetObjectProperty("document").GetStringProperty("URL"); | |
473 return url_; | |
474 } | |
475 | |
476 std::wstring ActiveXPlugin::ResolveURL(const std::wstring& url) { | |
477 // TODO(ruijiang): consider the base element of document. | |
478 std::wstring doc_url = GetCurrentURL(); | |
479 GURL base(doc_url); | |
480 GURL ret = base.Resolve(url); | |
481 return UTF8ToWide(ret.spec()); | |
482 } | |
483 | |
484 | |
485 } // namespace activex_shim | |
OLD | NEW |