// Copyright (c) 2006-2008 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 <windows.h>
#include <objidl.h>
#include <mlang.h>

#include "chrome/renderer/render_process.h"

#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/message_loop.h"
#include "base/histogram.h"
#include "base/path_service.h"
#include "chrome/browser/net/dns_global.h"  // TODO(jar): DNS calls should be renderer specific, not including browser.
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/ipc_channel.h"
#include "chrome/common/ipc_message_utils.h"
#include "chrome/common/render_messages.h"
#include "chrome/renderer/render_view.h"
#include "webkit/glue/webkit_glue.h"

//-----------------------------------------------------------------------------

bool RenderProcess::load_plugins_in_process_ = false;

//-----------------------------------------------------------------------------

RenderProcess::RenderProcess(const std::wstring& channel_name)
    : render_thread_(channel_name),
#pragma warning(suppress: 4355)  // Okay to pass "this" here.
      clearer_factory_(this) {
  for (int i = 0; i < arraysize(shared_mem_cache_); ++i)
    shared_mem_cache_[i] = NULL;
}

RenderProcess::~RenderProcess() {
  // We need to stop the RenderThread as the clearer_factory_
  // member could be in use while the object itself is destroyed,
  // as a result of the containing RenderProcess object being destroyed.
  // This race condition causes a crash when the renderer process is shutting
  // down.
  render_thread_.Stop();
  ClearSharedMemCache();
}

// static
bool RenderProcess::GlobalInit(const std::wstring &channel_name) {
  // HACK:  See http://b/issue?id=1024307 for rationale.
  if (GetModuleHandle(L"LPK.DLL") == NULL) {
    // Makes sure lpk.dll is loaded by gdi32 to make sure ExtTextOut() works
    // when buffering into a EMF buffer for printing.
    typedef BOOL (__stdcall *GdiInitializeLanguagePack)(int LoadedShapingDLLs);
    GdiInitializeLanguagePack gdi_init_lpk =
        reinterpret_cast<GdiInitializeLanguagePack>(GetProcAddress(
            GetModuleHandle(L"GDI32.DLL"),
            "GdiInitializeLanguagePack"));
    DCHECK(gdi_init_lpk);
    if (gdi_init_lpk) {
      gdi_init_lpk(0);
    }
  }

  CommandLine command_line;
  if (command_line.HasSwitch(switches::kJavaScriptFlags)) {
    webkit_glue::SetJavaScriptFlags(
      command_line.GetSwitchValue(switches::kJavaScriptFlags));
  }
  if (command_line.HasSwitch(switches::kPlaybackMode) ||
      command_line.HasSwitch(switches::kRecordMode)) {
      webkit_glue::SetRecordPlaybackMode(true);
  }

  if (command_line.HasSwitch(switches::kInProcessPlugins) ||
      command_line.HasSwitch(switches::kSingleProcess))
    load_plugins_in_process_ = true;

  if (command_line.HasSwitch(switches::kEnableWatchdog)) {
    // TODO(JAR): Need to implement renderer IO msgloop watchdog.
  }

  if (command_line.HasSwitch(switches::kDumpHistogramsOnExit)) {
    StatisticsRecorder::set_dump_on_exit(true);
  }

  if (command_line.HasSwitch(switches::kGearsInRenderer)) {
    // Load gears.dll on startup so we can access it before the sandbox
    // blocks us.
    std::wstring path;
    if (PathService::Get(chrome::FILE_GEARS_PLUGIN, &path))
      LoadLibrary(path.c_str());
  }

  ChildProcessFactory<RenderProcess> factory;
  return ChildProcess::GlobalInit(channel_name, &factory);
}

// static
void RenderProcess::GlobalCleanup() {
  ChildProcess::GlobalCleanup();
}

// static
bool RenderProcess::ShouldLoadPluginsInProcess() {
  return load_plugins_in_process_;
}

// static
base::SharedMemory* RenderProcess::AllocSharedMemory(size_t size) {
  self()->clearer_factory_.RevokeAll();

  base::SharedMemory* mem = self()->GetSharedMemFromCache(size);
  if (mem)
    return mem;

  // Round-up size to allocation granularity
  SYSTEM_INFO info;
  GetSystemInfo(&info);

  size = size / info.dwAllocationGranularity + 1;
  size = size * info.dwAllocationGranularity;

  mem = new base::SharedMemory();
  if (!mem)
    return NULL;
  if (!mem->Create(L"", false, true, size)) {
    delete mem;
    return NULL;
  }

  return mem;
}

// static
void RenderProcess::FreeSharedMemory(base::SharedMemory* mem) {
  if (self()->PutSharedMemInCache(mem)) {
    self()->ScheduleCacheClearer();
    return;
  }
  DeleteSharedMem(mem);
}

// static
void RenderProcess::DeleteSharedMem(base::SharedMemory* mem) {
  delete mem;
}

base::SharedMemory* RenderProcess::GetSharedMemFromCache(size_t size) {
  // look for a cached object that is suitable for the requested size.
  for (int i = 0; i < arraysize(shared_mem_cache_); ++i) {
    base::SharedMemory* mem = shared_mem_cache_[i];
    if (mem && mem->max_size() >= size) {
      shared_mem_cache_[i] = NULL;
      return mem;
    }
  }
  return NULL;
}

bool RenderProcess::PutSharedMemInCache(base::SharedMemory* mem) {
  // simple algorithm:
  //  - look for an empty slot to store mem, or
  //  - if full, then replace any existing cache entry that is smaller than the
  //    given shared memory object.
  for (int i = 0; i < arraysize(shared_mem_cache_); ++i) {
    if (!shared_mem_cache_[i]) {
      shared_mem_cache_[i] = mem;
      return true;
    }
  }
  for (int i = 0; i < arraysize(shared_mem_cache_); ++i) {
    base::SharedMemory* cached_mem = shared_mem_cache_[i];
    if (cached_mem->max_size() < mem->max_size()) {
      shared_mem_cache_[i] = mem;
      DeleteSharedMem(cached_mem);
      return true;
    }
  }
  return false;
}

void RenderProcess::ClearSharedMemCache() {
  for (int i = 0; i < arraysize(shared_mem_cache_); ++i) {
    if (shared_mem_cache_[i]) {
      DeleteSharedMem(shared_mem_cache_[i]);
      shared_mem_cache_[i] = NULL;
    }
  }
}

void RenderProcess::ScheduleCacheClearer() {
  // If we already have a deferred clearer, then revoke it so we effectively
  // delay cache clearing until idle for our desired interval.
  clearer_factory_.RevokeAll();

  MessageLoop::current()->PostDelayedTask(FROM_HERE,
      clearer_factory_.NewRunnableMethod(&RenderProcess::ClearSharedMemCache),
      5000 /* 5 seconds */);
}

void RenderProcess::Cleanup() {
#ifndef NDEBUG
  // log important leaked objects
  webkit_glue::CheckForLeaks();
#endif
}