2014-11-01 10 views
4

爲Firefox開發插件我發現我需要能夠啓動下載,就好像用戶請求它一樣,即顯示正常的文件保存對話框或保存文件到用戶喜歡的任何地方,因爲它可以在首選項>內容下配置。如何從插件啓動正常下載

關於下載的每一篇文章或文檔似乎只考慮了我知道在哪裏下載文件的場景,但這不是我在這種情況下需要的場景,在這種情況下,它需要像用戶一樣開始下載。

這怎麼能最好通過SDK的方法來完成?

+0

尼斯Q值。我對這個答案也很感興趣,下載並不是太好。 – Noitidart 2014-11-03 00:32:00

+1

我認爲你需要深入研究[Download.jsm](https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Downloads.jsm),看看它是如何在Firefox中觸發的UI。 Upvoted因爲很好的問題。 :) – canuckistani 2014-11-03 17:24:42

回答

2

那麼,你可以啓動一個實際的保存。

啓動從您的代碼保存鏈接:
在上下文菜單中的按需值gContextMenu.saveLink();。 saveLink()的定義如下:chrome://browser/content/nsContextMenu.js。它做了一些家務管理,然後撥打saveHelper() which is defined in the same file。你可以用適當的參數調用saveHelper()。它包括在面板從chrome://browser/content/web-panels.xul有:

<script type="application/javascript" 
      src="chrome://browser/content/nsContextMenu.js"/> 

然後在chrome://browser/content/browser.js聲明爲nullgContextMenu變量分配:
gContextMenu = new nsContextMenu(this, event.shiftKey);
在上下文菜單的onpopupshowing事件處理程序 。它返回到:
'gContextMenu = null;'
onpopuphiding事件處理程序中。

如果你想在自己的代碼中使用它,你可以這樣做:

let urlToSave = "http://stackoverflow.com/questions/26694442"; 
let linkText = "Some Link text"; 

// Add a "/" to un-comment the code appropriate for your add-on type. 
/* Overlay and bootstrap: 
const Cc = Components.classes; 
const Ci = Components.interfaces; 
const Cr = Components.results; 
//*/ 
/* Add-on SDK: 
var {Cc, Ci, Cr} = require("chrome"); 
//*/ 

if (window === null || typeof window !== "object") { 
    //If you do not already have a window reference, you need to obtain one: 
    // Add a "/" to un-comment the code appropriate for your add-on type. 
    /* Add-on SDK: 
    var window = require('sdk/window/utils').getMostRecentBrowserWindow(); 
    //*/ 
    /* Overlay and bootstrap (from almost any context/scope): 
    var window=Components.classes["@mozilla.org/appshell/window-mediator;1"] 
         .getService(Components.interfaces.nsIWindowMediator) 
         .getMostRecentWindow("navigator:browser");   
    //*/ 
} 

//Create an object in which we attach nsContextMenu.js. 
// It needs some support properties/functions which 
// nsContextMenu.js assumes are part of its context. 
let contextMenuObj = { 
    makeURI: function (aURL, aOriginCharset, aBaseURI) { 
     var ioService = Cc["@mozilla.org/network/io-service;1"] 
           .getService(Ci.nsIIOService); 
     return ioService.newURI(aURL, aOriginCharset, aBaseURI); 
    }, 
    gPrefService: Cc["@mozilla.org/preferences-service;1"] 
          .getService(Ci.nsIPrefService) 
          .QueryInterface(Ci.nsIPrefBranch), 
    Cc: Cc, 
    Ci: Ci, 
    Cr: Cr 
}; 

Cc["@mozilla.org/moz/jssubscript-loader;1"] 
     .getService(Ci.mozIJSSubScriptLoader) 
     .loadSubScript("chrome://browser/content/nsContextMenu.js" 
         ,contextMenuObj); 

//Re-define the initMenu function, as there is not a desire to actually 
// initialize a menu.   
contextMenuObj.nsContextMenu.prototype.initMenu = function() { }; 

let myContextMenu = new contextMenuObj.nsContextMenu(); 
//Save the specified URL 
myContextMenu.saveHelper(urlToSave,linkText,null,true,window.content.document); 

替代使用loadSubScript加載nsContextMenu.js
我的選擇是使用loadSubScript從nsContextMenu.js加載saveHelper碼。這將使代碼保持最新狀態,以及未來Firefox版本中所做的任何更改。但是,它引入了您正在使用非官方API函數的依賴關係。因此,它可能在未來的Firefox版本中以某種方式發生變化,並且需要對附加組件進行更改。另一種方法是在您的擴展中複製saveHelper()代碼。它被定義爲如下:

// Helper function to wait for appropriate MIME-type headers and 
// then prompt the user with a file picker 
saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) { 
    // canonical def in nsURILoader.h 
    const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020; 

    // an object to proxy the data through to 
    // nsIExternalHelperAppService.doContent, which will wait for the 
    // appropriate MIME-type headers and then prompt the user with a 
    // file picker 
    function saveAsListener() {} 
    saveAsListener.prototype = { 
    extListener: null, 

    onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) { 

     // if the timer fired, the error status will have been caused by that, 
     // and we'll be restarting in onStopRequest, so no reason to notify 
     // the user 
     if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT) 
     return; 

     timer.cancel(); 

     // some other error occured; notify the user... 
     if (!Components.isSuccessCode(aRequest.status)) { 
     try { 
      const sbs = Cc["@mozilla.org/intl/stringbundle;1"]. 
         getService(Ci.nsIStringBundleService); 
      const bundle = sbs.createBundle(
        "chrome://mozapps/locale/downloads/downloads.properties"); 

      const title = bundle.GetStringFromName("downloadErrorAlertTitle"); 
      const msg = bundle.GetStringFromName("downloadErrorGeneric"); 

      const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"]. 
          getService(Ci.nsIPromptService); 
      promptSvc.alert(doc.defaultView, title, msg); 
     } catch (ex) {} 
     return; 
     } 

     var extHelperAppSvc = 
     Cc["@mozilla.org/uriloader/external-helper-app-service;1"]. 
     getService(Ci.nsIExternalHelperAppService); 
     var channel = aRequest.QueryInterface(Ci.nsIChannel); 
     this.extListener = 
     extHelperAppSvc.doContent(channel.contentType, aRequest, 
            doc.defaultView, true); 
     this.extListener.onStartRequest(aRequest, aContext); 
    }, 

    onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext, 
                aStatusCode) { 
     if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) { 
     // do it the old fashioned way, which will pick the best filename 
     // it can without waiting. 
     saveURL(linkURL, linkText, dialogTitle, bypassCache, false, 
       doc.documentURIObject, doc); 
     } 
     if (this.extListener) 
     this.extListener.onStopRequest(aRequest, aContext, aStatusCode); 
    }, 

    onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext, 
                 aInputStream, 
                 aOffset, aCount) { 
     this.extListener.onDataAvailable(aRequest, aContext, aInputStream, 
             aOffset, aCount); 
    } 
    } 

    function callbacks() {} 
    callbacks.prototype = { 
    getInterface: function sLA_callbacks_getInterface(aIID) { 
     if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) { 
     // If the channel demands authentication prompt, we must cancel it 
     // because the save-as-timer would expire and cancel the channel 
     // before we get credentials from user. Both authentication dialog 
     // and save as dialog would appear on the screen as we fall back to 
     // the old fashioned way after the timeout. 
     timer.cancel(); 
     channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT); 
     } 
     throw Cr.NS_ERROR_NO_INTERFACE; 
    } 
    } 

    // if it we don't have the headers after a short time, the user 
    // won't have received any feedback from their click. that's bad. so 
    // we give up waiting for the filename. 
    function timerCallback() {} 
    timerCallback.prototype = { 
    notify: function sLA_timer_notify(aTimer) { 
     channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT); 
     return; 
    } 
    } 

    // set up a channel to do the saving 
    var ioService = Cc["@mozilla.org/network/io-service;1"]. 
        getService(Ci.nsIIOService); 
    var channel = ioService.newChannelFromURI(makeURI(linkURL)); 
    if (channel instanceof Ci.nsIPrivateBrowsingChannel) { 
    let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView); 
    channel.setPrivate(docIsPrivate); 
    } 
    channel.notificationCallbacks = new callbacks(); 

    let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS; 

    if (bypassCache) 
    flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; 

    if (channel instanceof Ci.nsICachingChannel) 
    flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; 

    channel.loadFlags |= flags; 

    if (channel instanceof Ci.nsIHttpChannel) { 
    channel.referrer = doc.documentURIObject; 
    if (channel instanceof Ci.nsIHttpChannelInternal) 
     channel.forceAllowThirdPartyCookie = true; 
    } 

    // fallback to the old way if we don't see the headers quickly 
    var timeToWait = 
    gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout"); 
    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 
    timer.initWithCallback(new timerCallback(), timeToWait, 
         timer.TYPE_ONE_SHOT); 

    // kick off the channel with our proxy object as the listener 
    channel.asyncOpen(new saveAsListener(), null); 
} 
+0

如何確定linkText? – 2016-05-22 15:22:34

+0

@РоманКоптев,我不太清楚你在問什麼。您確定鏈接文本的方式取決於您已有的信息。此問題未指定要下載的鏈接的來源。因此,我沒有太多可以爲你提供答案的工作環境。如果你已經擁有''元素,那麼你可以用['Node.textContent'](https://developer.mozilla.org/en-US/docs/Web/API/)完成你想要的東西。節點/的textContent)。我建議你問一個新問題,並提供一些你想知道的更多細節。 [問] – Makyen 2016-05-22 19:53:30

0

像@canuckistani說,使用Downloads.jsm

let { Downloads } = require("resource://gre/modules/Downloads.jsm"); 
let { OS } = require("resource://gre/modules/osfile.jsm") 
let { Task } = require("resource://gre/modules/Task.jsm"); 

Task.spawn(function() { 

    yield Downloads.fetch("http://www.mozilla.org/", 
         OS.Path.join(OS.Constants.Path.tmpDir, 
            "example-download.html")); 

    console.log("example-download.html has been downloaded."); 

}).then(null, Components.utils.reportError);