fcp-css.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /**
  2. * 注册命名空间:baidu.css
  3. */
  4. baidu.namespace.register("baidu.css");
  5. /**
  6. * css相关处理
  7. * @author zhaoxianlie
  8. */
  9. baidu.css = (function(){
  10. var _readyQueen = null;
  11. var _localdata = null;
  12. var _stats = null;
  13. var _styleBlockCount = 0;
  14. var _rootPath = null;
  15. /**
  16. * 存储页面上的css源代码
  17. * @item {fileName:'',fileContent:''}
  18. */
  19. var _rawCssSource = [];
  20. var _summaryInformation = null;
  21. /**
  22. * 初始化css文件的读取队列
  23. * @param {Object} isFinished 是否读取完成
  24. */
  25. var _initReadyQueen = function(isFinished){
  26. _readyQueen = {
  27. curIndex : 0, //当前正处于读取的index
  28. queen : [], //css文件队列,格式为:{link:"",style:""},其中link和style不可能同时有值
  29. callback : new Function(), //回调方法
  30. finished : isFinished //是否读取完成
  31. };
  32. _localdata = {};
  33. };
  34. /**
  35. * 初始化侦测结果
  36. */
  37. var _initSummaryInformation = function(){
  38. _summaryInformation = {
  39. styles : [], //所有的style标签和所有的link[rel=stylesheet]
  40. cssMinified : { //css文件是否被压缩
  41. files : [],
  42. count : 0
  43. },
  44. backgroundImages : [], //css背景图片统计
  45. expressions : [], //css expression统计
  46. duplicatedFiles : [] //重复引入的文件
  47. };
  48. };
  49. /**
  50. * 初始化Stats
  51. */
  52. var _initStats = function(){
  53. _stats = {
  54. matched: {count:0,selectors:[]}, //匹配上的
  55. unmatched: {count:0,selectors:[]}, //未匹配上的
  56. ignored: {count:0,selectors:[]} //忽略的
  57. };
  58. };
  59. /**
  60. * 增加一项读取项
  61. * @param {Object} readyItem 格式为:{link:Object,style:Object}
  62. */
  63. var _addReadyItem = function(readyItem){
  64. _readyQueen.queen.push(readyItem);
  65. };
  66. /**
  67. * 获取当前正在解析的Style块
  68. */
  69. var _getCurrentReadyItem = function(){
  70. return _readyQueen.queen[_readyQueen.curIndex];
  71. };
  72. /**
  73. * 判断当前读取的是否为最后一个Style块
  74. */
  75. var _isLastReadyItem = function(){
  76. return (_readyQueen.curIndex == _readyQueen.queen.length);
  77. };
  78. /**
  79. * 读取队列移动到下一个元素
  80. */
  81. var _moveToNextReadyItem = function(){
  82. _readyQueen.curIndex += 1;
  83. };
  84. /**
  85. * 判断当前队列是否读取完毕
  86. */
  87. var _isDealFinished = function(){
  88. return _readyQueen.finished;
  89. };
  90. /**
  91. * 设置当前文件的根路径
  92. * @param {Object} path
  93. */
  94. var _setCurRootPath = function(path) {
  95. var reg = /(.*\/)([^\/]+\.css)/;
  96. var p = reg.exec((path || '').replace(/\?.*/,''));
  97. _rootPath = p ? p[1] : '';
  98. };
  99. /**
  100. * 获取当前正在解析的文件的根路径
  101. */
  102. var _getCurRootPath = function(){
  103. return _rootPath || '';
  104. };
  105. /**
  106. * 根据文件路径,提取文件名
  107. * @param {Object} path 文件url
  108. */
  109. var _getFileName = function(path){
  110. var reg = /(.*\/)([^\/]+\.css)/;
  111. var p = reg.exec((path || '').replace(/\?.*/,''));
  112. return p ? p[2] : "style块" + ++_styleBlockCount;
  113. };
  114. /**
  115. * 保存CSS代码
  116. * @param {Object} _filePath 文件完整路径
  117. * @param {Object} _fileContent 内容
  118. */
  119. var _saveCssSource = function(_filePath,_fileContent){
  120. //过滤CSS注释
  121. _fileContent = _fileContent.replace(/\/\*[\S\s]*?\*\//g,'');
  122. //提取文件名
  123. var _fileName = _getFileName(_filePath);
  124. _rawCssSource.push({
  125. href : _filePath ? _filePath : '#',
  126. fileName : _fileName,
  127. fileContent : _fileContent
  128. });
  129. //对@import处理
  130. try{
  131. var reg = /@import\s+url\(\s*(\"|\')(.*)\1\s*\)(\s*;)?/ig;
  132. _fileContent.replace(reg,function($0,$1,$2){
  133. _addReadyItem({link:{href:_getCurRootPath() + $2},style:null});
  134. });
  135. }catch(err){
  136. }
  137. };
  138. /**
  139. * 获取一个比较标准的图片地址
  140. * @param {Object} bgUrl
  141. */
  142. var _getBgImageUrl = function(bgUrl){
  143. if(bgUrl.indexOf('http://') != 0) {
  144. bgUrl = bgUrl.replace(/['"]/g,"");
  145. var __rp = _getCurRootPath();
  146. if(bgUrl.indexOf('/') == 0){
  147. __rp = '';
  148. } else if(bgUrl.indexOf('./') == 0) {
  149. bgUrl = bgUrl.substr(2);
  150. } else if(bgUrl.indexOf('../') == 0) {
  151. bgUrl = bgUrl.substr(3);
  152. if(__rp.lastIndexOf('/') == __rp.length - 1) {
  153. __rp = __rp.substr(0,__rp.length - 1);
  154. }
  155. __rp = __rp.substr(0,__rp.lastIndexOf('/') + 1);
  156. }
  157. bgUrl = __rp + bgUrl
  158. }
  159. return bgUrl;
  160. };
  161. /**
  162. * 寻找并统计CSS背景图片
  163. * @param {Object} _fileName
  164. * @param {Object} _fileContent
  165. */
  166. var _findBackgroundImage = function(_fileName,_fileContent){
  167. var reg = /(background|background-image):(?:[\#\w]+\s+)?url\(([^\)]*)\)/ig;
  168. var arr = [];
  169. _fileContent.replace(/\/\*[\S\s]*?\*\//g,'').replace(/\r?\n/,'')
  170. .replace(/\s+\'|\"/g,'').replace(reg,function($0,$1,$2){
  171. $2 = $2.replace(/\?.*/,'');
  172. arr.push(_getBgImageUrl($2));
  173. });
  174. if(arr.length) {
  175. _summaryInformation.backgroundImages.push({
  176. fileName:_fileName,
  177. bgImages : arr
  178. });
  179. }
  180. };
  181. /**
  182. * 寻找并统计css中的expression
  183. * @param {Object} _fileName
  184. * @param {Object} _fileContent
  185. */
  186. var _findExpression = function(_fileName,_fileContent){
  187. var reg = /:expression\(/ig;
  188. var arr = _fileContent.replace(/\/\*[\S\s]*?\*\//g,'').replace(/\r?\n/,'')
  189. .replace(/\s+/g,'').split(reg);
  190. if(arr.length - 1) {
  191. _summaryInformation.expressions.push({
  192. fileName : _fileName,
  193. count : arr.length - 1
  194. });
  195. }
  196. };
  197. /**
  198. * 检测某个css文件是否被压缩
  199. * @param {Object} cssObj css文件对象
  200. * @config {String} href 文件路径
  201. * @config {String} fileName 文件名
  202. * @config {String} fileContent 文件内容
  203. */
  204. var _detectCssMinify = function(cssObj){
  205. var lines = cssObj.fileContent.split(/\n/);
  206. var average_length_perline = cssObj.fileContent.length / lines.length;
  207. if (average_length_perline < 150 && lines.length > 1) {
  208. _summaryInformation.cssMinified.count++;
  209. _summaryInformation.cssMinified.files.push({
  210. href : cssObj.href,
  211. fileName : cssObj.fileName
  212. });
  213. }
  214. };
  215. /**
  216. * 将stylesheet归类进行检测
  217. * @param {Array} styleheets CSSstyleheet对象数组
  218. */
  219. var _getCssData = function(styleheets){
  220. //从页面上获取<link> 或者 <style> 内容
  221. var cssdata = (function(){
  222. var ss = {link:[],style:[]};
  223. jQuery.each(styleheets,function(i,styleheet){
  224. //通过 <link> 标签引用的css样式
  225. if(!!styleheet.href) { ss.link.push(styleheet); }
  226. //通过 <style> 标签定义的css
  227. else { ss.style.push(styleheet); }
  228. });
  229. return ss;
  230. })();
  231. return cssdata;
  232. };
  233. /**
  234. * 提取CSS文件内容
  235. * @param {String} link 需要读取的css文件
  236. * @see 具体可参见css-background.js文件中定义的Function: _readFileContent
  237. */
  238. var _getCssSourceByServer = function(link){
  239. _setCurRootPath(link.href);
  240. //向background发送一个消息,要求其加载并处理css文件内容
  241. chrome.runtime.sendMessage(null,{
  242. type : MSG_TYPE.GET_CSS,
  243. link : link.href
  244. },function(respData){
  245. //保存源代码
  246. _saveCssSource(respData.path,respData.content)
  247. //继续读取(是从就绪队列中进行读取)
  248. _readRawCss();
  249. });
  250. };
  251. /**
  252. * 读取页面上已经存在的<style>标签内的样式
  253. * @param {StyleSheet} stylesheet 样式
  254. * @return {Undefined} 无返回值
  255. */
  256. var _readStyleTagContent = function(stylesheet){
  257. //保存源代码
  258. _saveCssSource('',stylesheet.ownerNode.innerText)
  259. //继续读取(是从就绪队列中进行读取)
  260. _readRawCss();
  261. };
  262. /**
  263. * 从就绪队列中,逐个加载css
  264. */
  265. var _readRawCss = function(){
  266. //取得当前需要读取的数据
  267. var curData = _getCurrentReadyItem();
  268. //是否读取完成
  269. if(_isDealFinished() || !curData) {
  270. chrome.runtime.sendMessage({
  271. type : MSG_TYPE.CSS_READY
  272. });
  273. return;
  274. }
  275. //_readyQueen.curIndex是会被累加的
  276. _moveToNextReadyItem();
  277. //清空队列
  278. if(_isLastReadyItem()) {
  279. _initReadyQueen(true);
  280. }
  281. //如果是<style>标签
  282. if(!!curData.style) {
  283. _readStyleTagContent(curData.style);
  284. }
  285. //如果是<link>标签
  286. else if(!!curData.link){
  287. _getCssSourceByServer(curData.link);
  288. }
  289. };
  290. /**
  291. * 初始化CSS数据
  292. */
  293. var _initCssData = function(){
  294. styleheets = _getCssData(document.styleSheets);
  295. //处理<style>定义的样式
  296. if(styleheets.style && styleheets.style.length >0) {
  297. jQuery.each(styleheets.style,function(i,style){
  298. //加入读取队列
  299. _addReadyItem({link:null,style:style});
  300. });
  301. }
  302. //处理<link>引入的css文件
  303. if(styleheets.link && styleheets.link.length >0) {
  304. jQuery.each(styleheets.link,function(i,link){
  305. //加入读取队列
  306. _addReadyItem({link:link,style:null});
  307. });
  308. }
  309. //开始读取
  310. _readRawCss();
  311. };
  312. /**
  313. * 输出结果
  314. */
  315. var _outputResult = function(){
  316. return [{
  317. type : 0, //"冗余的CSS选择器"
  318. count : _stats.unmatched.count,
  319. content : _stats.unmatched.selectors
  320. }, {
  321. type : 1, //"可能用到的CSS伪类选择器"
  322. count : _stats.ignored.count,
  323. content : _stats.ignored.selectors
  324. }, {
  325. type : 2, //"实际用到的CSS选择器"
  326. count : _stats.matched.count,
  327. content : _stats.matched.selectors
  328. }];
  329. };
  330. /**
  331. * 对以存储的css代码进行解析
  332. */
  333. var _dealCssFile = function(){
  334. var isStop = false;
  335. jQuery.each(_rawCssSource,function(i,fileObj){
  336. _dealCssRule(fileObj);
  337. });
  338. };
  339. /**
  340. * 处理某个css文件内的css rules
  341. * @param {Object} _fileObj
  342. * @config {String} fileName
  343. * @config {String} fileContent
  344. */
  345. var _dealCssRule = function(_fileObj){
  346. var fileName = _fileObj.fileName;
  347. var fileContent = _fileObj.fileContent;
  348. //检测css是否压缩
  349. _detectCssMinify(_fileObj);
  350. //检测css expression
  351. _findExpression(fileName,fileContent);
  352. //检测css background-image
  353. _findBackgroundImage(fileName,fileContent);
  354. //css源码分析
  355. var _cssAnalyticRst = (new baidu.cssAnalytic()).run(fileContent);
  356. //获得所有选择器
  357. var _selectors = _getSelectors(_cssAnalyticRst);
  358. //初始化结果集
  359. _initStats();
  360. //检测
  361. jQuery.each(_selectors,function(i,item){
  362. if(item.selector) {
  363. _detectSelector(item.selector,item.csstext);
  364. }
  365. });
  366. _summaryInformation.styles.push({
  367. path : fileName,
  368. content : _outputResult()
  369. });
  370. };
  371. /**
  372. * 从css分析结果中汇总每个独立的selector和rule的对应关系
  373. * @param {Object} _cssAnalyticRst
  374. */
  375. var _getSelectors = function(_cssAnalyticRst){
  376. var rst = [],_selector='',_csstext = [],_pre_type_start;
  377. for(var i = 0,len = _cssAnalyticRst.length;i < len;i++){
  378. var item = _cssAnalyticRst[i];
  379. //new line
  380. if(item[1] == baidu.FL.FL_NEW_LINE) {
  381. continue;
  382. }
  383. //device description
  384. else if(item[1] == baidu.FL.CSS_DEVICE_DESC) {
  385. _selector = item[0];
  386. _pre_type_start = baidu.FL.CSS_DEVICE_START;
  387. continue;
  388. }
  389. //selector
  390. else if(item[1] == baidu.FL.CSS_SELECTOER) {
  391. _selector = item[0];
  392. _pre_type_start = baidu.FL.CSS_SELECTOER_START;
  393. continue;
  394. }
  395. //@import、@charset
  396. else if(item[1] == baidu.FL.CSS_AT) {
  397. _selector = item[0];
  398. }
  399. //csstext
  400. else {
  401. var j = i ;
  402. for(;j < len;j++) {
  403. var jtem = _cssAnalyticRst[j];
  404. //@import、@charset
  405. if(item[1] == baidu.FL.CSS_AT) {
  406. j--;
  407. break;
  408. }
  409. //device description
  410. else if(jtem[1] == baidu.FL.CSS_DEVICE_END && _pre_type_start == baidu.FL.CSS_DEVICE_START) {
  411. _csstext.push(jtem[0]);
  412. break;
  413. }
  414. //selector
  415. else if(jtem[1] == baidu.FL.CSS_SELECTOER_END && _pre_type_start == baidu.FL.CSS_SELECTOER_START) {
  416. _csstext.push(jtem[0]);
  417. break;
  418. }
  419. //csstext
  420. else {
  421. _csstext.push(jtem[0]);
  422. }
  423. }
  424. i = j ;
  425. }
  426. //结果记录
  427. rst.push({
  428. selector : _selector,
  429. csstext : _csstext.join('')
  430. });
  431. //清空结果集
  432. _csstext = [];
  433. _pre_type_start = '';
  434. }
  435. return rst;
  436. };
  437. /**
  438. * 检测某个selector是否用到
  439. * @param {Object} _selector
  440. * @param {Object} _csstext
  441. */
  442. var _detectSelector = function(_selector,_csstext){
  443. //用‘,’分割多个selector
  444. var _selectors = _selector.replace(/\r?\n/g,'').trim().split(',');
  445. var rawSelector = '',arr;
  446. var reg = /^([\*\+]+[^ ]+)[ ]+(.*)$/;
  447. var vreg = /([^:]+)(:hover|:focus|:visited|:link|:active|:before|:after|::)/;
  448. var type;
  449. jQuery.each(_selectors,function(i,currSelector){
  450. rawSelector = currSelector;
  451. arr = reg.exec(currSelector);
  452. if(arr && arr[1] && arr[2]) {
  453. currSelector = arr[2];
  454. }
  455. //是否为CSS伪类
  456. var isVirtual = false;
  457. if(currSelector.indexOf('@') > -1 || currSelector.indexOf('-moz-') > -1) {
  458. _localdata[currSelector] = 1;
  459. } else if (!_localdata[currSelector]) {
  460. //检测是否是伪类
  461. var virtualCss = vreg.exec(currSelector);
  462. if(virtualCss && virtualCss[1]) {
  463. isVirtual = true;
  464. currSelector = virtualCss[1];
  465. }
  466. try{
  467. //核心:试图根据该选择器去渲染节点,看能找到几个节点,如果没有找到,则表示匹配失败
  468. _localdata[currSelector] = jQuery(currSelector + ':not("[id^=fe-helper-],[id^=fe-helper-] ' + currSelector + '")').length;
  469. }catch(err){
  470. _localdata[currSelector] = 0;
  471. }
  472. }
  473. type = _localdata[currSelector] ? isVirtual ? "ignored" : "matched" : "unmatched";
  474. //记录结果
  475. _stats[type].count++;
  476. _stats[type].selectors.push({
  477. selector : rawSelector,
  478. cssText : _csstext
  479. });
  480. });
  481. };
  482. /**
  483. * 检测css选择器
  484. */
  485. var _detectCSS = function(){
  486. //处理css文件以及style块
  487. _dealCssFile();
  488. };
  489. /**
  490. * 检测是否引入的重复的文件
  491. */
  492. var _detectDuplicatedFile = function(){
  493. styleheets = _getCssData(document.styleSheets);
  494. var files = {};
  495. var duplicatedFiles = [];
  496. var dealedFiles = {};
  497. //处理<link>引入的css文件
  498. if(styleheets.link && styleheets.link.length >0) {
  499. jQuery.each(styleheets.link,function(i,link){
  500. files[link.href] = parseInt(files[link.href] || 0,10) + 1;
  501. });
  502. jQuery.each(files,function(href,count){
  503. //文件href重复
  504. if(count > 1) {
  505. duplicatedFiles.push({
  506. href : href,
  507. count : count
  508. });
  509. } else { //href不重复的情况下,检测文件内容是否相同
  510. var _fileContent = '';
  511. var _dupFiles = [];
  512. jQuery.each(_rawCssSource,function(i,file){
  513. if(file.href == href) {
  514. _fileContent = file.fileContent.replace(/\s+/g,'');
  515. return false;
  516. }
  517. });
  518. jQuery.each(_rawCssSource,function(i,file){
  519. if(_fileContent == file.fileContent.replace(/\s+/g,'') && !dealedFiles[file.href] && _dupFiles.join(',').indexOf(file.href) == -1) {
  520. _dupFiles.push(file.href);
  521. }
  522. });
  523. if(_dupFiles.length > 1) {
  524. duplicatedFiles.push({
  525. href : href,
  526. dupFiles : _dupFiles
  527. });
  528. dealedFiles[href] = true;
  529. }
  530. }
  531. });
  532. }
  533. _summaryInformation.duplicatedFiles = duplicatedFiles;
  534. };
  535. /**
  536. * 初始化
  537. */
  538. var _init = function(){
  539. //初始化就绪队列
  540. _initReadyQueen(false);
  541. //初始化CSS数据
  542. _initCssData();
  543. };
  544. /**
  545. * css相关处理
  546. * @param {Function} callback 侦测完毕后的回调方法,形如:function(data){}
  547. * @config {Object} data 就是_summaryInformation
  548. */
  549. var _detect = function(callback){
  550. //初始化结果集
  551. _initSummaryInformation();
  552. //执行侦测
  553. _detectCSS();
  554. //检测重复引入的文件
  555. _detectDuplicatedFile();
  556. //执行回调
  557. if(callback && typeof callback == "function") {
  558. callback.call(null,_summaryInformation);
  559. }
  560. };
  561. return {
  562. init : _init,
  563. detect : _detect
  564. };
  565. })();