OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 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 "chrome_frame/np_event_listener.h" |
| 6 |
| 7 #include "base/string_util.h" |
| 8 |
| 9 #include "third_party/xulrunner-sdk/win/include/string/nsEmbedString.h" |
| 10 #include "third_party/xulrunner-sdk/win/include/dom/nsIDOMElement.h" |
| 11 #include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEventTarget.h" |
| 12 #include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEvent.h" |
| 13 |
| 14 #include "chrome_frame/scoped_ns_ptr_win.h" |
| 15 #include "chrome_frame/ns_associate_iid_win.h" |
| 16 |
| 17 ASSOCIATE_IID(NS_IDOMELEMENT_IID_STR, nsIDOMElement); |
| 18 ASSOCIATE_IID(NS_IDOMNODE_IID_STR, nsIDOMNode); |
| 19 ASSOCIATE_IID(NS_IDOMEVENTTARGET_IID_STR, nsIDOMEventTarget); |
| 20 ASSOCIATE_IID(NS_IDOMEVENTLISTENER_IID_STR, nsIDOMEventListener); |
| 21 |
| 22 DomEventListener::DomEventListener(NpEventDelegate* delegate) |
| 23 : NpEventListenerBase<DomEventListener>(delegate) { |
| 24 } |
| 25 |
| 26 DomEventListener::~DomEventListener() { |
| 27 } |
| 28 |
| 29 // We implement QueryInterface etc ourselves in order to avoid |
| 30 // extra dependencies brought on by the NS_IMPL_* macros. |
| 31 NS_IMETHODIMP DomEventListener::QueryInterface(REFNSIID iid, void** ptr) { |
| 32 DCHECK(thread_id_ == ::GetCurrentThreadId()); |
| 33 nsresult res = NS_NOINTERFACE; |
| 34 |
| 35 if (memcmp(&iid, &__uuidof(nsIDOMEventListener), sizeof(nsIID)) == 0 || |
| 36 memcmp(&iid, &__uuidof(nsISupports), sizeof(nsIID)) == 0) { |
| 37 *ptr = static_cast<nsIDOMEventListener*>(this); |
| 38 AddRef(); |
| 39 res = NS_OK; |
| 40 } |
| 41 |
| 42 return res; |
| 43 } |
| 44 |
| 45 NS_IMETHODIMP DomEventListener::HandleEvent(nsIDOMEvent *event) { |
| 46 DCHECK(thread_id_ == ::GetCurrentThreadId()); |
| 47 DCHECK(event); |
| 48 |
| 49 nsEmbedString tag; |
| 50 event->GetType(tag); |
| 51 delegate_->OnEvent(WideToUTF8(tag.get()).c_str()); |
| 52 |
| 53 return NS_OK; |
| 54 } |
| 55 |
| 56 bool DomEventListener::Subscribe(NPP instance, |
| 57 const char* event_names[], |
| 58 int event_name_count) { |
| 59 DCHECK(event_names); |
| 60 DCHECK(event_name_count > 0); |
| 61 |
| 62 ScopedNsPtr<nsIDOMElement> element; |
| 63 bool ret = GetObjectElement(instance, element.Receive()); |
| 64 if (ret) { |
| 65 ScopedNsPtr<nsIDOMEventTarget> target; |
| 66 target.QueryFrom(element); |
| 67 if (target) { |
| 68 for (int i = 0; i < event_name_count && ret; ++i) { |
| 69 nsEmbedString name(ASCIIToWide(event_names[i]).c_str()); |
| 70 // See NPObjectEventListener::Subscribe (below) for a note on why |
| 71 // we set the useCapture parameter to PR_FALSE. |
| 72 nsresult res = target->AddEventListener(name, this, PR_FALSE); |
| 73 DCHECK(res == NS_OK) << "AddEventListener: " << event_names[i]; |
| 74 ret = NS_SUCCEEDED(res); |
| 75 } |
| 76 } else { |
| 77 DLOG(ERROR) << "failed to get nsIDOMEventTarget"; |
| 78 ret = false; |
| 79 } |
| 80 } |
| 81 |
| 82 return ret; |
| 83 } |
| 84 |
| 85 bool DomEventListener::Unsubscribe(NPP instance, |
| 86 const char* event_names[], |
| 87 int event_name_count) { |
| 88 DCHECK(event_names); |
| 89 DCHECK(event_name_count > 0); |
| 90 |
| 91 ScopedNsPtr<nsIDOMElement> element; |
| 92 bool ret = GetObjectElement(instance, element.Receive()); |
| 93 if (ret) { |
| 94 ScopedNsPtr<nsIDOMEventTarget> target; |
| 95 target.QueryFrom(element); |
| 96 if (target) { |
| 97 for (int i = 0; i < event_name_count && ret; ++i) { |
| 98 nsEmbedString name(ASCIIToWide(event_names[i]).c_str()); |
| 99 nsresult res = target->RemoveEventListener(name, this, PR_FALSE); |
| 100 DCHECK(res == NS_OK) << "RemoveEventListener: " << event_names[i]; |
| 101 ret = NS_SUCCEEDED(res) && ret; |
| 102 } |
| 103 } else { |
| 104 DLOG(ERROR) << "failed to get nsIDOMEventTarget"; |
| 105 ret = false; |
| 106 } |
| 107 } |
| 108 |
| 109 return ret; |
| 110 } |
| 111 |
| 112 bool DomEventListener::GetObjectElement(NPP instance, nsIDOMElement** element) { |
| 113 DCHECK(element); |
| 114 |
| 115 ScopedNsPtr<nsIDOMElement> elem; |
| 116 // Fetching the dom element works in Firefox, but is not implemented |
| 117 // in webkit. |
| 118 npapi::GetValue(instance, NPNVDOMElement, elem.Receive()); |
| 119 if (!elem.get()) { |
| 120 DLOG(INFO) << "Failed to get NPNVDOMElement"; |
| 121 return false; |
| 122 } |
| 123 |
| 124 nsEmbedString tag; |
| 125 nsresult res = elem->GetTagName(tag); |
| 126 if (NS_SUCCEEDED(res)) { |
| 127 if (LowerCaseEqualsASCII(tag.get(), tag.get() + tag.Length(), "embed")) { |
| 128 ScopedNsPtr<nsIDOMNode> parent; |
| 129 elem->GetParentNode(parent.Receive()); |
| 130 if (parent) { |
| 131 elem.Release(); |
| 132 res = parent.QueryInterface(elem.Receive()); |
| 133 DCHECK(NS_SUCCEEDED(res)); |
| 134 } |
| 135 } |
| 136 } else { |
| 137 NOTREACHED() << " GetTagName"; |
| 138 } |
| 139 |
| 140 *element = elem.Detach(); |
| 141 |
| 142 return *element != NULL; |
| 143 } |
| 144 |
| 145 /////////////////////////////////// |
| 146 // NPObjectEventListener |
| 147 |
| 148 NPObjectEventListener::NPObjectEventListener(NpEventDelegate* delegate) |
| 149 : NpEventListenerBase<NPObjectEventListener>(delegate) { |
| 150 } |
| 151 |
| 152 NPObjectEventListener::~NPObjectEventListener() { |
| 153 DLOG_IF(ERROR, npo_.get() == NULL); |
| 154 } |
| 155 |
| 156 NPObject* NPObjectEventListener::GetObjectElement(NPP instance) { |
| 157 NPObject* object = NULL; |
| 158 // We can't trust the return value from getvalue. |
| 159 // In Opera, the return value can be false even though the correct |
| 160 // object is returned. |
| 161 npapi::GetValue(instance, NPNVPluginElementNPObject, &object); |
| 162 |
| 163 if (object) { |
| 164 NPIdentifier* ids = GetCachedStringIds(); |
| 165 NPVariant var; |
| 166 if (npapi::GetProperty(instance, object, ids[TAG_NAME], &var)) { |
| 167 DCHECK(NPVARIANT_IS_STRING(var)); |
| 168 const NPString& np_tag = NPVARIANT_TO_STRING(var); |
| 169 std::string tag(np_tag.UTF8Characters, np_tag.UTF8Length); |
| 170 npapi::ReleaseVariantValue(&var); |
| 171 |
| 172 if (lstrcmpiA(tag.c_str(), "embed") == 0) { |
| 173 // We've got the <embed> element but we really want |
| 174 // the <object> element. |
| 175 if (npapi::GetProperty(instance, object, ids[PARENT_ELEMENT], &var)) { |
| 176 DCHECK(NPVARIANT_IS_OBJECT(var)); |
| 177 npapi::ReleaseObject(object); |
| 178 object = NPVARIANT_TO_OBJECT(var); |
| 179 } |
| 180 } else { |
| 181 DLOG(INFO) << __FUNCTION__ << " got " << tag; |
| 182 } |
| 183 } else { |
| 184 DLOG(INFO) << __FUNCTION__ << " failed to get the element's tag"; |
| 185 } |
| 186 } else { |
| 187 DLOG(WARNING) << __FUNCTION__ << " failed to get NPNVPluginElementNPObject"; |
| 188 } |
| 189 |
| 190 return object; |
| 191 } |
| 192 |
| 193 // Implementation of NpEventListener |
| 194 bool NPObjectEventListener::Subscribe(NPP instance, |
| 195 const char* event_names[], |
| 196 int event_name_count) { |
| 197 DCHECK(event_names); |
| 198 DCHECK(event_name_count > 0); |
| 199 DCHECK(npo_.get() == NULL); |
| 200 |
| 201 ScopedNpObject<> plugin_element(GetObjectElement(instance)); |
| 202 if (!plugin_element.get()) |
| 203 return false; |
| 204 |
| 205 // This object seems to be getting leaked :-( |
| 206 bool ret = false; |
| 207 npo_.Attach(reinterpret_cast<Npo*>( |
| 208 npapi::CreateObject(instance, PluginClass()))); |
| 209 |
| 210 if (!npo_.get()) { |
| 211 NOTREACHED() << "createobject"; |
| 212 } else { |
| 213 npo_->Initialize(this); |
| 214 ret = true; |
| 215 |
| 216 NPIdentifier* ids = GetCachedStringIds(); |
| 217 |
| 218 NPVariant args[3]; |
| 219 OBJECT_TO_NPVARIANT(npo_, args[1]); |
| 220 // We don't want to set 'capture' (last parameter) to true. |
| 221 // If we do, then in Opera, we'll simply not get callbacks unless |
| 222 // the target <object> or <embed> element we're syncing with has its |
| 223 // on[event] property assigned to some function handler. weird. |
| 224 // Ideally though we'd like to set capture to true since we'd like to |
| 225 // only be triggered for this particular object (and not for bubbling |
| 226 // events, but alas it's not meant to be. |
| 227 BOOLEAN_TO_NPVARIANT(false, args[2]); |
| 228 for (int i = 0; i < event_name_count; ++i) { |
| 229 ScopedNpVariant result; |
| 230 STRINGZ_TO_NPVARIANT(event_names[i], args[0]); |
| 231 ret = npapi::Invoke(instance, plugin_element, ids[ADD_EVENT_LISTENER], |
| 232 args, arraysize(args), &result) && ret; |
| 233 if (!ret) { |
| 234 DLOG(WARNING) << __FUNCTION__ << " invoke failed for " |
| 235 << event_names[i]; |
| 236 break; |
| 237 } |
| 238 } |
| 239 } |
| 240 |
| 241 return ret; |
| 242 } |
| 243 |
| 244 bool NPObjectEventListener::Unsubscribe(NPP instance, |
| 245 const char* event_names[], |
| 246 int event_name_count) { |
| 247 DCHECK(event_names); |
| 248 DCHECK(event_name_count > 0); |
| 249 DCHECK(npo_.get() != NULL); |
| 250 |
| 251 ScopedNpObject<> plugin_element(GetObjectElement(instance)); |
| 252 if (!plugin_element.get()) |
| 253 return false; |
| 254 |
| 255 NPIdentifier* ids = GetCachedStringIds(); |
| 256 |
| 257 NPVariant args[3]; |
| 258 OBJECT_TO_NPVARIANT(npo_, args[1]); |
| 259 BOOLEAN_TO_NPVARIANT(false, args[2]); |
| 260 for (int i = 0; i < event_name_count; ++i) { |
| 261 // TODO(tommi): look into why chrome isn't releasing the reference |
| 262 // count here. As it stands the reference count doesn't go down |
| 263 // and as a result, the NPO gets leaked. |
| 264 ScopedNpVariant result; |
| 265 STRINGZ_TO_NPVARIANT(event_names[i], args[0]); |
| 266 bool ret = npapi::Invoke(instance, plugin_element, |
| 267 ids[REMOVE_EVENT_LISTENER], args, arraysize(args), &result); |
| 268 DLOG_IF(ERROR, !ret) << __FUNCTION__ << " invoke: " << ret; |
| 269 } |
| 270 |
| 271 npo_.Free(); |
| 272 |
| 273 return true; |
| 274 } |
| 275 |
| 276 void NPObjectEventListener::HandleEvent(Npo* npo, NPObject* event) { |
| 277 DCHECK(npo); |
| 278 DCHECK(event); |
| 279 |
| 280 NPIdentifier* ids = GetCachedStringIds(); |
| 281 ScopedNpVariant result; |
| 282 bool ret = npapi::GetProperty(npo->npp(), event, ids[TYPE], &result); |
| 283 DCHECK(ret) << "getproperty(type)"; |
| 284 if (ret) { |
| 285 DCHECK(NPVARIANT_IS_STRING(result)); |
| 286 // Opera doesn't zero terminate its utf8 strings. |
| 287 const NPString& type = NPVARIANT_TO_STRING(result); |
| 288 std::string zero_terminated(type.UTF8Characters, type.UTF8Length); |
| 289 DLOG(INFO) << "handleEvent: " << zero_terminated; |
| 290 delegate_->OnEvent(zero_terminated.c_str()); |
| 291 } |
| 292 } |
| 293 |
| 294 NPClass* NPObjectEventListener::PluginClass() { |
| 295 static NPClass _np_class = { |
| 296 NP_CLASS_STRUCT_VERSION, |
| 297 reinterpret_cast<NPAllocateFunctionPtr>(AllocateObject), |
| 298 reinterpret_cast<NPDeallocateFunctionPtr>(DeallocateObject), |
| 299 NULL, // invalidate |
| 300 reinterpret_cast<NPHasMethodFunctionPtr>(HasMethod), |
| 301 reinterpret_cast<NPInvokeFunctionPtr>(Invoke), |
| 302 NULL, // InvokeDefault, |
| 303 NULL, // HasProperty, |
| 304 NULL, // GetProperty, |
| 305 NULL, // SetProperty, |
| 306 NULL // construct |
| 307 }; |
| 308 |
| 309 return &_np_class; |
| 310 } |
| 311 |
| 312 bool NPObjectEventListener::HasMethod(NPObjectEventListener::Npo* npo, |
| 313 NPIdentifier name) { |
| 314 NPIdentifier* ids = GetCachedStringIds(); |
| 315 if (name == ids[HANDLE_EVENT]) |
| 316 return true; |
| 317 |
| 318 return false; |
| 319 } |
| 320 |
| 321 bool NPObjectEventListener::Invoke(NPObjectEventListener::Npo* npo, |
| 322 NPIdentifier name, const NPVariant* args, |
| 323 uint32_t arg_count, NPVariant* result) { |
| 324 NPIdentifier* ids = GetCachedStringIds(); |
| 325 if (name != ids[HANDLE_EVENT]) |
| 326 return false; |
| 327 |
| 328 if (arg_count != 1 || !NPVARIANT_IS_OBJECT(args[0])) { |
| 329 NOTREACHED(); |
| 330 } else { |
| 331 NPObject* ev = NPVARIANT_TO_OBJECT(args[0]); |
| 332 npo->listener()->HandleEvent(npo, ev); |
| 333 } |
| 334 |
| 335 return true; |
| 336 } |
| 337 |
| 338 NPObject* NPObjectEventListener::AllocateObject(NPP instance, |
| 339 NPClass* class_name) { |
| 340 return new Npo(instance); |
| 341 } |
| 342 |
| 343 void NPObjectEventListener::DeallocateObject(NPObjectEventListener::Npo* npo) { |
| 344 delete npo; |
| 345 } |
| 346 |
| 347 NPIdentifier* NPObjectEventListener::GetCachedStringIds() { |
| 348 static NPIdentifier _identifiers[IDENTIFIER_COUNT] = {0}; |
| 349 if (!_identifiers[0]) { |
| 350 const NPUTF8* identifier_names[] = { |
| 351 "handleEvent", |
| 352 "type", |
| 353 "addEventListener", |
| 354 "removeEventListener", |
| 355 "tagName", |
| 356 "parentElement", |
| 357 }; |
| 358 COMPILE_ASSERT(arraysize(identifier_names) == arraysize(_identifiers), |
| 359 mismatched_array_size); |
| 360 npapi::GetStringIdentifiers(identifier_names, IDENTIFIER_COUNT, |
| 361 _identifiers); |
| 362 for (int i = 0; i < IDENTIFIER_COUNT; ++i) { |
| 363 DCHECK(_identifiers[i] != 0); |
| 364 } |
| 365 } |
| 366 return _identifiers; |
| 367 } |
OLD | NEW |