Bug 826325 - Handle preventDefault on mozbrowserwindowopen events r=bz
authorWes Johnston <wjohnston@mozilla.com>
Mon, 09 Sep 2013 21:18:46 -0700
changeset 146396 bd53e981282f9ed59ca35ce59776c2f94e9f6e7d
parent 146317 ea33604f6232a07d3e4be77718226d569490407f
child 146397 32e968bcba07e2296ea6ab9f9a7c20a939f5b025
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbz
bugs826325
milestone26.0a1
Bug 826325 - Handle preventDefault on mozbrowserwindowopen events r=bz
dom/browser-element/BrowserElementParent.cpp
dom/browser-element/BrowserElementParent.h
dom/browser-element/Makefile.in
dom/browser-element/mochitest/browserElement_AppWindowNamespace.js
dom/browser-element/mochitest/browserElement_OpenWindowRejected.js
dom/browser-element/mochitest/browserElement_TargetBlank.js
dom/ipc/TabParent.cpp
xpfe/appshell/src/nsContentTreeOwner.cpp
--- a/dom/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -22,16 +22,17 @@
 #include "nsCxPusher.h"
 #include "GeneratedEventClasses.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace {
 
+using mozilla::BrowserElementParent;
 /**
  * Create an <iframe mozbrowser> owned by the same document as
  * aOpenerFrameElement.
  */
 already_AddRefed<HTMLIFrameElement>
 CreateIframe(Element* aOpenerFrameElement, const nsAString& aName, bool aRemote)
 {
   nsNodeInfoManager *nodeInfoManager =
@@ -67,17 +68,18 @@ CreateIframe(Element* aOpenerFrameElemen
                                        NS_LITERAL_STRING("false"),
                              /* aNotify = */ false);
 
   return popupFrameElement.forget();
 }
 
 bool
 DispatchCustomDOMEvent(Element* aFrameElement, const nsAString& aEventName,
-                       JSContext* cx, JS::Handle<JS::Value> aDetailValue)
+                       JSContext* cx, JS::Handle<JS::Value> aDetailValue,
+                       nsEventStatus *aStatus)
 {
   NS_ENSURE_TRUE(aFrameElement, false);
   nsIPresShell *shell = aFrameElement->OwnerDoc()->GetShell();
   nsRefPtr<nsPresContext> presContext;
   if (shell) {
     presContext = shell->GetPresContext();
   }
 
@@ -89,39 +91,44 @@ DispatchCustomDOMEvent(Element* aFrameEl
 
   nsCOMPtr<nsIDOMCustomEvent> customEvent = do_QueryInterface(domEvent);
   NS_ENSURE_TRUE(customEvent, false);
   ErrorResult res;
   CustomEvent* event = static_cast<CustomEvent*>(customEvent.get());
   event->InitCustomEvent(cx,
                          aEventName,
                          /* bubbles = */ true,
-                         /* cancelable = */ false,
+                         /* cancelable = */ true,
                          aDetailValue,
                          res);
   if (res.Failed()) {
     return false;
   }
   customEvent->SetTrusted(true);
   // Dispatch the event.
-  nsEventStatus status = nsEventStatus_eIgnore;
+  *aStatus = nsEventStatus_eConsumeNoDefault;
   nsresult rv = nsEventDispatcher::DispatchDOMEvent(aFrameElement, nullptr,
-                                                    domEvent, presContext, &status);
+                                                    domEvent, presContext, aStatus);
   return NS_SUCCEEDED(rv);
 }
 
+} // anonymous namespace
+
+namespace mozilla {
+
 /**
  * Dispatch a mozbrowseropenwindow event to the given opener frame element.
  * The "popup iframe" (event.detail.frameElement) will be |aPopupFrameElement|.
  *
  * Returns true iff there were no unexpected failures and the window.open call
  * was accepted by the embedder.
  */
-bool
-DispatchOpenWindowEvent(Element* aOpenerFrameElement,
+/*static*/
+BrowserElementParent::OpenWindowResult
+BrowserElementParent::DispatchOpenWindowEvent(Element* aOpenerFrameElement,
                         Element* aPopupFrameElement,
                         const nsAString& aURL,
                         const nsAString& aName,
                         const nsAString& aFeatures)
 {
   // Dispatch a CustomEvent at aOpenerFrameElement with a detail object
   // (OpenWindowEventDetail) containing aPopupFrameElement, aURL, aName, and
   // aFeatures.
@@ -133,83 +140,91 @@ DispatchOpenWindowEvent(Element* aOpener
   detail.mFeatures = aFeatures;
   detail.mFrameElement = aPopupFrameElement;
 
   AutoJSContext cx;
   JS::Rooted<JS::Value> val(cx);
 
   nsIGlobalObject* sgo = aPopupFrameElement->OwnerDoc()->GetScopeObject();
   if (!sgo) {
-    return false;
+    return BrowserElementParent::OPEN_WINDOW_IGNORED;
   }
 
   JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject());
   JSAutoCompartment ac(cx, global);
   if (!detail.ToObject(cx, global, &val)) {
     MOZ_CRASH("Failed to convert dictionary to JS::Value due to OOM.");
-    return false;
+    return BrowserElementParent::OPEN_WINDOW_IGNORED;
   }
 
+  nsEventStatus status;
   bool dispatchSucceeded =
     DispatchCustomDOMEvent(aOpenerFrameElement,
                            NS_LITERAL_STRING("mozbrowseropenwindow"),
                            cx,
-                           val);
+                           val, &status);
 
-  // If the iframe is not in some document's DOM at this point, the embedder
-  // has "blocked" the popup.
-  return (dispatchSucceeded && aPopupFrameElement->IsInDoc());
+  if (dispatchSucceeded) {
+    if (aPopupFrameElement->IsInDoc()) {
+      return BrowserElementParent::OPEN_WINDOW_ADDED;
+    } else if (status == nsEventStatus_eConsumeNoDefault) {
+      // If the frame was not added to a document, report to callers whether
+      // preventDefault was called on or not
+      return BrowserElementParent::OPEN_WINDOW_CANCELLED;
+    }
+  }
+
+  return BrowserElementParent::OPEN_WINDOW_IGNORED;
 }
 
-} // anonymous namespace
-
-namespace mozilla {
-
-/*static*/ bool
+/*static*/
+BrowserElementParent::OpenWindowResult
 BrowserElementParent::OpenWindowOOP(TabParent* aOpenerTabParent,
                                     TabParent* aPopupTabParent,
                                     const nsAString& aURL,
                                     const nsAString& aName,
                                     const nsAString& aFeatures)
 {
   // Create an iframe owned by the same document which owns openerFrameElement.
   nsCOMPtr<Element> openerFrameElement = aOpenerTabParent->GetOwnerElement();
-  NS_ENSURE_TRUE(openerFrameElement, false);
+  NS_ENSURE_TRUE(openerFrameElement,
+                 BrowserElementParent::OPEN_WINDOW_IGNORED);
   nsRefPtr<HTMLIFrameElement> popupFrameElement =
     CreateIframe(openerFrameElement, aName, /* aRemote = */ true);
 
   // Normally an <iframe> element will try to create a frameLoader when the
   // page touches iframe.contentWindow or sets iframe.src.
   //
   // But in our case, we want to delay the creation of the frameLoader until
   // we've verified that the popup has gone through successfully.  If the popup
   // is "blocked" by the embedder, we don't want to load the popup's url.
   //
   // Therefore we call DisallowCreateFrameLoader() on the element and call
   // AllowCreateFrameLoader() only after we've verified that the popup was
   // allowed.
   popupFrameElement->DisallowCreateFrameLoader();
 
-  bool dispatchSucceeded =
+  OpenWindowResult opened =
     DispatchOpenWindowEvent(openerFrameElement, popupFrameElement,
                             aURL, aName, aFeatures);
-  if (!dispatchSucceeded) {
-    return false;
+
+  if (opened != BrowserElementParent::OPEN_WINDOW_ADDED) {
+    return opened;
   }
 
   // The popup was not blocked, so hook up the frame element and the popup tab
   // parent, and return success.
   aPopupTabParent->SetOwnerElement(popupFrameElement);
   popupFrameElement->AllowCreateFrameLoader();
   popupFrameElement->CreateRemoteFrameLoader(aPopupTabParent);
-
-  return true;
+  return opened;
 }
 
-/* static */ bool
+/* static */
+BrowserElementParent::OpenWindowResult
 BrowserElementParent::OpenWindowInProcess(nsIDOMWindow* aOpenerWindow,
                                           nsIURI* aURI,
                                           const nsAString& aName,
                                           const nsACString& aFeatures,
                                           nsIDOMWindow** aReturnWindow)
 {
   *aReturnWindow = NULL;
 
@@ -221,50 +236,53 @@ BrowserElementParent::OpenWindowInProces
   // GetScriptableTop gets us the <iframe mozbrowser>'s window; we'll use its
   // frame element, rather than aOpenerWindow's frame element, as our "opener
   // frame element" below.
   nsCOMPtr<nsIDOMWindow> topWindow;
   aOpenerWindow->GetScriptableTop(getter_AddRefs(topWindow));
 
   nsCOMPtr<nsIDOMElement> openerFrameDOMElement;
   topWindow->GetFrameElement(getter_AddRefs(openerFrameDOMElement));
-  NS_ENSURE_TRUE(openerFrameDOMElement, false);
+  NS_ENSURE_TRUE(openerFrameDOMElement, BrowserElementParent::OPEN_WINDOW_IGNORED);
 
   nsCOMPtr<Element> openerFrameElement =
     do_QueryInterface(openerFrameDOMElement);
 
   nsRefPtr<HTMLIFrameElement> popupFrameElement =
     CreateIframe(openerFrameElement, aName, /* aRemote = */ false);
-  NS_ENSURE_TRUE(popupFrameElement, false);
+  NS_ENSURE_TRUE(popupFrameElement, BrowserElementParent::OPEN_WINDOW_IGNORED);
 
   nsAutoCString spec;
   if (aURI) {
     aURI->GetSpec(spec);
   }
-  bool dispatchSucceeded =
+
+  OpenWindowResult opened =
     DispatchOpenWindowEvent(openerFrameElement, popupFrameElement,
                             NS_ConvertUTF8toUTF16(spec),
                             aName,
                             NS_ConvertUTF8toUTF16(aFeatures));
-  if (!dispatchSucceeded) {
-    return false;
+
+  if (opened != BrowserElementParent::OPEN_WINDOW_ADDED) {
+    return opened;
   }
 
   // Return popupFrameElement's window.
   nsCOMPtr<nsIFrameLoader> frameLoader;
   popupFrameElement->GetFrameLoader(getter_AddRefs(frameLoader));
-  NS_ENSURE_TRUE(frameLoader, false);
+  NS_ENSURE_TRUE(frameLoader, BrowserElementParent::OPEN_WINDOW_IGNORED);
 
   nsCOMPtr<nsIDocShell> docshell;
   frameLoader->GetDocShell(getter_AddRefs(docshell));
-  NS_ENSURE_TRUE(docshell, false);
+  NS_ENSURE_TRUE(docshell, BrowserElementParent::OPEN_WINDOW_IGNORED);
 
   nsCOMPtr<nsIDOMWindow> window = do_GetInterface(docshell);
   window.forget(aReturnWindow);
-  return !!*aReturnWindow;
+
+  return !!*aReturnWindow ? opened : BrowserElementParent::OPEN_WINDOW_CANCELLED;
 }
 
 class DispatchAsyncScrollEventRunnable : public nsRunnable
 {
 public:
   DispatchAsyncScrollEventRunnable(TabParent* aTabParent,
                                    const CSSRect& aContentRect,
                                    const CSSSize& aContentSize)
@@ -297,20 +315,21 @@ NS_IMETHODIMP DispatchAsyncScrollEventRu
 
   // We can get away with a null global here because
   // AsyncScrollEventDetail only contains numeric values.
   if (!detail.ToObject(cx, JS::NullPtr(), &val)) {
     MOZ_CRASH("Failed to convert dictionary to JS::Value due to OOM.");
     return NS_ERROR_FAILURE;
   }
 
+  nsEventStatus status = nsEventStatus_eIgnore;
   DispatchCustomDOMEvent(frameElement,
                          NS_LITERAL_STRING("mozbrowserasyncscroll"),
                          cx,
-                         val);
+                         val, &status);
   return NS_OK;
 }
 
 bool
 BrowserElementParent::DispatchAsyncScrollEvent(TabParent* aTabParent,
                                                const CSSRect& aContentRect,
                                                const CSSSize& aContentSize)
 {
--- a/dom/browser-element/BrowserElementParent.h
+++ b/dom/browser-element/BrowserElementParent.h
@@ -4,16 +4,17 @@
 
 #ifndef mozilla_BrowserElementHelpers_h
 #define mozilla_BrowserElementHelpers_h
 
 #include "nsAString.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/gfx/Rect.h"
 #include "Units.h"
+#include "mozilla/dom/Element.h"
 
 class nsIDOMWindow;
 class nsIURI;
 
 namespace mozilla {
 
 namespace dom {
 class TabParent;
@@ -28,16 +29,32 @@ class TabParent;
  * functionality which must be written in C++.
  *
  * We don't communicate with the JS code that lives in BrowserElementParent.js;
  * the JS and C++ parts are completely separate.
  */
 class BrowserElementParent
 {
 public:
+
+  /**
+   * Possible results from a window.open call.
+   * ADDED     - The frame was added to a document (i.e. handled by the embedder).
+   * IGNORED   - The frame was not added to a document and the embedder didn't
+   *             call preventDefault() to prevent the platform from handling the call.
+   * CANCELLED - The frame was not added to a document, but the embedder still
+   *             called preventDefault() to prevent the platform from handling the call.
+   */
+
+  enum OpenWindowResult {
+    OPEN_WINDOW_ADDED,
+    OPEN_WINDOW_IGNORED,
+    OPEN_WINDOW_CANCELLED
+  };
+
   /**
    * Handle a window.open call from an out-of-process <iframe mozbrowser>.
    *
    * window.open inside <iframe mozbrowser> doesn't actually open a new
    * top-level window.  Instead, the "embedder" (the document which contains
    * the <iframe mozbrowser> whose content called window.open) gets the
    * opportunity to place a new <iframe mozbrowser> in the DOM somewhere.  This
    * new "popup" iframe acts as the opened window.
@@ -56,39 +73,39 @@ public:
    *    set aPopupTabParent's frame element to event.detail.frameElement.
    *    Otherwise, we return false.
    *
    * @param aURL the URL the new window should load.  The empty string is
    *             allowed.
    * @param aOpenerTabParent the TabParent whose TabChild called window.open.
    * @param aPopupTabParent the TabParent inside which the opened window will
    *                        live.
-   * @return true on success, false otherwise.  Failure is not (necessarily)
-   *         an error; it may indicate that the embedder simply rejected the
-   *         window.open request.
+   * @return an OpenWindowresult that describes whether the embedder added the
+   *         frame to a document and whether it called preventDefault to prevent
+   *         the platform from handling the open request.
    */
-  static bool
+  static OpenWindowResult
   OpenWindowOOP(dom::TabParent* aOpenerTabParent,
                 dom::TabParent* aPopupTabParent,
                 const nsAString& aURL,
                 const nsAString& aName,
                 const nsAString& aFeatures);
 
   /**
    * Handle a window.open call from an in-process <iframe mozbrowser>.
    *
-   * As with OpenWindowOOP, we return true if the window.open request
-   * succeeded, and return false if the embedder denied the request.
-   *
    * (These parameter types are silly, but they match what our caller has in
    * hand.  Feel free to add an override, if they are inconvenient to you.)
    *
    * @param aURI the URI the new window should load.  May be null.
+   * @return an OpenWindowResult that describes whether the browser added the
+   *         frame to a document or whether they called preventDefault to prevent
+   *         the platform from handling the open request
    */
-  static bool
+  static OpenWindowResult
   OpenWindowInProcess(nsIDOMWindow* aOpenerWindow,
                       nsIURI* aURI,
                       const nsAString& aName,
                       const nsACString& aFeatures,
                       nsIDOMWindow** aReturnWindow);
 
   /**
    * Fire a mozbrowserasyncscroll CustomEvent on the given TabParent's frame element.
@@ -104,13 +121,21 @@ public:
    * page "rubber-bands" after being scrolled all the way to the bottom.
    * Similarly, aContentRect.left + aContentRect.width may be greater than
    * contentSize.width, and both left and top may be negative.
    */
   static bool
   DispatchAsyncScrollEvent(dom::TabParent* aTabParent,
                            const CSSRect& aContentRect,
                            const CSSSize& aContentSize);
+
+private:
+  static OpenWindowResult
+  DispatchOpenWindowEvent(dom::Element* aOpenerFrameElement,
+                          dom::Element* aPopupFrameElement,
+                          const nsAString& aURL,
+                          const nsAString& aName,
+                          const nsAString& aFeatures);
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/browser-element/Makefile.in
+++ b/dom/browser-element/Makefile.in
@@ -5,11 +5,12 @@
 include $(topsrcdir)/dom/dom-config.mk
 
 
 include $(topsrcdir)/config/rules.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 
 INCLUDES	+= \
 		-I$(topsrcdir)/dom/base \
+		-I$(topsrcdir)/dom/ \
 		-I$(topsrcdir)/dom/ipc \
 		-I$(topsrcdir)/content/base/src \
 		$(NULL)
--- a/dom/browser-element/mochitest/browserElement_AppWindowNamespace.js
+++ b/dom/browser-element/mochitest/browserElement_AppWindowNamespace.js
@@ -27,16 +27,19 @@ function runTest() {
     SimpleTest.executeSoon(function() {
       var iframe2 = document.createElement('iframe');
       SpecialPowers.wrap(iframe2).mozbrowser = true;
       iframe2.setAttribute('mozapp', 'http://example.com/manifest.webapp');
 
       iframe2.addEventListener('mozbrowseropenwindow', function(e) {
         ok(true, "Got second mozbrowseropenwindow event.");
         SpecialPowers.removePermission("embed-apps", document);
+
+        // We're not going to open this, but we don't want the platform to either
+        e.preventDefault();
         SimpleTest.finish();
       });
 
       document.body.appendChild(iframe2);
       iframe2.src = 'http://example.com/tests/dom/browser-element/mochitest/file_browserElement_AppWindowNamespace.html';
     });
   });
 
--- a/dom/browser-element/mochitest/browserElement_OpenWindowRejected.js
+++ b/dom/browser-element/mochitest/browserElement_OpenWindowRejected.js
@@ -16,16 +16,17 @@ function runTest() {
   iframe.addEventListener('mozbrowseropenwindow', function(e) {
     ok(e.detail.url.indexOf('does_not_exist.html') != -1,
        'Opened URL; got ' + e.detail.url);
     is(e.detail.name, '');
     is(e.detail.features, '');
 
     // Don't add e.detail.frameElement to the DOM, so the window.open is
     // effectively blocked.
+    e.preventDefault();
   });
 
   iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
     var msg = e.detail.message;
     if (msg.indexOf('success:') == 0) {
       ok(true, msg);
     }
     else if (msg == 'finish') {
--- a/dom/browser-element/mochitest/browserElement_TargetBlank.js
+++ b/dom/browser-element/mochitest/browserElement_TargetBlank.js
@@ -10,16 +10,17 @@ browserElementTestHelpers.setEnabledPref
 browserElementTestHelpers.addPermission();
 
 function runTest() {
   var iframe = document.createElement('iframe');
   SpecialPowers.wrap(iframe).mozbrowser = true;
 
   iframe.addEventListener('mozbrowseropenwindow', function(e) {
     is(e.detail.url, 'http://example.com/');
+    e.preventDefault();
     SimpleTest.finish();
   });
 
   iframe.src = "file_browserElement_TargetBlank.html";
   document.body.appendChild(iframe);
 }
 
 addEventListener('testready', runTest);
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1519,19 +1519,20 @@ TabParent::MaybeForwardEventToRenderFram
 
 bool
 TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                       const nsString& aURL,
                                       const nsString& aName,
                                       const nsString& aFeatures,
                                       bool* aOutWindowOpened)
 {
-  *aOutWindowOpened =
+  BrowserElementParent::OpenWindowResult opened =
     BrowserElementParent::OpenWindowOOP(static_cast<TabParent*>(aOpener),
                                         this, aURL, aName, aFeatures);
+  *aOutWindowOpened = (opened != BrowserElementParent::OPEN_WINDOW_CANCELLED);
   return true;
 }
 
 bool
 TabParent::RecvPRenderFrameConstructor(PRenderFrameParent* actor,
                                        ScrollingBehavior* scrolling,
                                        TextureFactoryIdentifier* factoryIdentifier,
                                        uint64_t* layersId)
--- a/xpfe/appshell/src/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/src/nsContentTreeOwner.cpp
@@ -26,16 +26,19 @@
 #include "nsIPrompt.h"
 #include "nsIAuthPrompt.h"
 #include "nsIWindowMediator.h"
 #include "nsIXULBrowserWindow.h"
 #include "nsIPrincipal.h"
 #include "nsIURIFixup.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIWebNavigation.h"
+#include "nsDocShellCID.h"
+#include "nsIExternalURLHandlerService.h"
+#include "nsIMIMEInfo.h"
 #include "mozilla/BrowserElementParent.h"
 
 #include "nsIDOMDocument.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIURI.h"
 #if defined(XP_MACOSX)
 #include "nsThreadUtils.h"
 #endif
@@ -827,24 +830,46 @@ nsContentTreeOwner::ProvideWindow(nsIDOM
   // If aParent is inside an <iframe mozbrowser> and this isn't a request to
   // open a modal-type window, we're going to create a new <iframe mozbrowser>
   // and return its window here.
   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
   if (docshell && docshell->GetIsInBrowserOrApp() &&
       !(aChromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
                         nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
                         nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
-    *aWindowIsNew =
+
+    BrowserElementParent::OpenWindowResult opened =
       BrowserElementParent::OpenWindowInProcess(aParent, aURI, aName,
                                                 aFeatures, aReturn);
 
-    // If OpenWindowInProcess failed (perhaps because the embedder blocked the
+    // If OpenWindowInProcess handled the open (by opening it or blocking the
     // popup), tell our caller not to proceed trying to create a new window
     // through other means.
-    return *aWindowIsNew ? NS_OK : NS_ERROR_ABORT;
+    if (opened != BrowserElementParent::OPEN_WINDOW_IGNORED) {
+      *aWindowIsNew = opened == BrowserElementParent::OPEN_WINDOW_ADDED;
+      return *aWindowIsNew ? NS_OK : NS_ERROR_ABORT;
+    }
+
+    // If we're in an app and the target is _blank, send the url to the OS
+    if (aName.LowerCaseEqualsLiteral("_blank")) {
+      nsCOMPtr<nsIExternalURLHandlerService> exUrlServ(
+                        do_GetService(NS_EXTERNALURLHANDLERSERVICE_CONTRACTID));
+      if (exUrlServ) {
+
+        nsCOMPtr<nsIHandlerInfo> info;
+        bool found;
+        exUrlServ->GetURLHandlerInfoFromOS(aURI, &found, getter_AddRefs(info));
+  
+        if (info && found) {
+          info->LaunchWithURI(aURI, nullptr);
+          return NS_ERROR_ABORT;
+        }
+
+      }
+    }
   }
 
   // the parent window is fullscreen mode or not.
   bool isFullScreen = false;
   if (aParent) {
     aParent->GetFullScreen(&isFullScreen);
   }