• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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