| 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 |