Bug 681550 - Add ability to save current frame of video. r=dolske
authorMatthew Wein <mwein2009@gmail.com>
Mon, 03 Oct 2011 08:13:50 -0700
changeset 78711 97f1bf573fc8c85dd821b0c92a0d308999d69149
parent 78705 de760ccf81a7c0030e9b179406510a699f4c2f18
child 78712 c2288d6886d0fa63c41f1d10cf799bb8965442c1
push id506
push userclegnitto@mozilla.com
push dateWed, 09 Nov 2011 02:03:18 +0000
treeherdermozilla-aurora@63587fc7bb93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske
bugs681550
milestone10.0a1
Bug 681550 - Add ability to save current frame of video. r=dolske
browser/base/content/browser-context.inc
browser/base/content/nsContextMenu.js
browser/base/content/test/test_contextmenu.html
browser/locales/en-US/chrome/browser/browser.dtd
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -162,16 +162,20 @@
       <menuitem id="context-savevideo"
                 label="&saveVideoCmd.label;"
                 accesskey="&saveVideoCmd.accesskey;"
                 oncommand="gContextMenu.saveMedia();"/>
       <menuitem id="context-saveaudio"
                 label="&saveAudioCmd.label;"
                 accesskey="&saveAudioCmd.accesskey;"
                 oncommand="gContextMenu.saveMedia();"/>
+      <menuitem id="context-video-saveimage"
+                accesskey="&videoSaveImage.accesskey;"
+                label="&videoSaveImage.label;"
+                oncommand="gContextMenu.saveVideoFrameAsImage();"/>
       <menuitem id="context-sendvideo"
                 label="&sendVideoCmd.label;"
                 accesskey="&sendVideoCmd.accesskey;"
                 oncommand="gContextMenu.sendMedia();"/>
       <menuitem id="context-sendaudio"
                 label="&sendAudioCmd.label;"
                 accesskey="&sendAudioCmd.accesskey;"
                 oncommand="gContextMenu.sendMedia();"/>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -219,16 +219,17 @@ nsContextMenu.prototype = {
     // Save+Send link depends on whether we're in a link.
     this.showItem("context-savelink", this.onSaveableLink);
     this.showItem("context-sendlink", this.onSaveableLink);
 
     // Save image depends on having loaded its content, video and audio don't.
     this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
     this.showItem("context-savevideo", this.onVideo);
     this.showItem("context-saveaudio", this.onAudio);
+    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-sendaudio", this.onAudio);
     this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL);
     this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL);
@@ -422,18 +423,21 @@ nsContextMenu.prototype = {
       var hasError = this.target.error != null ||
                      this.target.networkState == this.target.NETWORK_NO_SOURCE;
       this.setItemAttr("context-media-play",  "disabled", hasError);
       this.setItemAttr("context-media-pause", "disabled", hasError);
       this.setItemAttr("context-media-mute",   "disabled", hasError);
       this.setItemAttr("context-media-unmute", "disabled", hasError);
       this.setItemAttr("context-media-showcontrols", "disabled", hasError);
       this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
-      if (this.onVideo)
-        this.setItemAttr("context-video-fullscreen",  "disabled", hasError);
+      if (this.onVideo) {
+        let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA;
+        this.setItemAttr("context-video-saveimage",  "disabled", !canSaveSnapshot);
+        this.setItemAttr("context-video-fullscreen", "disabled", hasError);
+      }
     }
     this.showItem("context-media-sep-commands",  onMedia);
   },
 
   inspectNode: function CM_inspectNode() {
     if (InspectorUI.isTreePanelOpen) {
       InspectorUI.inspectNode(this.target);
       InspectorUI.stopInspecting();
@@ -821,16 +825,37 @@ nsContextMenu.prototype = {
                        this.browser.contentPrincipal,
                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     }
 
     var doc = this.target.ownerDocument;
     openUILink(viewURL, e, null, null, null, null, doc.documentURIObject );
   },
 
+  saveVideoFrameAsImage: function () {
+    urlSecurityCheck(this.mediaURL, this.browser.contentPrincipal,
+                     Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+    let name = "";
+    try {
+      let uri = makeURI(this.mediaURL);
+      let url = uri.QueryInterface(Ci.nsIURL);
+      if (url.fileBaseName)
+        name = url.fileBaseName + ".jpg";
+    } catch (e) { }
+    if (!name)
+      name = "snapshot.jpg";
+    var video = this.target;
+    var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+    canvas.width = video.videoWidth;
+    canvas.height = video.videoHeight;
+    var ctxDraw = canvas.getContext("2d");
+    ctxDraw.drawImage(video, 0, 0);
+    saveImageURL(canvas.toDataURL("image/jpg", ""), name, "SaveImageTitle", true, false, document.documentURIObject);
+  },
+
   fullScreenVideo: function () {
     this.target.pause();
 
     openDialog("chrome://browser/content/fullscreen-video.xhtml",
                "", "chrome,centerscreen,dialog=no", this.target);
   },
 
   // Change current window to the URL of the background image.
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -334,16 +334,17 @@ function runTest(testNum) {
                           "context-media-mute",         true,
                           "context-media-showcontrols", true,
                           "context-video-fullscreen",   true,
                           "---",                        null,
                           "context-viewvideo",          true,
                           "context-copyvideourl",       true,
                           "---",                        null,
                           "context-savevideo",          true,
+                          "context-video-saveimage"     true,
                           "context-sendvideo",          true,
                           "---",                        null,
                           "context-inspect",            true]);
         closeContextMenu();
         openContextMenuFor(video_bad); // Invoke context menu for next test.
         break;
 
     case 9:
@@ -352,16 +353,17 @@ function runTest(testNum) {
                           "context-media-mute",         false,
                           "context-media-showcontrols", false,
                           "context-video-fullscreen",   false,
                           "---",                        null,
                           "context-viewvideo",          true,
                           "context-copyvideourl",       true,
                           "---",                        null,
                           "context-savevideo",          true,
+                          "context-video-saveimage",    false,
                           "context-sendvideo",          true,
                           "---",                        null,
                           "context-inspect",            true]);
         closeContextMenu();
         openContextMenuFor(video_bad2); // Invoke context menu for next test.
         break;
 
     case 10:
@@ -370,16 +372,17 @@ function runTest(testNum) {
                           "context-media-mute",         false,
                           "context-media-showcontrols", false,
                           "context-video-fullscreen",   false,
                           "---",                        null,
                           "context-viewvideo",          false,
                           "context-copyvideourl",       false,
                           "---",                        null,
                           "context-savevideo",          false,
+                          "context-video-saveimage",    false,
                           "context-sendvideo",          false,
                           "---",                        null,
                           "context-inspect",            true]);
         closeContextMenu();
         openContextMenuFor(iframe); // Invoke context menu for next test.
         break;
 
     case 11:
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -427,16 +427,18 @@ can reach it easily. -->
 <!ENTITY mediaUnmute.label           "Unmute">
 <!ENTITY mediaUnmute.accesskey       "m">
 <!ENTITY mediaShowControls.label     "Show Controls">
 <!ENTITY mediaShowControls.accesskey "C">
 <!ENTITY mediaHideControls.label     "Hide Controls">
 <!ENTITY mediaHideControls.accesskey "C">
 <!ENTITY videoFullScreen.label       "Full Screen">
 <!ENTITY videoFullScreen.accesskey   "F">
+<!ENTITY videoSaveImage.label        "Save Snapshot As…">
+<!ENTITY videoSaveImage.accesskey    "S">
 
 
 <!-- LOCALIZATION NOTE :
 fullZoomEnlargeCmd.commandkey3, fullZoomReduceCmd.commandkey2 and
 fullZoomResetCmd.commandkey2 are alternative acceleration keys for zoom.
 If shift key is needed with your locale popular keyboard for them,
 you can use these alternative items. Otherwise, their values should be empty.  -->