• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 the Chromium Embedded Framework Authors. Portions copyright
2 // 2014 The Chromium Authors. All rights reserved. Use of this source code is
3 // governed by a BSD-style license that can be found in the LICENSE file.
4 
5 #include "libcef/browser/extensions/extension_function_details.h"
6 
7 #include "libcef/browser/browser_context.h"
8 #include "libcef/browser/extensions/browser_extensions_util.h"
9 #include "libcef/browser/extensions/extension_system.h"
10 #include "libcef/browser/thread_util.h"
11 
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/task/post_task.h"
14 #include "base/task/thread_pool.h"
15 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
16 #include "chrome/browser/extensions/extension_tab_util.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "content/public/browser/favicon_status.h"
19 #include "content/public/browser/navigation_entry.h"
20 #include "extensions/browser/extension_function.h"
21 #include "extensions/browser/extension_function_dispatcher.h"
22 #include "extensions/common/error_utils.h"
23 
24 using content::RenderViewHost;
25 using content::WebContents;
26 
27 namespace extensions {
28 
29 namespace keys = extensions::tabs_constants;
30 
31 namespace {
32 
33 class CefGetExtensionLoadFileCallbackImpl
34     : public CefGetExtensionResourceCallback {
35  public:
CefGetExtensionLoadFileCallbackImpl(const std::string & file,CefExtensionFunctionDetails::LoadFileCallback callback)36   CefGetExtensionLoadFileCallbackImpl(
37       const std::string& file,
38       CefExtensionFunctionDetails::LoadFileCallback callback)
39       : file_(file), callback_(std::move(callback)) {}
40 
41   CefGetExtensionLoadFileCallbackImpl(
42       const CefGetExtensionLoadFileCallbackImpl&) = delete;
43   CefGetExtensionLoadFileCallbackImpl& operator=(
44       const CefGetExtensionLoadFileCallbackImpl&) = delete;
45 
~CefGetExtensionLoadFileCallbackImpl()46   ~CefGetExtensionLoadFileCallbackImpl() {
47     if (!callback_.is_null()) {
48       // The callback is still pending. Cancel it now.
49       if (CEF_CURRENTLY_ON_UIT()) {
50         RunNow(file_, std::move(callback_), nullptr);
51       } else {
52         CEF_POST_TASK(CEF_UIT, base::BindOnce(
53                                    &CefGetExtensionLoadFileCallbackImpl::RunNow,
54                                    file_, std::move(callback_), nullptr));
55       }
56     }
57   }
58 
Continue(CefRefPtr<CefStreamReader> stream)59   void Continue(CefRefPtr<CefStreamReader> stream) override {
60     if (CEF_CURRENTLY_ON_UIT()) {
61       if (!callback_.is_null()) {
62         // Always continue asynchronously.
63         CEF_POST_TASK(CEF_UIT, base::BindOnce(
64                                    &CefGetExtensionLoadFileCallbackImpl::RunNow,
65                                    file_, std::move(callback_), stream));
66       }
67     } else {
68       CEF_POST_TASK(CEF_UIT, base::BindOnce(
69                                  &CefGetExtensionLoadFileCallbackImpl::Continue,
70                                  this, stream));
71     }
72   }
73 
Cancel()74   void Cancel() override { Continue(nullptr); }
75 
Disconnect()76   void Disconnect() { callback_.Reset(); }
77 
78  private:
RunNow(const std::string & file,CefExtensionFunctionDetails::LoadFileCallback callback,CefRefPtr<CefStreamReader> stream)79   static void RunNow(const std::string& file,
80                      CefExtensionFunctionDetails::LoadFileCallback callback,
81                      CefRefPtr<CefStreamReader> stream) {
82     CEF_REQUIRE_UIT();
83 
84     if (!stream) {
85       std::move(callback).Run(nullptr);
86       return;
87     }
88 
89     base::ThreadPool::PostTaskAndReplyWithResult(
90         FROM_HERE,
91         {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
92         base::BindOnce(LoadFileFromStream, file, stream), std::move(callback));
93   }
94 
LoadFileFromStream(const std::string & file,CefRefPtr<CefStreamReader> stream)95   static std::unique_ptr<std::string> LoadFileFromStream(
96       const std::string& file,
97       CefRefPtr<CefStreamReader> stream) {
98     CEF_REQUIRE_BLOCKING();
99 
100     // Move to the end of the stream.
101     stream->Seek(0, SEEK_END);
102     const int64 size = stream->Tell();
103     if (size == 0) {
104       LOG(WARNING) << "Extension resource " << file << " is empty.";
105       return nullptr;
106     }
107 
108     std::unique_ptr<std::string> result(new std::string());
109     result->resize(size);
110 
111     // Move to the beginning of the stream.
112     stream->Seek(0, SEEK_SET);
113 
114     // Read all stream contents into the string.
115     int64 read, offset = 0;
116     do {
117       read =
118           static_cast<int>(stream->Read(&(*result)[offset], 1, size - offset));
119       offset += read;
120     } while (read > 0 && offset < size);
121 
122     if (offset != size) {
123       LOG(WARNING) << "Extension resource " << file << " read failed; expected "
124                    << size << ", got " << offset << " bytes.";
125       return nullptr;
126     }
127 
128     return result;
129   }
130 
131   const std::string file_;
132   CefExtensionFunctionDetails::LoadFileCallback callback_;
133 
134   IMPLEMENT_REFCOUNTING(CefGetExtensionLoadFileCallbackImpl);
135 };
136 
137 }  // namespace
138 
CefExtensionFunctionDetails(ExtensionFunction * function)139 CefExtensionFunctionDetails::CefExtensionFunctionDetails(
140     ExtensionFunction* function)
141     : function_(function) {}
142 
~CefExtensionFunctionDetails()143 CefExtensionFunctionDetails::~CefExtensionFunctionDetails() {}
144 
GetProfile() const145 Profile* CefExtensionFunctionDetails::GetProfile() const {
146   return Profile::FromBrowserContext(function_->browser_context());
147 }
148 
GetSenderBrowser() const149 CefRefPtr<AlloyBrowserHostImpl> CefExtensionFunctionDetails::GetSenderBrowser()
150     const {
151   content::WebContents* web_contents = function_->GetSenderWebContents();
152   if (web_contents)
153     return AlloyBrowserHostImpl::GetBrowserForContents(web_contents);
154   return nullptr;
155 }
156 
GetCurrentBrowser() const157 CefRefPtr<AlloyBrowserHostImpl> CefExtensionFunctionDetails::GetCurrentBrowser()
158     const {
159   // Start with the browser hosting the extension.
160   CefRefPtr<AlloyBrowserHostImpl> browser = GetSenderBrowser();
161   if (browser && browser->client()) {
162     CefRefPtr<CefExtensionHandler> handler = GetCefExtension()->GetHandler();
163     if (handler) {
164       // Give the handler an opportunity to specify a different browser.
165       CefRefPtr<CefBrowser> active_browser =
166           handler->GetActiveBrowser(GetCefExtension(), browser.get(),
167                                     function_->include_incognito_information());
168       if (active_browser && active_browser != browser) {
169         CefRefPtr<AlloyBrowserHostImpl> active_browser_impl =
170             static_cast<AlloyBrowserHostImpl*>(active_browser.get());
171 
172         // Make sure we're operating in the same CefBrowserContext.
173         if (CefBrowserContext::FromBrowserContext(
174                 browser->GetBrowserContext()) ==
175             CefBrowserContext::FromBrowserContext(
176                 active_browser_impl->GetBrowserContext())) {
177           browser = active_browser_impl;
178         } else {
179           LOG(WARNING) << "Browser with tabId "
180                        << active_browser->GetIdentifier()
181                        << " cannot be accessed because is uses a different "
182                           "CefRequestContext";
183         }
184       }
185     }
186   }
187 
188   // May be null during startup/shutdown.
189   return browser;
190 }
191 
CanAccessBrowser(CefRefPtr<AlloyBrowserHostImpl> target) const192 bool CefExtensionFunctionDetails::CanAccessBrowser(
193     CefRefPtr<AlloyBrowserHostImpl> target) const {
194   DCHECK(target);
195 
196   // Start with the browser hosting the extension.
197   CefRefPtr<AlloyBrowserHostImpl> browser = GetSenderBrowser();
198   if (browser == target) {
199     // A sender can always access itself.
200     return true;
201   }
202 
203   if (browser && browser->client()) {
204     CefRefPtr<CefExtensionHandler> handler = GetCefExtension()->GetHandler();
205     if (handler) {
206       return handler->CanAccessBrowser(
207           GetCefExtension(), browser.get(),
208           function_->include_incognito_information(), target);
209     }
210   }
211 
212   // Default to allowing access.
213   return true;
214 }
215 
216 CefRefPtr<AlloyBrowserHostImpl>
GetBrowserForTabIdFirstTime(int tab_id,std::string * error_message) const217 CefExtensionFunctionDetails::GetBrowserForTabIdFirstTime(
218     int tab_id,
219     std::string* error_message) const {
220   DCHECK(!get_browser_called_first_time_);
221   get_browser_called_first_time_ = true;
222 
223   CefRefPtr<AlloyBrowserHostImpl> browser;
224 
225   if (tab_id >= 0) {
226     // May be an invalid tabId or in the wrong BrowserContext.
227     browser = GetBrowserForTabId(tab_id, function_->browser_context());
228     if (!browser || !browser->web_contents() || !CanAccessBrowser(browser)) {
229       if (error_message) {
230         *error_message = ErrorUtils::FormatErrorMessage(
231             keys::kTabNotFoundError, base::NumberToString(tab_id));
232       }
233       return nullptr;
234     }
235   } else {
236     // May return NULL during shutdown.
237     browser = GetCurrentBrowser();
238     if (!browser || !browser->web_contents()) {
239       if (error_message) {
240         *error_message = keys::kNoCurrentWindowError;
241       }
242       return nullptr;
243     }
244   }
245 
246   return browser;
247 }
248 
249 CefRefPtr<AlloyBrowserHostImpl>
GetBrowserForTabIdAgain(int tab_id,std::string * error_message) const250 CefExtensionFunctionDetails::GetBrowserForTabIdAgain(
251     int tab_id,
252     std::string* error_message) const {
253   DCHECK_GE(tab_id, 0);
254   DCHECK(get_browser_called_first_time_);
255 
256   // May return NULL during shutdown.
257   CefRefPtr<AlloyBrowserHostImpl> browser =
258       GetBrowserForTabId(tab_id, function_->browser_context());
259   if (!browser || !browser->web_contents()) {
260     if (error_message) {
261       *error_message = ErrorUtils::FormatErrorMessage(
262           keys::kTabNotFoundError, base::NumberToString(tab_id));
263     }
264   }
265   return browser;
266 }
267 
LoadFile(const std::string & file,LoadFileCallback callback) const268 bool CefExtensionFunctionDetails::LoadFile(const std::string& file,
269                                            LoadFileCallback callback) const {
270   // Start with the browser hosting the extension.
271   CefRefPtr<AlloyBrowserHostImpl> browser = GetSenderBrowser();
272   if (browser && browser->client()) {
273     CefRefPtr<CefExtensionHandler> handler = GetCefExtension()->GetHandler();
274     if (handler) {
275       CefRefPtr<CefGetExtensionLoadFileCallbackImpl> cef_callback(
276           new CefGetExtensionLoadFileCallbackImpl(file, std::move(callback)));
277       if (handler->GetExtensionResource(GetCefExtension(), browser.get(), file,
278                                         cef_callback)) {
279         return true;
280       }
281       cef_callback->Disconnect();
282     }
283   }
284 
285   return false;
286 }
287 
OpenTabParams()288 CefExtensionFunctionDetails::OpenTabParams::OpenTabParams() {}
289 
~OpenTabParams()290 CefExtensionFunctionDetails::OpenTabParams::~OpenTabParams() {}
291 
OpenTab(const OpenTabParams & params,bool user_gesture,std::string * error_message) const292 base::DictionaryValue* CefExtensionFunctionDetails::OpenTab(
293     const OpenTabParams& params,
294     bool user_gesture,
295     std::string* error_message) const {
296   CefRefPtr<AlloyBrowserHostImpl> sender_browser = GetSenderBrowser();
297   if (!sender_browser)
298     return nullptr;
299 
300   // windowId defaults to "current" window.
301   int window_id = extension_misc::kCurrentWindowId;
302   if (params.window_id.get())
303     window_id = *params.window_id;
304 
305   // CEF doesn't have the concept of windows containing tab strips so we'll
306   // select an "active browser" for BrowserContext sharing instead.
307   CefRefPtr<AlloyBrowserHostImpl> active_browser =
308       GetBrowserForTabIdFirstTime(window_id, error_message);
309   if (!active_browser)
310     return nullptr;
311 
312   // If an opener browser was specified then we expect it to exist.
313   int opener_browser_id = -1;
314   if (params.opener_tab_id.get() && *params.opener_tab_id >= 0) {
315     if (GetBrowserForTabIdAgain(*params.opener_tab_id, error_message)) {
316       opener_browser_id = *params.opener_tab_id;
317     } else {
318       return nullptr;
319     }
320   }
321 
322   GURL url;
323   if (params.url.get()) {
324     std::string url_string = *params.url;
325     if (!ExtensionTabUtil::PrepareURLForNavigation(
326             url_string, function()->extension(), &url, error_message)) {
327       return nullptr;
328     }
329   }
330 
331   // Default to foreground for the new tab. The presence of 'active' property
332   // will override this default.
333   bool active = true;
334   if (params.active.get())
335     active = *params.active;
336 
337   // CEF doesn't use the index value but we let the client see/modify it.
338   int index = 0;
339   if (params.index.get())
340     index = *params.index;
341 
342   auto cef_browser_context = CefBrowserContext::FromBrowserContext(
343       active_browser->GetBrowserContext());
344 
345   // A CEF representation should always exist.
346   CefRefPtr<CefExtension> cef_extension =
347       cef_browser_context->GetExtension(function()->extension()->id());
348   DCHECK(cef_extension);
349   if (!cef_extension)
350     return nullptr;
351 
352   // Always use the same request context that the extension was registered with.
353   // GetLoaderContext() will return NULL for internal extensions.
354   CefRefPtr<CefRequestContext> request_context =
355       cef_extension->GetLoaderContext();
356   if (!request_context)
357     return nullptr;
358 
359   CefBrowserCreateParams create_params;
360   create_params.url = url.spec();
361   create_params.request_context = request_context;
362   create_params.window_info.reset(new CefWindowInfo);
363 
364 #if BUILDFLAG(IS_WIN)
365   create_params.window_info->SetAsPopup(nullptr, CefString());
366 #endif
367 
368   // Start with the active browser's settings.
369   create_params.client = active_browser->GetClient();
370   create_params.settings = active_browser->settings();
371 
372   CefRefPtr<CefExtensionHandler> handler = cef_extension->GetHandler();
373   if (handler.get() &&
374       handler->OnBeforeBrowser(cef_extension, sender_browser.get(),
375                                active_browser.get(), index, create_params.url,
376                                active, *create_params.window_info,
377                                create_params.client, create_params.settings)) {
378     // Cancel the browser creation.
379     return nullptr;
380   }
381 
382   if (active_browser->is_views_hosted()) {
383     // The new browser will also be Views hosted.
384     create_params.window_info.reset();
385   }
386 
387   // Browser creation may fail under certain rare circumstances.
388   CefRefPtr<AlloyBrowserHostImpl> new_browser =
389       AlloyBrowserHostImpl::Create(create_params);
390   if (!new_browser)
391     return nullptr;
392 
393   // Return data about the newly created tab.
394   auto extension = function()->extension();
395   auto web_contents = new_browser->web_contents();
396   auto result = CreateTabObject(new_browser, opener_browser_id, active, index);
397   auto scrub_tab_behavior = ExtensionTabUtil::GetScrubTabBehavior(
398       extension, extensions::Feature::Context::UNSPECIFIED_CONTEXT,
399       web_contents);
400   ExtensionTabUtil::ScrubTabForExtension(extension, web_contents, result.get(),
401                                          scrub_tab_behavior);
402   return result->ToValue().release();
403 }
404 
CreateTabObject(CefRefPtr<AlloyBrowserHostImpl> new_browser,int opener_browser_id,bool active,int index) const405 std::unique_ptr<api::tabs::Tab> CefExtensionFunctionDetails::CreateTabObject(
406     CefRefPtr<AlloyBrowserHostImpl> new_browser,
407     int opener_browser_id,
408     bool active,
409     int index) const {
410   content::WebContents* contents = new_browser->web_contents();
411 
412   bool is_loading = contents->IsLoading();
413   auto tab_object = std::make_unique<api::tabs::Tab>();
414   tab_object->id = std::make_unique<int>(new_browser->GetIdentifier());
415   tab_object->index = index;
416   tab_object->window_id = *tab_object->id;
417   tab_object->status = is_loading ? api::tabs::TAB_STATUS_LOADING
418                                   : api::tabs::TAB_STATUS_COMPLETE;
419   tab_object->active = active;
420   tab_object->selected = true;
421   tab_object->highlighted = true;
422   tab_object->pinned = false;
423   // TODO(extensions): Use RecentlyAudibleHelper to populate |audible|.
424   tab_object->discarded = false;
425   tab_object->auto_discardable = false;
426   tab_object->muted_info = CreateMutedInfo(contents);
427   tab_object->incognito = false;
428   gfx::Size contents_size = contents->GetContainerBounds().size();
429   tab_object->width = std::make_unique<int>(contents_size.width());
430   tab_object->height = std::make_unique<int>(contents_size.height());
431   tab_object->url = std::make_unique<std::string>(contents->GetURL().spec());
432   tab_object->title =
433       std::make_unique<std::string>(base::UTF16ToUTF8(contents->GetTitle()));
434 
435   content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
436   if (entry && entry->GetFavicon().valid) {
437     tab_object->fav_icon_url =
438         std::make_unique<std::string>(entry->GetFavicon().url.spec());
439   }
440 
441   if (opener_browser_id >= 0)
442     tab_object->opener_tab_id = std::make_unique<int>(opener_browser_id);
443 
444   return tab_object;
445 }
446 
447 // static
448 std::unique_ptr<api::tabs::MutedInfo>
CreateMutedInfo(content::WebContents * contents)449 CefExtensionFunctionDetails::CreateMutedInfo(content::WebContents* contents) {
450   DCHECK(contents);
451   std::unique_ptr<api::tabs::MutedInfo> info(new api::tabs::MutedInfo);
452   info->muted = contents->IsAudioMuted();
453   // TODO(cef): Maybe populate |info->reason|.
454   return info;
455 }
456 
GetCefExtension() const457 CefRefPtr<CefExtension> CefExtensionFunctionDetails::GetCefExtension() const {
458   if (!cef_extension_) {
459     cef_extension_ =
460         CefBrowserContext::FromBrowserContext(function_->browser_context())
461             ->GetExtension(function_->extension_id());
462     DCHECK(cef_extension_);
463   }
464   return cef_extension_;
465 }
466 
467 }  // namespace extensions
468