admin 发布于 10月02, 2013

用 FILESYSTEM API 实现文件下载器

各种基于 HTML5 的文件上传已经被大家玩得烂熟,常见的文件夹上传、拖拽上传以及拷贝剪切板数据上传,都可以在各大网盘、WebIM 中见到。本文实现的纯 JavaScript 多区块并发下载器,要使用到 FileSystem 中相对不那么常见的 FileWriter。

本文涉及知识较多,我并不打算全部介绍它们。示例代码中使用了 when.js 这个异步框架,它的用法请看官方文档或我写的《异步编程:WHEN.JS 快速上手》;文中涉及到的 HTTP 知识可以参考 RFC2616,或者《HTTP 权威指南》;本文代码仅在 Chrome 29 测试过,FileSystem 的完整浏览器支持度请直接去 CanIUse 查;本文未涉及到的 FileSystem 其它知识,请参考 html5rocks 的这篇文章,不过需要注意的这篇文章完成时间很早,部分代码已失效(如文中的 BlobBuilder 已被废弃);JavaScript 中的 Typed Array 也都可以在网上找到详细介绍。

再说一点,文中示例代码做封装,也完全没有考虑异常流程。这是我写博客的一惯原则:示例代码只为了把事情讲明白,如果要在实际项目中使用,需要读者自己去思考并加工。

废话说完,正式开始今天的主题。。。

获取下载文件信息

我们的 HTTP 下载器目标是下载指定 URL 对应的文件。为了方便后面的处理,首先,我们要获取这个文件的基本信息。虽然实际应用中我们需要的信息往往会由服务端提供,但本文目标是「完全不借助服务端语言(如 PHP),只使用浏览器 JavaScript 将 Nginx 托管的文件下载到本地」,所以这里采用纯客户端方案。

我们关心的是文件大小(便于分配任务)和文件类型(保存时要用到)。这两个信息在 HTTP 响应头中都有,分别是 Content-Length 和 Content-Type,可以通过 XHR(XMLHttpRequest)拿到它们。XHR 的 readyState 有 0 - 4 几种状态,等于 2 的时候就可以拿到响应头了。

按照上面的分析,通过 XHR 给目标文件发送 GET 请求,并在 readyState 等于 2 时获取响应头信息,再 abort() 掉请求就可以了。但实际上,HTTP 协议规定了「HEAD」这种请求方法,更适合做这件事情。文档对 HEAD 是这样说明的:

HEAD 方法与 GET 方法的行为很类似,但服务器在响应中只返回首部,不会返回实体的主体部分。这就允许客户端在未获取实际资源的情况下,对资源的首部进行检查。服务器开发者必须确保返回的首部与 GET 请求所返回的首部完全相同。遵循 HTTP/1.1 规范,就必须实现 HEAD 方法。

少数服务器不支持 HEAD,本文直接忽略。现在开始编写我们的 URL 分析工具,代码如下:

function UriAnalyser(url) {
    var deferred = when.defer();
    var xhr = new XMLHttpRequest();
    xhr.open('HEAD', url, true);
    xhr.onreadystatechange = function() {
        if(2 == this.readyState) {
            var ret = {
                    mimeType : xhr.getResponseHeader('Content-Type'),
                    size : xhr.getResponseHeader('Content-Length') | 0
                };

            deferred.resolve(ret);
        }
    };
    xhr.send();
    return deferred.promise;
}

代码很简单,不用解释了。UriAnalyser 接收 URL 参数并返回 promise,在获取到相关信息后 resolve。试用下:

var url = 'http://dl.qgy18.com/file.zip';
when(UriAnalyser(url)).then(function(o){ console.log(o) });

> Object {mimeType: "application/zip", size: 7992987}

在 FileSystem 创建文件

接下来,我们要在浏览器的 FileSystem 中创建一个区域,用来保存即将下载到的文件。出于安全考虑,浏览器中的 FileSystem 运行在沙箱中。要使用它,首先需要请求权限:

window.requestFileSystem(type, size, successCallback, errorCallback);

type 是文件系统类型,有 window.TEMPORARY 和 window.PERSISTENT 两种常量,区别是:TEMPORARY 类型的数据随时可能会被浏览器删掉;PERSISTENT 数据不会被浏览器清理,但需要用户额外授权。size 是存储大小,字节为单位。后两个参数分别是请求文件系统成功和失败对应的回调,successCallback 的第一个参数是对文件系统的引用,我们用 fs 表示,后面还会用到。

下面两个方法分别用来获取文件系统中的目录和文件(目标不存在则新建):

fs.root.getDirectory(dirName, {create: true}, function(dirEntry) { ... }, errorCallback);
fs.root.getFile(filePath, {create: true}, function(fileEntry) { ... }, errorCallback};

现在,我们编写「根据指定的文件名,在文件系统中创建对应空白文件」的方法如下:

function CreateFile(name) {
    var deferred = when.defer();
    window.webkitRequestFileSystem(window.TEMPORARY, 10 * 1024 * 1024, function(fs) {
        var dir = (+ new Date).toString(36);
        fs.root.getDirectory(dir, {create: true}, function(dirEntry) {
            var file = dir + '/' + name;
            fs.root.getFile(file, {create: true}, function(fileEntry) {
                fileEntry.createWriter(function(fileWriter) {
                    var ret = {
                            fileEntry  : fileEntry,
                            fileWriter : fileWriter
                        };
                    deferred.resolve(ret);
                });
            });
        });
    });
    return deferred.promise;
};

这个 promise 会返回 fileEntry 和 fileWriter,后面会用到。试用下:

var file = 'a.js';
when(CreateFile(file)).then(function(o){ console.log(o) });

> Object {fileEntry: FileEntry, fileWriter: FileWriter}

开始下载

现在开始编写下载代码。要实现并发下载,首先要合理分配任务。HTTP 协议中规定可以使用请求头的 Range 字段指定请求资源的范围。例如服务端收到「Range : bytes=10-100」这样的请求头,只需要返回资源的 10-100 字节这部分就可以了,这样的响应状态码为 206。有些服务器不支持 Range,本文继续忽略。

现在,离最终目标越来越近了。我们只需要再实现支持指定 Range 的下载器和任务分配器就可以了。下载器比较简单,直接看代码:

function Downloader(url, mimeType, range) {
    var deferred = when.defer();
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onreadystatechange = function() {
        if (this.readyState == 4) {
            if (this.response != null) {
                var blob = new Blob(
                        [new Uint8Array(this.response)], 
                        { type: mimeType }
                    );
                var ret = {
                        size : this.response.byteLength | 0,
                        blob : blob
                    };
                deferred.resolve(ret);
            }
        }
    };
    xhr.setRequestHeader("Range", "bytes=" + range);  
    xhr.responseType = 'arraybuffer'; 
    xhr.send();
    return deferred.promise;
};

下载器继续使用 promise 实现,它会在获取到响应数据时 resolve,返回指定 Range 的数据和大小。这样,把所有下载器丢给 when.all(),就可以在所有任务完成后触发下一步操作,顺序还不会乱。

文件全部下载完后,通过 fileWriter.write 方法就可以写入前面获得的 fileEntry 了。需要注意的是,当前数据写完之后才能开始新的写入,否则会产生异常。是否写完可以通过 fileWriter.onwriteend 来获得。下载完一部分数据就写一部分当然也可以,原理是类似的。

我把下载器 XHR 的 responseType 设置为「arraybuffer」,是为了获取每个下载器得到的长度。把全部下载器得到的长度加起来,跟分析器得到的文件大小做对比,可以粗略地检查文件完整性。

任务分配和合并文件的代码不贴了,太占篇幅,请直接看最后 Demo 的源代码。

文件合并完成后,通过 fileEntry.toURL() 得到文件在 FileSystem 中地址,赋给 标签的 href,再 click() 下就可以自动下载到本地了。

解决跨域问题

浏览器对同一个域名的并发连接数有限制,为了更好的实现并发下载,最好使用不同的域名访问要下载的文件,这用域名泛解析很容易实现。但是这样又会导致 XHR 被同源策略所限制。

还好,我们有 CORS(Cross-Origin Resource Sharing),专门用来解决这个问题。在 Nginx 配置文件中加上这几行就可以了:

add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Headers Range;
add_header Access-Control-Expose-Headers Content-Type,Content-Length;

重点看下后两行:Allow-Headers 用来设置允许 XHR 发送哪些请求头,必须加上前面提到的 Range;Expose-Headers 用来设置哪些响应头可以被 XHR 的 getResponseHeader 方法获得,必须加上分析器用到的 Content-Type 和 Content-Length。

Nginx 本身还有一个问题:对静态资源发起 OPTIONS 请求会得到「405 Not Allowed」,这个问题可以改成用 PHP 读取文件再输出来解决。但前面说过,本文讨论内容不依赖服务端语言。通过 Google 找到了一个方案,在 Nginx 配置中增加下面这一段就可以了:

location / {
    if ($request_method = OPTIONS ) {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
        add_header Access-Control-Allow-Headers Range;
        add_header Access-Control-Expose-Headers Content-Type,Content-Length;
        return 200;
    }
}

Demo

猛击这里观看 Demo,仅在 Chrome 29 测试通过。

downloader

原文链接:http://www.imququ.com/post/a-downloader-with-filesystem-api.html

阅读全文 »

admin 发布于 09月07, 2013

IE的浏览器模式和文本模式(二)

一年半之前我写了一篇《关于浏览器模式和文本模式的困惑》,介绍了IE8+特有的浏览器模式(Browser Mode)和文本模式(Document Mode),以及我的测试和微软文档有出入的部分。其中有一些没有提到的内容,本文继续讨论。

判断真正的IE版本

很多JS框架都通过UA判断IE的版本。对于IE6,这种做法没问题(IE6没有浏览器模式的概念,也没有其它IE可以把浏览器模式改为IE6;IE7虽然也没有浏览器模式,但IE8+可以把浏览器模式设置为IE7模式)。但是从IE8开始引入的浏览器模式会产生不同的UA。例如,IE9有这些:

浏览器模式 navitor.userAgent 默认文本模式
IE7 MSIE 7.0 IE7标准
IE8 MSIE 8.0 && Trident/4.0 IE8标准
IE9 MSIE 9.0 && Trident/5.0 IE9标准
IE9兼容性 MSIE 7.0 && Trident/5.0 IE7标准

如果仅通过UA中的“MSIE X.0”来判断,会得到IE7~9三种不同结果。

实际上,对于IE8+,根据UA字符串只能确定当前是否是兼容性视图。因为兼容性视图的UA中,IE版本和Trident版本不匹配。例如UA里同时有“MSIE 7.0”和“Trident/6.0”,说明浏览器模式肯定是IE10兼容性。这是因为IE8才开始给UA加上Trident信息,而Trident/6.0是IE10所特有。

除此之外,上面IE7IE8这两种浏览器模式,UA和跟真正的IE7或IE8没有任何区别,根据UA完全没办法区分。甚至连IE9模式,我们也无法确认这是IE9浏览器的默认模式,还是IE10浏览器的IE9模式。

下面来看看文本模式,依然用IE9测试。选择不同的文本模式,documentMode的值也不一样。

文本模式 document.documentMode
IE7标准 7
IE8标准 8
IE9标准 9
IE5怪异(Quirks) 5

document.documentMode这个JS属性是IE8引入的,对于IE8+无论选择什么文本模式,这个属性都有值。而IE6和IE7下,这个属性是undefined。根据这一点,可以结合UA判断出用户使用的是不是真正的IE7:UA包含IE7时,如果documentMode等于undefined,就一定是真正的IE7浏览器。对于IE8+,这种方法就力不从心。例如IE10在浏览器模式为IE8,文本模式为IE8标准时,与真正的IE8比较,无论是UA,还是document.documentMode,都一模一样。

综上,我们可以通过检查UA中Trident版本和IE版本是否匹配,来判断浏览器是否工作在兼容性视图模式下。结合document.documentMode,还可以判断出用户是否使用真正的IE7浏览器。

JScript引擎版本号

JScript是IE的JS引擎,IE提供了一系列JS接口来获取它的JScript信息:

ScriptEngine() JS中固定返回“JScript”
ScriptEngineMajorVersion() 大版本号
ScriptEngineMinorVersion() 小版本号
ScriptEngineBuildVersion() 内部版本号

我用这些接口测试了IE6~10,发现JScript的版本号只与浏览器有关,与浏览器的浏览器模式或文档模式无关。

浏览器 JScript版本号
IE6 5.6.8827
IE7 5.7.22145
IE8 5.8.18702
IE9 9.0.16434
IE10 10.0.16521

忽略内部版本号,只关注前两个数字,我们会发现,从IE9把JS引擎换成Chakra开始,版本号的规律变了。令人欣慰的是,不同的JScript版本号对应着不同版本的浏览器,这对判断出真正的IE版本很有帮助。例如,要识别低于IE8的浏览器,下面这样写就可以了。

if(ScriptEngineMinorVersion() != 0 && ScriptEngineMinorVersion < 8) {
    //ie8- browser
}

实际上,IE支持的条件编译功能中,有个表示JScript版本的条件编译变量,如下(完整的条件编译变量清单见这里.aspx)):

这个变量是“major.minor”格式的JScript版本号,跟使用JS接口获取到的版本号一致,也只取决于浏览器版本,不受浏览器模式和文本模式的影响。

文本模式对JScript没影响?

看完上一节,再看我之前写的这段:文本模式决定:1)排版引擎;2)JS引擎,有明显的矛盾。难道之前的结论有误?

实际上,JS获取到的JScript版本号仅仅表示了当前浏览器自带的JScript引擎版本(例如IE9始终是9.0,IE7始终是5.7),并不代表任何情况都可以使用这个版本JS引擎的所有功能,页面使用哪种版本的JScript引擎还是由页面的文本模式来决定

例如,IE8的JScript版本是5.8,但是只有在文本模式等于IE8标准时才可以使用JScript5.8的功能,其它任何文本模式都会导致页面使用JScript5.7。

举个例子,JScript5.8增加了对JSON的支持,所以IE8支持原生JSON对象。但网上很多人问为什么在IE8/9下无法使用原生JSON。一种可能是页面没写DTD,导致页面进入了IE5怪异这种文本模式,进而启用了不支持原生JSON的JScript5.7导致的。

再举几个例子:[,].length在JScript9.0之前是2;[1,2,3].join(undefined)在JScript5.8之前是"1undefined2undefined3"。把IE9的文本模式分别改成IE9标准IE8标准IE7标准,可以得到下表:

文本模式 [,].length [1,2,3].join(undefined) 使用的JScript版本
IE7标准 2 "1undefined2undefined3" 5.7
IE8标准 2 "1,2,3" 5.8
IE9标准 1 "1,2,3" 9.0

类似的例子还有很多。大部分情况下,页面使用的JScript引擎版本会随着文本模式的降低而退化,页面对JS的支持度也随之退化。但教主franky提供了一个“for in顺序”的反例:

var o = {1 : '0', 0 : '1'}; for(var i in o) { console.log(i); }

对于IE9+浏览器,这行代码输出顺序始终是0 1;对于IE9以下的浏览器,输出顺序都是1 0,并不受文本模式的影响。关于这一点我没想到比较好的解释。

一些DOM相关的方法,如document.querySelectorAll,在文本模式为IE7标准IE5怪异时不可用;addEventListener在文本模式为IE8标准IE7标准IE5怪异时不可用。这说明IE浏览器对DOM的支持度也会随着文本模式的降低而退化。

但是一些BOM方法,却跟文本模式无关。例如IE8开始支持的postMessage和localStorage,只要浏览器是IE8+,无论什么文本模式,这两个功能都可用。IE9+支持的window.performance,在IE9+浏览器上,也始终可用,跟当前的文本模式无关。

小结下:随着文本模式的降低,页面上实际使用的JScript引擎会退化,一些高版本支持的语言特性不再可用(如JSON),但for in的顺序问题似乎不会退化;DOM相关功能也有相应的退化;但大部分BOM接口却不会退化(如localStorage)。

总结

本文讨论的内容在各部分都小结过,最后只说一个结论:在解决JS兼容性问题时,一定要使用能力检测和特性检测。因为无论是从UA中得到的浏览器信息,还是从JS接口中获取到的JScript引擎版本,都非常不可靠。例如UA中包含IE7,并不一定不支持IE9+的window.performace。也可能是IE9浏览器使用了IE9兼容性视图,UA确实会变成IE7,文本模式为IE7标准,但不影响对window.performace的支持。

另外,虽然IE的浏览器模式和文本模式非常复杂,组合起来有几十种情况,但大部分情况只能通过开发者工具来构造。例如UA中包含IE9,实际上使用JScript5.7的情况(浏览器模式为IE9,文本模式为IE7标准),正常情况下不会出现。

参考:

原文链接:http://www.imququ.com/post/browser-mode-and-document-mode-in-ie-2.html

阅读全文 »

admin 发布于 02月18, 2012

图片自动旋转的前端实现方案

随着智能手机的普及,大家已经习惯随手拍点照片传网上了。有时候传上去的照片方向会不对,甚至还有脑袋朝下的情况。为此,各大微博不约而同的给照片 增加左右旋转的功能。这样能解决问题,但我们是否能更进一步,自动选择正确的照片方向呢?刚好最近我们有类似的场景,本文就讨论下这个问题。

实际上,数码设备拍摄照片时,会把许多属性附加在照片文件里,这些属性构成了大家常说的Exif信息。访问我的相册会发现照片下也有对应的Exif信息。Exif中有个Orientation字段,用来存放照片方向,这就是我们需要的,看下它的定义:

EXIF Orientation Value Row #0 is: Column #0 is:
1 Top Left side
2 Top Right side
3 Bottom Right side
4 Bottom Left side
5 Left side Top
6 Right side Top
7 Right side Bottom
8 Left side Bottom

NOTE: Values with "*" are uncommon since they represent "flipped" orientations. via

当 然,Exif中的Orientation属性,取决于拍照的设备是否拥有方向传感器。不过根据我的了解,目前大部分数码拍照设备都是支持记录方向的。1是 默认值,2、4、5、7表示照片进行了翻转。一般情况下,取值应该是1、3、6、8中的一种。下面有张更形象的图描述了具体的旋转策略:

如何从图片中获取Exif信息,各个语言都有封装好的代码可以直接使用。Javascript也不例外,国外某同学08年就发布了可用代码。他的做法分为两步,首先通过Ajax获取原始二进制,这在firefox和webkit比较好办,在IE下需要借助VBScript的帮忙;第二步是从原始数据不同位置获取相关信息,基本是体力活了。想深入了解的同学可以点本段几个链接围观下。

从图片Exif信息中取到Orientation后,就可以根据它来自动旋转图片了,canvas、filter滤镜、vml、css3都可以实现图片的旋转。网上也有很多封装好的代码可以直接拿过来用。

综合上文,我写了一个demo,除了Opera,兼容大部分其它浏览器。

另外,如果用FileReader Api获取图片的原始二进制,就可以在浏览器本地实现图片自动旋转。这在图片上传前预览时比较有用,这里有demo

PS:其实,大部分的图片查看客户端早已支持自动旋转,所以一般情况下数码设备拍的照片用电脑看,方向都是正确的。许多缩略图生成程序,也是可以指定缩放前自动旋转的(例如ImageMagick的-auto-orient参数)。

再PS:纯前端获取图片Exif,只是一种尝试。如果需要频繁使用Exif信息,还是后端获取完,存在数据库比较靠谱。

参考:

  1. JPEG Rotation and EXIF Orientation
  2. Read EXIF data with Javascript

原文地址:http://www.imququ.com/post/95.html

阅读全文 »

admin 发布于 02月18, 2012

关于浏览器模式和文本模式的困惑

什么是浏览器模式和文本模式?

经常使用IE开发者工具的同学,肯定见过浏览器模式和文本模式,对于这两个名词,综合相关文档解释如下:

浏览器模式(Browser Mode),用于切换IE针对该网页的默认文本模式、对不同版本浏览器的条件注释解析、决定请求头里userAgent的值。它在浏览器发出请求之前就已 经确定,网站没有办法修改这个值。它代表的是用户以何种浏览器访问网站。IE9支持下列浏览器模式:

userAgent 默认文本模式
IE7 MSIE 7.0 IE7标准
IE8 MSIE 8.0 && Trident/4.0 IE8标准
IE9 MSIE 9.0 && Trident/5.0 IE9 标准
IE9兼容性 MSIE 7.0 && Trident/5.0 IE7 标准

IE9兼容性模式与IE7模式的区别是:

  • 前者在UA里加上了Trident版本,后者和IE7完全一致无Trident标识
  • IE8中,IE9兼容性模式对应为IE8兼容性模式,UA里Trident版本为4.0,其他没变化
  • IE8中没有IE9模式

文本模式(Document Mode),其实就是经常说的文档模式。不同的文本模式对应不同的排版引擎,不同的JS引擎。上面提到,每一种浏览器模式对应一种默认的文本模式,网站还可以通过一些手段来更改文本模式,它代表的是浏览器以何种模式呈现页面。IE9有下列文本模式:

documentMode
IE7 标准 7
IE8 标准 8
IE9 标准 9
怪异(Quirks) 5

需 要说明的是,IE8开始支持的渲染机制有:

  • 怪异模式(quirks mode)
  • 完全标准模式(standards mode)
  • 近似标准模式(almost standards mode)

但开发者工具是无法选择近似标准模式的,实际上我们一般都选择触发完全标准模式

浏览器模式和文本模式有什么用?

用来解决IE各版本带来的兼容性问题。根据微软描述的IE兼容性策略,在IE8+访问一个页面要经过这样的流程:

一、首先,浏览器要确定浏览器模式。上面说过,浏览器模式是在请求发送之前就必须确定,默认取最新(IE9为IE9标准,IE8为IE8标准),有两种方式可以更改它:

  • 通过开发者工具选择(可选项见上表);
  • 通过点击兼容性视图按钮;
  • 命中兼容性视图列表(微软维护的需要采用兼容性视图的列表。IE8+默认对这个列表和局域网的网址都会采用相应的兼容性模式);

二、浏览器通过请求头里userAgent的值,告诉服务器当前是何种浏览器模式;

三、服务器可以通过下面方式改变浏览器文本模式:

  • doctype;
  • X-UA-Compatible Meta或对应的响应头;

四、浏览器综合考虑开发者工具设置、第三步服务器返回的设置、兼容性列表设置等等情况,决定页面使用何种文本模式。这个过程有点复杂,放一张Qwrap群里灰大提供的流程图,可以自己点开看大图。

(上图是IE9选取文本模式的流程图,这里还有IE8版本,有一些区别)

问题终于来了!

回 顾下前面的介绍,浏览器模式决定:1)发送给服务端的UA;2)默认的文本模式;3)如何解析条件注释。它在请求发送前就已经确定,且不受服务端控制。文 本模式决定:1)排版引擎;2)JS引擎。它在浏览器得到响应后最终确定,服务端可通过doctype或X-UA-Compatible来控制。

测试一、根据前文,如果用户浏览器没有激活兼容性视图;没有开启IE开发者工具。那么IE9的浏览器模式默认为IE9,默认对应的文本模式应该是IE9标准(对于IE8来说,是类似的),我们通过下列代码将它改到IE7标准

<``meta http-equiv``=``"X-UA-Compatible" content``=``"IE=7"``>

下面,我们分别用原生IE8、IE9测试这个页面

请求头UA navigator.userAgent 条件注释 documentMode JS引擎
IE8 MSIE 8.0 && Trident/4.0 MSIE 8.0 && Trident/4.0 IE7 7 IE7
IE9 MSIE 9.0 && Trident/5.0 MSIE 7.0 && Trident/5.0 IE7 7 IE7

上表说明,浏览器发送请求时的浏览器模式符合预期(根据请求头UA),X-UA-Compatible确实会将浏览器文本模式改到了IE7标准(根据documentMode和JS引擎)。奇怪的是,文本模式的改变导致了浏览器模式的改变,因为条件注释是由浏览器模式决定的。本例中,文本模式改到IE7标准,条件注释也跟着变成IE7,意味着浏览器模式变到IE9/IE8兼容性(从IE9的测试来看,不能是IE7,因为UA里包含Trident)。至于IE8中JS取到的UA为什么没有变化,可能是bug或者理解不一致。

测试二、那如果把测试地址加到兼容性列表呢?根据前文,这种情况浏览器模式应该是IE9/IE8兼容性,对应的文本模式依然是IE7标准。测试结果如下:

请求头UA navigator.userAgent 条件注释 documentMode JS引擎
IE8 MSIE 7.0 && Trident/4.0 MSIE 7.0 && Trident/4.0 IE7 7 IE7
IE9 MSIE 7.0 && Trident/5.0 MSIE 7.0 && Trident/5.0 IE7 7 IE7

上表是完全符合预期的。

测试三、如果把X-UA-Compatible改成IE=edge,继续使用兼容性模式测试呢?结论如下:

请求头UA navigator.userAgent 条件注释 documentMode JS引擎
IE8 MSIE 7.0 && Trident/4.0 MSIE 7.0 && Trident/4.0 IE8 8 IE8
IE9 MSIE 7.0 && Trident/5.0 MSIE 9.0 && Trident/5.0 IE9 9 IE9

这个结论其实跟测试一是一致的:X-UA-Compatible为IE=edge,意味着文本模式会使用最新可用的版本,然而文本模式的更改,又把浏览器模式从IE9/IE8兼容性变成IE9/IE8。IE9会按照新的浏览器模式来设置JS的navigator.userAgent,IE8下JS的UA不变。

测试四、那如果通过开发者工具人为设置浏览器模式和文本模式呢?经过测试,这样测试都是符合预期的。例如IE9下,设置浏览器模式为IE8,文本模式为IE7标准,请求头UA、JS的UA、条件注释都表明浏览器模式是IE8,documentMode和JS引擎都表明文本模式是IE7标准。因为,IE开发者工具的优先级最高,设置了这个,其他条件统统无视!

结论

IE8/9中X-UA-Compatible对文本模式的改变会导致浏览器模式的改变,也就是说服务端可以间接控制浏览器模式。这与微软文档里这一段描述有出入:

An important detail to remember is that Browser Mode is chosen before IE requests web content. This means that sites cannot choose a Browser Mode.

对 于IE8,如果网站通过X-UA-Compatible meta/header更改文本模式为当前浏览器模式默认文本模式之外的值,那么页面将按照新的文本模式来呈现,条件注释也按照新的文本模式对应的浏览器 模式来解析,但是JS获取的UA是浏览器模式初始状态。这样会导致用JS获取UA得到的浏览器版本,与实际渲染的浏览器版本不符,这会对基于UA的浏览器 检测造成干扰。

对于IE9,只有一点与IE8不同:JS获取到的是新文本模式对应的浏览器模式的UA。这会导致用JS获取UA得到的浏览器版本,与请求头发送给服务器UA里标识的浏览器版本不符,这可能对统计有影响。

对于IE这种兼容性方案,几乎不可能做到理论上的完美。个人感觉还是IE9的策略影响面较小,更好一些。

PS,上述结论都是我用Windows XP的原生IE8,Windows 7的原生IE9亲自测试得出来的。对于国内那些IE Shell们,实在过于奇葩,不在本文范围内。

参考:

  1. Testing sites with Browser Mode vs. Doc Mode
  2. X-UA-Compatible header/meta tag is NOT the same as the Internet Explorer 8+ Compatibility View button

原文地址:http://www.imququ.com/post/94.html

阅读全文 »

admin 发布于 01月01, 2012

一个浏览器诊断小工具

在我之前参与过的某产品上线后,我们经常会收到用户反馈的各种奇怪问题,很多都和浏览器环境有关,每次跟进起来费时费力。09年初,JK和Miller开发了一个页面工具用来排查浏览器环境相关问题,挺好用的。这个工具在我的小站也存放了一份,去掉了无关内容并改了一些提示文字,版权属于原作者。可以通过这个地址来访问:

http://qgy18.imququ.com/browser-doctor/

阅读全文 »

admin 发布于 12月30, 2011

[小Tip]JavaScript转整数杯具两则

今天看到某大牛之前写的某段代码里,用到了"| 0"这种写法将字符串取整。这本来没什么,很多人都这样做,但他那段代码里处理的是一个时间戳。如:

"1325239449538" | 0;

结果是-1905444926,这显然不是我们想要的。这个问题产生的原因是:A | B是将A、B先ToInt32再运算,返回结果是32位符号型整数。只要A超过2147483647,也就是231-1,就杯具了。

另一个常用的parseInt函数也容易发生杯具,如:

parseInt(0.0000001);

在大部分浏览器上,结果是1(新版firefox会得到0),这也显然不符合预期。原因是parseInt(A)第一步会执行ToString(A)。上面的例子中,0.0000001会转成"1e-7",后面的事情应该都知道的。

阅读全文 »

admin 发布于 12月23, 2011

Sublime Text2:超赞的现代编辑器

最近改用mac后,最纠结是割舍不能用了数年的Editplus,一时间甚至有了装XP虚拟机继续用EP的213想法,每天茶不思饭不想,码也不想写了。这样过了大半个星期,期间尝试Coda、BBEditor等等各种号称Mac下最好用的编辑器后仍不满意,终于在快要放弃的时候遇到Sublime Text2,成功的解救了我。

关于它的介绍,网上已经有很多文章了,可以先了解下:

Sublime Text 2 入门及技巧 Sublime Text 2 实用快捷键[Mac OS X]

然后就可以动手了,直接去官网下载最新的安装包,支持win/mac/linux三大系统。再参考上面的文章换个icon;从自带的丰富配色里选一个自己看着顺眼的;体验下无比爽快的cmd/ctrl+p实时文件切换功能;打开一些js、css或者php,cmd/ctrl+r看看有什么;创建一个project;了解每个菜单项;熟悉下各种快捷键。。。这个过程可长可短,总之一定会让你眼前一亮~

请点击下面的链接继续。。。

阅读全文 »