Visual Studio 2012 for Web JavaScript自动完成体验

从Visual Studio 2005用到2010, 现在又用上了2012. 实际上之前就有体验过, 但并没有测试什么细节, 今天偶然想到, 对自己感兴趣的几个细节进行了测试, 感受是: It’s just AWESOME!

之前喜欢VS的JavaScript自动提示的原因, 是因为它会在某个环境执行你的代码, 并且用真实的结果给以智能提示, 如:

 

但VS 2010的时候, 类似的功能并不能体现到函数的实参上:

我一直很期待这个功能, 因为如果有它, 在更新自己VEJIS库的intellisense文件之后, 可以实现相当全面的自动提示. 结果, 它真的支持了!

但不仅仅是如此! 另外一个相当棒的功能就是支持异步, 比如异步加载脚本, 还有setTimeout. 先说可能大家不如我关心的setTimeout. 之后在介绍VEJIS的时候会提到前者.

除此之外, 智能提示还对数组的提示进行了优化, 会根据数组中之前出现的数据类型猜测之后出现的类型.

除了这些关于代码执行的提示, VS 2012对于代码注释的提示也有改进, 不过不是说让你写注释更方便, 而是让你写的注释被看得更方便. 并且支持多行, //和/**/都可以. 虽然之前也有<summary>等一些列的注释可以实现这样的功能, 但还是有点繁琐.

那接下来, 我就针对VEJIS谈谈对于VS 2012智能提示特性的进一步应用(这个时候在文件开头添加脚本引用了). 首先是动态加载.  在VEJIS里, require_函数用于动态加载一个脚本文件, 并且该文件会以<script>标签的形式添加到文档中. 例中动态加载的是我面向数据的一个基础库.

接下来是关于函数实参的, 像上面看到的那样, VS 2012可以根据函数被首次调用时传入的参数, 给实参加上提示, 但我们平时书写的时候, 多数时候是先写完函数再去调用. 当然, 在VEJIS下这种境况稍有不同, 因为VEJIS的函数支持重载, 也是需要指定类型的. 这样一来, 稍作修改, 就可以实现如下的功能.

同时, 因为之前说到的对于数组的提示支持, 让我还可以对VEJIS中的params类型进行处理.

又因为对于setTimeout的支持, VEJIS中类的提示也得到了加强, 虽然这个可能解释起来不是那么直观, 毕竟牵涉到其实现了.

强悍吧~ 话说如果是VEJIS老些的版本, 还支持with_, return_这些东西, 分别用于确定this和返回值的类型, 那配合上VS 2012, 就可以实现媲美强类型语言的自动提示了, 所以… 其实我已经动心开发下一个版本, 添加上这些支持. 囧. VS 2012除了这些运行时间的提示优化之外, 还增加了对很多常见对象的支持, 比如XMLHttpRequest.

顺便不得不提的是, Visual Studio 2012 for Web也有Express版, 也就意味着这么强大的工具是免费的! 各位前端同学们也加入VS阵营吧!

Adobe AIR实现支持代理的HTTP请求

也是因为那个采集项目的需要. 很失落地发现Adobe AIR的XMLHttpRequest也好, URLLoader也好, 都不支持设置代理, 一般似乎是用的系统设置. 于是自己实现了一个, 比较简单, 不支持HTTPS, 但是cookie什么的都是做到位了的. 有responseText和responseBody, 分别用于读取文本和字节文件, 如图片. 暂不支持压缩, 不过chunked是支持的.

代码已经传到github上了, 这里上一段实例, 当然, 只用到少数功能. 顺便这个是基于VEJIS, 如果不想或者不会用VEJIS可以自己稍作修改.

这里是VEJIS https://github.com/vilic/vejis
这里是这个HTTP请求 https://github.com/vilic/air-proxy-enabled-http-request-for-js

use_("http-request", function (hr) {
    var req = new hr.Request();

    //you can turn off cookies
    //req.cookieEnabled = false;
    //or turn off auto redirect
    //req.autoRedirect = false;

    req.proxy.host = "localhost";
    req.proxy.port = 1107;

    req.open("get", "http://www.vilic.info/blog/");

    //you can set request headers
    //req.setRequestHeader("Referer", "http://www.vilic.info/");

    req.send(function (req) {
        if (req.error) {
            alert(req.error);
            return;
        }

        alert(req.status);
        alert(req.responseText);
    });

    //if you use post, you'll also need to send the data
    //string and ByteArray are supported
    //req.send(data, callback);
});

Adobe AIR JavaScript跨域获取iframe内容

老实说在想到这个解决方案之前, 超级吓, 因为不解决这个问题, 就意味着项目无法实现预期的功能.

Adobe AIR中跨域的问题貌似主要是因为安全沙箱(security sandbox)不同导致的, 官方提供了两个相关的解决方案, 一个是使用iframe的sandboxRoot和documentRoot属性, 如:

<iframe
    src="http://www.example.com/local/ui.html"
    sandboxRoot="http://www.example.com/local/"
    documentRoot="app:/sandbox/">
</iframe>

我花了很长很长的时间才理解这两个属性到底指什么, 一方面是因为官方文档说得不够清楚, 我一开始一直以为app:/sandbox/是表示应用沙箱的固定用法, 一方面也是因为相关资料相对较少.

这个方法使得跨域页面中的内容(不包括iframe的src, 但包括iframe加载的内容里的各种东西, 甚至是XMLHttpRequest), 如果其URL在sandboxRoot指定的URL之下, 则并不会请求该URL在互联网上的位置, 而是请求documentRoot所指定的文件目录中的内容. 比如按上面的例子, 如果iframe加载的页面中有一个脚本的URL是http://www.example.com/local/sample.js, 则实际会加载的则是app:/sandbox/sample.js, 这里的documentRoot可以是任意目录.

但是这个方案似乎只能说是加载下文件, 并不能让脚本获取iframe的内容. 另外则有两个bridge(桥), 可以实现脚本的交互. 一个是parentSandboxBridge, 另一个是childSandboxBridge. 这两个bridge都是iframe.contentWindow的属性, 指向一个对象, 这样双方就可以通过这个对象下的属性或者方法进行信息交换了. 但需要补充的是, 不要企图传document这类的对象, 只能传传自己的(非严格界定, 但是大体意思大家应该懂). 官网上也提醒道, 如果要在iframe页面加载过程中让其中的脚本访问parentSandboxBridge, 则要在iframe中的脚本执行前就给iframe绑上parentSandboxBridge. 这时可以使用 “dominitialize” 事件.

这样一来, 跨域交互看起来终于是可行的了. 但是… 我做的是一个采集程序, 对方如果不调用我的bridge, 即使我搭好了又有什么意义呢?

后来才明白前面提到的sandboxRoot和documentRoot的意义. 对于现在多数的网页, 里面一般都会有脚本, 但那天看Google Adwords Keyword Tool的时候差点就伤心了, 从上面往下, 眼看着全是内嵌的脚本… 终于… 发现一个cues.js文件, 可能是GWT相关的信息初始化文件, 其URL是: https://adwords.google.com/cues/cues.js 于是, 将sandboxRoot设置为https://adwords.google.com/cues/, documentRoot设置为app-storage:/, 再把经过更改的cues.js文件放到app-storage:/, 这样一来, 当网页加载完成, 我安排在cues.js里的 “木马” 也就可以执行他的任务了~

如果要替换的JS文件是静态的, 那完全也可以更改后放到app:/目录(只读, 可以用trick写入, 但有兼容问题), 我放到app-storage:/是因为cues.js是动态生成的, 悲催吧…

虽然不能说是一个完美的解决方案, 但至少解决了自己的问题. 不过, 要是真遇上一个页面里没有脚本或者全是内嵌脚本的时候… 就伤了. 有人会问为什么要用那么低级的方式, 不直接发HTTP请求来解决? 老实说我也觉得蛮低级. 但Google Adwords Keyword Tool是用GWT开发的, 里面用来交换数据的格式并非常见的application/x-www-form-urlencoded, 而是text/gwt-rpc, 因为时间紧, 不能保证这是能轻松破解的(同样, 资料极少), 于是退而求其次了.

Adobe AIR写程序目录(app:/)的兼容性问题

最近接了个Adobe AIR的采集项目, 遇到不少问题, 当然也解决了不少问题. 这篇是几篇经验的第一篇.

网上应该可以很容易的搜到一个解决方案, 将路径转换为nativePath:

var file = new air.File("app:/some-file");
file = new air.File(file.nativePath.toString());

经过这样一个转换, 在Windows下似乎就没有问题了, 但是程序到了Mac上还是会报错. 这就比较伤心了…

我想写这个目录主要是因为想存储一些程序设置文件, 还有一些必要的东西. 继续搜索, 发现有人提出使用用户目录来保存. 当然, 当我最后知道答案的时候非常想不通为什么还有那么多人跟我一样笨…

除了app:/这个目录之外, 还有一个目录是app-storage:/, 一开始我以为这两个目录是等价的, 没有意识到差别. 后来才清楚… 所以用后者就可以了… 但是如果你是想更改程序文件, 就没办法了, 除非不考虑兼容, 或者直接把程序文件写在app-storage里. 囧.