// Copyright 2014 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 "extensions/renderer/user_script_set.h"

#include "content/public/common/url_constants.h"
#include "content/public/renderer/render_thread.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/renderer/extensions_renderer_client.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/script_injection.h"
#include "extensions/renderer/user_script_injector.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "url/gurl.h"

namespace extensions {

namespace {

GURL GetDocumentUrlForFrame(blink::WebFrame* frame) {
  GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame);
  if (!data_source_url.is_empty() && frame->isViewSourceModeEnabled()) {
    data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
                           data_source_url.spec());
  }

  return data_source_url;
}

}  // namespace

UserScriptSet::UserScriptSet(const ExtensionSet* extensions)
    : extensions_(extensions) {
}

UserScriptSet::~UserScriptSet() {
}

void UserScriptSet::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void UserScriptSet::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void UserScriptSet::GetActiveExtensionIds(
    std::set<std::string>* ids) const {
  for (ScopedVector<UserScript>::const_iterator iter = scripts_.begin();
       iter != scripts_.end();
       ++iter) {
    DCHECK(!(*iter)->extension_id().empty());
    ids->insert((*iter)->extension_id());
  }
}

void UserScriptSet::GetInjections(
    ScopedVector<ScriptInjection>* injections,
    blink::WebFrame* web_frame,
    int tab_id,
    UserScript::RunLocation run_location) {
  GURL document_url = GetDocumentUrlForFrame(web_frame);
  for (ScopedVector<UserScript>::const_iterator iter = scripts_.begin();
       iter != scripts_.end();
       ++iter) {
    const Extension* extension = extensions_->GetByID((*iter)->extension_id());
    if (!extension)
      continue;
    scoped_ptr<ScriptInjection> injection = GetInjectionForScript(
        *iter,
        web_frame,
        tab_id,
        run_location,
        document_url,
        extension,
        false /* is_declarative */);
    if (injection.get())
      injections->push_back(injection.release());
  }
}

bool UserScriptSet::UpdateUserScripts(
    base::SharedMemoryHandle shared_memory,
    const std::set<std::string>& changed_extensions) {
  bool only_inject_incognito =
      ExtensionsRendererClient::Get()->IsIncognitoProcess();

  // Create the shared memory object (read only).
  shared_memory_.reset(new base::SharedMemory(shared_memory, true));
  if (!shared_memory_.get())
    return false;

  // First get the size of the memory block.
  if (!shared_memory_->Map(sizeof(Pickle::Header)))
    return false;
  Pickle::Header* pickle_header =
      reinterpret_cast<Pickle::Header*>(shared_memory_->memory());

  // Now map in the rest of the block.
  int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size;
  shared_memory_->Unmap();
  if (!shared_memory_->Map(pickle_size))
    return false;

  // Unpickle scripts.
  uint64 num_scripts = 0;
  Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size);
  PickleIterator iter(pickle);
  CHECK(pickle.ReadUInt64(&iter, &num_scripts));

  scripts_.clear();
  scripts_.reserve(num_scripts);
  for (uint64 i = 0; i < num_scripts; ++i) {
    scoped_ptr<UserScript> script(new UserScript());
    script->Unpickle(pickle, &iter);

    // Note that this is a pointer into shared memory. We don't own it. It gets
    // cleared up when the last renderer or browser process drops their
    // reference to the shared memory.
    for (size_t j = 0; j < script->js_scripts().size(); ++j) {
      const char* body = NULL;
      int body_length = 0;
      CHECK(pickle.ReadData(&iter, &body, &body_length));
      script->js_scripts()[j].set_external_content(
          base::StringPiece(body, body_length));
    }
    for (size_t j = 0; j < script->css_scripts().size(); ++j) {
      const char* body = NULL;
      int body_length = 0;
      CHECK(pickle.ReadData(&iter, &body, &body_length));
      script->css_scripts()[j].set_external_content(
          base::StringPiece(body, body_length));
    }

    if (only_inject_incognito && !script->is_incognito_enabled())
      continue;  // This script shouldn't run in an incognito tab.

    scripts_.push_back(script.release());
  }

  FOR_EACH_OBSERVER(Observer,
                    observers_,
                    OnUserScriptsUpdated(changed_extensions, scripts_.get()));
  return true;
}

scoped_ptr<ScriptInjection> UserScriptSet::GetDeclarativeScriptInjection(
    int script_id,
    blink::WebFrame* web_frame,
    int tab_id,
    UserScript::RunLocation run_location,
    const GURL& document_url,
    const Extension* extension) {
  for (ScopedVector<UserScript>::const_iterator it = scripts_.begin();
       it != scripts_.end();
       ++it) {
    if ((*it)->id() == script_id) {
      return GetInjectionForScript(*it,
                                   web_frame,
                                   tab_id,
                                   run_location,
                                   document_url,
                                   extension,
                                   true /* is_declarative */);
    }
  }
  return scoped_ptr<ScriptInjection>();
}

// TODO(dcheng): Scripts can't be injected on a remote frame, so this function
// signature needs to be updated.
scoped_ptr<ScriptInjection> UserScriptSet::GetInjectionForScript(
    UserScript* script,
    blink::WebFrame* web_frame,
    int tab_id,
    UserScript::RunLocation run_location,
    const GURL& document_url,
    const Extension* extension,
    bool is_declarative) {
  scoped_ptr<ScriptInjection> injection;
  if (web_frame->parent() && !script->match_all_frames())
    return injection.Pass();  // Only match subframes if the script declared it.

  GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
      web_frame, document_url, script->match_about_blank());

  if (!script->MatchesURL(effective_document_url))
    return injection.Pass();

  scoped_ptr<ScriptInjector> injector(new UserScriptInjector(script,
                                                             this,
                                                             is_declarative));
  if (injector->CanExecuteOnFrame(
          extension,
          web_frame,
          -1,  // Content scripts are not tab-specific.
          web_frame->top()->document().url()) ==
      PermissionsData::ACCESS_DENIED) {
    return injection.Pass();
  }

  bool inject_css = !script->css_scripts().empty() &&
                    run_location == UserScript::DOCUMENT_START;
  bool inject_js =
      !script->js_scripts().empty() && script->run_location() == run_location;
  if (inject_css || inject_js) {
    injection.reset(new ScriptInjection(
        injector.Pass(),
        web_frame->toWebLocalFrame(),
        extension->id(),
        run_location,
        tab_id));
  }
  return injection.Pass();
}

}  // namespace extensions