// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef V8_PROXY_H__
#define V8_PROXY_H__

#include <v8.h>
#include "v8_index.h"
#include "v8_custom.h"
#include "v8_utility.h"
#include "Node.h"
#include "NodeFilter.h"
#include "PlatformString.h"  // for WebCore::String
#include <wtf/HashMap.h>   // for HashMap

#include <iterator>
#include <list>

#ifdef ENABLE_DOM_STATS_COUNTERS
#include "base/stats_counters.h"
#define INC_STATS(name) StatsCounter(name).Increment()
#else
#define INC_STATS(name)
#endif

namespace WebCore {

class CSSStyleDeclaration;
class DOMImplementation;
class Element;
class Event;
class EventListener;
class Frame;
class HTMLCollection;
class HTMLOptionsCollection;
class HTMLElement;
class HTMLDocument;
class MediaList;
class NamedNodeMap;
class Node;
class NodeList;
class Screen;
class String;
class StyleSheet;
class SVGElement;
class DOMWindow;
class Document;
class EventTarget;
class Event;
class EventListener;
class Navigator;
class MimeType;
class MimeTypeArray;
class Plugin;
class PluginArray;
class EventTargetNode;
class StyleSheetList;
class CSSValue;
class CSSRule;
class CSSRuleList;
class CSSValueList;
class NodeFilter;

#if ENABLE(SVG)
class SVGElementInstance;
#endif

class V8EventListener;
class V8XHREventListener;
typedef std::list<V8EventListener*>  V8EventListenerList;

// TODO(fqian): use standard logging facilities in WebCore.
void log_info(Frame* frame, const String& msg, const String& url);


#ifndef NDEBUG

#define GlobalHandleTypeList(V) \
  V(PROXY)                      \
  V(NPOBJECT)                   \
  V(SCHEDULED_ACTION)           \
  V(EVENT_LISTENER)             \
  V(NODE_FILTER)                \
  V(JSINSTANCE)                 \


// Host information of persistent handles.
enum GlobalHandleType {
#define ENUM(name) name,
  GlobalHandleTypeList(ENUM)
#undef ENUM
};


class GlobalHandleInfo {
 public:
  GlobalHandleInfo(void* host, GlobalHandleType type)
      : host_(host), type_(type) { }
  void* host_;
  GlobalHandleType type_;
};

#endif  // NDEBUG

class V8Proxy {
 public:
  // The types of javascript errors that can be thrown.
  enum ErrorType {
    RANGE_ERROR,
    REFERENCE_ERROR,
    SYNTAX_ERROR,
    TYPE_ERROR,
    GENERAL_ERROR
  };

  explicit V8Proxy(Frame* frame)
      : m_frame(frame), m_inlineCode(false),
        m_timerCallback(false), m_recursion(0) { }

  ~V8Proxy();

  // Clear security token by setting the security token
  // for the context to the global object.
  void ClearSecurityToken();

  // Clear page-specific data, exception keep the global object identify.
  void clear();

  // Destroy the global object.
  void DestroyGlobal();

  Frame* frame() { return m_frame; }

  // TODO(mpcomplete): Need comment.  User Gesture related.
  bool inlineCode() const { return m_inlineCode; }
  void setInlineCode(bool value) { m_inlineCode = value; }

  bool timerCallback() const { return m_timerCallback; }
  void setTimerCallback(bool value) { m_timerCallback = value; }

  // Has the context for this proxy been initialized?
  bool ContextInitialized();

  // Disconnects the proxy from its owner frame,
  // and clears all timeouts on the DOM window.
  void disconnectFrame();

  bool isEnabled();

  // Remove 'document' property from the global object.
  void clearDocumentWrapper();

  // Find/Create/Remove event listener wrappers.
  V8EventListener* FindV8EventListener(v8::Local<v8::Value> listener,
                                       bool html);
  V8EventListener* FindOrCreateV8EventListener(v8::Local<v8::Value> listener,
                                               bool html);

  V8EventListener* FindXHREventListener(v8::Local<v8::Value> listener,
                                        bool html);
  V8EventListener* FindOrCreateXHREventListener(v8::Local<v8::Value> listener,
                                                bool html);

  void RemoveV8EventListener(V8EventListener* listener);
  void RemoveXHREventListener(V8XHREventListener* listener);

  // Protect/Unprotect JS wrappers of a DOM object.
  static void GCProtect(Peerable* dom_object);
  static void GCUnprotect(Peerable* dom_object);

  // Create a lazy event listener.
  EventListener* createHTMLEventHandler(const String& functionName,
                                        const String& code, Node* node);
#if ENABLE(SVG)
  EventListener* createSVGEventHandler(const String& functionName,
                                        const String& code, Node* node);

  static void SetSVGContext(void* object, SVGElement* context);
  static SVGElement* GetSVGContext(void* object);
#endif

  void setEventHandlerLineno(int lineno) { m_handlerLineno = lineno; }
  void finishedWithEvent(Event* event) { }

  // Evaluate a script file in the current execution environment.
  // The caller must hold an execution context.
  // If cannot evalute the script, it returns an error.
  v8::Local<v8::Value> Evaluate(const String& filename, int baseLine,
                                const String& code, Node* node);

  // Run an already compiled script.
  v8::Local<v8::Value> RunScript(v8::Handle<v8::Script> script,
                                 bool inline_code);

  // Call the function with the given receiver and arguments.
  v8::Local<v8::Value> CallFunction(v8::Handle<v8::Function> function,
                                    v8::Handle<v8::Object> receiver,
                                    int argc,
                                    v8::Handle<v8::Value> argv[]);

  // Returns the window object of the currently executing context.
  static DOMWindow* retrieveWindow();
  // Returns V8Proxy object of the currently executing context.
  static V8Proxy* retrieve();
  // Returns V8Proxy object associated with a frame.
  static V8Proxy* retrieve(Frame* frame);
  // Returns the frame object of the window object associated
  // with the currently executing context.
  static Frame* retrieveFrame();
  // Returns the frame object of the window object associated with
  // an context.
  static Frame* retrieveFrame(v8::Handle<v8::Context> context);
  // Returns the frame that started JS execution.
  // NOTE: cannot declare retrieveActiveFrame as inline function,
  // VS complains at linking time.
  static Frame* retrieveActiveFrame();

  // Returns V8 Context of a frame. If none exists, creates
  // a new context.  It is potentially slow and consumes memory.
  static v8::Local<v8::Context> GetContext(Frame* frame);
  static v8::Local<v8::Context> GetCurrentContext();

  // If the current context causes out of memory, JavaScript setting
  // is disabled and it returns true.
  static bool HandleOutOfMemory();

  // Generate the security token for a context.
  static v8::Handle<v8::Value> GenerateSecurityToken(
      v8::Local<v8::Context> context);

  // Check if the active execution context is from the same origin
  // as the target frame.
  static bool IsFromSameOrigin(Frame* target, bool report_error);

  // Check if it is safe to access the given node from the
  // current security context.
  static bool CheckNodeSecurity(Node* node);

  // Return true if the current security context can access the target frame.
  static bool CanAccess(Frame* target);

  // Create a V8 wrapper for a C pointer
  static v8::Handle<v8::Value> WrapCPointer(void* cptr);

  static v8::Handle<v8::Value> CheckNewLegal(const v8::Arguments& args);

  // Take C pointer out of a v8 wrapper
  template <class C>
  static C* ExtractCPointer(v8::Handle<v8::Value> obj) {
    return static_cast<C*>(ExtractCPointerImpl(obj));
  }


  static v8::Handle<v8::Script> CompileScript(v8::Handle<v8::String> code,
                                              const String& fileName,
                                              int baseLine);

  // Checks if a v8 value can be a DOM wrapper
  static bool MaybeDOMWrapper(v8::Handle<v8::Value> obj);

  // Sets contents of a DOM wrapper, returns false if
  // obj is not a DOM wrapper type
  static bool SetDOMWrapper(v8::Handle<v8::Object> obj, int type, void* ptr);

  static v8::Handle<v8::Object> LookupDOMWrapper(
    V8ClassIndex::V8WrapperType type, v8::Handle<v8::Value> value);

  // A helper function extract native object pointer from a DOM wrapper
  // and cast to the specified type.
  template <class C>
  static C* DOMWrapperToNative(v8::Handle<v8::Value> object) {
    if (!MaybeDOMWrapper(object))
      return 0;
    return ExtractCPointer<C>(
        v8::Handle<v8::Object>::Cast(object)->GetInternalField(0));
  }

  // A helper function extract native object pointer from a DOM wrapper
  // and cast to the specified type.
  template <class C>
  static C* FastDOMWrapperToNative(v8::Handle<v8::Value> object) {
    ASSERT(MaybeDOMWrapper(object));
    return ExtractCPointer<C>(
        v8::Handle<v8::Object>::Cast(object)->GetInternalField(0));
  }

  // A help function extract a node type pointer from a DOM wrapper.
  // Wrapped pointer must be cast to Node* first.
  template <class C>
  static C* DOMWrapperToNode(v8::Handle<v8::Value> object) {
    if (!MaybeDOMWrapper(object))
      return 0;
    v8::Handle<v8::Value> wrapper =
      v8::Handle<v8::Object>::Cast(object)->GetInternalField(0);
    return static_cast<C*>(ExtractCPointer<Node>(wrapper));
  }

  static v8::Handle<v8::Value> ToV8Object(V8ClassIndex::V8WrapperType type,
                                          void* imp);

  template <class C>
  static C* FastToNativeObject(V8ClassIndex::V8WrapperType type,
                               v8::Handle<v8::Value> object) {
    return static_cast<C*>(FastToNativeObjectImpl(type, object));
  }

  template <class C>
  static C* ToNativeObject(V8ClassIndex::V8WrapperType type,
                           v8::Handle<v8::Value> object) {
    return static_cast<C*>(ToNativeObjectImpl(type, object));
  }

  static V8ClassIndex::V8WrapperType GetDOMWrapperType(
      v8::Handle<v8::Object> object);

  // If the exception code is different from zero, a DOM exception is
  // schedule to be thrown.
  static void SetDOMException(int exception_code);

  // Schedule an error object to be thrown.
  static v8::Handle<v8::Value> ThrowError(ErrorType type, const char* message);

  // Create an instance of a function descriptor and set to the global object
  // as a named property. Used by v8_test_shell.
  static void V8Proxy::BindJSObjectToWindow(Frame* frame,
                                          const char* name,
                                          int type,
                                          v8::Handle<v8::FunctionTemplate> desc,
                                          void* imp);

  static v8::Handle<v8::Value> EventToV8Object(Event* event);
  static Event* ToNativeEvent(v8::Handle<v8::Value> jsevent) {
    return DOMWrapperToNative<Event>(jsevent);
  }

  static v8::Handle<v8::Value> EventTargetToV8Object(EventTarget* target);
  // Wrap and unwrap JS event listeners
  static v8::Handle<v8::Value> EventListenerToV8Object(EventListener* target);

  // DOMImplementation is a singleton and it is handled in a special
  // way.  A wrapper is generated per document and stored in an
  // internal field of the document.  When wrapping the
  // DOMImplementation object, the peer field is not set.
  static v8::Handle<v8::Value> DOMImplementationToV8Object(
      DOMImplementation* impl);

  // Wrap JS node filter in C++
  static NodeFilter* ToNativeNodeFilter(v8::Handle<v8::Value> filter);

  static v8::Persistent<v8::FunctionTemplate> GetTemplate(
      V8ClassIndex::V8WrapperType type);

  template <int tag, typename T>
    static v8::Handle<v8::Value> ConstructDOMObject(const v8::Arguments& args);

  // Set JS wrapper of a DOM object
  static void SetJSWrapperForDOMObject(Peerable* obj,
                                       v8::Persistent<v8::Object> wrapper);
  static void SetJSWrapperForDOMNode(Node* node,
                                     v8::Persistent<v8::Object> wrapper);

  // Domain of a frame is changed, invalidate its security token.
  static void DomainChanged(Frame* frame);

  // Process any pending JavaScript console messages.
  static void ProcessConsoleMessages();

#ifndef NDEBUG
  // For debugging and leak detection purpose
  static void RegisterGlobalHandle(GlobalHandleType type,
                                   void* host,
                                   v8::Persistent<v8::Value> handle);
  static void UnregisterGlobalHandle(void* host,
                                     v8::Persistent<v8::Value> handle);
#endif

 private:
  void initContextIfNeeded();
  void DisconnectEventListeners();

  static void* ToNativeObjectImpl(V8ClassIndex::V8WrapperType type,
                                  v8::Handle<v8::Value> object);
  static void* FastToNativeObjectImpl(V8ClassIndex::V8WrapperType type,
                                      v8::Handle<v8::Value> object);

  // Take C pointer out of a v8 wrapper
  static void* ExtractCPointerImpl(v8::Handle<v8::Value> obj);

  static v8::Handle<v8::Object> NodeToV8Object(Node* node);
  static v8::Handle<v8::Object> StyleSheetToV8Object(StyleSheet* sheet);
  static v8::Handle<v8::Object> CSSValueToV8Object(CSSValue* value);
  static v8::Handle<v8::Object> CSSRuleToV8Object(CSSRule* rule);
  // Returns the JS wrapper of a window object, initializes the environment
  // of the window frame if needed.
  static v8::Handle<v8::Object> V8Proxy::WindowToV8Object(DOMWindow* window);

#if ENABLE(SVG)
  static v8::Handle<v8::Object> SVGElementInstanceToV8Object(
      SVGElementInstance* instance);
  static v8::Handle<v8::Object> SVGObjectWithContextToV8Object(
    Peerable* object, V8ClassIndex::V8WrapperType type);
#endif

  static V8ClassIndex::V8WrapperType GetHTMLElementType(HTMLElement* elm);

  static v8::Local<v8::Object> InstantiateV8Object(
      V8ClassIndex::V8WrapperType type, void* impl);

  static const char* GetRangeExceptionName(int exception_code);
  static const char* GetEventExceptionName(int exception_code);
  static const char* GetXMLHttpRequestExceptionName(int exception_code);
  static const char* GetDOMExceptionName(int exception_code);

#if ENABLE(XPATH)
  static const char* GetXPathExceptionName(int exception_code);
#endif

#if ENABLE(SVG)
  static V8ClassIndex::V8WrapperType GetSVGElementType(SVGElement* elm);
  static const char* GetSVGExceptionName(int exception_code);
#endif

  // Update m_document field, dispose old one and create a string reference
  // to the new one.
  void UpdateDocumentHandle(v8::Local<v8::Object> handle);

  // Returns a local handle of the context.
  v8::Local<v8::Context> GetContext() {
    return v8::Local<v8::Context>::New(m_context);
  }

  Frame* m_frame;
  v8::Persistent<v8::Context> m_context;
  v8::Persistent<v8::Object> m_global;

  // Special handling of document wrapper;
  v8::Persistent<v8::Object> m_document;

  int m_handlerLineno;

  // A list of event listeners created for this frame,
  // the list gets cleared when removing all timeouts.
  V8EventListenerList m_event_listeners;

  // A list of event listeners create for XMLHttpRequest object for this frame,
  // the list gets cleared when removing all timeouts.
  V8EventListenerList m_xhr_listeners;

  // True for <a href="javascript:foo()"> and false for <script>foo()</script>.
  // Only valid during execution.
  bool m_inlineCode;

  // True when executing from within a timer callback.  Only valid during
  // execution.
  bool m_timerCallback;

  // Track the recursion depth to be able to avoid too deep recursion. The V8
  // engine allows much more recursion than KJS does so we need to guard against
  // excessive recursion in the binding layer.
  int m_recursion;
};


// Add indexed getter to the function template for a collection.
template <class T>
static void SetCollectionIndexedGetter(v8::Handle<v8::FunctionTemplate> desc,
                                       V8ClassIndex::V8WrapperType type) {
  desc->InstanceTemplate()->SetIndexedPropertyHandler(
      CollectionIndexedPropertyGetter<T>,
      0,
      0,
      0,
      CollectionIndexedPropertyEnumerator<T>,
      v8::External::New(reinterpret_cast<void*>(type)));
}

template <int tag, typename T>
v8::Handle<v8::Value> V8Proxy::ConstructDOMObject(const v8::Arguments& args) {
  if (!args.IsConstructCall()) {
    V8Proxy::ThrowError(V8Proxy::TYPE_ERROR,
        "DOM object constructor cannot be called as a function.");
    return v8::Undefined();
  }
  T* obj = new T();
  V8Proxy::SetDOMWrapper(args.Holder(), tag, obj);
  V8Proxy::SetJSWrapperForDOMObject(
      obj, v8::Persistent<v8::Object>::New(args.Holder()));
  return args.Holder();
}

// Add named getter to the function template for a collection.
template <class T>
static void SetCollectionNamedGetter(v8::Handle<v8::FunctionTemplate> desc,
                                     V8ClassIndex::V8WrapperType type) {
  desc->InstanceTemplate()->SetNamedPropertyHandler(
      CollectionNamedPropertyGetter<T>,
      0,
      0,
      0,
      0,
      v8::External::New(reinterpret_cast<void*>(type)));
}


// Add named and indexed getters to the function template for a collection.
template <class T>
static void SetCollectionIndexedAndNamedGetters(
    v8::Handle<v8::FunctionTemplate> desc, V8ClassIndex::V8WrapperType type) {
  // If we interceptor before object, accessing 'length' can trigger
  // a webkit assertion error.
  // (see fast/dom/HTMLDocument/document-special-properties.html
  desc->InstanceTemplate()->SetNamedPropertyHandler(
      CollectionNamedPropertyGetter<T>,
      0,
      0,
      0,
      0,
      v8::External::New(reinterpret_cast<void*>(type)));
  desc->InstanceTemplate()->SetIndexedPropertyHandler(
      CollectionIndexedPropertyGetter<T>,
      0,
      0,
      0,
      CollectionIndexedPropertyEnumerator<T>,
      v8::External::New(reinterpret_cast<void*>(type)));
}


// Add indexed getter returning a string or null to a function template
// for a collection.
template <class T>
static void SetCollectionStringOrNullIndexedGetter(
    v8::Handle<v8::FunctionTemplate> desc) {
  desc->InstanceTemplate()->SetIndexedPropertyHandler(
      CollectionStringOrNullIndexedPropertyGetter<T>,
      0,
      0,
      0,
      CollectionIndexedPropertyEnumerator<T>);
}


}  // namespace WebCore

#endif  // V8_PROXY_H__