1 // Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright
2 // 2012 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/api/tabs/tabs_api.h"
6
7 #include "libcef/browser/extensions/extension_web_contents_observer.h"
8
9 #include "base/notreached.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/stringprintf.h"
12 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
13 #include "chrome/browser/extensions/extension_tab_util.h"
14 #include "components/zoom/zoom_controller.h"
15 #include "content/public/browser/navigation_controller.h"
16 #include "content/public/browser/navigation_entry.h"
17 #include "content/public/browser/render_frame_host.h"
18 #include "content/public/browser/site_instance.h"
19 #include "extensions/browser/extension_api_frame_id_map.h"
20 #include "extensions/browser/extension_zoom_request_client.h"
21 #include "extensions/common/error_utils.h"
22 #include "extensions/common/manifest_constants.h"
23 #include "extensions/common/permissions/permissions_data.h"
24 #include "third_party/blink/public/common/page/page_zoom.h"
25
26 using zoom::ZoomController;
27
28 namespace extensions {
29 namespace cef {
30
31 namespace keys = extensions::tabs_constants;
32 namespace tabs = api::tabs;
33
34 using api::extension_types::InjectDetails;
35
36 namespace {
37
38 const char kNotImplementedError[] = "Not implemented";
39
ZoomModeToZoomSettings(zoom::ZoomController::ZoomMode zoom_mode,api::tabs::ZoomSettings * zoom_settings)40 void ZoomModeToZoomSettings(zoom::ZoomController::ZoomMode zoom_mode,
41 api::tabs::ZoomSettings* zoom_settings) {
42 DCHECK(zoom_settings);
43 switch (zoom_mode) {
44 case zoom::ZoomController::ZOOM_MODE_DEFAULT:
45 zoom_settings->mode = api::tabs::ZOOM_SETTINGS_MODE_AUTOMATIC;
46 zoom_settings->scope = api::tabs::ZOOM_SETTINGS_SCOPE_PER_ORIGIN;
47 break;
48 case zoom::ZoomController::ZOOM_MODE_ISOLATED:
49 zoom_settings->mode = api::tabs::ZOOM_SETTINGS_MODE_AUTOMATIC;
50 zoom_settings->scope = api::tabs::ZOOM_SETTINGS_SCOPE_PER_TAB;
51 break;
52 case zoom::ZoomController::ZOOM_MODE_MANUAL:
53 zoom_settings->mode = api::tabs::ZOOM_SETTINGS_MODE_MANUAL;
54 zoom_settings->scope = api::tabs::ZOOM_SETTINGS_SCOPE_PER_TAB;
55 break;
56 case zoom::ZoomController::ZOOM_MODE_DISABLED:
57 zoom_settings->mode = api::tabs::ZOOM_SETTINGS_MODE_DISABLED;
58 zoom_settings->scope = api::tabs::ZOOM_SETTINGS_SCOPE_PER_TAB;
59 break;
60 }
61 }
62
63 template <typename T>
AssignOptionalValue(const std::unique_ptr<T> & source,std::unique_ptr<T> & destination)64 void AssignOptionalValue(const std::unique_ptr<T>& source,
65 std::unique_ptr<T>& destination) {
66 if (source.get()) {
67 destination.reset(new T(*source));
68 }
69 }
70
71 } // namespace
72
Run()73 ExtensionFunction::ResponseAction TabsGetFunction::Run() {
74 return RespondNow(Error(kNotImplementedError));
75 }
76
TabsCreateFunction()77 TabsCreateFunction::TabsCreateFunction() : cef_details_(this) {}
78
Run()79 ExtensionFunction::ResponseAction TabsCreateFunction::Run() {
80 std::unique_ptr<tabs::Create::Params> params(
81 tabs::Create::Params::Create(args()));
82 EXTENSION_FUNCTION_VALIDATE(params.get());
83
84 CefExtensionFunctionDetails::OpenTabParams options;
85 AssignOptionalValue(params->create_properties.window_id, options.window_id);
86 AssignOptionalValue(params->create_properties.opener_tab_id,
87 options.opener_tab_id);
88 AssignOptionalValue(params->create_properties.selected, options.active);
89 // The 'active' property has replaced the 'selected' property.
90 AssignOptionalValue(params->create_properties.active, options.active);
91 AssignOptionalValue(params->create_properties.pinned, options.pinned);
92 AssignOptionalValue(params->create_properties.index, options.index);
93 AssignOptionalValue(params->create_properties.url, options.url);
94
95 std::string error;
96 std::unique_ptr<base::DictionaryValue> result(
97 cef_details_.OpenTab(options, user_gesture(), &error));
98 if (!result)
99 return RespondNow(Error(error));
100
101 // Return data about the newly created tab.
102 return RespondNow(
103 has_callback()
104 ? OneArgument(base::Value::FromUniquePtrValue(std::move(result)))
105 : NoArguments());
106 }
107
BaseAPIFunction()108 BaseAPIFunction::BaseAPIFunction() : cef_details_(this) {}
109
GetWebContents(int tab_id)110 content::WebContents* BaseAPIFunction::GetWebContents(int tab_id) {
111 // Find a browser that we can access, or set |error_| and return nullptr.
112 CefRefPtr<AlloyBrowserHostImpl> browser =
113 cef_details_.GetBrowserForTabIdFirstTime(tab_id, &error_);
114 if (!browser)
115 return nullptr;
116
117 return browser->web_contents();
118 }
119
Run()120 ExtensionFunction::ResponseAction TabsUpdateFunction::Run() {
121 std::unique_ptr<tabs::Update::Params> params(
122 tabs::Update::Params::Create(args()));
123 EXTENSION_FUNCTION_VALIDATE(params.get());
124
125 tab_id_ = params->tab_id ? *params->tab_id : -1;
126 content::WebContents* web_contents = GetWebContents(tab_id_);
127 if (!web_contents)
128 return RespondNow(Error(std::move(error_)));
129
130 web_contents_ = web_contents;
131
132 // TODO(rafaelw): handle setting remaining tab properties:
133 // -title
134 // -favIconUrl
135
136 // Navigate the tab to a new location if the url is different.
137 if (params->update_properties.url.get()) {
138 std::string updated_url = *params->update_properties.url;
139 if (!UpdateURL(updated_url, tab_id_, &error_))
140 return RespondNow(Error(std::move(error_)));
141 }
142
143 bool active = false;
144 // TODO(rafaelw): Setting |active| from js doesn't make much sense.
145 // Move tab selection management up to window.
146 if (params->update_properties.selected.get())
147 active = *params->update_properties.selected;
148
149 // The 'active' property has replaced 'selected'.
150 if (params->update_properties.active.get())
151 active = *params->update_properties.active;
152
153 if (active) {
154 // TODO: Activate the tab at |tab_id_|.
155 NOTIMPLEMENTED();
156 return RespondNow(Error(tabs_constants::kTabStripNotEditableError));
157 }
158
159 if (params->update_properties.highlighted.get() &&
160 *params->update_properties.highlighted) {
161 // TODO: Highlight the tab at |tab_id_|.
162 NOTIMPLEMENTED();
163 return RespondNow(Error(tabs_constants::kTabStripNotEditableError));
164 }
165
166 if (params->update_properties.pinned.get() &&
167 *params->update_properties.pinned) {
168 // TODO: Pin the tab at |tab_id_|.
169 NOTIMPLEMENTED();
170 return RespondNow(Error(tabs_constants::kTabStripNotEditableError));
171 }
172
173 if (params->update_properties.muted.get()) {
174 // TODO: Mute/unmute the tab at |tab_id_|.
175 NOTIMPLEMENTED();
176 return RespondNow(Error(ErrorUtils::FormatErrorMessage(
177 tabs_constants::kCannotUpdateMuteCaptured,
178 base::NumberToString(tab_id_))));
179 }
180
181 if (params->update_properties.opener_tab_id.get()) {
182 int opener_id = *params->update_properties.opener_tab_id;
183 if (opener_id == tab_id_)
184 return RespondNow(Error("Cannot set a tab's opener to itself."));
185
186 // TODO: Set the opener for the tab at |tab_id_|.
187 NOTIMPLEMENTED();
188 return RespondNow(Error(tabs_constants::kTabStripNotEditableError));
189 }
190
191 if (params->update_properties.auto_discardable.get()) {
192 // TODO: Set auto-discardable state for the tab at |tab_id_|.
193 NOTIMPLEMENTED();
194 }
195
196 return RespondNow(GetResult());
197 }
198
UpdateURL(const std::string & url_string,int tab_id,std::string * error)199 bool TabsUpdateFunction::UpdateURL(const std::string& url_string,
200 int tab_id,
201 std::string* error) {
202 GURL url;
203 if (!ExtensionTabUtil::PrepareURLForNavigation(url_string, extension(), &url,
204 error)) {
205 return false;
206 }
207
208 const bool is_javascript_scheme = url.SchemeIs(url::kJavaScriptScheme);
209 // JavaScript URLs are forbidden in chrome.tabs.update().
210 if (is_javascript_scheme) {
211 *error = tabs_constants::kJavaScriptUrlsNotAllowedInTabsUpdate;
212 return false;
213 }
214
215 content::NavigationController::LoadURLParams load_params(url);
216
217 // Treat extension-initiated navigations as renderer-initiated so that the URL
218 // does not show in the omnibox until it commits. This avoids URL spoofs
219 // since URLs can be opened on behalf of untrusted content.
220 load_params.is_renderer_initiated = true;
221 // All renderer-initiated navigations need to have an initiator origin.
222 load_params.initiator_origin = extension()->origin();
223 // |source_site_instance| needs to be set so that a renderer process
224 // compatible with |initiator_origin| is picked by Site Isolation.
225 load_params.source_site_instance = content::SiteInstance::CreateForURL(
226 web_contents_->GetBrowserContext(),
227 load_params.initiator_origin->GetURL());
228
229 // Marking the navigation as initiated via an API means that the focus
230 // will stay in the omnibox - see https://crbug.com/1085779.
231 load_params.transition_type = ui::PAGE_TRANSITION_FROM_API;
232
233 web_contents_->GetController().LoadURLWithParams(load_params);
234
235 DCHECK_EQ(url,
236 web_contents_->GetController().GetPendingEntry()->GetVirtualURL());
237
238 return true;
239 }
240
GetResult()241 ExtensionFunction::ResponseValue TabsUpdateFunction::GetResult() {
242 if (!has_callback())
243 return NoArguments();
244
245 return ArgumentList(tabs::Get::Results::Create(*cef_details_.CreateTabObject(
246 AlloyBrowserHostImpl::GetBrowserForContents(web_contents_),
247 /*opener_browser_id=*/-1, /*active=*/true, tab_id_)));
248 }
249
ExecuteCodeInTabFunction()250 ExecuteCodeInTabFunction::ExecuteCodeInTabFunction()
251 : cef_details_(this), execute_tab_id_(-1) {}
252
~ExecuteCodeInTabFunction()253 ExecuteCodeInTabFunction::~ExecuteCodeInTabFunction() {}
254
Init()255 ExecuteCodeFunction::InitResult ExecuteCodeInTabFunction::Init() {
256 if (init_result_)
257 return init_result_.value();
258
259 if (args().size() < 2)
260 return set_init_result(VALIDATION_FAILURE);
261
262 const auto& tab_id_value = args()[0];
263 // |tab_id| is optional so it's ok if it's not there.
264 int tab_id = -1;
265 if (tab_id_value.is_int()) {
266 // But if it is present, it needs to be non-negative.
267 tab_id = tab_id_value.GetInt();
268 if (tab_id < 0) {
269 return set_init_result(VALIDATION_FAILURE);
270 }
271 }
272
273 // |details| are not optional.
274 const base::Value& details_value = args()[1];
275 if (!details_value.is_dict())
276 return set_init_result(VALIDATION_FAILURE);
277 std::unique_ptr<InjectDetails> details(new InjectDetails());
278 if (!InjectDetails::Populate(details_value, details.get()))
279 return set_init_result(VALIDATION_FAILURE);
280
281 // Find a browser that we can access, or fail with error.
282 std::string error;
283 CefRefPtr<AlloyBrowserHostImpl> browser =
284 cef_details_.GetBrowserForTabIdFirstTime(tab_id, &error);
285 if (!browser)
286 return set_init_result_error(error);
287
288 execute_tab_id_ = browser->GetIdentifier();
289 details_ = std::move(details);
290 set_host_id(
291 mojom::HostID(mojom::HostID::HostType::kExtensions, extension()->id()));
292 return set_init_result(SUCCESS);
293 }
294
ShouldInsertCSS() const295 bool ExecuteCodeInTabFunction::ShouldInsertCSS() const {
296 return false;
297 }
298
ShouldRemoveCSS() const299 bool ExecuteCodeInTabFunction::ShouldRemoveCSS() const {
300 return false;
301 }
302
CanExecuteScriptOnPage(std::string * error)303 bool ExecuteCodeInTabFunction::CanExecuteScriptOnPage(std::string* error) {
304 CHECK_GE(execute_tab_id_, 0);
305
306 CefRefPtr<AlloyBrowserHostImpl> browser =
307 cef_details_.GetBrowserForTabIdAgain(execute_tab_id_, error);
308 if (!browser)
309 return false;
310
311 int frame_id = details_->frame_id ? *details_->frame_id
312 : ExtensionApiFrameIdMap::kTopFrameId;
313 content::RenderFrameHost* rfh =
314 ExtensionApiFrameIdMap::GetRenderFrameHostById(browser->web_contents(),
315 frame_id);
316 if (!rfh) {
317 *error = ErrorUtils::FormatErrorMessage(
318 keys::kFrameNotFoundError, base::NumberToString(frame_id),
319 base::NumberToString(execute_tab_id_));
320 return false;
321 }
322
323 // Content scripts declared in manifest.json can access frames at about:-URLs
324 // if the extension has permission to access the frame's origin, so also allow
325 // programmatic content scripts at about:-URLs for allowed origins.
326 GURL effective_document_url(rfh->GetLastCommittedURL());
327 bool is_about_url = effective_document_url.SchemeIs(url::kAboutScheme);
328 if (is_about_url && details_->match_about_blank &&
329 *details_->match_about_blank) {
330 effective_document_url = GURL(rfh->GetLastCommittedOrigin().Serialize());
331 }
332
333 if (!effective_document_url.is_valid()) {
334 // Unknown URL, e.g. because no load was committed yet. Allow for now, the
335 // renderer will check again and fail the injection if needed.
336 return true;
337 }
338
339 // NOTE: This can give the wrong answer due to race conditions, but it is OK,
340 // we check again in the renderer.
341 if (!extension()->permissions_data()->CanAccessPage(effective_document_url,
342 execute_tab_id_, error)) {
343 if (is_about_url &&
344 extension()->permissions_data()->active_permissions().HasAPIPermission(
345 mojom::APIPermissionID::kTab)) {
346 *error = ErrorUtils::FormatErrorMessage(
347 manifest_errors::kCannotAccessAboutUrl,
348 rfh->GetLastCommittedURL().spec(),
349 rfh->GetLastCommittedOrigin().Serialize());
350 }
351 return false;
352 }
353
354 return true;
355 }
356
GetScriptExecutor(std::string * error)357 ScriptExecutor* ExecuteCodeInTabFunction::GetScriptExecutor(
358 std::string* error) {
359 CHECK_GE(execute_tab_id_, 0);
360
361 CefRefPtr<AlloyBrowserHostImpl> browser =
362 cef_details_.GetBrowserForTabIdAgain(execute_tab_id_, error);
363 if (!browser)
364 return nullptr;
365
366 return CefExtensionWebContentsObserver::FromWebContents(
367 browser->web_contents())
368 ->script_executor();
369 }
370
IsWebView() const371 bool ExecuteCodeInTabFunction::IsWebView() const {
372 return false;
373 }
374
GetWebViewSrc() const375 const GURL& ExecuteCodeInTabFunction::GetWebViewSrc() const {
376 return GURL::EmptyGURL();
377 }
378
LoadFile(const std::string & file,std::string * error)379 bool ExecuteCodeInTabFunction::LoadFile(const std::string& file,
380 std::string* error) {
381 if (cef_details_.LoadFile(
382 file, base::BindOnce(&ExecuteCodeInTabFunction::LoadFileComplete,
383 this, file))) {
384 return true;
385 }
386
387 // Default handling.
388 return ExecuteCodeFunction::LoadFile(file, error);
389 }
390
LoadFileComplete(const std::string & file,std::unique_ptr<std::string> data)391 void ExecuteCodeInTabFunction::LoadFileComplete(
392 const std::string& file,
393 std::unique_ptr<std::string> data) {
394 std::vector<std::unique_ptr<std::string>> data_list;
395 absl::optional<std::string> error;
396 const bool success = !!data.get();
397 if (success) {
398 DCHECK(data);
399 data_list.push_back(std::move(data));
400 } else {
401 error = base::StringPrintf("Failed to load file '%s'.", file.c_str());
402 }
403 DidLoadAndLocalizeFile(file, std::move(data_list), std::move(error));
404 }
405
ShouldInsertCSS() const406 bool TabsInsertCSSFunction::ShouldInsertCSS() const {
407 return true;
408 }
409
ShouldRemoveCSS() const410 bool TabsRemoveCSSFunction::ShouldRemoveCSS() const {
411 return true;
412 }
413
Run()414 ExtensionFunction::ResponseAction TabsSetZoomFunction::Run() {
415 std::unique_ptr<tabs::SetZoom::Params> params(
416 tabs::SetZoom::Params::Create(args()));
417 EXTENSION_FUNCTION_VALIDATE(params);
418
419 int tab_id = params->tab_id ? *params->tab_id : -1;
420 content::WebContents* web_contents = GetWebContents(tab_id);
421 if (!web_contents)
422 return RespondNow(Error(std::move(error_)));
423
424 GURL url(web_contents->GetVisibleURL());
425 if (extension()->permissions_data()->IsRestrictedUrl(url, &error_))
426 return RespondNow(Error(std::move(error_)));
427
428 ZoomController* zoom_controller =
429 ZoomController::FromWebContents(web_contents);
430 double zoom_level =
431 params->zoom_factor > 0
432 ? blink::PageZoomFactorToZoomLevel(params->zoom_factor)
433 : zoom_controller->GetDefaultZoomLevel();
434
435 auto client = base::MakeRefCounted<ExtensionZoomRequestClient>(extension());
436 if (!zoom_controller->SetZoomLevelByClient(zoom_level, client)) {
437 // Tried to zoom a tab in disabled mode.
438 return RespondNow(Error(tabs_constants::kCannotZoomDisabledTabError));
439 }
440
441 return RespondNow(NoArguments());
442 }
443
Run()444 ExtensionFunction::ResponseAction TabsGetZoomFunction::Run() {
445 std::unique_ptr<tabs::GetZoom::Params> params(
446 tabs::GetZoom::Params::Create(args()));
447 EXTENSION_FUNCTION_VALIDATE(params);
448
449 int tab_id = params->tab_id ? *params->tab_id : -1;
450 content::WebContents* web_contents = GetWebContents(tab_id);
451 if (!web_contents)
452 return RespondNow(Error(std::move(error_)));
453
454 double zoom_level =
455 zoom::ZoomController::FromWebContents(web_contents)->GetZoomLevel();
456 double zoom_factor = blink::PageZoomLevelToZoomFactor(zoom_level);
457
458 return RespondNow(ArgumentList(tabs::GetZoom::Results::Create(zoom_factor)));
459 }
460
Run()461 ExtensionFunction::ResponseAction TabsSetZoomSettingsFunction::Run() {
462 using api::tabs::ZoomSettings;
463
464 std::unique_ptr<tabs::SetZoomSettings::Params> params(
465 tabs::SetZoomSettings::Params::Create(args()));
466 EXTENSION_FUNCTION_VALIDATE(params);
467
468 int tab_id = params->tab_id ? *params->tab_id : -1;
469 content::WebContents* web_contents = GetWebContents(tab_id);
470 if (!web_contents)
471 return RespondNow(Error(std::move(error_)));
472
473 GURL url(web_contents->GetVisibleURL());
474 std::string error;
475 if (extension()->permissions_data()->IsRestrictedUrl(url, &error_))
476 return RespondNow(Error(std::move(error_)));
477
478 // "per-origin" scope is only available in "automatic" mode.
479 if (params->zoom_settings.scope == tabs::ZOOM_SETTINGS_SCOPE_PER_ORIGIN &&
480 params->zoom_settings.mode != tabs::ZOOM_SETTINGS_MODE_AUTOMATIC &&
481 params->zoom_settings.mode != tabs::ZOOM_SETTINGS_MODE_NONE) {
482 return RespondNow(Error(tabs_constants::kPerOriginOnlyInAutomaticError));
483 }
484
485 // Determine the correct internal zoom mode to set |web_contents| to from the
486 // user-specified |zoom_settings|.
487 ZoomController::ZoomMode zoom_mode = ZoomController::ZOOM_MODE_DEFAULT;
488 switch (params->zoom_settings.mode) {
489 case tabs::ZOOM_SETTINGS_MODE_NONE:
490 case tabs::ZOOM_SETTINGS_MODE_AUTOMATIC:
491 switch (params->zoom_settings.scope) {
492 case tabs::ZOOM_SETTINGS_SCOPE_NONE:
493 case tabs::ZOOM_SETTINGS_SCOPE_PER_ORIGIN:
494 zoom_mode = ZoomController::ZOOM_MODE_DEFAULT;
495 break;
496 case tabs::ZOOM_SETTINGS_SCOPE_PER_TAB:
497 zoom_mode = ZoomController::ZOOM_MODE_ISOLATED;
498 }
499 break;
500 case tabs::ZOOM_SETTINGS_MODE_MANUAL:
501 zoom_mode = ZoomController::ZOOM_MODE_MANUAL;
502 break;
503 case tabs::ZOOM_SETTINGS_MODE_DISABLED:
504 zoom_mode = ZoomController::ZOOM_MODE_DISABLED;
505 }
506
507 ZoomController::FromWebContents(web_contents)->SetZoomMode(zoom_mode);
508
509 return RespondNow(NoArguments());
510 }
511
Run()512 ExtensionFunction::ResponseAction TabsGetZoomSettingsFunction::Run() {
513 std::unique_ptr<tabs::GetZoomSettings::Params> params(
514 tabs::GetZoomSettings::Params::Create(args()));
515 EXTENSION_FUNCTION_VALIDATE(params);
516
517 int tab_id = params->tab_id ? *params->tab_id : -1;
518 content::WebContents* web_contents = GetWebContents(tab_id);
519 if (!web_contents)
520 return RespondNow(Error(std::move(error_)));
521 ZoomController* zoom_controller =
522 ZoomController::FromWebContents(web_contents);
523
524 ZoomController::ZoomMode zoom_mode = zoom_controller->zoom_mode();
525 api::tabs::ZoomSettings zoom_settings;
526 ZoomModeToZoomSettings(zoom_mode, &zoom_settings);
527 zoom_settings.default_zoom_factor = std::make_unique<double>(
528 blink::PageZoomLevelToZoomFactor(zoom_controller->GetDefaultZoomLevel()));
529
530 return RespondNow(
531 ArgumentList(api::tabs::GetZoomSettings::Results::Create(zoom_settings)));
532 }
533
534 } // namespace cef
535 } // namespace extensions
536