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