1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "extensions/renderer/script_context_set.h"
6
7#include "base/message_loop/message_loop.h"
8#include "content/public/renderer/render_view.h"
9#include "extensions/common/extension.h"
10#include "extensions/renderer/script_context.h"
11#include "v8/include/v8.h"
12
13namespace extensions {
14
15ScriptContextSet::ScriptContextSet() {
16}
17ScriptContextSet::~ScriptContextSet() {
18}
19
20int ScriptContextSet::size() const {
21  return static_cast<int>(contexts_.size());
22}
23
24void ScriptContextSet::Add(ScriptContext* context) {
25#if DCHECK_IS_ON
26  // It's OK to insert the same context twice, but we should only ever have
27  // one ScriptContext per v8::Context.
28  for (ContextSet::iterator iter = contexts_.begin(); iter != contexts_.end();
29       ++iter) {
30    ScriptContext* candidate = *iter;
31    if (candidate != context)
32      DCHECK(candidate->v8_context() != context->v8_context());
33  }
34#endif
35  contexts_.insert(context);
36}
37
38void ScriptContextSet::Remove(ScriptContext* context) {
39  if (contexts_.erase(context)) {
40    context->Invalidate();
41    base::MessageLoop::current()->DeleteSoon(FROM_HERE, context);
42  }
43}
44
45ScriptContextSet::ContextSet ScriptContextSet::GetAll() const {
46  return contexts_;
47}
48
49ScriptContext* ScriptContextSet::GetCurrent() const {
50  v8::Isolate* isolate = v8::Isolate::GetCurrent();
51  return isolate->InContext() ? GetByV8Context(isolate->GetCurrentContext())
52                              : NULL;
53}
54
55ScriptContext* ScriptContextSet::GetCalling() const {
56  v8::Isolate* isolate = v8::Isolate::GetCurrent();
57  v8::Local<v8::Context> calling = isolate->GetCallingContext();
58  return calling.IsEmpty() ? NULL : GetByV8Context(calling);
59}
60
61ScriptContext* ScriptContextSet::GetByV8Context(
62    v8::Handle<v8::Context> v8_context) const {
63  for (ContextSet::const_iterator iter = contexts_.begin();
64       iter != contexts_.end();
65       ++iter) {
66    if ((*iter)->v8_context() == v8_context)
67      return *iter;
68  }
69
70  return NULL;
71}
72
73void ScriptContextSet::ForEach(
74    const std::string& extension_id,
75    content::RenderView* render_view,
76    const base::Callback<void(ScriptContext*)>& callback) const {
77  // We copy the context list, because calling into javascript may modify it
78  // out from under us.
79  ContextSet contexts = GetAll();
80
81  for (ContextSet::iterator it = contexts.begin(); it != contexts.end(); ++it) {
82    ScriptContext* context = *it;
83
84    // For the same reason as above, contexts may become invalid while we run.
85    if (!context->is_valid())
86      continue;
87
88    if (!extension_id.empty()) {
89      const Extension* extension = context->extension();
90      if (!extension || (extension_id != extension->id()))
91        continue;
92    }
93
94    content::RenderView* context_render_view = context->GetRenderView();
95    if (!context_render_view)
96      continue;
97
98    if (render_view && render_view != context_render_view)
99      continue;
100
101    callback.Run(context);
102  }
103}
104
105ScriptContextSet::ContextSet ScriptContextSet::OnExtensionUnloaded(
106    const std::string& extension_id) {
107  ContextSet contexts = GetAll();
108  ContextSet removed;
109
110  // Clean up contexts belonging to the unloaded extension. This is done so
111  // that content scripts (which remain injected into the page) don't continue
112  // receiving events and sending messages.
113  for (ContextSet::iterator it = contexts.begin(); it != contexts.end(); ++it) {
114    if ((*it)->extension() && (*it)->extension()->id() == extension_id) {
115      (*it)->DispatchOnUnloadEvent();
116      removed.insert(*it);
117      Remove(*it);
118    }
119  }
120
121  return removed;
122}
123
124}  // namespace extensions
125