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 346575 2ce7d3f03926687cd5ecdea58ffc890eba205743
parent 346574 cfe979de162ecf6f235e92e70b4d9ccc3c473e8d
child 346576 52c09a2cfdca49adafd157417fc19e467da51690
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik, baku
bugs1287657
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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