diff --git a/browser/base/content/content.js b/browser/base/content/content.js index eda387e8248c..eced2433fead 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -729,3 +729,16 @@ addEventListener("pageshow", function(event) { }); } }); + +addMessageListener("ContextMenu:SaveVideoFrameAsImage", (message) => { + let video = message.objects.target; + let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + + let ctxDraw = canvas.getContext("2d"); + ctxDraw.drawImage(video, 0, 0); + sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", { + dataURL: canvas.toDataURL("image/jpeg", ""), + }); +}); diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 25d237618ab1..2b66a9ac549e 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -1037,6 +1037,7 @@ nsContextMenu.prototype = { }, saveVideoFrameAsImage: function () { + let mm = this.browser.messageManager; let name = ""; if (this.mediaURL) { try { @@ -1048,13 +1049,18 @@ nsContextMenu.prototype = { } 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/jpeg", ""), name, "SaveImageTitle", true, false, document.documentURIObject, this.target.ownerDocument); + + mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage", {}, { + target: this.target, + }); + + let onMessage = (message) => { + mm.removeMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage); + let dataURL = message.data.dataURL; + saveImageURL(dataURL, name, "SaveImageTitle", true, false, + document.documentURIObject, this.target.ownerDocument); + }; + mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage); }, fullScreenVideo: function () { diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index 74a2c1ed32cd..7afd169ca76d 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -16,9 +16,6 @@ support-files = browser_star_hsts.sjs browser_tab_dragdrop2_frame1.xul browser_web_channel.html - bug564387.html - bug564387_video1.ogv - bug564387_video1.ogv^headers^ bug592338.html bug792517-2.html bug792517.html @@ -82,6 +79,9 @@ support-files = test_wyciwyg_copying.html title_test.svg video.ogg + web_video.html + web_video1.ogv + web_video1.ogv^headers^ zoom_test.html test_no_mcb_on_http_site_img.html test_no_mcb_on_http_site_img.css @@ -411,6 +411,7 @@ skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates c skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly [browser_save_video.js] skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (event.target) +[browser_save_video_frame.js] [browser_scope.js] [browser_searchSuggestionUI.js] skip-if = e10s diff --git a/browser/base/content/test/general/browser_autocomplete_a11y_label.js b/browser/base/content/test/general/browser_autocomplete_a11y_label.js index d806e0deea71..df30a26c77ea 100644 --- a/browser/base/content/test/general/browser_autocomplete_a11y_label.js +++ b/browser/base/content/test/general/browser_autocomplete_a11y_label.js @@ -24,6 +24,7 @@ add_task(function*() { let actionURL = makeActionURI("switchtab", {url: "about:about"}).spec; yield check_a11y_label("% about", "about:about " + actionURL + " Tab"); + gURLBar.popup.hidePopup(); yield promisePopupHidden(gURLBar.popup); gBrowser.removeTab(tab); }); diff --git a/browser/base/content/test/general/browser_save_video.js b/browser/base/content/test/general/browser_save_video.js index 6361ec468ba1..758edf8c4c75 100644 --- a/browser/base/content/test/general/browser_save_video.js +++ b/browser/base/content/test/general/browser_save_video.js @@ -12,7 +12,7 @@ function test() { waitForExplicitFinish(); var fileName; - gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/bug564387.html"); + gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html"); gBrowser.addEventListener("pageshow", function pageShown(event) { if (event.target.location == "about:blank") @@ -66,7 +66,7 @@ function test() { function onTransferComplete(downloadSuccess) { ok(downloadSuccess, "Video file should have been downloaded successfully"); - is(fileName, "Bug564387-expectedName.ogv", + is(fileName, "web-video1-expectedName.ogv", "Video file name is correctly retrieved from Content-Disposition http header"); finish(); diff --git a/browser/base/content/test/general/browser_save_video_frame.js b/browser/base/content/test/general/browser_save_video_frame.js new file mode 100644 index 000000000000..0b0469300ac2 --- /dev/null +++ b/browser/base/content/test/general/browser_save_video_frame.js @@ -0,0 +1,126 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const VIDEO_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html"; + +/** + * mockTransfer.js provides a utility that lets us mock out + * the "Save File" dialog. + */ +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this); + +/** + * Creates and returns an nsIFile for a new temporary save + * directory. + * + * @return nsIFile + */ +function createTemporarySaveDirectory() { + let saveDir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); + return saveDir; +} +/** + * MockTransfer exposes a "mockTransferCallback" global which + * allows us to define a callback to be called once the mock file + * selector has selected where to save the file. + */ +function waitForTransferComplete() { + return new Promise((resolve) => { + mockTransferCallback = () => { + ok(true, "Transfer completed"); + resolve(); + } + }); +} + +/** + * Given some browser, loads a framescript that right-clicks + * on the video1 element to spawn a contextmenu. + */ +function rightClickVideo(browser) { + let frame_script = () => { + const Ci = Components.interfaces; + let utils = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + let document = content.document; + let video = document.getElementById("video1"); + let rect = video.getBoundingClientRect(); + + /* Synthesize a click in the center of the video. */ + let left = rect.left + (rect.width / 2); + let top = rect.top + (rect.height / 2); + + utils.sendMouseEvent("contextmenu", left, top, + 2, /* aButton */ + 1, /* aClickCount */ + 0 /* aModifiers */); + }; + let mm = browser.messageManager; + mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true); +} + +/** + * Loads a page with a