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<WebContents>,
                  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 <string>
+
+#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<content::MediaStreamUI>());
+}
+
+}  // 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 <map>
+#include <string>
+
+#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<WebContentsPermissionHelper> {
+ 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<WebContentsPermissionHelper>;
+
+  std::map<std::string, base::Closure> 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 <vector>
 
+#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
+<webview src="https://www.github.com/" disablewebnotification></webview>
+```
+
+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',