Bug 1287657 - Context menu items should not be disabled for media with a blob URL; r=kinetik, r=baku
authorGeoff Lankow <geoff@darktrojan.net>
Mon, 25 Jul 2016 22:41:52 +1200
changeset 331609 2ce7d3f03926687cd5ecdea58ffc890eba205743
parent 331608 cfe979de162ecf6f235e92e70b4d9ccc3c473e8d
child 331610 52c09a2cfdca49adafd157417fc19e467da51690
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik, baku
bugs1287657
milestone50.0a1
Bug 1287657 - Context menu items should not be disabled for media with a blob URL; r=kinetik, r=baku
browser/base/content/nsContextMenu.js
dom/base/nsHostObjectProtocolHandler.cpp
dom/base/nsHostObjectProtocolHandler.h
dom/url/URL.cpp
dom/url/URL.h
dom/webidl/URL.webidl
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -218,18 +218,19 @@ nsContextMenu.prototype = {
     this.showItem("context-video-saveimage", this.onVideo);
     this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
     this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
     // Send media URL (but not for canvas, since it's a big data: URL)
     this.showItem("context-sendimage", this.onImage);
     this.showItem("context-sendvideo", this.onVideo);
     this.showItem("context-castvideo", this.onVideo);
     this.showItem("context-sendaudio", this.onAudio);
-    this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL);
-    this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL);
+    let mediaIsBlob = this.mediaURL.startsWith("blob:");
+    this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL || mediaIsBlob);
+    this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL || mediaIsBlob);
     let shouldShowCast = Services.prefs.getBoolPref("browser.casting.enabled");
     // getServicesForVideo alone would be sufficient here (it depends on
     // SimpleServiceDiscovery.services), but SimpleServiceDiscovery is guaranteed
     // to be already loaded, since we load it on startup in nsBrowserGlue,
     // and CastingApps isn't, so check SimpleServiceDiscovery.services first
     // to avoid needing to load CastingApps.jsm if we don't need to.
     shouldShowCast = shouldShowCast && this.mediaURL &&
                      SimpleServiceDiscovery.services.length > 0 &&
@@ -377,17 +378,17 @@ nsContextMenu.prototype = {
     let pageShare = shareEnabled && !(this.isContentSelected ||
                             this.onTextInput || this.onLink || this.onImage ||
                             this.onVideo || this.onAudio || this.onCanvas);
     this.showItem("context-sharepage", pageShare);
     this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
     this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
     this.showItem("context-shareimage", shareEnabled && this.onImage);
     this.showItem("context-sharevideo", shareEnabled && this.onVideo);
-    this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL);
+    this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:"));
   },
 
   initSpellingItems: function() {
     var canSpell = InlineSpellCheckerUI.canSpellCheck &&
                    !InlineSpellCheckerUI.initialSpellCheckPending &&
                    this.canSpellCheck;
     var onMisspelling = InlineSpellCheckerUI.overMisspelling;
     var showUndo = canSpell && InlineSpellCheckerUI.canUndo();
@@ -1636,17 +1637,20 @@ nsContextMenu.prototype = {
   },
 
   // Kept for addon compat
   linkText: function() {
     return this.linkTextStr;
   },
 
   isMediaURLReusable: function(aURL) {
-    return !/^(?:blob|mediasource):/.test(aURL);
+    if (aURL.startsWith("blob:")) {
+      return URL.isValidURL(aURL);
+    }
+    return true;
   },
 
   toString: function () {
     return "contextMenu.target     = " + this.target + "\n" +
            "contextMenu.onImage    = " + this.onImage + "\n" +
            "contextMenu.onLink     = " + this.onLink + "\n" +
            "contextMenu.link       = " + this.link + "\n" +
            "contextMenu.inFrame    = " + this.inFrame + "\n" +
--- a/dom/base/nsHostObjectProtocolHandler.cpp
+++ b/dom/base/nsHostObjectProtocolHandler.cpp
@@ -571,16 +571,22 @@ nsHostObjectProtocolHandler::RemoveDataE
     return;
   }
 
   gDataTable->Clear();
   delete gDataTable;
   gDataTable = nullptr;
 }
 
+/* static */ bool
+nsHostObjectProtocolHandler::HasDataEntry(const nsACString& aUri)
+{
+  return !!GetDataInfo(aUri);
+}
+
 nsresult
 nsHostObjectProtocolHandler::GenerateURIString(const nsACString &aScheme,
                                                nsIPrincipal* aPrincipal,
                                                nsACString& aUri)
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
--- a/dom/base/nsHostObjectProtocolHandler.h
+++ b/dom/base/nsHostObjectProtocolHandler.h
@@ -70,16 +70,18 @@ public:
                                mozilla::dom::BlobImpl* aBlobImpl);
 
   static void RemoveDataEntry(const nsACString& aUri,
                               bool aBroadcastToOTherProcesses = true);
 
   // This is for IPC only.
   static void RemoveDataEntries();
 
+  static bool HasDataEntry(const nsACString& aUri);
+
   static nsIPrincipal* GetDataEntryPrincipal(const nsACString& aUri);
   static void Traverse(const nsACString& aUri, nsCycleCollectionTraversalCallback& aCallback);
 
   static bool
   GetAllBlobURLEntries(nsTArray<mozilla::dom::BlobURLRegistrationData>& aRegistrations,
                        mozilla::dom::ContentParent* aCP);
 
 protected:
--- a/dom/url/URL.cpp
+++ b/dom/url/URL.cpp
@@ -100,16 +100,20 @@ public:
   CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
                   const objectURLOptions& aOptions, nsAString& aResult,
                   ErrorResult& aRv);
 
   static void
   RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
                   ErrorResult& aRv);
 
+  static bool
+  IsValidURL(const GlobalObject& aGlobal, const nsAString& aURL,
+             ErrorResult& aRv);
+
   URLMainThread(nsISupports* aParent, already_AddRefed<nsIURI> aURI)
     : URL(aParent)
     , mURI(aURI)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   virtual void
@@ -289,22 +293,30 @@ URLMainThread::RevokeObjectURL(const Glo
   nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal.Get());
 
   NS_LossyConvertUTF16toASCII asciiurl(aURL);
 
   nsIPrincipal* urlPrincipal =
     nsHostObjectProtocolHandler::GetDataEntryPrincipal(asciiurl);
 
   if (urlPrincipal && principal->Subsumes(urlPrincipal)) {
-    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
     global->UnregisterHostObjectURI(asciiurl);
     nsHostObjectProtocolHandler::RemoveDataEntry(asciiurl);
   }
 }
 
+/* static */ bool
+URLMainThread::IsValidURL(const GlobalObject& aGlobal, const nsAString& aURL,
+                          ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_LossyConvertUTF16toASCII asciiurl(aURL);
+  return nsHostObjectProtocolHandler::HasDataEntry(asciiurl);
+}
+
 void
 URLMainThread::GetHref(nsAString& aHref, ErrorResult& aRv) const
 {
   aHref.Truncate();
 
   nsAutoCString href;
   nsresult rv = mURI->GetSpec(href);
   if (NS_SUCCEEDED(rv)) {
@@ -663,16 +675,20 @@ public:
   CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
                   const mozilla::dom::objectURLOptions& aOptions,
                   nsAString& aResult, mozilla::ErrorResult& aRv);
 
   static void
   RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aUrl,
                   ErrorResult& aRv);
 
+  static bool
+  IsValidURL(const GlobalObject& aGlobal, const nsAString& aUrl,
+             ErrorResult& aRv);
+
   URLWorker(WorkerPrivate* aWorkerPrivate, URLProxy* aURLProxy);
 
   virtual void
   GetHref(nsAString& aHref, ErrorResult& aRv) const override;
 
   virtual void
   SetHref(const nsAString& aHref, ErrorResult& aRv) override;
 
@@ -892,16 +908,50 @@ public:
         global->UnregisterHostObjectURI(url);
       }
     }
 
     return true;
   }
 };
 
+// This class checks if an URL is valid on the main thread.
+class IsValidURLRunnable : public WorkerMainThreadRunnable
+{
+private:
+  const nsString mURL;
+  bool mValid;
+
+public:
+  IsValidURLRunnable(WorkerPrivate* aWorkerPrivate,
+                     const nsAString& aURL)
+  : WorkerMainThreadRunnable(aWorkerPrivate,
+                             NS_LITERAL_CSTRING("URL :: IsValidURL"))
+  , mURL(aURL)
+  , mValid(false)
+  {}
+
+  bool
+  MainThreadRun()
+  {
+    AssertIsOnMainThread();
+
+    NS_ConvertUTF16toUTF8 url(mURL);
+    mValid = nsHostObjectProtocolHandler::HasDataEntry(url);
+
+    return true;
+  }
+
+  bool
+  IsValidURL() const
+  {
+    return mValid;
+  }
+};
+
 // This class creates a URL object on the main thread.
 class ConstructorRunnable : public WorkerMainThreadRunnable
 {
 private:
   const nsString mURL;
 
   nsString mBase; // IsVoid() if we have no base URI string.
   RefPtr<URLProxy> mBaseProxy;
@@ -1302,16 +1352,34 @@ URLWorker::RevokeObjectURL(const GlobalO
   if (workerPrivate->IsSharedWorker() || workerPrivate->IsServiceWorker()) {
     WorkerGlobalScope* scope = workerPrivate->GlobalScope();
     MOZ_ASSERT(scope);
 
     scope->UnregisterHostObjectURI(NS_ConvertUTF16toUTF8(aUrl));
   }
 }
 
+/* static */ bool
+URLWorker::IsValidURL(const GlobalObject& aGlobal, const nsAString& aUrl,
+                      ErrorResult& aRv)
+{
+  JSContext* cx = aGlobal.Context();
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+  RefPtr<IsValidURLRunnable> runnable =
+    new IsValidURLRunnable(workerPrivate, aUrl);
+
+  runnable->Dispatch(aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return false;
+  }
+
+  return runnable->IsValidURL();
+}
+
 URLWorker::URLWorker(WorkerPrivate* aWorkerPrivate, URLProxy* aURLProxy)
   : URL(nullptr)
   , mWorkerPrivate(aWorkerPrivate)
   , mURLProxy(aURLProxy)
 {}
 
 URLWorker::~URLWorker()
 {
@@ -1694,16 +1762,26 @@ URL::RevokeObjectURL(const GlobalObject&
 {
   if (NS_IsMainThread()) {
     URLMainThread::RevokeObjectURL(aGlobal, aURL, aRv);
   } else {
     URLWorker::RevokeObjectURL(aGlobal, aURL, aRv);
   }
 }
 
+bool
+URL::IsValidURL(const GlobalObject& aGlobal, const nsAString& aURL,
+                ErrorResult& aRv)
+{
+  if (NS_IsMainThread()) {
+    return URLMainThread::IsValidURL(aGlobal, aURL, aRv);
+  }
+  return URLWorker::IsValidURL(aGlobal, aURL, aRv);
+}
+
 URLSearchParams*
 URL::SearchParams()
 {
   CreateSearchParamsIfNeeded();
   return mSearchParams;
 }
 
 bool IsChromeURI(nsIURI* aURI)
--- a/dom/url/URL.h
+++ b/dom/url/URL.h
@@ -76,16 +76,20 @@ public:
   CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
                   const objectURLOptions& aOptions, nsAString& aResult,
                   ErrorResult& aRv);
 
   static void
   RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
                   ErrorResult& aRv);
 
+  static bool
+  IsValidURL(const GlobalObject& aGlobal, const nsAString& aURL,
+             ErrorResult& aRv);
+
   virtual void
   GetHref(nsAString& aHref, ErrorResult& aRv) const = 0;
 
   virtual void
   SetHref(const nsAString& aHref, ErrorResult& aRv) = 0;
 
   virtual void
   GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const = 0;
--- a/dom/webidl/URL.webidl
+++ b/dom/webidl/URL.webidl
@@ -51,16 +51,18 @@ interface URL {
 
 partial interface URL {
   [Throws]
   static DOMString? createObjectURL(Blob blob, optional objectURLOptions options);
   [Throws]
   static DOMString? createObjectURL(MediaStream stream, optional objectURLOptions options);
   [Throws]
   static void revokeObjectURL(DOMString url);
+  [ChromeOnly, Throws]
+  static boolean isValidURL(DOMString url);
 };
 
 dictionary objectURLOptions
 {
 /* boolean autoRevoke = true; */ /* not supported yet */
 };
 
 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html