Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

np_event_listener.cc

// 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 "base/utf_string_conversions.h"
#include "chrome_frame/scoped_ns_ptr_win.h"
#include "chrome_frame/ns_associate_iid_win.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"

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",
      "parentNode",
    };
    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;
}

Generated by  Doxygen 1.6.0   Back to index