Index: chrome_frame/np_event_listener.cc |
=================================================================== |
--- chrome_frame/np_event_listener.cc (revision 0) |
+++ chrome_frame/np_event_listener.cc (revision 0) |
@@ -0,0 +1,367 @@ |
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome_frame/np_event_listener.h" |
+ |
+#include "base/string_util.h" |
+ |
+#include "third_party/xulrunner-sdk/win/include/string/nsEmbedString.h" |
+#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMElement.h" |
+#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEventTarget.h" |
+#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEvent.h" |
+ |
+#include "chrome_frame/scoped_ns_ptr_win.h" |
+#include "chrome_frame/ns_associate_iid_win.h" |
+ |
+ASSOCIATE_IID(NS_IDOMELEMENT_IID_STR, nsIDOMElement); |
+ASSOCIATE_IID(NS_IDOMNODE_IID_STR, nsIDOMNode); |
+ASSOCIATE_IID(NS_IDOMEVENTTARGET_IID_STR, nsIDOMEventTarget); |
+ASSOCIATE_IID(NS_IDOMEVENTLISTENER_IID_STR, nsIDOMEventListener); |
+ |
+DomEventListener::DomEventListener(NpEventDelegate* delegate) |
+ : NpEventListenerBase<DomEventListener>(delegate) { |
+} |
+ |
+DomEventListener::~DomEventListener() { |
+} |
+ |
+// We implement QueryInterface etc ourselves in order to avoid |
+// extra dependencies brought on by the NS_IMPL_* macros. |
+NS_IMETHODIMP DomEventListener::QueryInterface(REFNSIID iid, void** ptr) { |
+ DCHECK(thread_id_ == ::GetCurrentThreadId()); |
+ nsresult res = NS_NOINTERFACE; |
+ |
+ if (memcmp(&iid, &__uuidof(nsIDOMEventListener), sizeof(nsIID)) == 0 || |
+ memcmp(&iid, &__uuidof(nsISupports), sizeof(nsIID)) == 0) { |
+ *ptr = static_cast<nsIDOMEventListener*>(this); |
+ AddRef(); |
+ res = NS_OK; |
+ } |
+ |
+ return res; |
+} |
+ |
+NS_IMETHODIMP DomEventListener::HandleEvent(nsIDOMEvent *event) { |
+ DCHECK(thread_id_ == ::GetCurrentThreadId()); |
+ DCHECK(event); |
+ |
+ nsEmbedString tag; |
+ event->GetType(tag); |
+ delegate_->OnEvent(WideToUTF8(tag.get()).c_str()); |
+ |
+ return NS_OK; |
+} |
+ |
+bool DomEventListener::Subscribe(NPP instance, |
+ const char* event_names[], |
+ int event_name_count) { |
+ DCHECK(event_names); |
+ DCHECK(event_name_count > 0); |
+ |
+ ScopedNsPtr<nsIDOMElement> element; |
+ bool ret = GetObjectElement(instance, element.Receive()); |
+ if (ret) { |
+ ScopedNsPtr<nsIDOMEventTarget> target; |
+ target.QueryFrom(element); |
+ if (target) { |
+ for (int i = 0; i < event_name_count && ret; ++i) { |
+ nsEmbedString name(ASCIIToWide(event_names[i]).c_str()); |
+ // See NPObjectEventListener::Subscribe (below) for a note on why |
+ // we set the useCapture parameter to PR_FALSE. |
+ nsresult res = target->AddEventListener(name, this, PR_FALSE); |
+ DCHECK(res == NS_OK) << "AddEventListener: " << event_names[i]; |
+ ret = NS_SUCCEEDED(res); |
+ } |
+ } else { |
+ DLOG(ERROR) << "failed to get nsIDOMEventTarget"; |
+ ret = false; |
+ } |
+ } |
+ |
+ return ret; |
+} |
+ |
+bool DomEventListener::Unsubscribe(NPP instance, |
+ const char* event_names[], |
+ int event_name_count) { |
+ DCHECK(event_names); |
+ DCHECK(event_name_count > 0); |
+ |
+ ScopedNsPtr<nsIDOMElement> element; |
+ bool ret = GetObjectElement(instance, element.Receive()); |
+ if (ret) { |
+ ScopedNsPtr<nsIDOMEventTarget> target; |
+ target.QueryFrom(element); |
+ if (target) { |
+ for (int i = 0; i < event_name_count && ret; ++i) { |
+ nsEmbedString name(ASCIIToWide(event_names[i]).c_str()); |
+ nsresult res = target->RemoveEventListener(name, this, PR_FALSE); |
+ DCHECK(res == NS_OK) << "RemoveEventListener: " << event_names[i]; |
+ ret = NS_SUCCEEDED(res) && ret; |
+ } |
+ } else { |
+ DLOG(ERROR) << "failed to get nsIDOMEventTarget"; |
+ ret = false; |
+ } |
+ } |
+ |
+ return ret; |
+} |
+ |
+bool DomEventListener::GetObjectElement(NPP instance, nsIDOMElement** element) { |
+ DCHECK(element); |
+ |
+ ScopedNsPtr<nsIDOMElement> elem; |
+ // Fetching the dom element works in Firefox, but is not implemented |
+ // in webkit. |
+ npapi::GetValue(instance, NPNVDOMElement, elem.Receive()); |
+ if (!elem.get()) { |
+ DLOG(INFO) << "Failed to get NPNVDOMElement"; |
+ return false; |
+ } |
+ |
+ nsEmbedString tag; |
+ nsresult res = elem->GetTagName(tag); |
+ if (NS_SUCCEEDED(res)) { |
+ if (LowerCaseEqualsASCII(tag.get(), tag.get() + tag.Length(), "embed")) { |
+ ScopedNsPtr<nsIDOMNode> parent; |
+ elem->GetParentNode(parent.Receive()); |
+ if (parent) { |
+ elem.Release(); |
+ res = parent.QueryInterface(elem.Receive()); |
+ DCHECK(NS_SUCCEEDED(res)); |
+ } |
+ } |
+ } else { |
+ NOTREACHED() << " GetTagName"; |
+ } |
+ |
+ *element = elem.Detach(); |
+ |
+ return *element != NULL; |
+} |
+ |
+/////////////////////////////////// |
+// NPObjectEventListener |
+ |
+NPObjectEventListener::NPObjectEventListener(NpEventDelegate* delegate) |
+ : NpEventListenerBase<NPObjectEventListener>(delegate) { |
+} |
+ |
+NPObjectEventListener::~NPObjectEventListener() { |
+ DLOG_IF(ERROR, npo_.get() == NULL); |
+} |
+ |
+NPObject* NPObjectEventListener::GetObjectElement(NPP instance) { |
+ NPObject* object = NULL; |
+ // We can't trust the return value from getvalue. |
+ // In Opera, the return value can be false even though the correct |
+ // object is returned. |
+ npapi::GetValue(instance, NPNVPluginElementNPObject, &object); |
+ |
+ if (object) { |
+ NPIdentifier* ids = GetCachedStringIds(); |
+ NPVariant var; |
+ if (npapi::GetProperty(instance, object, ids[TAG_NAME], &var)) { |
+ DCHECK(NPVARIANT_IS_STRING(var)); |
+ const NPString& np_tag = NPVARIANT_TO_STRING(var); |
+ std::string tag(np_tag.UTF8Characters, np_tag.UTF8Length); |
+ npapi::ReleaseVariantValue(&var); |
+ |
+ if (lstrcmpiA(tag.c_str(), "embed") == 0) { |
+ // We've got the <embed> element but we really want |
+ // the <object> element. |
+ if (npapi::GetProperty(instance, object, ids[PARENT_ELEMENT], &var)) { |
+ DCHECK(NPVARIANT_IS_OBJECT(var)); |
+ npapi::ReleaseObject(object); |
+ object = NPVARIANT_TO_OBJECT(var); |
+ } |
+ } else { |
+ DLOG(INFO) << __FUNCTION__ << " got " << tag; |
+ } |
+ } else { |
+ DLOG(INFO) << __FUNCTION__ << " failed to get the element's tag"; |
+ } |
+ } else { |
+ DLOG(WARNING) << __FUNCTION__ << " failed to get NPNVPluginElementNPObject"; |
+ } |
+ |
+ return object; |
+} |
+ |
+// Implementation of NpEventListener |
+bool NPObjectEventListener::Subscribe(NPP instance, |
+ const char* event_names[], |
+ int event_name_count) { |
+ DCHECK(event_names); |
+ DCHECK(event_name_count > 0); |
+ DCHECK(npo_.get() == NULL); |
+ |
+ ScopedNpObject<> plugin_element(GetObjectElement(instance)); |
+ if (!plugin_element.get()) |
+ return false; |
+ |
+ // This object seems to be getting leaked :-( |
+ bool ret = false; |
+ npo_.Attach(reinterpret_cast<Npo*>( |
+ npapi::CreateObject(instance, PluginClass()))); |
+ |
+ if (!npo_.get()) { |
+ NOTREACHED() << "createobject"; |
+ } else { |
+ npo_->Initialize(this); |
+ ret = true; |
+ |
+ NPIdentifier* ids = GetCachedStringIds(); |
+ |
+ NPVariant args[3]; |
+ OBJECT_TO_NPVARIANT(npo_, args[1]); |
+ // We don't want to set 'capture' (last parameter) to true. |
+ // If we do, then in Opera, we'll simply not get callbacks unless |
+ // the target <object> or <embed> element we're syncing with has its |
+ // on[event] property assigned to some function handler. weird. |
+ // Ideally though we'd like to set capture to true since we'd like to |
+ // only be triggered for this particular object (and not for bubbling |
+ // events, but alas it's not meant to be. |
+ BOOLEAN_TO_NPVARIANT(false, args[2]); |
+ for (int i = 0; i < event_name_count; ++i) { |
+ ScopedNpVariant result; |
+ STRINGZ_TO_NPVARIANT(event_names[i], args[0]); |
+ ret = npapi::Invoke(instance, plugin_element, ids[ADD_EVENT_LISTENER], |
+ args, arraysize(args), &result) && ret; |
+ if (!ret) { |
+ DLOG(WARNING) << __FUNCTION__ << " invoke failed for " |
+ << event_names[i]; |
+ break; |
+ } |
+ } |
+ } |
+ |
+ return ret; |
+} |
+ |
+bool NPObjectEventListener::Unsubscribe(NPP instance, |
+ const char* event_names[], |
+ int event_name_count) { |
+ DCHECK(event_names); |
+ DCHECK(event_name_count > 0); |
+ DCHECK(npo_.get() != NULL); |
+ |
+ ScopedNpObject<> plugin_element(GetObjectElement(instance)); |
+ if (!plugin_element.get()) |
+ return false; |
+ |
+ NPIdentifier* ids = GetCachedStringIds(); |
+ |
+ NPVariant args[3]; |
+ OBJECT_TO_NPVARIANT(npo_, args[1]); |
+ BOOLEAN_TO_NPVARIANT(false, args[2]); |
+ for (int i = 0; i < event_name_count; ++i) { |
+ // TODO(tommi): look into why chrome isn't releasing the reference |
+ // count here. As it stands the reference count doesn't go down |
+ // and as a result, the NPO gets leaked. |
+ ScopedNpVariant result; |
+ STRINGZ_TO_NPVARIANT(event_names[i], args[0]); |
+ bool ret = npapi::Invoke(instance, plugin_element, |
+ ids[REMOVE_EVENT_LISTENER], args, arraysize(args), &result); |
+ DLOG_IF(ERROR, !ret) << __FUNCTION__ << " invoke: " << ret; |
+ } |
+ |
+ npo_.Free(); |
+ |
+ return true; |
+} |
+ |
+void NPObjectEventListener::HandleEvent(Npo* npo, NPObject* event) { |
+ DCHECK(npo); |
+ DCHECK(event); |
+ |
+ NPIdentifier* ids = GetCachedStringIds(); |
+ ScopedNpVariant result; |
+ bool ret = npapi::GetProperty(npo->npp(), event, ids[TYPE], &result); |
+ DCHECK(ret) << "getproperty(type)"; |
+ if (ret) { |
+ DCHECK(NPVARIANT_IS_STRING(result)); |
+ // Opera doesn't zero terminate its utf8 strings. |
+ const NPString& type = NPVARIANT_TO_STRING(result); |
+ std::string zero_terminated(type.UTF8Characters, type.UTF8Length); |
+ DLOG(INFO) << "handleEvent: " << zero_terminated; |
+ delegate_->OnEvent(zero_terminated.c_str()); |
+ } |
+} |
+ |
+NPClass* NPObjectEventListener::PluginClass() { |
+ static NPClass _np_class = { |
+ NP_CLASS_STRUCT_VERSION, |
+ reinterpret_cast<NPAllocateFunctionPtr>(AllocateObject), |
+ reinterpret_cast<NPDeallocateFunctionPtr>(DeallocateObject), |
+ NULL, // invalidate |
+ reinterpret_cast<NPHasMethodFunctionPtr>(HasMethod), |
+ reinterpret_cast<NPInvokeFunctionPtr>(Invoke), |
+ NULL, // InvokeDefault, |
+ NULL, // HasProperty, |
+ NULL, // GetProperty, |
+ NULL, // SetProperty, |
+ NULL // construct |
+ }; |
+ |
+ return &_np_class; |
+} |
+ |
+bool NPObjectEventListener::HasMethod(NPObjectEventListener::Npo* npo, |
+ NPIdentifier name) { |
+ NPIdentifier* ids = GetCachedStringIds(); |
+ if (name == ids[HANDLE_EVENT]) |
+ return true; |
+ |
+ return false; |
+} |
+ |
+bool NPObjectEventListener::Invoke(NPObjectEventListener::Npo* npo, |
+ NPIdentifier name, const NPVariant* args, |
+ uint32_t arg_count, NPVariant* result) { |
+ NPIdentifier* ids = GetCachedStringIds(); |
+ if (name != ids[HANDLE_EVENT]) |
+ return false; |
+ |
+ if (arg_count != 1 || !NPVARIANT_IS_OBJECT(args[0])) { |
+ NOTREACHED(); |
+ } else { |
+ NPObject* ev = NPVARIANT_TO_OBJECT(args[0]); |
+ npo->listener()->HandleEvent(npo, ev); |
+ } |
+ |
+ return true; |
+} |
+ |
+NPObject* NPObjectEventListener::AllocateObject(NPP instance, |
+ NPClass* class_name) { |
+ return new Npo(instance); |
+} |
+ |
+void NPObjectEventListener::DeallocateObject(NPObjectEventListener::Npo* npo) { |
+ delete npo; |
+} |
+ |
+NPIdentifier* NPObjectEventListener::GetCachedStringIds() { |
+ static NPIdentifier _identifiers[IDENTIFIER_COUNT] = {0}; |
+ if (!_identifiers[0]) { |
+ const NPUTF8* identifier_names[] = { |
+ "handleEvent", |
+ "type", |
+ "addEventListener", |
+ "removeEventListener", |
+ "tagName", |
+ "parentElement", |
+ }; |
+ COMPILE_ASSERT(arraysize(identifier_names) == arraysize(_identifiers), |
+ mismatched_array_size); |
+ npapi::GetStringIdentifiers(identifier_names, IDENTIFIER_COUNT, |
+ _identifiers); |
+ for (int i = 0; i < IDENTIFIER_COUNT; ++i) { |
+ DCHECK(_identifiers[i] != 0); |
+ } |
+ } |
+ return _identifiers; |
+} |