问题描述
为Firefox开发插件我发现我需要能够像用户请求那样启动下载,也就是说,显示正常的文件保存对话框或将文件保存到用户喜欢的任何地方,因为它可以在首选项>内容下配置。每个关于下载的文章或文档似乎只考虑了我知道在哪里下载文件的情况,但是这不是在这种情况下,我需要什么,在这种情况下,它需要像用户开始下载。
这怎么能最好通过SDK的方法来完成?
好的,您可以启动一个实际的保存。
从您的代码启动保存链接:
在上下文菜单中,oncommand的值是 gContextMenu.saveLink();
。 saveLink()的定义如下:。它做一些家务,然后调用。你可以用适当的参数调用saveHelper()。它包含在来自与:
< script type =application / javascript
src =chrome://browser/content/nsContextMenu.js/>
然后在 gContextMenu href =http://dxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#45 =nofollow> 如果你想在你自己的代码中使用它,你可以这样做: 使用loadSubScript加载 Developing an addon for Firefox I find that I need to be able to launch a download as if the user requested it, that is, either showing the normal file save dialog or saving the file to wherever the user prefers, as it could be configured under preferences > content. Every single post or documentation regarding downloads seem to only take in consideration the scenario where I know where to download the file, but that is not what I need in this case, in this case it needs to be as if the user started the download. How can this be accomplished preferably via the methods of the SDK? Well, you could just initiate an actual save. Initiating a save link from your code: Then the If you want to use it in your own code you can do: Alternative to using loadSubScript to load 这篇关于如何从一个插件启动一个正常的下载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! chrome:// browser / content /browser.js
分配为 null
:
用于上下文菜单的onpopupshowing事件处理程序中的
。它返回到:
$ p $ let urlToSave =http: //stackoverflow.com/questions/26694442\" ;
让linkText =一些链接文字;
//添加一个/以取消注释适合您的加载项类型的代码。
/ *覆盖和引导:
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
// * /
/ *附加SDK:
var {Cc,Ci,Cr} = require(chrome);
// * /
if(window === null || typeof window!==object){
//如果您还没有窗口引用,您需要获得一个:
//添加一个/以取消注释适合您的加载项类型的代码。
/ *附加SDK:
var window = require('sdk / window / utils')。getMostRecentBrowserWindow();
// * /
/ *覆盖和引导(几乎来自任何上下文/作用域):
var window = Components.classes [@ mozilla.org/appshell/window-mediator;1 ]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow(navigator:browser);
// * /
}
//创建一个我们附加nsContextMenu.js的对象。
//它需要一些支持属性/函数
// nsContextMenu.js假定是其上下文的一部分。
let contextMenuObj = {
makeURI:function(aURL,aOriginCharset,aBaseURI){
var ioService = Cc [@ mozilla.org/network/io-service;1]
.getService(Ci.nsIIOService);
返回ioService.newURI(aURL,aOriginCharset,aBaseURI);
},
gPrefService:Cc [@ mozilla.org/preferences-service;1]
.getService(Ci.nsIPrefService)
.QueryInterface(Ci.nsIPrefBranch),
Cc:Cc,
Ci:Ci,
Cr:Cr
};
$ b cc [@ mozilla.org/moz/jssubscript-loader;1]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript(chrome:// browser / content / nsContextMenu.js
,contextMenuObj);
//重新定义initMenu函数,因为实际上并不希望
//初始化一个菜单。
contextMenuObj.nsContextMenu.prototype.initMenu = function(){};
let myContextMenu = new contextMenuObj.nsContextMenu();
//保存指定的URL
myContextMenu.saveHelper(urlToSave,linkText,null,true,window.content.document);
nsContextMenu.js
:
我的首选是使用loadSubScript从 nsContextMenu.js
加载saveHelper代码。这将使代码保持最新,以及在将来的Firefox版本中所做的任何更改。但是,它引入了您正在使用非官方API函数的依赖关系。因此,在未来的Firefox发行版中可能会有所变化,并且需要对附加组件进行更改。另一种方法是复制扩展中的 saveHelper()
代码。它定义如下:
// Helper函数等待适当的MIME类型头文件和
//然后使用文件选择器提示用户
saveHelper:function(linkURL,linkText,dialogTitle,bypassCache,doc){
//在nsURILoader.h中的canonical def $ b $ const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
//一个对象将数据代理到
// nsIExternalHelperAppService.doContent,它将等待
//相应的MIME类型头文件,然后提示用户a
//文件选择器
函数saveAsListener(){}
saveAsListener.prototype = {
extListener:null,
onStartRequest:function saveLinkAs_onStartRequest(aRequest ,aContext){
//如果定时器被触发,错误状态将会由于
//导致,我们将在onStopRequest中重新启动,所以没有理由通知
//用户
if(aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
return;
timer.cancel();
//发生其他错误;通知用户...
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老式的方式,它会选择最好的文件名
//它可以不等待。
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,
Offset,aCount);
函数callbacks(){}
callbacks.prototype = {
getInterface:function sLA_callbacks_getInterface(aIID){
if (aIID.equals(Ci.nsIAuthPrompt)|| aIID.equals(Ci.nsIAuthPrompt2)){
//如果频道要求认证提示,我们必须取消
// //因为save-as-定时器会在用户获得凭据之前过期并取消频道
// //。两个认证对话框
//保存为对话框会出现在屏幕上,因为我们回到
//超时后的老式方式。
timer.cancel();
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
//如果我们在短时间内没有标题,用户
//将不会收到任何反馈他们的点击。那很糟。所以
//我们放弃等待文件名。
函数timerCallback(){}
timerCallback.prototype = {
notify:function sLA_timer_notify(aTimer){
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
return;
//设置一个通道来保存
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();
让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;
//如果我们没有快速的看到头文件,则回退到旧的方式
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);
//用我们的代理对象作为监听器启动通道
channel.asyncOpen(new saveAsListener(),null);
}
In the context menu the oncommand value is gContextMenu.saveLink();
. saveLink() is defined in: chrome://browser/content/nsContextMenu.js
. It does some housekeeping and then calls saveHelper() which is defined in the same file. You could just call saveHelper() with appropriate arguments. It is included in panels from chrome://browser/content/web-panels.xul
with:<script type="application/javascript"
src="chrome://browser/content/nsContextMenu.js"/>
gContextMenu
variable declared in chrome://browser/content/browser.js
as null
is assigned:gContextMenu = new nsContextMenu(this, event.shiftKey);
in the onpopupshowing event handler for context menus. It is returned to:
'gContextMenu = null;'
in the onpopuphiding
event handler.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);
nsContextMenu.js
:
My preference is to use loadSubScript to load the saveHelper code from nsContextMenu.js
. This keeps the code up to date with any changes which are made in future Firefox releases. However, it introduces the dependency that you are using a function from a non-official API. Thus, it might change in some way in future Firefox release and require changes in your add-on. The alternative is to duplicate the saveHelper()
code in your extension. It is defined as the following:// 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);
}