From 85e13333c3e0bd1b15738fb98c23ebaedbf1f6e8 Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 23 Jan 2016 18:59:47 +0530 Subject: [PATCH] webview: add permission-request event --- atom/browser/api/atom_api_web_contents.cc | 13 ++ atom/browser/api/atom_api_web_contents.h | 4 + atom/browser/atom_browser_client.cc | 18 +++ atom/browser/atom_browser_client.h | 2 + atom/browser/lib/guest-view-manager.js | 22 +++- .../browser/web_contents_permission_helper.cc | 114 ++++++++++++++++++ atom/browser/web_contents_permission_helper.h | 48 ++++++++ atom/common/api/event_emitter_caller.h | 1 + .../lib/web-view/guest-view-internal.js | 12 +- .../lib/web-view/web-view-attributes.js | 1 + docs/api/web-view-tag.md | 26 ++++ filenames.gypi | 2 + 12 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 atom/browser/web_contents_permission_helper.cc create mode 100644 atom/browser/web_contents_permission_helper.h diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 2b14bdc60d83..990d78aa9caf 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" @@ -262,6 +263,9 @@ WebContents::WebContents(v8::Isolate* isolate, // Save the preferences in C++. new WebContentsPreferences(web_contents, options); + // Initialze permission helper. + new WebContentsPermissionHelper(web_contents, this); + web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); if (is_guest) { @@ -444,6 +448,15 @@ 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::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 10ac7a4f7695..6587759efb9f 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -195,6 +195,10 @@ 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; // 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..49637f5342f0 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,23 @@ brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( return new AtomBrowserMainParts; } +void AtomBrowserClient::WebNotificationAllowed(int render_process_id, + const base::Closure& callback) { + content::WebContents* web_contents = content::WebContents::FromRenderViewHost( + content::RenderViewHost::FromID(render_process_id, kDefaultRoutingID)); + if (!web_contents) { + callback.Run(); + return; + } + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + if (!permission_helper) { + callback.Run(); + 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..9e0e979db881 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -81,6 +81,8 @@ class AtomBrowserClient : public brightray::BrowserClient, // brightray::BrowserClient: brightray::BrowserMainParts* OverrideCreateBrowserMainParts( const content::MainFunctionParams&) override; + void WebNotificationAllowed(int render_process_id, + const base::Closure& callback) override; // content::RenderProcessHostObserver: void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; diff --git a/atom/browser/lib/guest-view-manager.js b/atom/browser/lib/guest-view-manager.js index 40f59691e7a1..233b1f1e4151 100644 --- a/atom/browser/lib/guest-view-manager.js +++ b/atom/browser/lib/guest-view-manager.js @@ -6,10 +6,12 @@ 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', 'permission-request']; var nextInstanceId = 0; +var permissionRequests; var guestInstances = {}; +var guestPermissionRequestsMap = {}; var embedderElementsMap = {}; var reverseEmbedderElementsMap = {}; @@ -110,6 +112,13 @@ var createGuest = function(embedder, params) { fn = function(event) { return guest.on(event, function() { var args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + if (event === 'permission-request') { + if (!guestPermissionRequestsMap[guest.viewInstanceId]) + guestPermissionRequestsMap[guest.viewInstanceId] = {}; + var permission = args[0]; + guestPermissionRequestsMap[guest.viewInstanceId][permission] = args[1]; + args.pop(); + } return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + guest.viewInstanceId, event].concat(slice.call(args))); }); }; @@ -157,7 +166,8 @@ var attachGuest = function(embedder, elementInstanceId, guestInstanceId, params) nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false, plugins: params.plugins, webSecurity: !params.disablewebsecurity, - blinkFeatures: params.blinkfeatures + blinkFeatures: params.blinkfeatures, + webNotification: !params.disablewebnotification, }; if (params.preload) { webPreferences.preloadURL = params.preload; @@ -174,6 +184,7 @@ var destroyGuest = function(embedder, id) { webViewManager.removeGuest(embedder, id); guestInstances[id].guest.destroy(); delete guestInstances[id]; + delete permissionRequests[id]; key = reverseEmbedderElementsMap[id]; if (key != null) { delete reverseEmbedderElementsMap[id]; @@ -193,6 +204,13 @@ ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', function(event, id) { return destroyGuest(event.sender, id); }); +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_PERMISSION_RESPONSE', function(event, viewInstanceId, permission, allowed) { + permissionRequests = guestPermissionRequestsMap[viewInstanceId]; + if (permissionRequests && permissionRequests[permission] !== null) { + permissionRequests[permission].apply(null, [allowed]); + } +}); + ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', function(event, id, params) { var ref1; return (ref1 = guestInstances[id]) != null ? ref1.guest.setSize(params) : void 0; diff --git a/atom/browser/web_contents_permission_helper.cc b/atom/browser/web_contents_permission_helper.cc new file mode 100644 index 000000000000..91ae60461770 --- /dev/null +++ b/atom/browser/web_contents_permission_helper.cc @@ -0,0 +1,114 @@ +// 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/api/atom_api_web_contents.h" +#include "content/public/browser/media_capture_devices.h" + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPermissionHelper); + +namespace atom { + +namespace { + +const content::MediaStreamDevice* FindDeviceWithId( + const content::MediaStreamDevices& devices, + const std::string& device_id) { + if (device_id.empty()) + return &(*devices.begin()); + for (const auto& iter : devices) + if (iter.id == device_id) + return &(iter); + return nullptr; +} + +void MediaAccessAllowed( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback) { + content::MediaStreamDevices devices; + content::MediaStreamRequestResult result = content::MEDIA_DEVICE_NO_HARDWARE; + + if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { + const content::MediaStreamDevices& audio_devices = + content::MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices(); + const content::MediaStreamDevice* audio_device = + FindDeviceWithId(audio_devices, request.requested_audio_device_id); + if (audio_device) + devices.push_back(*audio_device); + } + + if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { + const content::MediaStreamDevices& video_devices = + content::MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices(); + const content::MediaStreamDevice* video_device = + FindDeviceWithId(video_devices, request.requested_video_device_id); + if (video_device) + devices.push_back(*video_device); + } + + if (!devices.empty()) + result = content::MEDIA_DEVICE_OK; + + callback.Run(devices, result, scoped_ptr()); +} + +} // namespace + +WebContentsPermissionHelper::WebContentsPermissionHelper( + content::WebContents* web_contents, + api::WebContents* api_web_contents) + : api_web_contents_(api_web_contents) { + web_contents->SetUserData(UserDataKey(), this); +} + +WebContentsPermissionHelper::~WebContentsPermissionHelper() { +} + +void WebContentsPermissionHelper::RequestMediaAccessPermission( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback) { + if (api_web_contents_->IsGuest()) { + const std::string& permission = "media"; + permission_map_[permission] = base::Bind(&MediaAccessAllowed, + request, + callback); + api_web_contents_->Emit( + "permission-request", + permission, + base::Bind(&WebContentsPermissionHelper::OnPermissionResponse, + base::Unretained(this), permission)); + return; + } + MediaAccessAllowed(request, callback); +} + +void WebContentsPermissionHelper::RequestWebNotificationPermission( + const base::Closure& callback) { + if (api_web_contents_->IsGuest()) { + const std::string& permission = "webNotification"; + permission_map_[permission] = callback; + api_web_contents_->Emit( + "permission-request", + permission, + base::Bind(&WebContentsPermissionHelper::OnPermissionResponse, + base::Unretained(this), permission)); + return; + } + callback.Run(); +} + +void WebContentsPermissionHelper::OnPermissionResponse( + const std::string& permission, bool allowed) { + auto it = permission_map_.find(permission); + if (it != permission_map_.end()) { + if (allowed) + it->second.Run(); + permission_map_.erase(permission); + } +} + +} // 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..80e76fd685a3 --- /dev/null +++ b/atom/browser/web_contents_permission_helper.h @@ -0,0 +1,48 @@ +// 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 +#include + +#include "base/callback.h" +#include "content/public/browser/web_contents_user_data.h" +#include "content/public/common/media_stream_request.h" + +namespace atom { + +namespace api { +class WebContents; +} + +// Applies the permission requested for WebContents. +class WebContentsPermissionHelper + : public content::WebContentsUserData { + public: + WebContentsPermissionHelper(content::WebContents* web_contents, + api::WebContents* api_web_contents); + ~WebContentsPermissionHelper() override; + + void RequestMediaAccessPermission( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback); + void RequestWebNotificationPermission(const base::Closure& callback); + + void OnPermissionResponse(const std::string& permission, bool allowed); + + private: + friend class content::WebContentsUserData; + + std::map permission_map_; + + api::WebContents* api_web_contents_; // Weak reference + + DISALLOW_COPY_AND_ASSIGN(WebContentsPermissionHelper); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_WEB_CONTENTS_PERMISSION_HELPER_H_ diff --git a/atom/common/api/event_emitter_caller.h b/atom/common/api/event_emitter_caller.h index a2567da9d109..26a4cbc45b90 100644 --- a/atom/common/api/event_emitter_caller.h +++ b/atom/common/api/event_emitter_caller.h @@ -7,6 +7,7 @@ #include +#include "atom/common/native_mate_converters/callback.h" #include "native_mate/converter.h" namespace mate { diff --git a/atom/renderer/lib/web-view/guest-view-internal.js b/atom/renderer/lib/web-view/guest-view-internal.js index a7427abd631c..e6a1519b0f4c 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.js +++ b/atom/renderer/lib/web-view/guest-view-internal.js @@ -34,7 +34,8 @@ var WEB_VIEW_EVENTS = { 'page-favicon-updated': ['favicons'], 'enter-html-full-screen': [], 'leave-html-full-screen': [], - 'found-in-page': ['result'] + 'found-in-page': ['result'], + 'permission-request': ['permission', 'allow', 'deny'] }; var DEPRECATED_EVENTS = { @@ -64,6 +65,15 @@ module.exports = { ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId, function() { var eventName = arguments[1]; var args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + if (eventName === 'permission-request') { + var allow = function allow() { + ipcRenderer.send("ATOM_SHELL_GUEST_VIEW_MANAGER_SET_PERMISSION_RESPONSE", viewInstanceId, args[0], true); + }; + var deny = function deny() { + ipcRenderer.send("ATOM_SHELL_GUEST_VIEW_MANAGER_SET_PERMISSION_RESPONSE", viewInstanceId, args[0], false); + }; + args = args.concat([allow, deny]); + } return dispatchEvent.apply(null, [webView, eventName, eventName].concat(slice.call(args))); }); ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId, function() { diff --git a/atom/renderer/lib/web-view/web-view-attributes.js b/atom/renderer/lib/web-view/web-view-attributes.js index 4ad4bd725012..180924f5b569 100644 --- a/atom/renderer/lib/web-view/web-view-attributes.js +++ b/atom/renderer/lib/web-view/web-view-attributes.js @@ -311,6 +311,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function() { this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this); this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this); this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this); + this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBNOTIFICATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBNOTIFICATION, this); autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]; results = []; for (i = 0, len = autosizeAttributes.length; i < len; i++) { diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 9cb8f49af352..630dbdc95857 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -167,6 +167,14 @@ A list of strings which specifies the blink features to be enabled separated by The full list of supported feature strings can be found in the [setFeatureEnabledFromString][blink-feature-string] function. +### `disablewebnotification` + +```html + +``` + +If "on", the guest page will have web notifications disabled. + ## Methods The `webview` tag has the following methods: @@ -736,4 +744,22 @@ Emitted when DevTools is closed. Emitted when DevTools is focused / opened. +### Event: 'permission-request' + +Returns: + +* `permission` String - The type of permission being requested. Enum of 'media', 'webNotification'. +* `allow` Function - Allows the permission. +* `deny` Function - Deny the permission. This is the default behaviour if `allow` is not called. + +Emitted when guest page requires special permission. + +```javascript +// This will deny guest page access to the webkitGetUserMedia API. +webview.addEventListener('permission-request', function(e) { + if (e.permission === 'media') + e.deny(); +}); +``` + [blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 diff --git a/filenames.gypi b/filenames.gypi index 4aa47fc3580f..3c9cd4b69198 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -256,6 +256,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',