diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 1cc76ebfa23d..b9032409e463 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -14,8 +14,10 @@ #include "atom/browser/api/save_page_handler.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/atom_permission_manager.h" #include "atom/browser/net/atom_cert_verifier.h" #include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/content_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/net_converter.h" @@ -397,6 +399,18 @@ void Session::SetCertVerifyProc(v8::Local val, browser_context_->cert_verifier()->SetVerifyProc(proc); } +void Session::SetPermissionRequestHandler(v8::Local val, + mate::Arguments* args) { + AtomPermissionManager::RequestHandler handler; + if (!(val->IsNull() || mate::ConvertFromV8(args->isolate(), val, &handler))) { + args->ThrowError("Must pass null or function"); + return; + } + auto permission_manager = static_cast( + browser_context()->GetPermissionManager()); + permission_manager->SetPermissionRequestHandler(handler); +} + v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = atom::api::Cookies::Create(isolate, browser_context()); @@ -448,6 +462,8 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) + .SetMethod("setPermissionRequestHandler", + &Session::SetPermissionRequestHandler) .SetProperty("cookies", &Session::Cookies) .SetProperty("webRequest", &Session::WebRequest); } diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 37a5a45a6c95..efcafbfe5e33 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -76,6 +76,8 @@ class Session: public mate::TrackableObject, void EnableNetworkEmulation(const mate::Dictionary& options); void DisableNetworkEmulation(); void SetCertVerifyProc(v8::Local proc, mate::Arguments* args); + void SetPermissionRequestHandler(v8::Local val, + mate::Arguments* args); v8::Local Cookies(v8::Isolate* isolate); v8::Local WebRequest(v8::Isolate* isolate); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index b7b0576d8bfa..80e5b606ef8d 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -14,6 +14,7 @@ #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/native_window.h" +#include "atom/browser/web_contents_permission_helper.h" #include "atom/browser/web_contents_preferences.h" #include "atom/browser/web_view_guest_delegate.h" #include "atom/common/api/api_messages.h" @@ -263,6 +264,9 @@ WebContents::WebContents(v8::Isolate* isolate, // Save the preferences in C++. new WebContentsPreferences(web_contents, options); + // Intialize permission helper. + WebContentsPermissionHelper::CreateForWebContents(web_contents); + web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); if (is_guest) { @@ -387,6 +391,18 @@ void WebContents::HandleKeyboardEvent( void WebContents::EnterFullscreenModeForTab(content::WebContents* source, const GURL& origin) { + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(source); + auto callback = base::Bind(&WebContents::OnEnterFullscreenModeForTab, + base::Unretained(this), source, origin); + permission_helper->RequestFullscreenPermission(callback); +} + +void WebContents::OnEnterFullscreenModeForTab(content::WebContents* source, + const GURL& origin, + bool allowed) { + if (!allowed) + return; CommonWebContentsDelegate::EnterFullscreenModeForTab(source, origin); Emit("enter-html-full-screen"); } @@ -445,6 +461,24 @@ void WebContents::FindReply(content::WebContents* web_contents, } } +void WebContents::RequestMediaAccessPermission( + content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback) { + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + permission_helper->RequestMediaAccessPermission(request, callback); +} + +void WebContents::RequestToLockMouse( + content::WebContents* web_contents, + bool user_gesture, + bool last_unlocked_by_target) { + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + permission_helper->RequestPointerLockPermission(user_gesture); +} + void WebContents::BeforeUnloadFired(const base::TimeTicks& proceed_time) { // Do nothing, we override this method just to avoid compilation error since // there are two virtual functions named BeforeUnloadFired. diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index e00798b6c137..63c4a0177716 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -134,6 +134,11 @@ class WebContents : public mate::TrackableObject, void SetAllowTransparency(bool allow); bool IsGuest() const; + // Callback triggered on permission response. + void OnEnterFullscreenModeForTab(content::WebContents* source, + const GURL& origin, + bool allowed); + // Returns the web preferences of current WebContents. v8::Local GetWebPreferences(v8::Isolate* isolate); @@ -196,6 +201,14 @@ class WebContents : public mate::TrackableObject, const gfx::Rect& selection_rect, int active_match_ordinal, bool final_update) override; + void RequestMediaAccessPermission( + content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback) override; + void RequestToLockMouse( + content::WebContents* web_contents, + bool user_gesture, + bool last_unlocked_by_target) override; // content::WebContentsObserver: void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 5ad8e69ffc74..1fe435673db6 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -15,6 +15,7 @@ #include "atom/browser/atom_resource_dispatcher_host_delegate.h" #include "atom/browser/atom_speech_recognition_manager_delegate.h" #include "atom/browser/native_window.h" +#include "atom/browser/web_contents_permission_helper.h" #include "atom/browser/web_contents_preferences.h" #include "atom/browser/window_list.h" #include "atom/common/options_switches.h" @@ -281,6 +282,24 @@ brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( return new AtomBrowserMainParts; } +void AtomBrowserClient::WebNotificationAllowed( + int render_process_id, + const base::Callback& callback) { + content::WebContents* web_contents = content::WebContents::FromRenderViewHost( + content::RenderViewHost::FromID(render_process_id, kDefaultRoutingID)); + if (!web_contents) { + callback.Run(false); + return; + } + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + if (!permission_helper) { + callback.Run(false); + return; + } + permission_helper->RequestWebNotificationPermission(callback); +} + void AtomBrowserClient::RenderProcessHostDestroyed( content::RenderProcessHost* host) { int process_id = host->GetID(); diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index 3c54fab40bc1..4af66cc041ae 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -81,6 +81,9 @@ class AtomBrowserClient : public brightray::BrowserClient, // brightray::BrowserClient: brightray::BrowserMainParts* OverrideCreateBrowserMainParts( const content::MainFunctionParams&) override; + void WebNotificationAllowed( + int render_process_id, + const base::Callback& callback) override; // content::RenderProcessHostObserver: void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index ac59f8c31330..be004c8f6db8 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -13,6 +13,7 @@ #include "atom/browser/net/atom_url_request_job_factory.h" #include "atom/browser/net/asar/asar_protocol_handler.h" #include "atom/browser/net/http_protocol_handler.h" +#include "atom/browser/atom_permission_manager.h" #include "atom/browser/web_view_manager.h" #include "atom/common/atom_version.h" #include "atom/common/chrome_version.h" @@ -169,6 +170,12 @@ content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() { return guest_manager_.get(); } +content::PermissionManager* AtomBrowserContext::GetPermissionManager() { + if (!permission_manager_.get()) + permission_manager_.reset(new AtomPermissionManager); + return permission_manager_.get(); +} + scoped_ptr AtomBrowserContext::CreateCertVerifier() { DCHECK(!cert_verifier_); cert_verifier_ = new AtomCertVerifier; diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 9c94a60c305b..d959adbc753a 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -14,6 +14,7 @@ namespace atom { class AtomDownloadManagerDelegate; class AtomCertVerifier; class AtomNetworkDelegate; +class AtomPermissionManager; class AtomURLRequestJobFactory; class WebViewManager; @@ -37,6 +38,7 @@ class AtomBrowserContext : public brightray::BrowserContext { // content::BrowserContext: content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; content::BrowserPluginGuestManager* GetGuestManager() override; + content::PermissionManager* GetPermissionManager() override; // brightray::BrowserContext: void RegisterPrefs(PrefRegistrySimple* pref_registry) override; @@ -52,6 +54,7 @@ class AtomBrowserContext : public brightray::BrowserContext { private: scoped_ptr download_manager_delegate_; scoped_ptr guest_manager_; + scoped_ptr permission_manager_; // Managed by brightray::BrowserContext. AtomCertVerifier* cert_verifier_; diff --git a/atom/browser/atom_permission_manager.cc b/atom/browser/atom_permission_manager.cc new file mode 100644 index 000000000000..e7fdaaffea71 --- /dev/null +++ b/atom/browser/atom_permission_manager.cc @@ -0,0 +1,136 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/atom_permission_manager.h" + +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/permission_type.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" + +namespace atom { + +namespace { + +// Must be kept in sync with atom_browser_client.cc +int kDefaultRoutingID = 2; + +bool WebContentsDestroyed(int process_id) { + auto rvh = content::RenderViewHost::FromID(process_id, kDefaultRoutingID); + if (rvh) { + auto contents = content::WebContents::FromRenderViewHost(rvh); + return contents->IsBeingDestroyed(); + } + + return true; +} + +} // namespace + +AtomPermissionManager::AtomPermissionManager() + : request_id_(0) { +} + +AtomPermissionManager::~AtomPermissionManager() { +} + +void AtomPermissionManager::SetPermissionRequestHandler( + const RequestHandler& handler) { + if (handler.is_null() && !pending_requests_.empty()) { + for (const auto& request : pending_requests_) { + if (!WebContentsDestroyed(request.second.render_process_id)) + request.second.callback.Run(content::PERMISSION_STATUS_DENIED); + } + pending_requests_.clear(); + } + request_handler_ = handler; +} + +int AtomPermissionManager::RequestPermission( + content::PermissionType permission, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const ResponseCallback& response_callback) { + int process_id = render_frame_host->GetProcess()->GetID(); + + if (permission == content::PermissionType::MIDI_SYSEX) { + content::ChildProcessSecurityPolicy::GetInstance()-> + GrantSendMidiSysExMessage(process_id); + } + + if (!request_handler_.is_null()) { + auto web_contents = + content::WebContents::FromRenderFrameHost(render_frame_host); + ++request_id_; + auto callback = base::Bind(&AtomPermissionManager::OnPermissionResponse, + base::Unretained(this), + request_id_, + requesting_origin, + response_callback); + pending_requests_[request_id_] = { process_id, callback }; + request_handler_.Run(web_contents, permission, callback); + return request_id_; + } + + response_callback.Run(content::PERMISSION_STATUS_GRANTED); + return kNoPendingOperation; +} + +void AtomPermissionManager::OnPermissionResponse( + int request_id, + const GURL& origin, + const ResponseCallback& callback, + content::PermissionStatus status) { + auto request = pending_requests_.find(request_id); + if (request != pending_requests_.end()) { + if (!WebContentsDestroyed(request->second.render_process_id)) + callback.Run(status); + pending_requests_.erase(request); + } +} + +void AtomPermissionManager::CancelPermissionRequest(int request_id) { + auto request = pending_requests_.find(request_id); + if (request != pending_requests_.end()) { + if (!WebContentsDestroyed(request->second.render_process_id)) + request->second.callback.Run(content::PERMISSION_STATUS_DENIED); + pending_requests_.erase(request); + } +} + +void AtomPermissionManager::ResetPermission( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) { +} + +content::PermissionStatus AtomPermissionManager::GetPermissionStatus( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) { + return content::PERMISSION_STATUS_GRANTED; +} + +void AtomPermissionManager::RegisterPermissionUsage( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) { +} + +int AtomPermissionManager::SubscribePermissionStatusChange( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin, + const ResponseCallback& callback) { + return -1; +} + +void AtomPermissionManager::UnsubscribePermissionStatusChange( + int subscription_id) { +} + +} // namespace atom diff --git a/atom/browser/atom_permission_manager.h b/atom/browser/atom_permission_manager.h new file mode 100644 index 000000000000..4bebbbc84f29 --- /dev/null +++ b/atom/browser/atom_permission_manager.h @@ -0,0 +1,84 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_ATOM_PERMISSION_MANAGER_H_ +#define ATOM_BROWSER_ATOM_PERMISSION_MANAGER_H_ + +#include + +#include "base/callback.h" +#include "content/public/browser/permission_manager.h" + +namespace content { +class WebContents; +} + +namespace atom { + +class AtomPermissionManager : public content::PermissionManager { + public: + AtomPermissionManager(); + ~AtomPermissionManager() override; + + using ResponseCallback = + base::Callback; + using RequestHandler = + base::Callback; + + // Handler to dispatch permission requests in JS. + void SetPermissionRequestHandler(const RequestHandler& handler); + + // content::PermissionManager: + int RequestPermission( + content::PermissionType permission, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const ResponseCallback& callback) override; + + protected: + void OnPermissionResponse(int request_id, + const GURL& url, + const ResponseCallback& callback, + content::PermissionStatus status); + + // content::PermissionManager: + void CancelPermissionRequest(int request_id) override; + void ResetPermission(content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) override; + content::PermissionStatus GetPermissionStatus( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) override; + void RegisterPermissionUsage(content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) override; + int SubscribePermissionStatusChange( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin, + const base::Callback& callback) override; + void UnsubscribePermissionStatusChange(int subscription_id) override; + + private: + struct RequestInfo { + int render_process_id; + ResponseCallback callback; + }; + + RequestHandler request_handler_; + + std::map pending_requests_; + + int request_id_; + + DISALLOW_COPY_AND_ASSIGN(AtomPermissionManager); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_PERMISSION_MANAGER_H_ diff --git a/atom/browser/lib/guest-view-manager.js b/atom/browser/lib/guest-view-manager.js index 40f59691e7a1..b41b9b3a0f5c 100644 --- a/atom/browser/lib/guest-view-manager.js +++ b/atom/browser/lib/guest-view-manager.js @@ -6,7 +6,38 @@ var slice = [].slice; // Doesn't exist in early initialization. var webViewManager = null; -var supportedWebViewEvents = ['load-commit', 'did-finish-load', 'did-fail-load', 'did-frame-finish-load', 'did-start-loading', 'did-stop-loading', 'did-get-response-details', 'did-get-redirect-request', 'dom-ready', 'console-message', 'devtools-opened', 'devtools-closed', 'devtools-focused', 'new-window', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'close', 'crashed', 'gpu-crashed', 'plugin-crashed', 'destroyed', 'page-title-updated', 'page-favicon-updated', 'enter-html-full-screen', 'leave-html-full-screen', 'media-started-playing', 'media-paused', 'found-in-page', 'did-change-theme-color']; +var supportedWebViewEvents = [ + 'load-commit', + 'did-finish-load', + 'did-fail-load', + 'did-frame-finish-load', + 'did-start-loading', + 'did-stop-loading', + 'did-get-response-details', + 'did-get-redirect-request', + 'dom-ready', + 'console-message', + 'devtools-opened', + 'devtools-closed', + 'devtools-focused', + 'new-window', + 'will-navigate', + 'did-navigate', + 'did-navigate-in-page', + 'close', + 'crashed', + 'gpu-crashed', + 'plugin-crashed', + 'destroyed', + 'page-title-updated', + 'page-favicon-updated', + 'enter-html-full-screen', + 'leave-html-full-screen', + 'media-started-playing', + 'media-paused', + 'found-in-page', + 'did-change-theme-color' +]; var nextInstanceId = 0; var guestInstances = {}; diff --git a/atom/browser/web_contents_permission_helper.cc b/atom/browser/web_contents_permission_helper.cc new file mode 100644 index 000000000000..f5ce414dd2ca --- /dev/null +++ b/atom/browser/web_contents_permission_helper.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/web_contents_permission_helper.h" + +#include + +#include "atom/browser/atom_permission_manager.h" +#include "brightray/browser/media/media_stream_devices_controller.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/render_process_host.h" + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPermissionHelper); + +namespace atom { + +namespace { + +void MediaAccessAllowed( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback, + bool allowed) { + brightray::MediaStreamDevicesController controller(request, callback); + if (allowed) + controller.Accept(); + else + controller.Deny(content::MEDIA_DEVICE_PERMISSION_DENIED); +} + +void OnPointerLockResponse(content::WebContents* web_contents, bool allowed) { + if (web_contents) + web_contents->GotResponseToLockMouseRequest(allowed); +} + +void OnPermissionResponse(const base::Callback& callback, + content::PermissionStatus status) { + if (status == content::PERMISSION_STATUS_GRANTED) + callback.Run(true); + else + callback.Run(false); +} + +} // namespace + +WebContentsPermissionHelper::WebContentsPermissionHelper( + content::WebContents* web_contents) + : web_contents_(web_contents) { +} + +WebContentsPermissionHelper::~WebContentsPermissionHelper() { +} + +void WebContentsPermissionHelper::RequestPermission( + content::PermissionType permission, + const base::Callback& callback, + bool user_gesture) { + auto rfh = web_contents_->GetMainFrame(); + auto permission_manager = static_cast( + web_contents_->GetBrowserContext()->GetPermissionManager()); + auto origin = web_contents_->GetLastCommittedURL(); + permission_manager->RequestPermission( + permission, rfh, origin, user_gesture, + base::Bind(&OnPermissionResponse, callback)); +} + +void WebContentsPermissionHelper::RequestFullscreenPermission( + const base::Callback& callback) { + RequestPermission((content::PermissionType)(PermissionType::FULLSCREEN), + callback); +} + +void WebContentsPermissionHelper::RequestMediaAccessPermission( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& response_callback) { + auto callback = base::Bind(&MediaAccessAllowed, request, response_callback); + // The permission type doesn't matter here, AUDIO_CAPTURE/VIDEO_CAPTURE + // are presented as same type in content_converter.h. + RequestPermission(content::PermissionType::AUDIO_CAPTURE, callback); +} + +void WebContentsPermissionHelper::RequestWebNotificationPermission( + const base::Callback& callback) { + RequestPermission(content::PermissionType::NOTIFICATIONS, callback); +} + +void WebContentsPermissionHelper::RequestPointerLockPermission( + bool user_gesture) { + RequestPermission((content::PermissionType)(PermissionType::POINTER_LOCK), + base::Bind(&OnPointerLockResponse, web_contents_), + user_gesture); +} + +} // namespace atom diff --git a/atom/browser/web_contents_permission_helper.h b/atom/browser/web_contents_permission_helper.h new file mode 100644 index 000000000000..90ae6dff56f5 --- /dev/null +++ b/atom/browser/web_contents_permission_helper.h @@ -0,0 +1,50 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_WEB_CONTENTS_PERMISSION_HELPER_H_ +#define ATOM_BROWSER_WEB_CONTENTS_PERMISSION_HELPER_H_ + +#include "content/public/browser/permission_type.h" +#include "content/public/browser/web_contents_user_data.h" +#include "content/public/common/media_stream_request.h" + +namespace atom { + +// Applies the permission requested for WebContents. +class WebContentsPermissionHelper + : public content::WebContentsUserData { + public: + ~WebContentsPermissionHelper() override; + + enum class PermissionType { + POINTER_LOCK = static_cast(content::PermissionType::NUM) + 1, + FULLSCREEN + }; + + void RequestFullscreenPermission( + const base::Callback& callback); + void RequestMediaAccessPermission( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback); + void RequestWebNotificationPermission( + const base::Callback& callback); + void RequestPointerLockPermission(bool user_gesture); + + private: + explicit WebContentsPermissionHelper(content::WebContents* web_contents); + friend class content::WebContentsUserData; + + void RequestPermission( + content::PermissionType permission, + const base::Callback& callback, + bool user_gesture = false); + + content::WebContents* web_contents_; + + DISALLOW_COPY_AND_ASSIGN(WebContentsPermissionHelper); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_WEB_CONTENTS_PERMISSION_HELPER_H_ diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index d79094f79d43..699f101d0e4b 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -7,6 +7,8 @@ #include #include +#include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/web_contents_permission_helper.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "content/public/browser/web_contents.h" @@ -98,6 +100,55 @@ v8::Local Converter::ToV8( return mate::ConvertToV8(isolate, dict); } +// static +bool Converter::FromV8( + v8::Isolate* isolate, + v8::Local val, + content::PermissionStatus* out) { + bool result; + if (!ConvertFromV8(isolate, val, &result)) + return false; + + if (result) + *out = content::PERMISSION_STATUS_GRANTED; + else + *out = content::PERMISSION_STATUS_DENIED; + + return true; +} + +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, const content::PermissionType& val) { + using PermissionType = atom::WebContentsPermissionHelper::PermissionType; + switch (val) { + case content::PermissionType::MIDI_SYSEX: + return StringToV8(isolate, "midiSysex"); + case content::PermissionType::PUSH_MESSAGING: + return StringToV8(isolate, "pushMessaging"); + case content::PermissionType::NOTIFICATIONS: + return StringToV8(isolate, "notifications"); + case content::PermissionType::GEOLOCATION: + return StringToV8(isolate, "geolocation"); + case content::PermissionType::AUDIO_CAPTURE: + case content::PermissionType::VIDEO_CAPTURE: + return StringToV8(isolate, "media"); + case content::PermissionType::PROTECTED_MEDIA_IDENTIFIER: + return StringToV8(isolate, "mediaKeySystem"); + case content::PermissionType::MIDI: + return StringToV8(isolate, "midi"); + default: + break; + } + + if (val == (content::PermissionType)(PermissionType::POINTER_LOCK)) + return StringToV8(isolate, "pointerLock"); + else if (val == (content::PermissionType)(PermissionType::FULLSCREEN)) + return StringToV8(isolate, "fullscreen"); + + return StringToV8(isolate, "unknown"); +} + // static bool Converter::FromV8( v8::Isolate* isolate, @@ -119,4 +170,10 @@ bool Converter::FromV8( return true; } +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, content::WebContents* val) { + return atom::api::WebContents::CreateFrom(isolate, val).ToV8(); +} + } // namespace mate diff --git a/atom/common/native_mate_converters/content_converter.h b/atom/common/native_mate_converters/content_converter.h index a5708e022b57..b1a42b6897ca 100644 --- a/atom/common/native_mate_converters/content_converter.h +++ b/atom/common/native_mate_converters/content_converter.h @@ -7,7 +7,9 @@ #include +#include "content/public/browser/permission_type.h" #include "content/public/common/menu_item.h" +#include "content/public/common/permission_status.mojom.h" #include "content/public/common/stop_find_action.h" #include "native_mate/converter.h" @@ -33,12 +35,30 @@ struct Converter { const ContextMenuParamsWithWebContents& val); }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + content::PermissionStatus* out); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const content::PermissionType& val); +}; + template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, content::StopFindAction* out); }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + content::WebContents* val); +}; + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_CONTENT_CONVERTER_H_ diff --git a/docs/api/session.md b/docs/api/session.md index fed8c31c7462..8b0fa4ed5505 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -290,6 +290,29 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c }); ``` +#### `ses.setPermissionRequestHandler(handler)` + +* `handler` Function + * `webContents` Object - [WebContents](web-contents.md) requesting the permission. + * `permission` String - Enum of 'media', 'geolocation', 'notifications', 'midiSysex', 'pointerLock', 'fullscreen'. + * `callback` Function - Allow or deny the permission. + +Sets the handler which can be used to respond to permission requests for the `session`. +Calling `callback(true)` will allow the permission and `callback(false)` will reject it. + +```javascript +session.fromPartition(partition).setPermissionRequestHandler(function(webContents, permission, callback) { + if (webContents.getURL() === host) { + if (permission == "notifications") { + callback(false); // denied. + return; + } + } + + callback(true); +}); +``` + #### `ses.webRequest` The `webRequest` API set allows to intercept and modify contents of a request at diff --git a/filenames.gypi b/filenames.gypi index 748b3b9252cd..a5369570cade 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -144,6 +144,8 @@ 'atom/browser/atom_browser_main_parts_posix.cc', 'atom/browser/atom_javascript_dialog_manager.cc', 'atom/browser/atom_javascript_dialog_manager.h', + 'atom/browser/atom_permission_manager.cc', + 'atom/browser/atom_permission_manager.h', 'atom/browser/atom_quota_permission_context.cc', 'atom/browser/atom_quota_permission_context.h', 'atom/browser/atom_resource_dispatcher_host_delegate.cc', @@ -256,6 +258,8 @@ 'atom/browser/ui/x/window_state_watcher.h', 'atom/browser/ui/x/x_window_utils.cc', 'atom/browser/ui/x/x_window_utils.h', + 'atom/browser/web_contents_permission_helper.cc', + 'atom/browser/web_contents_permission_helper.h', 'atom/browser/web_contents_preferences.cc', 'atom/browser/web_contents_preferences.h', 'atom/browser/web_dialog_helper.cc', diff --git a/spec/fixtures/pages/permissions/geolocation.html b/spec/fixtures/pages/permissions/geolocation.html new file mode 100644 index 000000000000..1d1b4fc42451 --- /dev/null +++ b/spec/fixtures/pages/permissions/geolocation.html @@ -0,0 +1,5 @@ + diff --git a/spec/fixtures/pages/permissions/media.html b/spec/fixtures/pages/permissions/media.html new file mode 100644 index 000000000000..0d968a9a66b9 --- /dev/null +++ b/spec/fixtures/pages/permissions/media.html @@ -0,0 +1,7 @@ + diff --git a/spec/fixtures/pages/permissions/midi.html b/spec/fixtures/pages/permissions/midi.html new file mode 100644 index 000000000000..c6bd8c137e36 --- /dev/null +++ b/spec/fixtures/pages/permissions/midi.html @@ -0,0 +1,5 @@ + diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 0eaa59c5fd3c..cac5fb56b444 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -624,7 +624,7 @@ describe(' tag', function() { return document.body.appendChild(webview); }); }); - return xdescribe('did-change-theme-color event', function() { + xdescribe('did-change-theme-color event', function() { return it('emits when theme color changes', function(done) { webview.addEventListener('did-change-theme-color', function() { return done(); @@ -633,4 +633,55 @@ describe(' tag', function() { return document.body.appendChild(webview); }); }); + describe('permission-request event', function() { + function setUpRequestHandler(webview, requested_permission) { + const session = require('electron').remote.session; + var listener = function(webContents, permission, callback) { + if (webContents.getId() === webview.getId() ) { + assert.equal(permission, requested_permission); + callback(false); + } + }; + session.fromPartition(webview.partition).setPermissionRequestHandler(listener); + } + + it ('emits when using navigator.getUserMedia api', function(done) { + webview.addEventListener('ipc-message', function(e) { + assert(e.channel, 'message'); + assert(e.args, ['PermissionDeniedError']); + done(); + }); + webview.src = "file://" + fixtures + "/pages/permissions/media.html"; + webview.partition = "permissionTest"; + webview.setAttribute('nodeintegration', 'on'); + setUpRequestHandler(webview, "media"); + document.body.appendChild(webview); + }); + + it ('emits when using navigator.geolocation api', function(done) { + webview.addEventListener('ipc-message', function(e) { + assert(e.channel, 'message'); + assert(e.args, ['ERROR(1): User denied Geolocation']); + done(); + }); + webview.src = "file://" + fixtures + "/pages/permissions/geolocation.html"; + webview.partition = "permissionTest"; + webview.setAttribute('nodeintegration', 'on'); + setUpRequestHandler(webview, "geolocation"); + document.body.appendChild(webview); + }); + + it ('emits when using navigator.requestMIDIAccess api', function(done) { + webview.addEventListener('ipc-message', function(e) { + assert(e.channel, 'message'); + assert(e.args, ['SecurityError']); + done(); + }); + webview.src = "file://" + fixtures + "/pages/permissions/midi.html"; + webview.partition = "permissionTest"; + webview.setAttribute('nodeintegration', 'on'); + setUpRequestHandler(webview, "midiSysex"); + document.body.appendChild(webview); + }); + }); });