diff --git a/dom/browser-element/BrowserElementParent.jsm b/dom/browser-element/BrowserElementParent.jsm index d6a533cd829d..3f16b982e00e 100644 --- a/dom/browser-element/BrowserElementParent.jsm +++ b/dom/browser-element/BrowserElementParent.jsm @@ -135,6 +135,7 @@ function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) { defineNoReturnMethod('goForward', this._goForward); defineNoReturnMethod('reload', this._reload); defineNoReturnMethod('stop', this._stop); + defineMethod('download', this._download); defineDOMRequestMethod('purgeHistory', 'purge-history'); defineMethod('getScreenshot', this._getScreenshot); defineMethod('addNextPaintListener', this._addNextPaintListener); @@ -620,6 +621,106 @@ BrowserElementParent.prototype = { this._sendAsyncMsg('stop'); }, + _download: function(_url, _options) { + let ioService = + Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService); + let uri = ioService.newURI(_url, null, null); + let url = uri.QueryInterface(Ci.nsIURL); + + // Ensure we have _options, we always use it to send the filename. + _options = _options || {}; + if (!_options.filename) { + _options.filename = url.fileName; + } + + debug('_options = ' + uneval(_options)); + + // Ensure we have a filename. + if (!_options.filename) { + throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG); + } + + let interfaceRequestor = + this._frameLoader.loadContext.QueryInterface(Ci.nsIInterfaceRequestor); + let req = Services.DOMRequest.createRequest(this._window); + + function DownloadListener() { + debug('DownloadListener Constructor'); + } + DownloadListener.prototype = { + extListener: null, + onStartRequest: function(aRequest, aContext) { + debug('DownloadListener - onStartRequest'); + let extHelperAppSvc = + Cc['@mozilla.org/uriloader/external-helper-app-service;1']. + getService(Ci.nsIExternalHelperAppService); + let channel = aRequest.QueryInterface(Ci.nsIChannel); + + this.extListener = + extHelperAppSvc.doContent( + channel.contentType, + aRequest, + interfaceRequestor, + true); + this.extListener.onStartRequest(aRequest, aContext); + }, + onStopRequest: function(aRequest, aContext, aStatusCode) { + debug('DownloadListener - onStopRequest (aStatusCode = ' + + aStatusCode + ')'); + if (aStatusCode == Cr.NS_OK) { + // Everything looks great. + debug('DownloadListener - Download Successful.'); + this.services.DOMRequest.fireSuccess(this.req, aStatusCode); + } + else { + // In case of failure, we'll simply return the failure status code. + debug('DownloadListener - Download Failed!'); + this.services.DOMRequest.fireError(this.req, aStatusCode); + } + + if (this.extListener) { + this.extListener.onStopRequest(aRequest, aContext, aStatusCode); + } + }, + onDataAvailable: function(aRequest, aContext, aInputStream, + aOffset, aCount) { + this.extListener.onDataAvailable(aRequest, aContext, aInputStream, + aOffset, aCount); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener, + Ci.nsIRequestObserver]) + }; + + let channel = ioService.newChannelFromURI(url); + + // XXX We would set private browsing information prior to calling this. + channel.notificationCallbacks = interfaceRequestor; + + // Since we're downloading our own local copy we'll want to bypass the + // cache and local cache if the channel let's us specify this. + let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS | + Ci.nsIChannel.LOAD_BYPASS_CACHE; + if (channel instanceof Ci.nsICachingChannel) { + debug('This is a caching channel. Forcing bypass.'); + flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; + } + + channel.loadFlags |= flags; + + if (channel instanceof Ci.nsIHttpChannel) { + debug('Setting HTTP referrer = ' + this._window.document.documentURIObject); + channel.referrer = this._window.document.documentURIObject; + if (channel instanceof Ci.nsIHttpChannelInternal) { + channel.forceAllowThirdPartyCookie = true; + } + } + + // Set-up complete, let's get things started. + channel.asyncOpen(new DownloadListener(), null); + + return req; + }, + _getScreenshot: function(_width, _height, _mimeType) { let width = parseInt(_width); let height = parseInt(_height); diff --git a/dom/browser-element/mochitest/browserElement_Download.js b/dom/browser-element/mochitest/browserElement_Download.js new file mode 100644 index 000000000000..f7060fb897e6 --- /dev/null +++ b/dom/browser-element/mochitest/browserElement_Download.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the public domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Bug 983747 - Test 'download' method on iframe. + +"use strict"; + +SimpleTest.waitForExplicitFinish(); +browserElementTestHelpers.setEnabledPref(true); +browserElementTestHelpers.addPermission(); + +var iframe; +var downloadURL = 'http://test/tests/dom/browser-element/mochitest/file_download_bin.sjs'; + +function runTest() { + iframe = document.createElement('iframe'); + SpecialPowers.wrap(iframe).mozbrowser = true; + + iframe.addEventListener('mozbrowserloadend', loadend); + iframe.src = 'data:text/html,
hello'; + iframe.setAttribute('remote', 'true'); + + document.body.appendChild(iframe); +} + +function loadend() { + var req = iframe.download(downloadURL, { filename: 'test.bin' }); + req.onsuccess = function() { + ok(true, 'Download finished as expected.'); + SimpleTest.finish(); + } + req.onerror = function() { + ok(false, 'Expected no error, got ' + req.error); + } +} + +addEventListener('testready', runTest); diff --git a/dom/browser-element/mochitest/file_download_bin.sjs b/dom/browser-element/mochitest/file_download_bin.sjs new file mode 100644 index 000000000000..fd394ea0a7ac --- /dev/null +++ b/dom/browser-element/mochitest/file_download_bin.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "application/octet-stream", false); + response.write("BIN"); +} \ No newline at end of file diff --git a/dom/browser-element/mochitest/mochitest-oop.ini b/dom/browser-element/mochitest/mochitest-oop.ini index 3d6db0105270..8e444696c2e8 100644 --- a/dom/browser-element/mochitest/mochitest-oop.ini +++ b/dom/browser-element/mochitest/mochitest-oop.ini @@ -30,6 +30,8 @@ skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_DOMRequestError.html] [test_browserElement_oop_DataURI.html] [test_browserElement_oop_DocumentFirstPaint.html] +[test_browserElement_oop_Download.html] +disabled = bug 1022281 [test_browserElement_oop_ErrorSecurity.html] skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_FirstPaint.html] diff --git a/dom/browser-element/mochitest/mochitest.ini b/dom/browser-element/mochitest/mochitest.ini index 0a7d0ff136aa..92d44aa41fe5 100644 --- a/dom/browser-element/mochitest/mochitest.ini +++ b/dom/browser-element/mochitest/mochitest.ini @@ -21,6 +21,7 @@ support-files = browserElement_DOMRequestError.js browserElement_DataURI.js browserElement_DocumentFirstPaint.js + browserElement_Download.js browserElement_ErrorSecurity.js browserElement_ExposableURI.js browserElement_FirstPaint.js @@ -96,6 +97,7 @@ support-files = file_browserElement_XFrameOptionsSameOrigin.html file_bug709759.sjs file_bug741717.sjs + file_download_bin.sjs file_empty.html file_empty_script.js file_focus.html @@ -134,6 +136,8 @@ skip-if = buildapp == 'b2g' [test_browserElement_inproc_DOMRequestError.html] [test_browserElement_inproc_DataURI.html] [test_browserElement_inproc_DocumentFirstPaint.html] +[test_browserElement_inproc_Download.html] +disabled = bug 1022281 [test_browserElement_inproc_ExposableURI.html] [test_browserElement_inproc_FirstPaint.html] [test_browserElement_inproc_ForwardName.html] diff --git a/dom/browser-element/mochitest/test_browserElement_inproc_Download.html b/dom/browser-element/mochitest/test_browserElement_inproc_Download.html new file mode 100644 index 000000000000..c2e5e2ed345e --- /dev/null +++ b/dom/browser-element/mochitest/test_browserElement_inproc_Download.html @@ -0,0 +1,13 @@ + + + +