瀏覽代碼

升级js覆盖率检测工具,支持任意页面

1、升级js覆盖率检测工具,从@dron处获取源码,重新设计&实现,以后单独维护
2、build.sh脚本升级,对crx文件进行瘦身,体积减小20%+
3、升级二维码工具、字符串编解码等工具,支持右键选中数据进行使用
4、调整右键菜单项目
Alien 9 年之前
父節點
當前提交
31e4ebb25b
共有 41 個文件被更改,包括 4272 次插入420 次删除
  1. 二進制
      .DS_Store
  2. 36 2
      chrome/build.sh
  3. 41 10
      chrome/manifest.json
  4. 13 11
      chrome/online.manifest.json
  5. 8 0
      chrome/static/css/js-tracker.css
  6. 二進制
      chrome/static/img/baidufe_bj.gif
  7. 二進制
      chrome/static/img/colorpicker.png
  8. 二進制
      chrome/static/img/grid.png
  9. 2 2
      chrome/static/js/colorpicker/colorpicker.js
  10. 4 1
      chrome/static/js/core/fe-const.js
  11. 37 52
      chrome/static/js/fe-background.js
  12. 0 14
      chrome/static/js/google_analytics.js
  13. 1 1
      chrome/static/js/jsonformat/fe-jsonformat.js
  14. 4 4
      chrome/static/js/mod/mod_codebeautify.js
  15. 1 1
      chrome/static/js/mod/mod_manifest.js
  16. 33 0
      chrome/static/js/mod/mod_tracker.js
  17. 238 0
      chrome/static/js/tracker/code.js
  18. 234 0
      chrome/static/js/tracker/combocodegen.js
  19. 129 0
      chrome/static/js/tracker/decorate.js
  20. 60 0
      chrome/static/js/tracker/event.js
  21. 308 0
      chrome/static/js/tracker/general.js
  22. 106 0
      chrome/static/js/tracker/inject.js
  23. 365 0
      chrome/static/js/tracker/main.js
  24. 53 0
      chrome/static/js/tracker/path.js
  25. 151 0
      chrome/static/js/tracker/plugin.js
  26. 133 0
      chrome/static/js/tracker/promise.js
  27. 64 0
      chrome/static/js/tracker/status-pool.js
  28. 178 0
      chrome/static/js/tracker/token.js
  29. 33 0
      chrome/static/js/tracker/tracker.js
  30. 489 0
      chrome/static/js/tracker/util.js
  31. 1275 0
      chrome/static/js/tracker/view.js
  32. 276 0
      chrome/static/js/tracker/watch.js
  33. 0 107
      chrome/static/vendor/jquery-ui-1.8/css/jquery-ui-1.8.16.custom.blue_datauri.css
  34. 0 215
      chrome/static/vendor/jquery-ui-1.8/css/jquery-ui-1.8.16.custom.color_datauri.css
  35. 0 0
      chrome/static/vendor/jquery-ui-1.8/jquery-ui.hot.css
  36. 0 0
      chrome/static/vendor/jquery-ui-1.8/jquery-ui.min.js
  37. 0 0
      chrome/static/vendor/syntaxhighlighter/shBrushCss.js
  38. 0 0
      chrome/static/vendor/syntaxhighlighter/shBrushJScript.js
  39. 0 0
      chrome/static/vendor/syntaxhighlighter/shBrushXml.js
  40. 0 0
      chrome/static/vendor/syntaxhighlighter/shCore.js
  41. 0 0
      chrome/template/fehelper_popup.html

二進制
.DS_Store


+ 36 - 2
chrome/build.sh

@@ -27,7 +27,41 @@ find . -type d -name ".svn" | xargs rm -rf
 rm -rf static.uncompress
 cd ../ && mv output $MOD_NAME && mkdir output && mv $MOD_NAME output
 
+# 扫描所有的文件
+function scandir(){
+
+    for f in $(ls $1) ;do
+        abspath=$1"/"$f
+        if [[ -d $abspath ]];then
+            scandir $abspath
+        elif [[ -f $abspath ]];then
+            echo $abspath
+        fi
+    done
+}
+
+# 冗余文件清理
+cd output/fe-helper
+rootpath=$(pwd)
+cd static
+# 待清理的目录
+cleandir="js css img"
+for d in $cleandir;do
+
+    thefiles=$(scandir $d)
+
+    for f in $thefiles;do
+        result=$(grep $f -rl $rootpath)
+        if [[ x"$result" == x ]];then
+            rm -f $f
+            echo "清理文件成功:static/$f"
+        fi
+    done
+done
+
 #生成zip包
-cd output
+cd $rootpath/../
 zip -r $MOD_NAME.zip $MOD_NAME/ > /dev/null
-cd ../
+
+echo ""
+echo "生成压缩包成功,可发布到Chrome web store了!"

+ 41 - 10
chrome/manifest.json

@@ -1,11 +1,11 @@
 {
     "name": "WEB前端助手(FeHelper)",
-    "version": "7.4",
+    "version": "7.5",
     "manifest_version": 2,
 
     "default_locale": "zh_CN",
 
-    "description": "FE助手:包括字符串编解码、代码压缩、美化、JSON格式化、正则表达式、时间转换工具、二维码生成器、编码规范检测、页面性能检测、页面取色等",
+    "description": "FE助手:包括字符串编解码、代码压缩、美化、JSON格式化、正则表达式、时间转换工具、二维码生成器、编码规范检测、页面性能检测、页面取色、Js覆盖率检测等",
     "icons": {
         "16": "static/img/fe-16.png",
         "48": "static/img/fe-48.png",
@@ -38,10 +38,7 @@
     ],
 
     "web_accessible_resources": [
-        "static/img/43.png",
-        "static/img/baidufe_bj.png",
-        "static/img/baidufe_bj.gif",
-        "static/img/fe-18.png",
+        "static/img/fe-16.png",
         "static/img/fe-48.png",
         "static/img/fe-128.png",
         "static/img/pbar-ani.gif",
@@ -50,7 +47,25 @@
         "static/img/close.png",
         "static/css/fe-helper.css",
         "static/css/fe-jsonformat-content.css",
-        "static/vendor/jquery-ui-1.8/css/jquery-ui-1.8.16.custom.hot_datauri.css"
+        "static/vendor/jquery-ui-1.8/jquery-ui.hot.css",
+        "static/css/js-tracker.css",
+
+
+        "static/js/tracker/tracker.js",
+        "static/js/tracker/util.js",
+        "static/js/tracker/path.js",
+        "static/js/tracker/promise.js",
+        "static/js/tracker/event.js",
+        "static/js/tracker/status-pool.js",
+        "static/js/tracker/plugin.js",
+        "static/js/tracker/code.js",
+        "static/js/tracker/combocodegen.js",
+        "static/js/tracker/decorate.js",
+        "static/js/tracker/token.js",
+        "static/js/tracker/view.js",
+        "static/js/tracker/general.js",
+        "static/js/tracker/watch.js",
+        "static/js/tracker/inject.js"
     ],
 
     "content_scripts": [
@@ -62,7 +77,7 @@
             ],
             "js": [
                 "static/js/core/jquery-1.5.min.js",
-                "static/vendor/jquery-ui-1.8/js/jquery-ui-1.8.11.custom.min.js",
+                "static/vendor/jquery-ui-1.8/jquery-ui.min.js",
 
                 "static/js/core/core.js",
                 "static/js/core/fe-const.js",
@@ -86,9 +101,25 @@
 
                 "static/js/jsonformat/json-format-dealer.js",
                 "static/js/jsonformat/json-format-ent.js",
-                "static/js/jsonformat/contentscript-jsonformat.js"
+                "static/js/jsonformat/contentscript-jsonformat.js",
+
+                "static/js/tracker/tracker.js",
+                "static/js/tracker/util.js",
+                "static/js/tracker/path.js",
+                "static/js/tracker/promise.js",
+                "static/js/tracker/event.js",
+                "static/js/tracker/status-pool.js",
+                "static/js/tracker/plugin.js",
+                "static/js/tracker/code.js",
+                "static/js/tracker/combocodegen.js",
+                "static/js/tracker/decorate.js",
+                "static/js/tracker/token.js",
+                "static/js/tracker/view.js",
+                "static/js/tracker/general.js",
+                "static/js/tracker/watch.js",
+                "static/js/tracker/main.js"
             ],
-            "run_at": "document_start",
+            "run_at": "document_end",
             "all_frames": false
         },
         {

+ 13 - 11
chrome/online.manifest.json

@@ -1,11 +1,11 @@
 {
     "name": "WEB前端助手(FeHelper)",
-    "version": "7.4",
+    "version": "7.5",
     "manifest_version": 2,
 
     "default_locale": "zh_CN",
 
-    "description": "FE助手:包括字符串编解码、代码压缩、美化、JSON格式化、正则表达式、时间转换工具、二维码生成器、编码规范检测、页面性能检测、页面取色等",
+    "description": "FE助手:包括字符串编解码、代码压缩、美化、JSON格式化、正则表达式、时间转换工具、二维码生成器、编码规范检测、页面性能检测、页面取色、Js覆盖率检测等",
     "icons": {
         "16": "static/img/fe-16.png",
         "48": "static/img/fe-48.png",
@@ -38,10 +38,7 @@
     ],
 
     "web_accessible_resources": [
-        "static/img/43.png",
-        "static/img/baidufe_bj.png",
-        "static/img/baidufe_bj.gif",
-        "static/img/fe-18.png",
+        "static/img/fe-16.png",
         "static/img/fe-48.png",
         "static/img/fe-128.png",
         "static/img/pbar-ani.gif",
@@ -50,7 +47,9 @@
         "static/img/close.png",
         "static/css/fe-helper.css",
         "static/css/fe-jsonformat-content.css",
-        "static/vendor/jquery-ui-1.8/css/jquery-ui-1.8.16.custom.hot_datauri.css"
+        "static/vendor/jquery-ui-1.8/jquery-ui.hot.css",
+        "static/css/js-tracker.css",
+        "static/js/tracker/inject.js"
     ],
 
     "content_scripts": [
@@ -62,17 +61,20 @@
             ],
             "js": [
                 "static/js/core/jquery-1.5.min.js",
-                "static/vendor/jquery-ui-1.8/js/jquery-ui-1.8.11.custom.min.js",
+                "static/vendor/jquery-ui-1.8/jquery-ui.min.js",
 
-                "static/js/mod/mod_manifest.js"
+                "static/js/mod/mod_manifest.js",
+                "static/js/mod/mod_tracker.js"
             ],
-            "run_at": "document_start",
+            "run_at": "document_end",
             "all_frames": false
         },
         {
             "matches": [
                 "http://*.baidufe.com/fehelper/*",
-                "http://*.baidufe.com/fehelper"
+                "http://*.baidufe.com/fehelper",
+                "http://*.fehelper.com/fehelper/*",
+                "http://*.fehelper.com/fehelper"
             ],
             "js": [
                 "static/js/fe-contentscript-fehelper.js"

File diff suppressed because it is too large
+ 8 - 0
chrome/static/css/js-tracker.css


二進制
chrome/static/img/baidufe_bj.gif


二進制
chrome/static/img/colorpicker.png


二進制
chrome/static/img/grid.png


+ 2 - 2
chrome/static/js/colorpicker/colorpicker.js

@@ -315,10 +315,10 @@ FeHelper.ColorPicker = (function () {
 
         setTimeout(function () {
             try {
-                chrome.runtime.sendMessage({type: 'color-picker:newImage'}, function (response) {
+                chrome.runtime.sendMessage({type: MSG_TYPE.COLOR_PICKER}, function (response) {
                 });
             } catch (e) {
-                exitAndDetach();
+                console.log('有错误发生,可提交此反馈到官网!',e);
             }
         }, 255);
     }

+ 4 - 1
chrome/static/js/core/fe-const.js

@@ -79,7 +79,10 @@ const MSG_TYPE = {
     IMAGE_BASE64 : 'imagebase64',
 
     //页面json代码自动格式化
-    AUTO_FORMART_PAGE_JSON : "opt_item_autojson"
+    AUTO_FORMART_PAGE_JSON : "opt_item_autojson",
+
+    //页面取色器
+    COLOR_PICKER : "color-picker:newImage"
 };
 
 /**

+ 37 - 52
chrome/static/js/fe-background.js

@@ -72,16 +72,6 @@ var BgPageInstance = (function () {
         }
     };
 
-    /**
-     * 执行栅格检测
-     */
-    var _doGridDetect = function (tab) {
-        chrome.tabs.sendMessage(tab.id, {
-            type: MSG_TYPE.BROWSER_CLICKED,
-            event: MSG_TYPE.GRID_DETECT
-        });
-    };
-
     /**
      * 查看页面wpo信息
      */
@@ -120,15 +110,11 @@ var BgPageInstance = (function () {
      */
     var _doJsTracker = function () {
         chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
-            var tab = tabs[0];
-            chrome.tabs.executeScript(tab.id, {
-                code: "void function(t,r,a,c,k){t.tracker_type='bm';t.tracker_uid='fehelper';"
-                + "(k=t.TrackerGlobalEvent)?k.f(r):[(k=t[a]('script')).charset='utf-8',"
-                + "k.src='http://www.ucren.com/'+c+'/'+c+'.js?'+Math.random(),"
-                + "t.documentElement.appendChild(k)]}(document,'TrackerJSLoad','createElement','tracker') ",
-                allFrames: false,
-                runAt: 'document_end'
+
+            chrome.tabs.sendMessage(tabs[0].id, {
+                type: MSG_TYPE.JS_TRACKER
             });
+
         });
     };
 
@@ -169,11 +155,13 @@ var BgPageInstance = (function () {
     var _tabUpdatedCallback = function (evt, content) {
         return function (newTab) {
             if (content) {
-                chrome.tabs.sendMessage(newTab.id, {
-                    type: MSG_TYPE.TAB_CREATED_OR_UPDATED,
-                    content: content,
-                    event: evt
-                });
+                setTimeout(function () {
+                    chrome.tabs.sendMessage(newTab.id, {
+                        type: MSG_TYPE.TAB_CREATED_OR_UPDATED,
+                        content: content,
+                        event: evt
+                    });
+                }, 300)
             }
         };
     };
@@ -216,17 +204,14 @@ var BgPageInstance = (function () {
             var tab = tabs[0];
             // 如果是采用独立文件方式访问,直接打开该页面即可
             if (config.useFile == '1') {
-                _openFileAndRun(tab, config.msgType);
+                var content = config.msgType == MSG_TYPE.QR_CODE ? tab.url : '';
+                _openFileAndRun(tab, config.msgType, content);
             } else {
                 switch (config.msgType) {
                     //fcphelper检测
                     case MSG_TYPE.FCP_HELPER_DETECT:
                         _doFcpDetect(tab);
                         break;
-                    //栅格检测
-                    case MSG_TYPE.GRID_DETECT:
-                        _doGridDetect(tab);
-                        break;
                     //查看网页加载时间
                     case MSG_TYPE.SHOW_PAGE_LOAD_TIME:
                         _getPageWpoInfo();
@@ -255,17 +240,17 @@ var BgPageInstance = (function () {
             documentUrlPatterns: ['http://*/*', 'https://*/*']
         });
         chrome.contextMenus.create({
-            title: "JSON格式化",
-            contexts: ['page', 'selection', 'editable'],
+            title: "生成二维码",
+            contexts: ['page', 'selection', 'editable', 'link'],
             parentId: baidu.contextMenuId,
             onclick: function (info, tab) {
                 chrome.tabs.executeScript(tab.id, {
                     code: '(' + (function () {
-                        return window.getSelection().toString();
+                        return window.getSelection().toString() || location.href;
                     }).toString() + ')()',
                     allFrames: false
                 }, function (txt) {
-                    _openFileAndRun(tab, 'jsonformat', txt);
+                    _openFileAndRun(tab, 'qrcode', txt);
                 });
             }
         });
@@ -275,18 +260,11 @@ var BgPageInstance = (function () {
             parentId: baidu.contextMenuId
         });
         chrome.contextMenus.create({
-            title: "字符串编解码",
+            title: "页面取色器",
             contexts: ['page', 'selection', 'editable'],
             parentId: baidu.contextMenuId,
             onclick: function (info, tab) {
-                chrome.tabs.executeScript(tab.id, {
-                    code: '(' + (function () {
-                        return window.getSelection().toString();
-                    }).toString() + ')()',
-                    allFrames: false
-                }, function (txt) {
-                    _openFileAndRun(tab, 'endecode', txt);
-                });
+                _showColorPicker();
             }
         });
         chrome.contextMenus.create({
@@ -295,17 +273,17 @@ var BgPageInstance = (function () {
             parentId: baidu.contextMenuId
         });
         chrome.contextMenus.create({
-            title: "生成二维码",
-            contexts: ['page', 'selection', 'editable', 'link'],
+            title: "字符串编解码",
+            contexts: ['page', 'selection', 'editable'],
             parentId: baidu.contextMenuId,
             onclick: function (info, tab) {
                 chrome.tabs.executeScript(tab.id, {
                     code: '(' + (function () {
-                        return window.getSelection().toString() || location.href;
+                        return window.getSelection().toString();
                     }).toString() + ')()',
                     allFrames: false
                 }, function (txt) {
-                    _openFileAndRun(tab, 'qrcode', txt);
+                    _openFileAndRun(tab, 'endecode', txt);
                 });
             }
         });
@@ -315,7 +293,7 @@ var BgPageInstance = (function () {
             parentId: baidu.contextMenuId
         });
         chrome.contextMenus.create({
-            title: "代码格式化",
+            title: "JSON格式化",
             contexts: ['page', 'selection', 'editable'],
             parentId: baidu.contextMenuId,
             onclick: function (info, tab) {
@@ -325,7 +303,7 @@ var BgPageInstance = (function () {
                     }).toString() + ')()',
                     allFrames: false
                 }, function (txt) {
-                    _openFileAndRun(tab, 'codebeautify', txt);
+                    _openFileAndRun(tab, 'jsonformat', txt);
                 });
             }
         });
@@ -335,11 +313,18 @@ var BgPageInstance = (function () {
             parentId: baidu.contextMenuId
         });
         chrome.contextMenus.create({
-            title: "代码压缩",
+            title: "代码格式化",
             contexts: ['page', 'selection', 'editable'],
             parentId: baidu.contextMenuId,
             onclick: function (info, tab) {
-                _goCompressTool();
+                chrome.tabs.executeScript(tab.id, {
+                    code: '(' + (function () {
+                        return window.getSelection().toString();
+                    }).toString() + ')()',
+                    allFrames: false
+                }, function (txt) {
+                    _openFileAndRun(tab, 'codebeautify', txt);
+                });
             }
         });
         chrome.contextMenus.create({
@@ -348,11 +333,11 @@ var BgPageInstance = (function () {
             parentId: baidu.contextMenuId
         });
         chrome.contextMenus.create({
-            title: "页面取色器",
+            title: "Js覆盖率检测",
             contexts: ['page', 'selection', 'editable'],
             parentId: baidu.contextMenuId,
             onclick: function (info, tab) {
-                _showColorPicker();
+                _doJsTracker();
             }
         });
     };
@@ -478,7 +463,7 @@ var BgPageInstance = (function () {
                 _runHelper(request.config);
             }
             // color picker
-            else if (request.type == "color-picker:newImage") {
+            else if (request.type == MSG_TYPE.COLOR_PICKER) {
                 _drawColorPicker(callback);
             }
 

+ 0 - 14
chrome/static/js/google_analytics.js

@@ -1,14 +0,0 @@
-/*
-var _gaq = _gaq || [];
-_gaq.push(['_setAccount', 'UA-32516930-1']);
-_gaq.push(['_trackPageview']);
-
-(function() {
-	var ga = document.createElement('script');
-	ga.type = 'text/javascript';
-	ga.async = true;
-	ga.src = 'https://ssl.google-analytics.com/ga.js';
-	var s = document.getElementsByTagName('script')[0];
-	s.parentNode.insertBefore(ga, s);
-})();
-*/

+ 1 - 1
chrome/static/js/jsonformat/fe-jsonformat.js

@@ -106,7 +106,7 @@ baidu.jsonformat = (function () {
         chrome.runtime.onMessage.addListener(function (request, sender, callback) {
             if (request.type == MSG_TYPE.TAB_CREATED_OR_UPDATED && request.event == 'jsonformat') {
                 if (request.content) {
-                    _g('jsonSource').value = (request.content);
+                    document.getElementById('jsonSource').value = (request.content);
                     _format();
                 }
             }

+ 4 - 4
chrome/static/js/mod/mod_codebeautify.js

@@ -22,8 +22,8 @@ importScript("js/codebeautify/beautify-css.js");
 importScript("js/codebeautify/beautify-html.js");
 importScript("js/core/fe-const.js");
 importScript("js/core/core.js");
-importScript("js/syntaxhighlighter/shCore.js");
-importScript("js/syntaxhighlighter/shBrushCss.js");
-importScript("js/syntaxhighlighter/shBrushJScript.js");
-importScript("js/syntaxhighlighter/shBrushXml.js");
+importScript("vendor/syntaxhighlighter/shCore.js");
+importScript("vendor/syntaxhighlighter/shBrushCss.js");
+importScript("vendor/syntaxhighlighter/shBrushJScript.js");
+importScript("vendor/syntaxhighlighter/shBrushXml.js");
 importScript("js/codebeautify/codebeautify.js");

+ 1 - 1
chrome/static/js/mod/mod_manifest.js

@@ -29,7 +29,7 @@ importScript("js/fcp/html/fcp-html.js");
 importScript("js/fcp/js/fcp-js.js");
 importScript("js/fcp/fcp-tabs.js");
 importScript("js/fcp/fcp-main.js");
-			
+
 importScript("js/fe-helper.js");
 importScript("js/wpo/fe-calc-wpo.js");
 

+ 33 - 0
chrome/static/js/mod/mod_tracker.js

@@ -0,0 +1,33 @@
+(function ( /*importstart*/ ) {
+	var scripts = document.getElementsByTagName('script'),
+		length = scripts.length,
+		src = scripts[length - 1].src,
+		scriptPath = chrome.extension.getURL('static/');
+	if (!window.importScriptList) window.importScriptList = {};
+	window.importScript = function (filename) {
+		if (!filename) return;
+		if (filename.indexOf("http://") == -1 && filename.indexOf("https://") == -1) {
+			if (filename.substr(0, 1) == '/') filename = filename.substr(1);
+			filename = scriptPath + filename;
+		}
+		if (filename in importScriptList) return;
+		importScriptList[filename] = true;
+		document.write('<script src="' + filename + '" type="text/javascript" charset="utf-8"><\/' + 'script>');
+	}
+})( /*importend*/ )
+
+importScript("js/tracker/tracker.js");
+importScript("js/tracker/util.js");
+importScript("js/tracker/path.js");
+importScript("js/tracker/promise.js");
+importScript("js/tracker/event.js");
+importScript("js/tracker/status-pool.js");
+importScript("js/tracker/plugin.js");
+importScript("js/tracker/code.js");
+importScript("js/tracker/combocodegen.js");
+importScript("js/tracker/decorate.js");
+importScript("js/tracker/token.js");
+importScript("js/tracker/view.js");
+importScript("js/tracker/general.js");
+importScript("js/tracker/watch.js");
+importScript("js/tracker/main.js");

+ 238 - 0
chrome/static/js/tracker/code.js

@@ -0,0 +1,238 @@
+window.Tracker = window.Tracker || {};
+/**
+ * Code
+ */
+Tracker.Code = function(){
+    var klass;
+
+    klass = function( url, content, scriptElementIndex ){
+        var comboCode, beautifyCode;
+
+        this.id = Tracker.Util.id();
+        this.url = url;
+        this.type = "";
+        this.state = "normal";
+        this.rowsCount = 0;
+        this.arriveRowsCount = 0;
+        this.size = content ? Tracker.Util.getByteLength( content ) : -1;
+        this.fileName = url ? Tracker.Util.fileName( url ) : "-";
+        this.fullUrl = url ? Tracker.Path.merge( Tracker.Path.getBase(window.document), url ) : null;
+        this.origContent = content || null;
+        this.lastModified = Tracker.Util.time();
+        this.beautifySize = -1;
+        this.runErrors = [];
+        this.syntaxErrors = [];
+        this.props = {};
+
+        this.executiveCode = "";
+        this.linesViewHtml = [];
+
+        this.loadConsum =
+            this.runConsum = -1;
+
+        this.onReady = Tracker.Promise.fuze();
+
+        if( content ){
+            comboCode = new Tracker.ComboCode( this );
+            comboCode.onReady( Tracker.Util.bind( function(){
+                if( comboCode.errorMessage ){
+                    this.executiveCode = this.origContent;
+                    this.syntaxErrors.push( comboCode );
+                    this.fire( "error", "syntaxErrors" );
+                }else{
+                    this.executiveCode = comboCode.getExecutiveCode( scriptElementIndex );
+                    beautifyCode = comboCode.getBeautifyCode();
+                    this.beautifySize = Tracker.Util.getByteLength( beautifyCode );
+                    this.rowsCount = Tracker.Util.splitToLines( beautifyCode ).length;
+                }
+
+                this.linesViewHtml = comboCode.getViewHtmlByLines();
+                this.onReady.fire();
+            }, this ) );
+        }else{
+            this.executiveCode = ";";
+            this.beautifySize = this.size = 0;
+            this.rowsCount = 0;
+            this.linesViewHtml = [];
+            this.setState( "empty" );
+            this.onReady.fire();
+        }
+    };
+
+    klass.prototype = Tracker.Event.bind( {
+        setType: function( type ){
+            this.type = type; // embed, link, append
+        },
+
+        setState: function( state ){ // normal, timeout, empty
+            this.state = state;
+        },
+
+        addError: function( message ){
+            this.runErrors.push( new Error( message ) );
+            this.lastModified = Tracker.Util.time();
+            this.fire( "error", "runErrors" );
+        },
+
+        prop: function( name, value ){
+            if( arguments.length == 2 )
+                return this.props[ name ] = value;
+            else
+                return this.props[ name ];
+        }
+    } );
+
+    return klass;
+}();
+
+Tracker.ComboCode = function(){
+    var klass, closeTagRegx, viewHtmlRegx, executiveCodeRegx, comboCodeBoundaryRegx,
+        lineFirstIdRegx, topLocationToRegx;
+
+    closeTagRegx = /<\/(\w{0,10})>/g;
+
+    viewHtmlRegx = /\{<\}(<!-- TRACKERINJECTHTML -->.*?)\{>\}/g;
+    executiveCodeRegx = /\{<\}\/\* TRACKERINJECTJS \*\/.*?\{>\}/g;
+    comboCodeBoundaryRegx = /\{(?:<|>)\}/g;
+    lineFirstIdRegx = /id=ckey\-(\d+)/;
+    topLocationToRegx = /(\s*)(top)(\.location\s*=)(?!=)/g;
+
+    klass = function( CodeInstance ){
+        this.CodeInstance = CodeInstance;
+        this.code = null;
+        this.errorMessage = null;
+        this.onReady = Tracker.Promise.fuze();
+
+        try{
+            this.code = window.document.combocodegen( CodeInstance );
+        }catch(e){
+            this.errorMessage = e.message;
+        }
+
+        this.onReady.fire();
+    };
+
+    klass.prototype = Tracker.Event.bind( {
+        getCode: function(){
+            return this.code;
+        },
+
+        getBeautifyCode: function(){
+            var code = this.code;
+            code = code.replace( viewHtmlRegx, "" );
+            code = code.replace( executiveCodeRegx, "" );
+            code = code.replace( comboCodeBoundaryRegx, "" );
+            return code;
+        },
+
+        getExecutiveCode: function( scriptElementIndex ){
+            var code, inst, a;
+
+            code = this.code;
+            inst = this.CodeInstance;
+            code = code.replace( viewHtmlRegx, "" );
+            code = code.replace( comboCodeBoundaryRegx, "" );
+
+            code = code.replace( closeTagRegx, function( s, a ){
+                return "<\\/" + a + ">";
+            } );
+
+            code = code.replace( topLocationToRegx, function( s, a, b, c ){
+                return a + "__trackerMockTop__()" + c;
+            } );
+
+            code = "try{" + code +
+            "}catch(e){__trackerError__('" + inst.id + "',e.message);throw e;}";
+
+            a = typeof scriptElementIndex == "undefined" ? "" : "," + scriptElementIndex;
+            code = "__trackerScriptStart__('" + inst.id + "'" + a + ");" +
+            code + "; __trackerScriptEnd__('" + inst.id + "');";
+
+            return code;
+        },
+
+        getViewHtmlByLines: function(){
+            var code, lines, firstId;
+
+            code = this.code || this.CodeInstance.origContent;
+
+            code = code.replace( viewHtmlRegx, function( s, a ){
+                return a.replace( /</g, "\x00" ).replace( />/g, "\x01" );
+            } );
+
+            code = code.replace( executiveCodeRegx, "" );
+            code = code.replace( comboCodeBoundaryRegx, "" );
+            lines = Tracker.Util.splitToLines( code );
+
+            // Tracker.Util.forEach( lines, function( line, index ){
+            //     var firstId;
+
+            //     firstId = line.match( lineFirstIdRegx );
+
+            //     if( firstId )
+            //         Tracker.StatusPool.beginOfLineSnippetPut( firstId[1] );
+            // } );
+
+            Tracker.Util.forEach( lines, function( line ){
+                if( firstId = line.match( lineFirstIdRegx ) )
+                    Tracker.StatusPool.snippetGroupCoverLineAdd( firstId[ 1 ] );
+            } );
+
+            return lines;
+        }
+    } );
+
+    return klass;
+}();
+
+Tracker.CodeList = function(){
+    var single, codes;
+
+    codes = [];
+    single = Tracker.Event.bind( {
+        add: function(){
+            var code;
+
+            if( arguments.length == 1 ){
+                code = arguments[ 0 ];
+            }else if( arguments.length == 2 ){
+                code = new Tracker.Code( arguments[ 0 ], arguments[ 1 ] );
+            }else{
+                return ;
+            }
+
+            codes[ code.id ] = code;
+            codes.push( code );
+        },
+
+        get: function( idOrIndex ){
+            return codes[ idOrIndex ];
+        },
+
+        list: function(){
+            return codes;
+        },
+
+        each: function( fn ){
+            Tracker.Util.forEach( codes, fn );
+        },
+
+        sort: function(){
+            for( var i = codes.length - 1, r; i >= 0; i -- ){
+                r = codes[ i ];
+                if( r.type == "embed" )
+                    codes.splice( i, 1 ),
+                        codes.push( r );
+            }
+
+            Tracker.Util.forEach( codes, function( code, index ){
+                code.index = index;
+            } );
+        },
+
+        count: function(){
+            return codes.length;
+        } } );
+
+    return single;
+}();

File diff suppressed because it is too large
+ 234 - 0
chrome/static/js/tracker/combocodegen.js


+ 129 - 0
chrome/static/js/tracker/decorate.js

@@ -0,0 +1,129 @@
+window.Tracker = window.Tracker || {};
+
+Tracker.Decorate = function (window, pageWin) {
+    var Element, appendChild, insertBefore, getAttribute, check, checklist, scriptElements, i, l;
+
+    Element = pageWin.Element.prototype;
+    appendChild = Element.appendChild;
+    insertBefore = Element.insertBefore;
+    scriptElements = pageWin.document.scripts;
+
+    var overideInsertingNodeFunction = function (fn) {
+        return function (node) {
+            var me, args, url, code, content;
+
+            me = this;
+            args = [].slice.apply(arguments);
+
+            if (!node || node.nodeName != "SCRIPT")
+                return fn.apply(me, args);
+
+            url = node.getAttribute("src");
+
+            if (!url)
+                return fn.apply(me, args);
+
+            Tracker.Util.intelligentGet(url).then(function (data) {
+                content = data.response;
+                code = new Tracker.Code(url, content, Tracker.Util.handle(node));
+                code.setType("append");
+                code.loadConsum = data.consum;
+                Tracker.CodeList.add(code);
+                node.removeAttribute("src");
+                Tracker.View.ControlPanel.addCode(code);
+
+                code.onReady(function () {
+                    node.appendChild(pageWin.document.createTextNode(code.executiveCode));
+                    fn.apply(me, args);
+                    node.src = url;
+                });
+            }, function () {
+                code = new Tracker.Code(url);
+                code.setType("append");
+                code.setState("timeout");
+                Tracker.CodeList.add(code);
+                fn.apply(me, args);
+                Tracker.View.ControlPanel.addCode(code);
+            });
+
+            return node;
+        };
+    };
+
+
+    check = function (item, name) {
+        if (item && item.prototype && item.prototype[name])
+            if (item.prototype[name] != Element[name])
+                item.prototype[name] = Element[name];
+    };
+
+    checklist = [window.HTMLElement, window.HTMLHeadElement, window.HTMLBodyElement];
+
+    Element.appendChild = overideInsertingNodeFunction(appendChild);
+    Element.insertBefore = overideInsertingNodeFunction(insertBefore);
+
+    Tracker.Util.forEach(checklist, function (object) {
+        check(object, "appendChild");
+        check(object, "insertBefore");
+    });
+
+    window.__tracker__ = function (groupId) {
+        [].concat((groupId || '').split(',')).forEach(function (item) {
+            Tracker.StatusPool.arrivedSnippetGroupPut(item);
+        });
+    };
+
+    window.__trackerError__ = function (codeId, msg) {
+        Tracker.CodeList.get(codeId).addError(msg);
+    };
+
+    window.__trackerMockTop__ = function () {
+        return {
+            location: {},
+            document: {write: Tracker.Util.blank}
+        };
+    };
+
+    window.__trackerScriptStart__ = function (codeId, scriptTagIndex) {
+        var script, code;
+
+        script = scriptTagIndex === "undefined" ?
+            scriptElements[scriptElements.length - 1] : Tracker.Util.handle(scriptTagIndex);
+
+        if (script && script.hasAttribute("tracker-src"))
+            script.src = script.getAttribute("tracker-src");
+
+        setTimeout(function () {
+            if (script.onreadystatechange)
+                script.onreadystatechange();
+        }, 0);
+
+        code = Tracker.CodeList.get(codeId);
+        code._startTime = new Date();
+    };
+
+    window.__trackerScriptEnd__ = function (codeId) {
+        var code, endTime;
+
+        endTime = new Date();
+        code = Tracker.CodeList.get(codeId);
+        code.runConsum = endTime.getTime() - code._startTime.getTime();
+        // TODO: 此值虚高,因为钩子运行本身也产生耗时,需要扣除钩子时间才准确
+        delete code._startTime;
+        code.lastModified = Tracker.Util.time();
+    };
+
+    window.onbeforeunload = function () {
+        var startTime = Tracker.Util.time();
+        return function () {
+            var now = Tracker.Util.time();
+            if (now - startTime < 3e3)
+                setTimeout(function () {
+                    var h = window.location.hash;
+                    window.location.href = ~h.indexOf("#") ? h : "#" + h;
+                }, 0);
+            while (Tracker.Util.time() - now < 500);
+        }
+    }();
+
+};

+ 60 - 0
chrome/static/js/tracker/event.js

@@ -0,0 +1,60 @@
+window.Tracker = window.Tracker || {};
+
+Tracker.Event = function(){
+    return {
+        add: function( target, event, fn ){
+            if( typeof event == "object" ){
+                for(var name in event)
+                    this.add( target, name, event[ name ] );
+                return ;
+            }
+
+            var call = function(){
+                var args = [].slice.call( arguments ), e;
+
+                if( ( e = args[ 0 ] ) && typeof e == "object" ){
+                    e = e || event;
+                    e.target = e.target || e.srcElement;
+                    args[ 0 ] = e;
+                }
+
+                fn.apply( target, args );
+            };
+
+            if( target.addEventListener )
+                target.addEventListener( event, call, false );
+            else if( target.attachEvent )
+                target.attachEvent( "on" + event, call );
+        },
+
+        bind: function( object ){
+            var events;
+
+            object = object || {};
+            events = object.events = {};
+
+            object.on = function( name, fn ){
+                if( typeof name == "object" ){
+                    for(var n in name)
+                        this.on( n, name[ n ] );
+                    return ;
+                }
+
+                if( events[ name ] )
+                    events[ name ].push( fn );
+                else
+                    events[ name ] = [ fn ];
+            };
+
+            object.fire = object.f = function( name ){
+                var args = [].slice.call( arguments, 1 ), e;
+
+                if( e = events[ name ] )
+                    for( var i = 0, l = e.length; i < l; i ++ )
+                        e[ i ].apply( this, args );
+            };
+
+            return object;
+        }
+    }
+}();

+ 308 - 0
chrome/static/js/tracker/general.js

@@ -0,0 +1,308 @@
+/** 
+ * General Plugin
+ * @version 1.0
+ * @author dron
+ * @create 2013-04-21
+ */
+
+Tracker.setupGeneralPlugin = function(){
+    Tracker.Plugins.addOn( "general", function(){
+        var template, data, calculate, render, update, eventDelegate,
+            i, l, r, q, tmpl, now, window, document, mainBody, mainBodyId;
+
+        tmpl = Tracker.Util.tmpl;
+
+        now = function( time ){
+            time = new Date();
+            time = [ time.getFullYear(), time.getMonth() + 1, time.getDate() ].join( "-" ) + " " +
+                [ time.getHours(), time.getMinutes(), time.getSeconds() ].join( ":" );
+            time = time.replace( /\b(\d)\b/g, "0$1" );
+            return time;
+        };
+
+        Tracker.Plugins.addStyle( [
+            "#plugin-general-page .toolbar{ height: 42px; background-color: #fafafa; border-bottom: 1px solid #d5d5d5; }",
+            "#plugin-general-page .toolbar li{ display: block; float: left; }",
+            "#plugin-general-page .toolbar li.button-like{ padding: 5px 0 0 5px; }",
+            "#plugin-general-page .toolbar li.text-like{ padding: 12px 0 0 20px; line-height: 20px; }",
+            "#plugin-general-page .toolbar li.first{ padding-left: 30px; }",
+            "#plugin-general-page .body{ position: absolute; top: 43px; bottom: 0; left: 0; right: 0; padding: 10px 30px; }",
+            "#plugin-general-page .body .table{ margin-bottom: 30px; width: 900px; table-layout:fixed; }",
+            "#plugin-general-page .body .table td{  }",
+            "#plugin-general-page .body .table td .ellipsisable{ width: 100%; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }",
+            "#plugin-general-page .body .unit{ font-style: italic; font-weight: 400; }",
+            "#plugin-general-page .body .close-up{ position: relative; top: -4px; }",
+            "#plugin-general-page .body .low{ color: #ff814e; }",
+            "#plugin-general-page .body .high{ color: #2bb027; }",
+            "#plugin-general-page .body .close-up b{ font-family: Constantia, Georgia; font-size: 24px; font-weight: 400; position: absolute; }"
+        ].join( "" ) );
+
+        template = {
+            page: tmpl( [
+                "<ul class='toolbar unstyled clearfix'>",
+                    "<li class='first button-like'>",
+                        "<button class='btn' action='update'>&#21047;&#26032;</button>",
+                    "</li>",
+                    "<li class='text-like muted'>&#26368;&#21518;&#26356;&#26032;&#26102;&#38388;&#65306;<%= now() %></li>",
+                "</ul>",
+                "<div id='<%= mainBodyId %>' class='body scrollable'>",
+                    "<% if( count > 0 ){ %>",
+                        "<h5>&#24403;&#21069;&#32593;&#39029;&#20849;&#21253;&#21547; <%= count %> &#20010;&#33050;&#26412;&#36164;&#28304;&#65306;<%= embedCount %> &#20010;&#20869;&#23884;&#65292;<%= linkCount %> &#20010;&#22806;&#38142;&#25991;&#20214;&#65292;<%= appendCount %> &#20010;&#21160;&#24577;&#21152;&#36733;&#12290;</h5>",
+                        "<table class='table table-bordered table-striped'>",
+                            "<thead>",
+                                "<tr>",
+                                    "<th width='*'>&#20351;&#29992;&#37327; <span class='unit'>(%)</span></th>",
+                                    "<th width='90'>&#20887;&#20313;&#37327; <span class='unit'>(%)</span></th>",
+                                    "<th width='120'>&#20351;&#29992;&#34892;&#25968;</th>",
+                                    "<th width='90'>&#24635;&#34892;&#25968;</th>",
+                                    "<th width='90'>&#24635;&#22823;&#23567; <span class='unit'>(k)</span></th>",
+                                    "<th width='120'>&#24635;&#21152;&#36733;&#32791;&#26102; <span class='unit'>(ms)</span></th>",
+                                    "<th width='120'>&#24635;&#36816;&#34892;&#32791;&#26102; <span class='unit'>(ms)</span></th>",
+                                "</tr>",
+                            "</thead>",
+                            "<tbody>",
+                                "<tr>",
+                                    "<td><span class='close-up <%= usefulRate < 50 ? 'low' : 'high' %>'><b><%= usefulRate %><b></span></td>",
+                                    "<td><%= redundancyRate %></td>",
+                                    "<td><%= usefulLines %></td>",
+                                    "<td><%= totalLines %></td>",
+                                    "<td><%= totalSize %></td>",
+                                    "<td><%= totalLoadConsum %></td>",
+                                    "<td><%= totalRunConsum %></td>",
+                                "</tr>",
+                            "</tbody>",
+                        "</table>",
+
+                        "<h5>&#20887;&#20313;&#37327;&#26368;&#39640; top <%= showCodeCount %> &#33050;&#26412;&#65306;</h5>",
+                        "<table class='table table-bordered table-striped table-condensed'>",
+                            "<thead>",
+                                "<tr>",
+                                    "<th width='*'>&#25991;&#20214;&#21517;</th>",
+                                    "<th width='70'>&#31867;&#22411;</th>",
+                                    "<th width='90'>&#20887;&#20313;&#37327; <span class='unit'>(%)</span></th>",
+                                    "<th width='40'></th>",
+                                "</tr>",
+                            "</thead>",
+                            "<tbody>",
+                                "<% for( var i = 0, r; i < redundancyRateTableData.length; i ++ ){ %>",
+                                    "<% r = redundancyRateTableData[ i ]; %>",
+                                    "<tr>",
+                                        "<td><div class='ellipsisable'><%= r.fullUrl || '-' %></div></td>",
+                                        "<td>",
+                                            "<% switch( r.type ){ ",
+                                                "case 'embed':",
+                                                    "print( '&#20869;&#23884;' );",
+                                                    "break;",
+                                                "case 'link':",
+                                                    "print( '&#25991;&#20214;&#38142;&#25509;' );",
+                                                    "break;",
+                                                "case 'append':",
+                                                    "print( '&#21160;&#24577;&#25554;&#20837;' );",
+                                                    "break;",
+                                            "} %>",
+                                        "</td>",
+                                        "<td><%= Math.round( ( 1 - r.prop( 'usefulRate' ) ) * 1e4 ) / 1e2 %></td>",
+                                        "<td><button class='btn btn-mini' action='show-code' data-code-id='<%= r.id %>'>&#26597;&#30475;</button></td>",
+                                    "</tr>",
+                                "<% } %>",
+                            "</tbody>",
+                        "</table>",
+
+                        "<h5>&#20307;&#31215;&#26368;&#22823; top <%= showCodeCount %> &#33050;&#26412;&#65306;</h5>",
+                        "<table class='table table-bordered table-striped table-condensed'>",
+                            "<thead>",
+                                "<tr>",
+                                    "<th width='*'>&#25991;&#20214;&#21517;</th>",
+                                    "<th width='70'>&#31867;&#22411;</th>",
+                                    "<th width='90'>&#22823;&#23567; <span class='unit'>(k)</span></th>",
+                                    "<th width='40'></th>",
+                                "</tr>",
+                            "</thead>",
+                            "<tbody>",
+                                "<% for( var i = 0, r; i < sizeTableData.length; i ++ ){ %>",
+                                    "<% r = sizeTableData[ i ]; %>",
+                                    "<tr>",
+                                        "<td><div class='ellipsisable'><%= r.fullUrl || '-' %></div></td>",
+                                        "<td>",
+                                            "<% switch( r.type ){ ",
+                                                "case 'embed':",
+                                                    "print( '&#20869;&#23884;' );",
+                                                    "break;",
+                                                "case 'link':",
+                                                    "print( '&#25991;&#20214;&#38142;&#25509;' );",
+                                                    "break;",
+                                                "case 'append':",
+                                                    "print( '&#21160;&#24577;&#25554;&#20837;' );",
+                                                    "break;",
+                                            "} %>",
+                                        "</td>",
+                                        "<td><%= ( r.size / 1024 ).toFixed( 2 ) %></td>",
+                                        "<td><button class='btn btn-mini' action='show-code' data-code-id='<%= r.id %>'>&#26597;&#30475;</button></td>",
+                                    "</tr>",
+                                "<% } %>",
+                            "</tbody>",
+                        "</table>",
+
+                        "<h5>&#36816;&#26102;&#32791;&#26102;&#26368;&#39640; top <%= showCodeCount %> &#33050;&#26412;&#65306;</h5>",
+                        "<table class='table table-bordered table-striped table-condensed'>",
+                            "<thead>",
+                                "<tr>",
+                                    "<th width='*'>&#25991;&#20214;&#21517;</th>",
+                                    "<th width='70'>&#31867;&#22411;</th>",
+                                    "<th width='90'>&#32791;&#26102; <span class='unit'>(ms)</span></th>",
+                                    "<th width='40'></th>",
+                                "</tr>",
+                            "</thead>",
+                            "<tbody>",
+                                "<% for( var i = 0, r; i < runConsumTableData.length; i ++ ){ %>",
+                                    "<% r = runConsumTableData[ i ]; %>",
+                                    "<tr>",
+                                        "<td><div class='ellipsisable'><%= r.fullUrl || '-' %></div></td>",
+                                        "<td>",
+                                            "<% switch( r.type ){ ",
+                                                "case 'embed':",
+                                                    "print( '&#20869;&#23884;' );",
+                                                    "break;",
+                                                "case 'link':",
+                                                    "print( '&#25991;&#20214;&#38142;&#25509;' );",
+                                                    "break;",
+                                                "case 'append':",
+                                                    "print( '&#21160;&#24577;&#25554;&#20837;' );",
+                                                    "break;",
+                                            "} %>",
+                                        "</td>",
+                                        "<td><%= r.runConsum %></td>",
+                                        "<td><button class='btn btn-mini' action='show-code' data-code-id='<%= r.id %>'>&#26597;&#30475;</button></td>",
+                                    "</tr>",
+                                "<% } %>",
+                            "</tbody>",
+                        "</table>",
+                    "<% } else { %>",
+                        "&#35813;&#32593;&#39029;&#27809;&#26377;&#20219;&#20309; JS &#20195;&#30721;&#12290;",
+                    "<% } %>",
+                "</div>"
+            ].join( "" ) )
+        };
+
+        data = function(){};
+
+        data.prototype = {
+            count: 0,
+            embedCount: 0,
+            linkCount: 0,
+            appendCount: 0,
+
+            usefulRate: 0,
+            redundancyRate: 100,
+
+            usefulLines: 0,
+            totalLines: 0,
+
+            totalSize: 0,
+
+            totalLoadConsum: 0,
+            totalRunConsum: 0,
+
+            now: now
+        };
+
+        data = new data;
+
+        calculate = function(){
+            var redundancyRate = function( code1, code2 ){
+                return code1.prop( "usefulRate" ) - code2.prop( "usefulRate" );
+            };
+
+            var size = function( code1, code2 ){
+                return code2.size - code1.size;
+            };
+
+            var runConsum = function( code1, code2 ){
+                return code2.runConsum - code1.runConsum;
+            };
+
+            return function (){
+                for( i in data )
+                    delete data[ i ];
+                l = data.count = Tracker.CodeList.count();
+
+                data.showCodeCount = l > 2 ? 3 : l;
+
+                for( i = 0; i < l; i ++ ){
+                    r = Tracker.CodeList.get( i );
+
+                    if( r.type == "embed" ){
+                        data.embedCount ++;
+                    }else if( r.type == "link" ){
+                        data.linkCount ++;
+                    }else if( r.type == "append" ){
+                        data.appendCount ++;
+                    }
+
+                    data.usefulLines += r.arriveRowsCount;
+                    data.totalLines += r.rowsCount;
+                    data.totalSize += r.size;
+                    data.totalLoadConsum += r.loadConsum;
+                    data.totalRunConsum += r.runConsum;
+
+                    r.prop( "usefulRate", r.arriveRowsCount / r.rowsCount );
+                }
+
+                r = data.usefulLines / data.totalLines;
+
+                data.usefulRate = Math.round( ( r ) * 1e4 ) / 1e2;
+                data.redundancyRate = Math.round( ( 1 - r ) * 1e4 ) / 1e2;
+                data.totalSize = ( data.totalSize / 1000 ).toFixed( 2 );
+
+                q = Tracker.CodeList.list().slice( 0 );
+                q.sort( redundancyRate );
+                data.redundancyRateTableData = q.slice( 0, data.showCodeCount );
+
+                q = Tracker.CodeList.list().slice( 0 );
+                q.sort( size );
+                data.sizeTableData = q.slice( 0, data.showCodeCount );
+
+                q = Tracker.CodeList.list().slice( 0 );
+                q.sort( runConsum );
+                data.runConsumTableData = q.slice( 0, data.showCodeCount );
+
+                mainBodyId = data.mainBodyId = Tracker.Util.id();
+            }
+        }();
+
+        render = Tracker.Util.bind( function( text ){
+            this.body.innerHTML = text || template.page( data );
+        }, this );
+
+        update = function(){
+            calculate();
+            render();
+        };
+
+        eventDelegate = function( el ){
+            Tracker.Event.add( el, "click", function( e ){
+                var target, action, codeId;
+
+                target = e.target;
+                action = target.getAttribute( "action" );
+                codeId = target.getAttribute( "data-code-id" );
+
+                if( action == "show-code" ){
+                    Tracker.View.ControlPanel.activeTab( "code-list" );
+                    Tracker.View.ControlPanel.showCodeDetail( codeId );
+                }else if( action == "update" ){
+                    document.getElementById( mainBodyId ).innerHTML = "loading...";
+                    window.setTimeout( update, 1e2 );
+                }
+            } );
+        };
+
+        this.onStartUp( function( win, doc ){
+            window = win;
+            document = doc;
+            eventDelegate( this.body );
+        } );
+
+        this.onActive( update );
+    } );
+};

+ 106 - 0
chrome/static/js/tracker/inject.js

@@ -0,0 +1,106 @@
+window.Tracker = window.Tracker || {};
+/**
+ * 此脚本是用于注入被检测的iframe中的,因为page-script无法与content-script交互,所以可以用一个button来作为代理
+ * @param window
+ * @param document
+ * @constructor
+ * @author  xianliezhao
+ */
+Tracker.Inject = function (window, document) {
+
+    var getProxyEl = function () {
+        return top.document.getElementById('btnTrackerProxy');
+    };
+
+    /**
+     * 队列管理器
+     * 钩子“tracker”会执行的非常频繁,如果每次执行都去trigger proxy click,性能会极其低下,所以需要用一个队列来批量执行
+     */
+    var QueueMgr = (function () {
+        var _queue = [];
+        var _limit = 500;
+        var _lastPopTime = 0;
+
+        // 检测队列是否已满
+        var full = function () {
+            return _queue.length >= _limit;
+        };
+
+        // 入队列
+        var push = function (item) {
+            _queue.push(item);
+        };
+
+        // 全部出队列
+        var popAll = function () {
+            var result = _queue.join(',');
+            _queue = [];
+            _lastPopTime = new Date().getTime();
+            return result;
+        };
+
+        // 判断距离上一次出队列是否已经大于200ms
+        var timesUp = function () {
+            return (new Date().getTime() - _lastPopTime) >= 100;
+        };
+
+        return {
+            full: full,
+            timesUp: timesUp,
+            push: push,
+            pop: popAll
+        };
+    })();
+
+
+    window.__tracker__ = function (groupId) {
+        // 先入队列,不丢下任何一条消息
+        QueueMgr.push(groupId);
+
+        // 队列已满 or 等待时间到了
+        if (QueueMgr.full() || QueueMgr.timesUp()) {
+            var allGroupIds = QueueMgr.pop();
+            var proxy = getProxyEl();
+            proxy.setAttribute('data-type', '__tracker__');
+            proxy.setAttribute('data-groupId', allGroupIds);
+            proxy.click();
+        }
+    };
+
+    window.__trackerError__ = function (codeId, msg) {
+        var proxy = getProxyEl();
+        proxy.setAttribute('data-type', '__trackerError__');
+        proxy.setAttribute('data-codeId', codeId);
+        proxy.setAttribute('data-msg', msg);
+        proxy.click();
+    };
+
+    window.__trackerMockTop__ = function () {
+        var proxy = getProxyEl();
+        proxy.setAttribute('data-type', '__trackerMockTop__');
+        proxy.click();
+    };
+
+    window.__trackerScriptStart__ = function (codeId, scriptTagIndex) {
+        var proxy = getProxyEl();
+        proxy.setAttribute('data-type', '__trackerScriptStart__');
+        proxy.setAttribute('data-codeId', codeId);
+        proxy.setAttribute('data-scriptTagIndex', scriptTagIndex);
+        proxy.click();
+    };
+
+    window.__trackerScriptEnd__ = function (codeId) {
+        var proxy = getProxyEl();
+        proxy.setAttribute('data-type', '__trackerScriptEnd__');
+        proxy.setAttribute('data-codeId', codeId);
+        proxy.click();
+    };
+
+    window.onbeforeunload = function () {
+        var proxy = getProxyEl();
+        proxy.setAttribute('data-type', 'onbeforeunload');
+        proxy.click();
+    }();
+
+};
+Tracker.Inject(window, document);

+ 365 - 0
chrome/static/js/tracker/main.js

@@ -0,0 +1,365 @@
+void function () {
+    var currentCodeId, codes, pageComments, host;
+
+    var restorePageEnvironments = function () {
+
+        var i, lastTimerId, tempIframe, fixList, tempArray, sourceArray,
+            tempDocument;
+
+        lastTimerId = setTimeout("1", 0);
+
+        for (i = lastTimerId, down = Math.max(0, lastTimerId - 200);
+             i >= down; i--)
+            clearTimeout(i),
+                clearInterval(i);
+
+        // NOTE: 恢复可能被目标页面破坏掉的几个主要的 Array 方法
+        tempIframe = host.createElement("iframe");
+        tempIframe.style.cssText = "width: 1px; height: 1px; top: -1000px; position: absolute;";
+        host.body.appendChild(tempIframe);
+        fixList = ( "push, pop, slice, splice, shift, unshift, concat, join, reverse" ).split(", ");
+        tempArray = tempIframe.contentWindow.Array.prototype;
+        sourceArray = Array.prototype;
+
+        for (i = 0, l = fixList.length; i < l; i++)
+            sourceArray[fixList[i]] = tempArray[fixList[i]];
+
+        fixList = ["open", "write", "close"];
+        tempDocument = tempIframe.contentDocument;
+
+        for (i = 0, l = fixList.length; i < l; i++)
+            host[fixList[i]] = function (fn, host) {
+                return function () {
+                    return fn.apply(host, arguments);
+                }
+            }(tempDocument[fixList[i]], host);
+
+        host.body.removeChild(tempIframe);
+        tempIframe = null;
+        tempDocument = null;
+    };
+
+    var AsynStringReplacer = function () {
+        var restoreRegx = /\({3}AsynStringReplacer:(\d+)\){3}/g;
+
+        return {
+            replace: function (origContent, regx, pmReplaceFn) {
+                var cache, tasks = [], content = origContent, index = 0,
+                    pm = new Tracker.Promise();
+
+                content = content.replace(regx, function () {
+                    tasks.push(pmReplaceFn.apply(null, arguments));
+                    return "(((AsynStringReplacer:" + ( index++ ) + ")))";
+                });
+
+                Tracker.Promise.when(tasks).then(function () {
+                    cache = [].slice.call(arguments, 0);
+                    content = content.replace(restoreRegx, function (s, index) {
+                        return cache[index - 0];
+                    });
+                    pm.resolve(content);
+                });
+
+                return pm;
+            }
+        };
+    }();
+
+    var initControllerOnLoad = function () {
+        Tracker.controllerOnLoad = Tracker.Promise.fuze();
+        Tracker.controllerOnLoad(function (window, document) {
+            var waitTime, loadingEl;
+
+            window.onbeforeunload = function () {
+                var startTime = Tracker.Util.time();
+                return function () {
+                    if (Tracker.Util.time() - startTime < 3e3)
+                        setTimeout(function () {
+                            var h = window.location.hash;
+                            window.location.href = ~h.indexOf("#") ? h : "#" + h;
+                        }, 0);
+                }
+            }();
+
+            waitTime = document.getElementById("waitTime");
+            loadingEl = document.getElementById("loading");
+            loadingEl.style.display = "block";
+
+            Tracker.Util.onCpuFree(function () {
+                waitTime.innerHTML = "(100.000000 %)";
+                loadingEl.style.height = "0";
+                Tracker.View.ControlPanel.activeTab("code-list");
+                setTimeout(function () {
+                    loadingEl.parentNode.removeChild(loadingEl);
+                }, 5e2);
+            }, function (t) {
+                waitTime.innerHTML = "(" + ( 1e2 - 1e2 / Math.max(t, 1e2) ).toFixed(6) + "%)";
+            });
+
+            Tracker.TrackerGlobalEvent.on("bootstrap: dropdown.open", function () {
+                if (Tracker.View.ControlPanel.activeTab() == 0)
+                    Tracker.View.ControlPanel.setControlPower(true);
+            });
+
+            Tracker.TrackerGlobalEvent.on("bootstrap: dropdown.close", function () {
+                if (Tracker.View.ControlPanel.activeTab() == 0)
+                    Tracker.View.ControlPanel.setControlPower(false);
+            });
+
+            Tracker.TrackerGlobalEvent.on("bootstrap: dialog.open", function () {
+                setTimeout(function () { // trigger by dropdown
+                    if (Tracker.View.ControlPanel.activeTab() == 0)
+                        Tracker.View.ControlPanel.setControlPower(true);
+                }, 1);
+            });
+
+            Tracker.TrackerGlobalEvent.on("bootstrap: dialog.close", function () {
+                if (Tracker.View.ControlPanel.activeTab() == 0)
+                    Tracker.View.ControlPanel.setControlPower(false);
+            });
+
+            setTimeout(function () {
+                Tracker.Plugins.prepare({
+                    name: "general",
+                    type: "TopPanel",
+                    label: "综合结果",
+                    bodyId: "plugin-general-page"
+                });
+                Tracker.setupGeneralPlugin();
+
+                Tracker.Plugins.prepare({
+                    name: "watch",
+                    type: "TopPanel",
+                    label: "活动监视器",
+                    bodyId: "plugin-watch-page"
+                });
+                Tracker.setupWatchPlugin();
+            }, 5e2);
+        });
+
+        Tracker.controllerOnLoad(Tracker.Util.bind(Tracker.View.ControlFrame.show, Tracker.View.ControlFrame));
+    };
+
+    var initPageBuilder = function () {
+
+        Tracker.View.ControlFrame.pageBuilder(function (html) {
+            var pm = new Tracker.Promise(),
+                charset = document.characterSet,
+                allScriptTagRegx = /(<script\b[^>]*>)([\s\S]*?)(<\/script>)/gi,
+                srcPropertyRegx = / src=["']([^"']+)["']/,
+                typePropertyRegx = / type=["']([^"']+)["']/,
+                scriptRegx = /(<script\b [^>]*src=["']([^"']+)["'][^>]*>)\s*(<\/script>)/gi,
+                firstTagRegx = /(<[^>]+>)/,
+                lineBreakRegx = /\r\n|[\r\n]/g,
+                a, b, i, l, r;
+            var proxyScript = '<script type="text/javascript" src="' + chrome.runtime.getURL('/static/js/tracker/inject.js') + '"></script>';
+
+            Tracker.Util.request(location.href, charset).then(function (html) {
+                html = html.response;
+
+                for (i = 0, l = pageComments.length; i < l; i++) {
+                    r = "<!--" + pageComments[i] + "-->";
+                    a = r.replace(lineBreakRegx, "\r\n");
+                    b = r.replace(lineBreakRegx, "\n");
+                    html = html.replace(a, "").replace(b, "");
+                }
+
+                AsynStringReplacer.replace(html, allScriptTagRegx,
+                    function (raw, openTag, content, closeTag) {
+                        var pm, code;
+
+                        pm = new Tracker.Promise();
+
+                        if (srcPropertyRegx.test(openTag)) { // is outer script
+                            Tracker.View.Loading.addCount();
+                            pm.resolve(raw);
+                            return pm;
+                        }
+
+                        if (typePropertyRegx.test(openTag) &&
+                            RegExp.$1.toLowerCase() != "text/javascript") { // is not javascript
+                            // TODO: 对于非 text/javascript,将来也放到 code list 中,以便于查询
+                            pm.resolve(raw);
+                            return pm;
+                        }
+
+                        // embed script
+                        Tracker.View.Loading.addCount();
+
+                        code = new Tracker.Code(null, content);
+                        code.loadConsum = 0;
+                        code.setType("embed");
+                        Tracker.CodeList.add(code);
+
+                        code.onReady(function () {
+                            pm.resolve(openTag + code.executiveCode + closeTag);
+                            Tracker.View.Loading.addProgress();
+                        });
+
+                        return pm;
+                    }
+                ).then(function (html) {
+                        AsynStringReplacer.replace(html, scriptRegx,
+                            function (raw, openTag, url, closeTag) {
+                                var pm, content;
+
+                                pm = new Tracker.Promise();
+                                openTag = openTag.replace(srcPropertyRegx, " tracker-src='$1'");
+
+                                Tracker.Util.intelligentGet(url).then(function (data) {
+                                    var code;
+
+                                    content = data.response;
+                                    code = new Tracker.Code(url, content);
+                                    code.loadConsum = data.consum;
+                                    code.setType("link");
+                                    Tracker.CodeList.add(code);
+
+                                    code.onReady(function () {
+                                        Tracker.View.Loading.addProgress();
+                                        pm.resolve(openTag + code.executiveCode + closeTag);
+                                    });
+                                }, function () {
+                                    var code;
+
+                                    code = new Tracker.Code(url);
+                                    code.setState("timeout");
+                                    Tracker.CodeList.add(code);
+                                    Tracker.View.Loading.addProgress();
+                                    pm.resolve(raw);
+                                });
+
+                                return pm;
+                            }
+                        ).then(function (html) {
+                                Tracker.View.Loading.hide();
+                                Tracker.Util.delay(function () {
+                                    Tracker.CodeList.sort();
+                                    if (!~html.indexOf("<html"))
+                                        html = "<html>" + html;
+                                    pm.resolve(html.replace(firstTagRegx, "$1" + proxyScript));
+                                });
+                            });
+                    });
+            }, function () {
+                var message, refresh;
+
+                message = "处理超时"; // 处理超时
+                refresh = function () {
+                    location.assign(location.href);
+                };
+
+                Tracker.View.Loading.text(message).then(refresh);
+            });
+
+            return pm;
+        });
+    };
+
+    var initControlFrame = function () {
+
+        Tracker.View.ControlFrame.controllerBuilder(Tracker.View.ControlPanel.htmlBuilder);
+        Tracker.View.ControlFrame.on({
+            "controllerLoad": function (window, document) {
+                Tracker.View.ControlPanel.bindWindow(window);
+                Tracker.View.ControlPanel.addCode(codes = Tracker.CodeList.list());
+                Tracker.View.ControlPanel.eventBuilder();
+                Tracker.controllerOnLoad.fire(window, document);
+
+                if (currentCodeId)
+                    Tracker.View.ControlPanel.showCodeDetail(currentCodeId);
+
+                document.getElementById("top-nav").tabEvent.on("active", function (index, name) {
+                    var b;
+
+                    Tracker.View.ControlPanel.setControlPower(b = index > 0, b);
+
+                    if (name == "code-list")
+                        Tracker.View.ControlPanel.showCodeDetail(false);
+                });
+            },
+
+            "hide": function () {
+                Tracker.View.ControlPanel.autoUpdateCodeStop();
+            },
+
+            "show": function () {
+                Tracker.View.ControlPanel.autoUpdateCodeStart();
+            },
+
+            pageLoad: function (topWin, pageWin) {
+                Tracker.Decorate(topWin, pageWin);
+                topWin.document.getElementById('btnTrackerProxy').addEventListener('click', function (e) {
+                    var type = this.getAttribute('data-type');
+                    switch (type) {
+                        case '__tracker__':
+                            var groupId = this.getAttribute('data-groupId');
+                            topWin[type](groupId);
+                            break;
+                        case '__trackerError__':
+                            var codeId = this.getAttribute('data-codeId');
+                            var msg = this.getAttribute('data-msg');
+                            topWin[type](codeId, msg);
+                            break;
+                        case '__trackerMockTop__':
+                            topWin[type]();
+                            break;
+                        case '__trackerScriptStart__':
+                            var codeId = this.getAttribute('data-codeId');
+                            var scriptTagIndex = this.getAttribute('data-scriptTagIndex');
+                            topWin[type](codeId, scriptTagIndex);
+                            break;
+                        case '__trackerScriptEnd__':
+                            var codeId = this.getAttribute('data-codeId');
+                            topWin[type](codeId);
+                            break;
+                        case 'onbeforeunload':
+                            topWin[type]();
+                            break;
+                    }
+                }, false);
+            }
+        });
+
+        Tracker.View.ControlPanel.actions(function (be) {
+            be = function (action) {
+                return function () {
+                    if (Tracker.View.ControlFrame.getMode() == "embed" &&
+                        Tracker.View.ControlPanel.activeTab() > 0)
+                        Tracker.View.ControlPanel.activeTab(0);
+                    Tracker.View.ControlFrame[action]();
+                };
+            };
+
+            return {
+                "frame#close": be("hide"),
+                "frame#toggle": be("toggleMode")
+            }
+        }());
+    };
+
+    /**
+     * 启动js tracker
+     */
+    var startTrack = function () {
+
+        initControllerOnLoad();
+        host = window.document;
+        pageComments = Tracker.Util.getHtmlComments(host.documentElement);
+        Tracker.TrackerGlobalEvent = Tracker.Event.bind();
+        initPageBuilder();
+        initControlFrame();
+
+        restorePageEnvironments();
+        Tracker.View.Loading.show();
+        Tracker.View.ControlFrame.createEmbed();
+    };
+
+    // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
+    chrome.runtime.onMessage.addListener(function (request, sender, callback) {
+        if (request.type == MSG_TYPE.JS_TRACKER) {
+            startTrack();
+        }
+    });
+
+}();

+ 53 - 0
chrome/static/js/tracker/path.js

@@ -0,0 +1,53 @@
+window.Tracker = window.Tracker || {};
+
+Tracker.Path = function(){
+    var protocolRegx, absoluteRegx, rootRegx, doubleDotRegx, singleDotRegx;
+
+    protocolRegx = /^\w+:\/\//;
+    absoluteRegx = /^\//;
+    rootRegx = /^(\w*:?\/?\/?)([\w.]+)(\/)/;
+    doubleDotRegx = /\/[^\/\.]+\/\.\.\//;
+    singleDotRegx = /\/\.\//;
+
+    return {
+        getBase: function( document ){
+            var base, url;
+
+            base = document.querySelector( "base[href]" );
+
+            if( base )
+                url = base.href;
+            else
+                url = document.URL;
+
+            url = url.split( /[?#]/ )[ 0 ];
+
+            return url.slice( 0, url.lastIndexOf( "/" ) + 1 );
+        },
+
+        merge: function( base, url ){
+            if( url.indexOf( "//" ) === 0 )
+                return pageBaseProtocol + ":" + url;
+
+            if( protocolRegx.test( url ) )
+                return url;
+
+            if( absoluteRegx.test( url ) ){
+                if( rootRegx.test( base ) )
+                    url = RegExp.$1 + RegExp.$2 + url;
+                else
+                    return url;
+            }else{
+                url = base + url;
+            }
+
+            while( doubleDotRegx.test( url ) )
+                url = url.replace( doubleDotRegx, "/" );
+
+            while( singleDotRegx.test( url ) )
+                url = url.replace( singleDotRegx, "/" );
+
+            return url;
+        }
+    }
+}();

+ 151 - 0
chrome/static/js/tracker/plugin.js

@@ -0,0 +1,151 @@
+window.Tracker = window.Tracker || {};
+Tracker.Plugins = function(){
+    var klass, instCache;
+
+    klass = function( name ){
+        instCache[ name ] = this;
+        this.activeFuze = Tracker.Promise.fuze();
+        this.startUpFuze = Tracker.Promise.fuze();
+    };
+
+    klass.prototype = Tracker.Event.bind( {
+        onStartUp: function( fn ){
+            // this.startUpFuze( Tracker.Util.bind( fn, this ) );
+            Tracker.Plugins.onControllerLoad( Tracker.Util.bind( fn, this ) );
+        },
+
+        onActive: function( fn ){
+            if( this.activeFuze.fired )
+                this.activeFuze( fn );
+            this.on( "active", fn );
+        }
+    } );
+
+    instCache = {};
+
+    return {
+        // prepare for a plugin, use in this script.
+        prepare: function( config ){
+            var name, type, define, inst, label, bodyId, setuped, started;
+
+            name = config.name;
+            type = config.type;
+            define = config.define; // plugin define source
+            inst = new klass( name );
+
+            if( type == "TopPanel" ){
+                label = config.label;
+                bodyId = config.bodyId;
+
+                this.addPanel( name, label, function(){
+                    this.innerHTML = "<div id='" + bodyId + "'></div>";
+                    inst.body = this.firstChild;
+                }, Tracker.Util.bind( function(){
+                    if( !setuped )
+                        setuped = true,
+                            define && this.setup( define );
+
+                    // if( !started )
+                    //     started = true,
+                    //     inst.startUpFuze.fire();
+
+                    if( !inst.actived )
+                        inst.activeFuze.fire(),
+                            inst.fire( "active" );
+                }, this ) );
+            }
+        },
+
+        // define a plugin, use in a separate script.
+        addOn: function( name, fn ){
+            var inst, window, document;
+
+            window = Tracker.View.ControlFrame.getWindow( "tracker_controller" );
+            document = window.document;
+
+            if( inst = instCache[ name ] )
+                fn.call( inst, window, document );
+            else
+                throw new Error( "illegal plugin name." );
+        },
+
+        // for appending a style tag to 'ControlPanel' view.
+        addStyle: function( text ){
+            this.onControllerLoad( function( window, document ){
+                var style;
+
+                style = document.createElement( "style" );
+                style.type = "text/css";
+                style.appendChild( document.createTextNode( text ) );
+
+                document.head.appendChild( style );
+            } );
+        },
+
+        // for appending a top bar item to 'ControlPanel' view.
+        addPanel: function( name, title, panelDefine, activeHandler ){
+
+            if( typeof title == "function" ){ // missing argument 'name'
+                activeHandler = panelDefine;
+                panelDefine = title;
+                title = name;
+                name = null;
+            }
+
+            this.onControllerLoad( function( window, document ){
+                var topNav, panels, titleEl, panelEl, meIndex;
+
+                topNav = document.getElementById( "top-nav" );
+                panels = document.getElementById( topNav.getAttribute( "data-target" ) );
+                titleEl = document.createElement( "li" );
+                titleEl.className = "relative";
+
+                if( name )
+                    titleEl.setAttribute( "data-name", name );
+
+                titleEl.innerHTML = "<a href='' onclick='return false'>" + title + "</a>";
+                topNav.appendChild( titleEl );
+
+                panelEl = document.createElement( "li" );
+                panels.appendChild( panelEl );
+
+                meIndex = topNav.childNodes.length - 1;
+
+                if( activeHandler )
+                    topNav.tabEvent.on( "active", function( index ){
+                        if( index === meIndex )
+                            activeHandler();
+                    } );
+
+                panelDefine.call( panelEl, window, document );
+            } );
+        },
+
+        // for setup a plugin.
+        setup: function( pluginSrc ){
+            var script;
+
+            script = document.createElement( "script" );
+            script.charset = "utf-8";
+            script.type = "text/javascript";
+            script.src = pluginSrc;
+
+            document.head.appendChild( script );
+        },
+
+        // ::: privates :::
+        onControllerLoad: function( fn ){
+            var f = function(){
+                var window, document;
+                window = Tracker.View.ControlFrame.getWindow( "tracker_controller" );
+                document = window.document;
+                fn.call( this, window, document );
+            };
+
+            if( Tracker.controllerOnLoad.fired )
+                Tracker.controllerOnLoad( f );
+
+            Tracker.View.ControlFrame.on( "controllerLoad", f );
+        }
+    }
+}();

+ 133 - 0
chrome/static/js/tracker/promise.js

@@ -0,0 +1,133 @@
+window.Tracker = window.Tracker || {};
+/**
+ * Tracker.Promise
+ */
+Tracker.Promise = function () {
+    var concat = [].concat;
+
+    var promise = function () {
+        var list;
+
+        list = this.list = arguments.length ?
+            concat.apply([], arguments[0]) : null;
+        this.resolves = [];
+        this.rejects = [];
+        this.resolveValues = [];
+        this.rejectValues = [];
+        this.parents = [];
+        this.state = "pending";
+        this.fired = false;
+
+        if (list)
+            for (var i = 0, l = list.length; i < l; i++)
+                list[i].parents.push(this);
+    };
+
+    promise.prototype = {
+        resolve: function (arg) {
+            if (this.state == "pending")
+                this.state = "resolved",
+                    this.resolveValues = concat.apply([], arguments)
+            this.fire();
+        },
+
+        reject: function (arg) {
+            if (this.state == "pending")
+                this.state = "rejected",
+                    this.rejectValues = concat.apply([], arguments)
+            this.fire();
+        },
+
+        then: function (resolved, rejected) {
+            if (resolved)
+                this.resolves.push(resolved);
+
+            if (rejected)
+                this.rejects.push(rejected);
+
+            if (this.fired)
+                switch (this.state) {
+                    case "resolved":
+                        resolved &&
+                        resolved.apply(null, this.resolveValues);
+                        break;
+                    case "rejected":
+                        rejected &&
+                        rejected.apply(null, this.rejectValues);
+                }
+            else
+                this.fire();
+
+            return this;
+        },
+
+        fire: function () {
+            var callbacks, values, list = this.list, allResolved = true,
+                allResolveValues, parents;
+
+            if (this.fired)
+                return;
+
+            if (list && this.state == "pending") {
+                allResolveValues = [];
+
+                for (var i = 0, l = list.length; i < l; i++) {
+                    switch (list[i].state) {
+                        case "pending":
+                            allResolved = false;
+                            break;
+                        case "resolved":
+                            allResolveValues[i] =
+                                list[i].resolveValues[0];
+                            break;
+                        case "rejected":
+                            return this.reject(list[i].rejectValues[0]);
+                    }
+                }
+                if (allResolved)
+                    return this.resolve(allResolveValues);
+            }
+
+            if (this.state == "pending")
+                return;
+
+            if (this.state == "resolved")
+                callbacks = this.resolves,
+                    values = this.resolveValues;
+            else if (this.state == "rejected")
+                callbacks = this.rejects,
+                    values = this.rejectValues;
+
+            for (var i = 0, l = callbacks.length; i < l; i++)
+                callbacks[i].apply(null, values);
+
+            this.fired = true;
+
+            parents = this.parents;
+            for (var i = 0, l = parents.length; i < l; i++)
+                parents[i].fire();
+        }
+    };
+
+    promise.when = function () {
+        return new promise(arguments);
+    };
+
+    promise.fuze = function () {
+        var queue = [], fn, infire, args;
+
+        fn = function (process) {
+            infire ? process() : queue.push(process);
+        };
+
+        fn.fire = function () {
+            while (queue.length)
+                queue.shift().apply(null, arguments);
+            fn.fired = infire = true;
+        };
+
+        return fn;
+    };
+
+    return promise;
+}();

+ 64 - 0
chrome/static/js/tracker/status-pool.js

@@ -0,0 +1,64 @@
+window.Tracker = window.Tracker || {};
+
+Tracker.StatusPool = function(){
+    var arrivedSnippetGroupCache, snippetToGroupIdCache, snippetGroupCoverLineCache,
+        snippetGroupToCodeCache, code, i, id, l, t, groupId, currentArrivedSnippetGroupFlag;
+
+    arrivedSnippetGroupCache = {}; // 已到达的碎片组(所有代码)
+    snippetToGroupIdCache = {}; // 碎片到碎片组的映射(所有代码)
+    snippetGroupCoverLineCache = {}; // 碎片组覆盖代码行数(所有代码)
+    snippetGroupToCodeCache = {}; // 碎片组到代码的映射
+    currentArrivedSnippetGroupFlag = 1; // 当前已到达的代码碎片组标识    0:未到达  1:已到达  2,4,8.. 其他扩展
+
+    return {
+        snippetGroupCreate: function( code, snippetIds ){
+            groupId = Tracker.Util.nid();
+            for( i = 0, l = snippetIds.length; i < l; i ++ )
+                snippetToGroupIdCache[ snippetIds[ i ] ] = groupId;
+            snippetGroupToCodeCache[ groupId ] = code;
+            return groupId;
+        },
+
+        arrivedSnippetGroupFlagSet: function( flagNum ){
+            currentArrivedSnippetGroupFlag = flagNum;
+        },
+
+        arrivedSnippetGroupPut: function( groupId ){
+            t = arrivedSnippetGroupCache[ groupId ];
+
+            if( !( t & currentArrivedSnippetGroupFlag ) ){
+                arrivedSnippetGroupCache[ groupId ] = ( t + currentArrivedSnippetGroupFlag ) ||
+                currentArrivedSnippetGroupFlag;
+
+                if( snippetGroupCoverLineCache[ groupId ] ){
+                    code = snippetGroupToCodeCache[ groupId ];
+                    code.arriveRowsCount += snippetGroupCoverLineCache[ groupId ];
+                    code.lastModified = Tracker.Util.time();
+                    snippetGroupCoverLineCache[ groupId ] = 0;
+                }
+            }
+        },
+
+        arrivedSnippetGroupDelete: function( groupId, flagNum ){
+            t = arrivedSnippetGroupCache[ groupId ];
+            if( t == flagNum )
+                arrivedSnippetGroupCache[ groupId ] = 1;
+            else if( t & flagNum )
+                arrivedSnippetGroupCache[ groupId ] -= flagNum;
+        },
+
+        arrivedSnippetGet: function( snippetId ){
+            return arrivedSnippetGroupCache[ snippetToGroupIdCache[ snippetId ] ];
+        },
+
+        snippetGroupToCodeGet: function( groupId ){
+            return snippetGroupToCodeCache[ groupId ];
+        },
+
+        snippetGroupCoverLineAdd: function( snippetId ){
+            groupId = snippetToGroupIdCache[ snippetId ];
+            snippetGroupCoverLineCache[ groupId ] =
+                ++ snippetGroupCoverLineCache[ groupId ] || 1;
+        }
+    }
+}();

File diff suppressed because it is too large
+ 178 - 0
chrome/static/js/tracker/token.js


+ 33 - 0
chrome/static/js/tracker/tracker.js

@@ -0,0 +1,33 @@
+/**
+ * Tracker
+ */
+window.Tracker = function (host) {
+    var cmd = function (cmd) {
+        var n = arguments[1];
+        switch (cmd) {
+            case "code":
+                return typeof n != "undefined" ?
+                    Tracker.CodeList.get(n) : Tracker.CodeList.list();
+            default:
+                return "no such command";
+        }
+    };
+
+    var page = function (fn) {
+        var win, doc;
+
+        win = Tracker.View.ControlFrame.getWindow("tracker_page");
+        doc = win.document;
+
+        return fn(win, doc);
+    };
+
+    cmd.toString = page.toString = function () {
+        return "undefined";
+    };
+
+    host.cmd = cmd;
+    host.page = page;
+
+    return host;
+}(window.document);

+ 489 - 0
chrome/static/js/tracker/util.js

@@ -0,0 +1,489 @@
+window.Tracker = window.Tracker || {};
+Tracker.Util = function(){
+    var fileNameSplitWord = /[?#]/;
+
+    var excapeRegx = function(){
+        var specials, regx;
+
+        specials = [ "/", ".", "*", "+", "?", "|", "$", "^", "(", ")", "[", "]", "{",
+            "}", "\\" ];
+        regx = new RegExp( "(\\" + specials.join("|\\") + ")", "g" );
+
+        return function( text ){
+            return text.replace( regx, "\\$1" );
+        };
+    }();
+
+    var RemoteProxy = function(){
+        var callbacks, esc, service, timeout;
+
+        callbacks = {};
+        timeout = 10e3;
+
+        document.remoteProxyCallback = function( data ){
+            var c;
+            if( c = callbacks[ data.url ] )
+                c( data );
+        };
+
+        esc = function(){
+            var regx, rep;
+
+            regx = /[\\\/ \+%&=#\?]/g;
+            rep = function( s ){
+                return escape( s );
+            };
+
+            return function( url ){
+                return url.replace( regx, rep );
+            };
+        }();
+
+        service = "http://www.ucren.com/tracker/proxy.php";
+
+        var getProxyUrl = function( url ){
+            url = Tracker.Util.param( service, "url", esc( url ) );
+            url = Tracker.Util.param( url, "callback", "remoteProxyCallback" );
+            return url;
+        };
+
+        return {
+            get: function( url, charset ){
+                var pm, timer, script;
+
+                pm = new Tracker.Promise();
+
+                script = Tracker.Util.makeElement( document, "script" );
+                script.src = getProxyUrl( url );
+                script.charset = charset || "utf-8";
+                document.head.appendChild( script );
+
+                callbacks[ url ] = function( data ){
+                    clearTimeout( timer );
+                    pm.resolve( {
+                        response: data.content,
+                        consum: data.consum
+                    } );
+                    script = null;
+                    delete callbacks[ url ];
+                };
+
+                timer = setTimeout( function(){
+                    script.parentNode.removeChild( script );
+                    pm.reject();
+                    script = null;
+                }, timeout );
+
+                return pm;
+            },
+
+            request : function(url) {
+                var pm = new Tracker.Promise();
+                var timeStart = Tracker.Util.time();
+
+                var timer = setTimeout( function(){
+                    pm.reject();
+                }, timeout );
+
+                //向background发送一个消息,要求其加载并处理js文件内容
+                chrome.extension.sendMessage({
+                    type : MSG_TYPE.GET_JS,
+                    link : url
+                },function(respData){
+                    clearTimeout( timer );
+                    pm.resolve( {
+                        response: respData.content,
+                        consum: Tracker.Util.time() - timeStart
+                    } );
+                });
+
+                return pm;
+            }
+        };
+    }();
+
+    return {
+        blank: function(){
+        },
+
+        bind: function( fn, scope ){
+            return function(){
+                return fn.apply( scope, arguments );
+            };
+        },
+
+        forEach: function( unknow, iterator ){
+            var i, l;
+
+            if( unknow instanceof Array ||
+                ( unknow && typeof unknow.length == "number" ) )
+                for( i = 0, l = unknow.length; i < l; i ++ )
+                    iterator( unknow[ i ], i, unknow );
+            else if( typeof unknow === "string" )
+                for( i = 0, l = unknow.length; i < l; i ++ )
+                    iterator( unknow.charAt( i ), i, unknow );
+        },
+
+        // map: function( array, fn ){
+        //     for( var i = 0, l = array.length; i < l; i ++ )
+        //         array[ i ] = fn( array[ i ], i, array );
+        // },
+
+        id: function(){
+            return "_" + this.nid();
+        },
+
+        nid: function( id ){
+            return function(){
+                return id ++;
+            }
+        }( 1 ),
+
+        handle: function(){
+            var cache, number;
+
+            cache = [];
+            number = -1;
+
+            return function( unknown ){
+                var type;
+
+                type = typeof unknown;
+
+                if( type == "number" )
+                    return cache[ unknown ];
+                else if( type == "object" || type == "function" ){
+                    cache[ ++ number ] = unknown;
+                    return number;
+                }
+            }
+        }(),
+
+        trim: function(){
+            var regx = /^\s+|\s+$/g, rep = "";
+            return function( string ){
+                return string.replace( regx, rep );
+            }
+        }(),
+
+        random: function(){
+            return ( Math.random() * 1e6 ) | 0;
+        },
+
+        getByteLength: function( string ){
+            return string.replace( /[^\x00-\xff]/g, "  " ).length;
+        },
+
+        makeElement: function( doc, tagName, cssText ){
+            var el;
+
+            if( typeof doc == "string" )
+                cssText = tagName,
+                    tagName = doc,
+                    doc = null;
+
+            if( !doc )
+                doc = document;
+
+            el = doc.createElement( tagName );
+
+            if( cssText )
+                el.style.cssText = cssText;
+
+            return el;
+        },
+
+        getHtmlComments: function(){
+            var cn, push;
+
+            cn = document.COMMENT_NODE;
+            push = [].push;
+
+            return function f( node ){
+                var result, c, l, i;
+
+                result = [];
+
+                if( node.nodeType == cn )
+                    result.push( node.nodeValue );
+                else if( ( c = node.childNodes ) && ( l = c.length ) )
+                    for( i = 0; i < l; i ++ )
+                        push.apply( result, f( c[ i ] ) );
+
+                return result;
+            }
+        }(),
+
+        findParent: function( el, tag, endOf ){
+            do{
+                if( el.tagName.toLowerCase() == tag.toLowerCase() )
+                    return el;
+
+                if( el == endOf )
+                    return null;
+
+                el = el.parentNode;
+            }while( 1 );
+        },
+
+        tmpl: function( text, data ){
+            var settings, render, noMatch, matcher, index, source, escaper, escapes, template;
+
+            settings = { evaluate: /<%([\s\S]+?)%>/g, interpolate: /<%=([\s\S]+?)%>/g };
+            noMatch = /(.)^/;
+
+            matcher = new RegExp( [
+                ( settings.interpolate || noMatch ).source,
+                ( settings.evaluate || noMatch ).source
+            ].join('|') + '|$', 'g' );
+
+            index = 0;
+            source = "__p+='";
+            escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+            escapes = {
+                "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\t': 't', '\u2028': 'u2028',
+                '\u2029': 'u2029'
+            };
+
+            text.replace( matcher, function( match, interpolate, evaluate, offset ){
+                source += text.slice( index, offset ).replace( escaper, function( match ){
+                    return '\\' + escapes[ match ];
+                } );
+
+                if( interpolate )
+                    source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+
+                if( evaluate )
+                    source += "';\n" + evaluate + "\n__p+='";
+
+                index = offset + match.length;
+                return match;
+            } );
+
+            source += "';\n";
+            source = 'with(obj||{}){\n' + source + '}\n';
+            source = "var __t,__p='',__j=Array.prototype.join," +
+            "print=function(){__p+=__j.call(arguments,'');};\n" + source + "return __p;\n";
+
+            try{
+                render = new Function( 'obj', source );
+            }catch( e ){
+                e.source = source;
+                throw e;
+            }
+
+            if( data )
+                return render( data );
+
+            template = function( data ) {
+                return render.call( this, data );
+            };
+
+            template.source = 'function(obj){\n' + source + '}';
+
+            return template;
+        },
+
+        tag: function( html, tagName, className ){
+            var result, t;
+
+            result = html;
+            tagName = tagName.split( " " );
+
+            while( t = tagName.pop() )
+                result = "<" + t + ">" + result + "</" + t + ">";
+
+            if( className )
+                result = result.replace( /<(\w+)>/,
+                    "<$1 class='" + className + "'>" );
+
+            return result;
+        },
+
+        hasClass: function( el, className ){
+            var name;
+
+            name = " " + el.className + " ";
+
+            return ~name.indexOf( " " + className + " " );
+        },
+
+        addClass: function( el, className ){
+            var name;
+
+            name = " " + el.className + " ";
+
+            if( !~name.indexOf( " " + className + " " ) )
+                el.className += " " + className;
+        },
+
+        removeClass: function( el, className ){
+            var name;
+
+            name = " " + el.className + " ";
+
+            if( ~name.indexOf( " " + className + " " ) ){
+                name = name.replace( " " + className + " ", " " );
+                name = Tracker.Util.trim( name.replace( / +/g, " " ) );
+                el.className = name;
+            }
+        },
+
+        html: function( string ){
+            return string.replace( /&/g, "&amp;" )
+                .replace( /</g, "&lt;" )
+                .replace( />/g, "&gt;" )
+                .replace( /"/g, "&quot;" )
+                .replace( /'/g, "&#39;" );
+        },
+
+        splitToLines: function(){
+            var splitLineRegx = /\r\n|[\r\n]/;
+            return function( string ){
+                return string.split( splitLineRegx );
+            }
+        }(),
+
+        param: function( url, name, value ){
+            var spliter, suffix;
+
+            spliter = ~url.indexOf( "?" ) ? "&" : "?";
+            suffix = name + "=" + value;
+
+            return url + spliter + suffix;
+        },
+
+        excapeRegx: excapeRegx,
+
+        fileName: function( url ){
+            url = url.split( fileNameSplitWord )[ 0 ];
+            return url.slice( url.lastIndexOf( "/" ) + 1 );
+        },
+
+        time: function(){
+            return new Date().getTime();
+        },
+
+        browser: function(){
+            var ua, isOpera, ret;
+
+            ua = navigator.userAgent;
+            isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
+            ret = {
+                IE:     !!window.attachEvent && !isOpera,
+                Opera:  isOpera,
+                WebKit: ua.indexOf('AppleWebKit/') > -1,
+                Gecko:  ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1
+                // MobileSafari:   /Apple.*Mobile/.test(ua)
+            };
+
+            for( var name in ret )
+                if( ret.hasOwnProperty( name ) && ret[ name ] ){
+                    ret.name = name;
+                    break;
+                }
+
+            return ret;
+        }(),
+
+        isCrossDomain: function( url ){
+            return !( url.indexOf( Tracker.Path.getBase(window.document) ) == 0 );
+        },
+
+
+        intelligentGet: function( url ){
+            url = Tracker.Path.merge( Tracker.Path.getBase(window.document), url );
+            return Tracker.Util.isCrossDomain( url ) ? RemoteProxy.request( url ) : Tracker.Util.request( url );
+        },
+
+        request: function( url, charset ){
+            var xhr, pm, timeout, timer, timeStart, timeConsum;
+
+            pm = new Tracker.Promise();
+            timeout = 10e3;
+
+            if( XMLHttpRequest )
+                xhr = new XMLHttpRequest();
+            else
+                xhr = new ActiveXObject( "Microsoft.XMLHTTP" );
+
+            xhr.open( "GET", url, true );
+
+            if( charset && xhr.overrideMimeType )
+                xhr.overrideMimeType( "text/html;charset=" + charset );
+
+            xhr.onreadystatechange = function(){
+                if( xhr.readyState == 4 && xhr.status == 200 ){
+                    clearTimeout( timer );
+                    timeConsum = Tracker.Util.time();
+                    pm.resolve( {
+                        response: xhr.responseText,
+                        consum: timeConsum - timeStart
+                    } );
+                    xhr = null;
+                }
+            };
+
+            timer = setTimeout( function(){
+                xhr.abort();
+                pm.reject();
+                xhr = null;
+            }, timeout );
+
+            timeStart = Tracker.Util.time();
+            xhr.send( null );
+
+            return pm;
+        },
+
+        delay: function (){
+            // single thread
+            var tasks, start, timer, task;
+
+            tasks = [];
+
+            start = function(){
+                clearInterval( timer );
+                timer = setInterval( function(){
+                    if( tasks.length ){
+                        task = tasks.shift();
+                        task.apply();
+                    }else{
+                        clearInterval( timer );
+                    }
+                }, 1e2 );
+            };
+
+            return function( fn ){
+                tasks.push( fn );
+                start();
+            }
+        }(),
+
+        onCpuFree: function( fn, process ){
+            var now, start, last, count, d, timer, limit, times, space;
+
+            start = last = Tracker.Util.time();
+            count = 0;
+
+            times = 30;
+            space = 20;
+            limit = 100;
+
+            process = process || Tracker.Util.blank;
+
+            timer = setInterval( function(){
+                now = Tracker.Util.time();
+
+                if( ( d = now - last ) < limit && ++ count == times ){
+                    clearInterval( timer );
+                    return fn();
+                }else if( d > limit ){
+                    count = 0;
+                }
+
+                process( ( last = now ) - start );
+            }, space );
+        }
+    }
+}();

+ 1275 - 0
chrome/static/js/tracker/view.js

@@ -0,0 +1,1275 @@
+window.Tracker = window.Tracker || {};
+
+Tracker.View = (function( window ){
+    var global, host, location, push, join, version;
+
+    version = "1.8.9";
+    global = window;
+    host = global.document;
+    location = global.location;
+
+    var View = function(){
+        return {
+            templates: {
+                url: Tracker.Util.tmpl( "<a href='<%= url %>' target='_blank'><%= url %></a>" ),
+
+                frameset: Tracker.Util.tmpl( [
+                    "<!DOCTYPE html>",
+                    "<html>",
+                    "<head>",
+                        "<meta charset='<%= charset %>'>",
+                        "<meta name='description' content='fehelper-tracker-frame'>",
+                        "<title><%= title %></title>",
+                        "<style type='text/css'>",
+                            "html, body{ margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; position: relative; }",
+                            ".fullness{ position: absolute; left: 0; right: 0; top: 0; bottom: 0; }",
+                            "#tracker_proxy {display:none;}",
+                            "#wrapper{}",
+                            "#tracker_controller_ct{ z-index: 10; }",
+                            "#tracker_page_ct{ top: 43px; z-index: 20; background-color: #fff; }",
+                            "body.control-power-mode #tracker_page_ct{ z-index: 0; }",
+                            "body.hidden-page-mode #tracker_page_ct{ display: none; }",
+                            "iframe{ border: 0; width: 100%; height: 100%; }",
+                        "</style>",
+                    "</head>",
+                        "<body>",
+                            "<div id='wrapper' class='fullness'>",
+                                "<div id='tracker_proxy'><input type=\"button\" id=\"btnTrackerProxy\"></div>",
+                                "<div id='tracker_controller_ct' class='fullness'><iframe src='about:blank' id='tracker_controller' name='tracker_controller' frameborder='no'></iframe></div>",
+                                "<div id='tracker_page_ct' class='fullness'><iframe src='<%= url %>' id='tracker_page' name='tracker_page' frameborder='no'></iframe></div>",
+                            "</div>",
+                        "</body>",
+                    "</html>"
+                ].join( "" ) ),
+
+                controllerPage: Tracker.Util.tmpl( [
+                    "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Frameset//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd'>",
+                    "<html>",
+                    "<head>",
+                        "<meta charset='<%= charset %>'>",
+                        "<meta name='author' content='dron'>",
+                        "<title>Tracker!</title>",
+                        "<link href='<%= trackerCss %>' type='text/css' rel='stylesheet' />",
+                    "</head>",
+                    "<body>",
+                        "<%= header %>",
+                        "<div class='main' id='main'>",
+                            "<ul id='pages' class='unstyled tab-content'>",
+                                "<% if( mode == 'embed' ){ %>",
+                                    "<li class='tab-content-active target-web-page'></li>",
+                                "<% } %>",
+                                "<li class='<%= mode == 'embed' ? '' : 'tab-content-active' %>'>",
+                                    "<%= codeList %>",
+                                    "<%= codeDetail %>",
+                                "</li>",
+                            "</ul>",
+                        "</div>",
+                        "<script> window.readyState = 'done'; </script>",
+                    "</body>",
+                    "</html>"
+                ].join( "" ) ),
+
+                controllerTopbar: Tracker.Util.tmpl( [
+                    "<div id='loading' class='navbar'>",
+                        "<div class='navbar-inner'>",
+                            "<span>&#35831;&#31245;&#31561;&#65292;&#25910;&#38598;&#20013;...</span>",
+                            "<span id='waitTime'></span>",
+                        "</div>",
+                    "</div>",
+                    "<div id='top-navbar' class='navbar'>",
+                        "<div class='navbar-inner'>",
+                            '<a href="http://www.baidufe.com/feheler" target="_blank" class="fe-icon pull-left" action="frame#close">',
+                                '<img src="' + chrome.runtime.getURL('/static/img/fe-16.png') + '" alt="FeHelper">',
+                                "FeHelper:",
+                            '</a>',
+                            "<span class='fe-title'>Js覆盖面检测</span>",
+                            "<ul id='top-nav' class='nav pull-left' data-target='pages'>",
+                                "<% if( mode == 'embed' ){ %>",
+                                    "<li><a href='' onclick='return false'>当前网页</a></li>",
+                                    "<li data-name='code-list' class='active'><a href='' onclick='return false'>代码列表</a></li>",
+                                "<% }else{ %>",
+                                    "<li class='active' data-name='code-list'><a href='' onclick='return false'>代码列表</a></li>",
+                                "<% } %>",
+                            "</ul>",
+                            "<ul class='nav pull-right'>",
+                                "<li class='dropdown'>",
+                                    "<a href='' onclick='return false;' class='dropdown-toggle' data-toggle='dropdown'>",
+                                        "视图切换",
+                                        "<b class='caret'></b>",
+                                    "</a>",
+                                    "<ul class='dropdown-menu'>",
+                                        "<li><a id='window-mode-trigger' action='frame#toggle' href='#' onclick='return false;'>单窗口模式</a></li>",
+                                        "<li><a action='frame#close' href='#' onclick='return false;'>关闭控制台</a></li>",
+                                    "</ul>",
+                                "</li>",
+                                "<li><a href='http://www.baidufe.com/fehelper/feedback.html' target='_blank'>意见反馈</a></li>",
+                            "</ul>",
+                        "</div>",
+                    "</div>",
+                ].join( "" ) ),
+
+                controllerCodeList: Tracker.Util.tmpl( [
+                    "<table class='table compact-width'>",
+                        "<thead>",
+                            "<tr>",
+                                "<th width='<%= widthIndex %>'>#</th>",
+                                "<th width='<%= widthName %>'>&#21517;&#31216;</th>",
+                                "<th width='<%= widthType %>'>&#31867;&#22411;</th>",
+                                "<th width='<%= widthCover %>'>&#25191;&#34892;&#35206;&#30422;</th>",
+                                "<th width='<%= widthCoverLine %>'>&#25191;&#34892;&#34892;&#25968;</th>",
+                                "<th width='<%= widthLines %>'>&#24635;&#34892;&#25968;</th>",
+                                "<th width='<%= widthSize %>'>&#21407;&#22987;&#22823;&#23567;</th>",
+                                "<th width='<%= widthBSize %>'>&#35299;&#21387;&#22823;&#23567;</th>",
+                                "<th width='<%= widthLoadConsum %>'>&#21152;&#36733;&#32791;&#26102;</th>",
+                                "<th width='<%= widthRunConsum %>'>&#36816;&#34892;&#32791;&#26102;</th>",
+                                "<th width='<%= widthRError %>'>&#25191;&#34892;&#25253;&#38169;</th>",
+                                "<th width='<%= widthSError %>'>&#35821;&#27861;&#38169;&#35823;</th>",
+                                "<th width='<%= widthState %>'>&#29366;&#24577;</th>",
+                                "<th width='*'>&nbsp;</th>",
+                            "</tr>",
+                        "</thead>",
+                    "</table>",
+                    "<div id='list-codes' class='scrollable'>",
+                        "<table class='table table-striped table-hover table-condensed'>",
+                            "<colgroup>",
+                                "<col width='<%= widthIndex %>'>",
+                                "<col width='<%= widthName %>'>",
+                                "<col width='<%= widthType %>'>",
+                                "<col width='<%= widthCover %>'>",
+                                "<col width='<%= widthCoverLine %>'>",
+                                "<col width='<%= widthLines %>'>",
+                                "<col width='<%= widthSize %>'>",
+                                "<col width='<%= widthBSize %>'>",
+                                "<col width='<%= widthLoadConsum %>'>",
+                                "<col width='<%= widthRunConsum %>'>",
+                                "<col width='<%= widthRError %>'>",
+                                "<col width='<%= widthSError %>'>",
+                                "<col width='<%= widthState %>'>",
+                            "</colgroup>",
+                            "<tbody id='list-codes-tbody'>",
+                            "</tbody>",
+                        "</table>",
+                    "</div>"
+                ].join( "" ) ),
+
+                controllerCodeDetail: Tracker.Util.tmpl( [
+                    "<div id='code-detail' class='absolute'>",
+                        "<div class='code-toolbar clearfix'>",
+                            "<ul class='code-toolbar-inner'>",
+                                "<li class='close-button-like'><button class='close' action='code#close'>&times;</button></li>",
+
+                                "<li class='tab-like'>",
+                                    "<ul id='code-detail-head' class='nav nav-tabs' data-target='code-detail-body'>",
+                                        "<li class='active'><a href='' onclick='return false;'>&#20195;&#30721;</a></li>",
+                                        "<li><a href='' onclick='return false;'>&#20449;&#24687;</a></li>",
+                                    "</ul>",
+                                "</li>",
+
+                                "<li class='label-like right tab-desc tab-desc-0'>&#26032;&#27963;&#21160;</li>",
+                                "<li class='image-like right tab-desc tab-desc-0'><div class='hooking image'></div></li>",
+                                "<li class='label-like right tab-desc tab-desc-0'>&#26410;&#25191;&#34892;</li>",
+                                "<li class='image-like right tab-desc tab-desc-0'><div class='unarrive image'></div></li>",
+                                "<li class='label-like right tab-desc tab-desc-0'>&#24050;&#25191;&#34892;</li>",
+                                "<li class='image-like right tab-desc tab-desc-0'><div class='arrive image'></div></li>",
+                                "<li class='label-like right tab-desc tab-desc-0'>&#22270;&#20363;&#65306;</li>",
+                            "</ul>",
+                        "</div>",
+                        "<ul class='unstyled tab-content' id='code-detail-body'>",
+                            "<li class='tab-content-active'>",
+                                "<div id='code-content' class='relative scrollable'></div>",
+                            "</li>",
+                            "<li class='scrollable'>",
+                                "<div id='code-info'></div>",
+                            "</li>",
+                        "</ul>",
+                    "</div>"
+                ].join( "" ) ),
+
+                controllerCodeInfo: Tracker.Util.tmpl( [
+                    "<dl class='group'>",
+                        "<dt>&#26469;&#28304;</dt>",
+                        "<dd><%= fileName %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#31867;&#22411;</dt>",
+                        "<dd><%= type %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#25191;&#34892;&#35206;&#30422;&#29575;</dt>",
+                        "<dd><%= rate %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#25191;&#34892;&#34892;&#25968;</dt>",
+                        "<dd><%= arriveRowsCount %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#24635;&#34892;&#25968;</dt>",
+                        "<dd><%= rowsCount %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#21407;&#22987;&#22823;&#23567;</dt>",
+                        "<dd><%= size %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#35299;&#21387;&#22823;&#23567;</dt>",
+                        "<dd><%= bsize %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#21152;&#36733;&#32791;&#26102;</dt>",
+                        "<dd><%= loadConsum %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#36816;&#34892;&#32791;&#26102;</dt>",
+                        "<dd><%= runConsum %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#25191;&#34892;&#25253;&#38169;</dt>",
+                        "<dd><%= rerror %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#35821;&#27861;&#38169;&#35823;</dt>",
+                        "<dd><%= serror %></dd>",
+                    "</dl>",
+                    "<dl class='group'>",
+                        "<dt>&#29366;&#24577;</dt>",
+                        "<dd><%= state %></dd>",
+                    "</dl>",
+                ].join( "" ) ),
+
+                codeListLine: Tracker.Util.tmpl( [
+                    "<tr data-code-id='<%= id %>'>",
+                        "<td><div class='ellipsisable' style='width: <%= widthIndex %>px;'><%= index %></div></td>",
+                        "<td><div class='ellipsisable' style='width: <%= widthName %>px;'><%= fileName %></div></td>",
+                        "<td><div class='ellipsisable' style='width: <%= widthType %>px;'><%= type %></div></td>",
+                        "<td><div id='code-<%= id %>-rate' class='ellipsisable' style='width: <%= widthCover %>px;'><%= rate %></div></td>",
+                        "<td><div id='code-<%= id %>-arriveRowsCount' class='ellipsisable' style='width: <%= widthCoverLine %>px;'><%= arriveRowsCount %></div></td>",
+                        "<td><div class='ellipsisable' style='width: <%= widthLines %>px;'><%= rowsCount %></div></td>",
+                        "<td><div class='ellipsisable' style='width: <%= widthSize %>px;'><%= size %></div></td>",
+                        "<td><div class='ellipsisable' style='width: <%= widthBSize %>px;'><%= bsize %></div></td>",
+                        "<td><div id='code-<%= id %>-loadConsum' class='ellipsisable' style='width: <%= widthLoadConsum %>px;'><%= loadConsum %></div></td>",
+                        "<td><div id='code-<%= id %>-runConsum' class='ellipsisable' style='width: <%= widthRunConsum %>px;'><%= runConsum %></div></td>",
+                        "<td><div id='code-<%= id %>-runErrors' class='ellipsisable' style='width: <%= widthRError %>px;'><%= rerror %></div></td>",
+                        "<td><div class='ellipsisable' style='width: <%= widthSError %>px;'><%= serror %></div></td>",
+                        "<td><div class='ellipsisable' style='width: <%= widthState %>px;'><%= state %></div></td>",
+                        "<td></td>",
+                    "</tr>"
+                ].join( "" ) )
+            },
+
+            Loading: function(){
+                var layer, span1, span2, animateTimer, count, progress, body;
+
+                count = progress = 0;
+                body = host.body;
+
+                var create = function(){
+                    var span;
+
+                    layer = Tracker.Util.makeElement( "div", "position: fixed; padding: 30px; border: 1px solid rgba(255, 255, 255, .2); border-radius: 10px; background: #000; font-size: 20px; line-height: 20px; text-align: center; color: #fff; top: 50px; left: 50px; box-shadow: 0 0 5px #fff; z-index: 65535; font-family: 'Courier New', 'Heiti SC', 'Microsoft Yahei';" );
+                    layer.innerHTML = "&#27491;&#22312;&#20998;&#26512;&#32593;&#39029; <span>...</span> <span>(0/0)</span>";
+                    body.appendChild( layer );
+                    host.documentElement.scrollTop = body.scrollTop = 0;
+                    span = layer.getElementsByTagName( "span" );
+                    span1 = span[0];
+                    span2 = span[1];
+                };
+
+                var animate = function(){
+                    var count, word, n, s, e;
+
+                    count = 0;
+                    word = "......";
+                    clearInterval( animateTimer );
+                    animateTimer = setInterval( function(){
+                        n = count % 7;
+                        s = word.substr( 0, n );
+                        e = word.substr( 0, 6 - n );
+                        span1.innerHTML = s + "<span style='color: #000;'>" +
+                            e + "</span>";
+                        count += 1;
+                    }, 100 );
+                };
+
+                return {
+                    show: function(){
+                        if( !layer )
+                            create();
+                        else
+                            layer.style.display = "block";
+
+                        animate();
+                    },
+
+                    hide: function(){
+                        if( layer )
+                            layer.style.display = "none";
+
+                        clearInterval( animateTimer );
+                    },
+
+                    text: function( text ){
+                        var me, pm;
+
+                        if( layer )
+                            layer.innerHTML = text;
+
+                        me = this;
+                        pm = new Tracker.Promise();
+                        clearInterval( animateTimer );
+                        setTimeout( function(){
+                            me.hide();
+                            pm.resolve();
+                        }, 2e3 );
+
+                        return pm;
+                    },
+
+                    addCount: function(){
+                        count ++;
+                        span2.innerHTML = "(" + ( progress / count * 100 ).toFixed( 2 ) + "%)";
+                    },
+
+                    addProgress: function(){
+                        progress ++;
+                        span2.innerHTML = "(" + ( progress / count * 100 ).toFixed( 2 ) + "%)";
+                    }
+                }
+            }(),
+
+            ControlFrame: function(){
+                var document = window.document, controlWindow, hasCreateEmbeded = false,
+                    currentMode = "embed", pageBuilder, controllerBuilder;
+
+                var config = {
+                    windowWidth: 800,
+                    windowHeight: 600
+                };
+
+                var lookupForWindowReady = function( target ){
+                    var pm, timer, timer2, now, timeout, clear;
+
+                    pm = new Tracker.Promise();
+                    timeout = 5000;
+                    now = Tracker.Util.time();
+
+                    clear = function(){
+                        clearInterval( timer );
+                        clearTimeout( timer2 );
+                    };
+
+                    timer = setInterval( function(){
+                        if( target.readyState == "complete" ){
+                            clear();
+                            pm.resolve();
+                        }
+                    }, 10 );
+
+                    timer2 = setTimeout( function(){
+                        pm.reject();
+                    }, timeout );
+
+                    return pm;
+                };
+
+                return Tracker.Event.bind( {
+                    state: "preshow",
+
+                    pageBuilder: function( fn ){
+                        pageBuilder = fn;
+                    },
+
+                    controllerBuilder: function( fn ){
+                        controllerBuilder = fn;
+                    },
+
+                    show: function(){
+                        var controller, page, window;
+
+                        if( currentMode === "embed" ){
+                            controller = document.getElementById( "tracker_controller_ct" ),
+                            page = document.getElementById( "tracker_page_ct" ),
+                            controller.style.display = "block",
+                            page.style.top = "";
+                        }else if( currentMode === "window" ){
+                            window = this.getWindow( "tracker_controller" );
+
+                            if( window && !window.closed )
+                                window.focus();
+                            else
+                                this.createWindow();
+                        }
+
+                        this.state = "show";
+                        this.fire( "show" );
+                    },
+
+                    hide: function(){
+                        var controller, page;
+
+                        if( currentMode === "embed" )
+                            controller = document.getElementById( "tracker_controller_ct" ),
+                            page = document.getElementById( "tracker_page_ct" ),
+                            controller.style.display = "none",
+                            page.style.top = "0";
+                        else if( currentMode === "window" )
+                            controlWindow.close();
+
+                        this.state = "hide";
+                        this.fire( "hide" );
+                    },
+
+                    toggleMode: function(){
+                        this.removeControllerFrame();
+
+                        if( currentMode === "embed" )
+                            currentMode = "window",
+                            this.createWindow();
+                        else if( currentMode === "window" )
+                            currentMode = "embed",
+                            this.createEmbed(),
+                            this.show();
+                    },
+
+                    getMode: function(){
+                        return currentMode;
+                    },
+
+                    getWindow: function( name ){
+                        // name: tracker_main | tracker_page | tracker_controller
+                        var w;
+                        if( !arguments.length || name === "tracker_main" )
+                            return window;
+
+                        if( currentMode === "window" && name === "tracker_controller" )
+                            return controlWindow;
+                        else if( w = window.frames[ name ] )
+                            return window.document.getElementById( name ).contentWindow;
+                    },
+
+                    // privates
+                    createEmbed: function(){
+                        var page, controller;
+
+                        Tracker.Promise.when(
+                            hasCreateEmbeded ? [ controllerBuilder( "embed" ) ] :
+                            [ pageBuilder(), controllerBuilder( "embed" ) ]
+                        ).then( Tracker.Util.bind( function( pageHtml, controllerHtml ){
+                            if( !controllerHtml )
+                                controllerHtml = pageHtml,
+                                pageHtml = null;
+
+                            if( pageHtml ){
+                                window.name = "tracker_main";
+
+                                this.write( "tracker_main", View.templates.frameset( {
+                                    url: location.href,
+                                    title: document.title,
+                                    charset: document.characterSet || "utf-8"
+                                } ) );
+
+                                this.write( "tracker_page", pageHtml );
+                                var pageWin = this.getWindow( "tracker_page" );
+
+                                // 整个页面都加载完成了,可以开始script inject了
+                                this.fire('pageLoad',window,pageWin);
+                            }
+
+                            this.write( "tracker_controller", controllerHtml );
+                            controller = this.getWindow( "tracker_controller" );
+
+                            lookupForWindowReady( controller.document ).then( Tracker.Util.bind( function(){
+                                this.fire( "controllerLoad", controller, controller.document );
+                            }, this ) );
+                        }, this ) );
+
+                        hasCreateEmbeded = true;
+                    },
+
+                    createWindow: function( conf ){
+                        var width = screen.width - 200, height = screen.height - 200,
+                            left = 100, top = 100, controller;
+
+                        controlWindow = window.open( "about:blank", "", "width=" + width +
+                            ", height=" + height + ", left=" + left + ", top=" + top +
+                            ", toolbar=no, menubar=no, resizable=yes, status=no, " +
+                            "location=no, scrollbars=yes" );
+
+                        controllerBuilder( "window" ).then( Tracker.Util.bind( function( html ){
+                            this.write( "tracker_controller", html );
+                            controller = this.getWindow( "tracker_controller" );
+
+                            lookupForWindowReady( controller.document ).then( Tracker.Util.bind( function(){
+                                this.fire( "controllerLoad", controller, controller.document );
+                            }, this ) );
+                        }, this ) );
+                    },
+
+                    removeControllerFrame: function(){
+                        this.hide();
+
+                        if( currentMode === "embed" )
+                            this.write( "tracker_controller", "about:blank" );
+                        else if( currentMode === "window" )
+                            controlWindow = null;
+                    },
+
+                    write: function( name, content ){
+                        var document, i, l, t, timer, write;
+
+                        document = this.getWindow( name ).document;
+                        document.open( "text/html", "replace" );
+
+                        if( name == "tracker_page" ){
+                            i = 0;
+                            t = 10240; // 10k/ms
+
+                            l = content.length;
+
+                            write = function(){
+                                c = content.substr( i, t );
+                                document.write( c );
+                                i += t;
+
+                                if( i > l )
+                                    document.close(),
+                                    clearInterval( timer );
+                            };
+
+                            timer = setInterval( write, 1 );
+                        }else{
+                            document.write( content );
+                            document.close();
+                        }
+                    }
+                } );
+            }(),
+
+            ControlPanel: function(){
+                var actions, window, document, currentSelectedCode, updateInterval, codeEl,
+                    codeIndex;
+
+                actions = {};
+                codeIndex = 0;
+
+                var rate = function( code ){
+                    var r, c;
+
+                    r = code.arriveRowsCount / code.rowsCount * 100 || 0;
+                    c = r == 0 ? "stress" : "";
+
+                    return "<span class='" + c + "'>" + r.toFixed( 1 ) + "%</span>";
+                };
+
+                var size = function( number ){
+                    return ( number / 1024 ).toFixed( 1 ) + "k";
+                };
+
+                var yesno = function( bool ){
+                    return ( bool && bool.length ) ?
+                    "<span class='stress'>&#26159;<span>" : "&#21542;";
+                };
+
+                var state = function( state ){
+                    switch( state ){
+                        case "normal":
+                            return "&#27491;&#24120;";
+                        case "timeout":
+                            return "<span class='stress'>&#36229;&#26102;</span>";
+                        case "empty":
+                            return "<span class='stress'>&#26080;&#20869;&#23481;</span>";
+                    }
+                };
+
+                var type = function( code ){
+                    switch( code.type ){
+                        case "embed":
+                            return "内嵌";
+                        case "link":
+                            return "文件链接";
+                        case "append":
+                            return "动态插入";
+                    };
+                };
+
+                var time = function( time, s ){
+                    if( time == -1 )
+                        return "-1";
+                    if( !s )
+                        return time + "ms";
+                    else
+                        return ( time / 1000 ).toFixed( 2 ) + "s";
+                };
+
+                var width = function(){
+                    var mapping, offsets;
+
+                    mapping = {
+                        index: 30, name: 220, type: 90, cover: 60, "cover-line": 60, lines: 60,
+                        size: 60, bsize: 60, rerror: 60, serror: 60, state: 50, loadConsum: 60,
+                        runConsum: 60
+                    };
+
+                    offsets = {
+                    };
+
+                    return function( name, type ){
+                        return mapping[ name ] + ( offsets[ type ] || 0 );
+                    };
+                }();
+
+                var withWidths = function( data ){
+                    var widths = {
+                        widthIndex: width( "index" ),
+                        widthName: width( "name" ),
+                        widthType: width( "type" ),
+                        widthCover: width( "cover" ),
+                        widthCoverLine: width( "cover-line" ),
+                        widthLines: width( "lines" ),
+                        widthSize: width( "size" ),
+                        widthBSize: width( "bsize" ),
+                        widthRError: width( "rerror" ),
+                        widthSError: width( "serror" ),
+                        widthState: width( "state" ),
+                        widthLoadConsum: width( "loadConsum" ),
+                        widthRunConsum: width( "runConsum" )
+                    };
+
+                    if( !data )
+                        return widths;
+
+                    for( var i in widths )
+                        data[ i ] = widths[ i ];
+
+                    return data;
+                };
+
+                var codeTemplate = function( code ){
+                    return View.templates.codeListLine( withWidths( {
+                        id: code.id,
+
+                        index: ++ codeIndex,
+                        fileName: code.fileName,
+                        type: type( code ),
+                        rate: rate( code ),
+                        arriveRowsCount: code.arriveRowsCount,
+                        rowsCount: code.rowsCount,
+                        size: size( code.size ),
+                        bsize: size( code.beautifySize ),
+                        rerror: yesno( code.runErrors ),
+                        serror: yesno( code.syntaxErrors ),
+                        state: state( code.state ),
+                        runConsum: time( code.runConsum ),
+                        loadConsum: time( code.loadConsum )
+                    } ) );
+                };
+
+                var codeListTemplate = function( codeList ){
+                    var htmls;
+
+                    htmls = [];
+
+                    if( codeList.length ){
+                        Tracker.Util.forEach( codeList, function( code, index ){
+                            htmls[ index ] = codeTemplate( code );
+                        } );
+
+                        return htmls.join( "" );
+                    }else{
+                        return "<tr><td colspan='20'><div class='none'>该网页没有任何JS代码</div></td></tr>";
+                    }
+                };
+
+                var makeCodeTr = function( code ){
+                    var layer, html;
+
+                    layer = document.createElement( "tbody" );
+                    html = codeTemplate( code );
+                    layer.innerHTML = html;
+
+                    return layer.firstChild;
+                };
+
+                var asnyShowCode = function(){
+                    var timer, timeout, interval, prepare, partCount, nowIndex, init,
+                        currentDisposeLines, gutterEl, linesEl, regx1, regx2, ckeyIdRegx, result,
+                        linesCount, h1, h2, focusOnFlag, focusOnFlagTarget;
+
+                    timeout = 1;
+                    partCount = 100;
+                    regx1 = /\x00/g;
+                    regx2 = /\x01/g;
+                    ckeyIdRegx = /id=ckey-(\d+)/g;
+                    h1 = [];
+                    h2 = [];
+
+                    init = function(){
+                        nowIndex = 0;
+                        linesCount = 0;
+                        window.clearInterval( timer );
+                    };
+
+                    prepare = function(){
+                        var innerElId = Tracker.Util.id();
+                        var gutterId = Tracker.Util.id();
+                        var linesId = Tracker.Util.id();
+
+                        codeEl.innerHTML = "<div id='" + innerElId + "' class='block clearfix' " +
+                            "style='height: " + ( linesCount * 20 + 10 ) + "px;'>" +
+                            "<div id='" + gutterId + "' class='gutter'></div>" +
+                            "<div id='" + linesId + "' class='lines'></div></div>";
+                        codeEl.scrollTop = 0;
+
+                        gutterEl = document.getElementById( gutterId );
+                        linesEl = document.getElementById( linesId );
+                    };
+
+                    interval = function(){
+                        var t, p1, p2, a;
+
+                        h1.length = h2.length = 0;
+
+                        for( var i = 0; i < partCount; i ++ ){
+                            if( nowIndex >= linesCount ){
+                                init();
+                                break;
+                            }
+
+                            t = Tracker.Util.html( currentDisposeLines[ nowIndex ] ).replace( regx1, "<" )
+                                .replace( regx2, ">" );
+
+                            t = t.replace( ckeyIdRegx, function( all, id ){
+                                a = Tracker.StatusPool.arrivedSnippetGet( id );
+
+                                if( a & 2 )
+                                    a = 2;
+                                else if( a & 1 )
+                                    a = 1;
+
+                                if( focusOnFlag && !focusOnFlagTarget && focusOnFlag == a ){
+                                    focusOnFlagTarget = id;
+                                    focusOnFlag = 0;
+                                }
+
+                                return a ? all + " class='arrive arrive-" + a + "'" : all;
+                            } );
+
+                            h1.push( Tracker.Util.tag( nowIndex + 1, "pre" ) );
+                            h2.push( Tracker.Util.tag( t || " ", "pre" ) );
+
+                            nowIndex ++;
+                        }
+
+                        p1 = document.createElement( "div" );
+                        p2 = document.createElement( "div" );
+
+                        p1.innerHTML = h1.join( "" );
+                        p2.innerHTML = h2.join( "" );
+
+                        gutterEl.appendChild( p1 );
+                        linesEl.appendChild( p2 );
+
+                        if( focusOnFlagTarget ){
+                            document.getElementById( "ckey-" + focusOnFlagTarget ).scrollIntoView();
+                            focusOnFlagTarget = undefined;
+                        }
+                    };
+
+                    result = function( code, _focusOnFlag ){
+                        init();
+
+                        focusOnFlag = _focusOnFlag;
+
+                        if( code.state == "empty" ){
+                            codeEl.innerHTML = "<div class='empty-code'>" +
+                                    "&#20869;&#23481;&#20026;&#31354;</div>"; // 内容为空
+                        }else if( code.state == "timeout" ){
+                            codeEl.innerHTML = "<div class='timeout-code'>" +
+                                    "&#35299;&#26512;&#36229;&#26102;</div>"; // 解析超时
+                        }else{
+                            currentDisposeLines = code.linesViewHtml;
+                            linesCount = currentDisposeLines.length;
+                            prepare();
+                            timer = window.setInterval( interval, timeout );
+                        }
+                    };
+
+                    result.clear = init;
+
+                    return result;
+                }();
+
+                var setupBootstrapPatch = function(){
+
+                    var setupDropdownMenu = function(){
+                        var lastOpen;
+
+                        var setup = function( el ){
+
+                            var dropdownMenu = el.querySelector( ".dropdown-menu" );
+
+                            Tracker.Event.add( dropdownMenu, "click", function( e ){
+                                Tracker.Util.removeClass( el, "open" );
+                                lastOpen = null;
+                                e.stopPropagation();
+                                Tracker.TrackerGlobalEvent.fire( "bootstrap: dropdown.close" );
+                            } );
+
+                            Tracker.Event.add( el, "click", function( e ){
+                                Tracker.Util.addClass( el, "open" );
+                                if( lastOpen && lastOpen != el )
+                                    Tracker.Util.removeClass( lastOpen, "open" );
+                                lastOpen = el;
+                                Tracker.TrackerGlobalEvent.fire( "bootstrap: dropdown.open" );
+                                e.stopPropagation();
+                            } );
+                        };
+
+                        return function(){
+                            var dropdowns = document.querySelectorAll( ".dropdown" );
+                            for( var i = 0, l = dropdowns.length; i < l; i ++ )
+                                setup( dropdowns[ i ] );
+                            Tracker.Event.add( document, "click", function(){
+                                var found;
+
+                                for( var i = 0, l = dropdowns.length; i < l; i ++ )
+                                    if( Tracker.Util.hasClass( dropdowns[ i ], "open" ) )
+                                        found = true,
+                                    Tracker.Util.removeClass( dropdowns[ i ], "open" );
+
+                                if( found )
+                                    Tracker.TrackerGlobalEvent.fire( "bootstrap: dropdown.close" );
+                            } );
+                        }
+                    }();
+
+                    var setupModalDialog = function(){
+
+                        var open = function( modal ){
+                            return function(){
+                                modal.style.display = "block";
+                                Tracker.TrackerGlobalEvent.fire( "bootstrap: dialog.open" );
+                            }
+                        };
+
+                        var close = function( modal ){
+                            return function(){
+                                modal.style.display = "none";
+                                Tracker.TrackerGlobalEvent.fire( "bootstrap: dialog.close" );
+                            }
+                        };
+
+                        var setup = function( modal ){
+                            var closeBtns = modal.querySelectorAll( ".modal-header .close, .modal-footer .btn" );
+                            var fclose = close( modal );
+                            var fopen = open( modal );
+                            for( var i = 0, l = closeBtns.length; i < l; i ++ )
+                                Tracker.Event.add( closeBtns[ i ], "click", fclose );
+                            modal.open = fopen;
+                        };
+
+                        return function(){
+                            var modals = document.querySelectorAll( ".modal" );
+                            for( var i = 0, l = modals.length; i < l; i ++ )
+                                setup( modals[ i ] );
+                        }
+                    }();
+
+                    var setupTab = function(){
+
+                        var setup = function( tab ){
+                            var target = tab.getAttribute( "data-target" );
+
+                            if( !target )
+                                return ;
+
+                            target = document.getElementById( target );
+
+                            var heads = tab.childNodes;
+                            var bodys = target.childNodes;
+
+                            tab.active = function( index ){
+                                Tracker.Util.removeClass( heads[ tab.actived ], "active" );
+                                Tracker.Util.removeClass( bodys[ tab.actived ], "tab-content-active" );
+
+                                Tracker.Util.addClass( heads[ index ], "active" );
+                                Tracker.Util.addClass( bodys[ index ], "tab-content-active" );
+
+                                tab.actived = index;
+                                tab.tabEvent.fire( "active",
+                                    index, heads[ index ].getAttribute( "data-name" ) );
+                            };
+
+                            Tracker.Event.add( tab, "click", function( e ){
+                                var li;
+
+                                li = Tracker.Util.findParent( e.target, "li", this );
+
+                                Tracker.Util.forEach( this.childNodes, function( l, index ){
+                                    if( li === l ){
+                                        if( tab.actived == index )
+                                            return ;
+                                        tab.active( index );
+                                    }
+                                } );
+                            } );
+
+                            tab.tabEvent = Tracker.Event.bind();
+                            tab.actived = 0;
+                        };
+
+                        return function(){
+                            var tabs = document.querySelectorAll( ".nav" );
+                            for( var i = tabs.length - 1; i >= 0; i -- )
+                                setup( tabs[ i ] );
+                        };
+                    }();
+
+                    return function(){
+                        setupDropdownMenu();
+                        setupModalDialog();
+                        setupTab();
+                    };
+                }();
+
+                var autoUpdateCodeFn = function(){
+                    Tracker.CodeList.each( function( code ){
+                        if( !code.lastUpdate || code.lastUpdate < code.lastModified )
+                            View.ControlPanel.updateCode( code );
+                    } );
+                };
+
+                return Tracker.Event.bind( {
+                    bindWindow: function( win ){
+                        window = win;
+                        document = window.document;
+                        codeEl = document.getElementById( "code-content" );
+                    },
+
+                    addCode: function( code ){
+                        var tbody, tr, index;
+
+                        if( !document )
+                            return ;
+
+                        tbody = document.getElementById( "list-codes-tbody" );
+                        index = Tracker.CodeList.count() - 1;
+
+                        if( code instanceof Array ){
+                            tbody.innerHTML = codeListTemplate( code );
+                        }else if( code instanceof Tracker.Code ){
+                            code.onReady( function(){
+                                // code.index = index;
+                                tr = makeCodeTr( code );
+                                tbody.appendChild( tr );
+                            } );
+                        }
+                    },
+
+                    showCodeDetail: function( id, focusOnFlag ){
+                        var codeDetailHeadTab, actived;
+
+                        codeDetailHeadTab = document.getElementById( "code-detail-head" );
+                        actived = codeDetailHeadTab.actived;
+
+                        currentSelectedCode = id || null;
+
+                        document.getElementById( "code-detail" ).style.display =
+                            id ? "block" : "none";
+
+                        if( id ){
+                            if( actived === 0 )
+                                this.showCode( id, focusOnFlag );
+                            else if( actived == 1 )
+                                this.showCodeInfo( id, focusOnFlag );
+
+                            var elementListCodes = document.getElementById( "list-codes" );
+                            var trs = elementListCodes.querySelectorAll( "tr" );
+
+                            Tracker.Util.forEach( trs, function( tr ){
+                                if( tr.getAttribute( "data-code-id" ) == id )
+                                    Tracker.Util.addClass( tr, "info" );
+                                else
+                                    Tracker.Util.removeClass( tr, "info" );
+                            } );
+                        }
+                    },
+
+                    showCode: function( id, focusOnFlag ){
+                        if( id ){
+                            code = Tracker.CodeList.get( id );
+                            asnyShowCode( code, focusOnFlag );
+                        }
+                    },
+
+                    showCodeInfo: function( id, focusOnFlag ){
+                        var elementCodeInfo, code;
+
+                        elementCodeInfo = document.getElementById( "code-info" );
+                        code = Tracker.CodeList.get( id );
+
+                        elementCodeInfo.innerHTML = View.templates.controllerCodeInfo( {
+                            // id: code.id,
+                            // index: ++ codeIndex,
+                            fileName: code.fullUrl ?
+                                View.templates.url( { url: code.fullUrl } ) :
+                                "&#26469;&#33258;&#39029;&#38754;",
+                            type: type( code ),
+                            rate: rate( code ),
+                            arriveRowsCount: code.arriveRowsCount,
+                            rowsCount: code.rowsCount,
+                            size: size( code.size ),
+                            bsize: size( code.beautifySize ),
+                            rerror: yesno( code.runErrors ),
+                            serror: yesno( code.syntaxErrors ),
+                            state: state( code.state ),
+                            loadConsum: time( code.loadConsum ),
+                            runConsum: time( code.runConsum )
+                        } );
+                    },
+
+                    updateCode: function( code ){
+                        if( currentSelectedCode == code.id )
+                            this.showCodeDetail( code.id );
+
+                        var rateEl, arriveRowsCountEl, runErrorsEl, runConsumEl, loadConsumEl;
+
+                        rateEl = document.getElementById( "code-" + code.id + "-rate" );
+                        arriveRowsCountEl = document.getElementById( "code-" + code.id +
+                            "-arriveRowsCount" );
+                        runErrorsEl = document.getElementById( "code-" + code.id + "-runErrors" );
+                        runConsumEl = document.getElementById( "code-" + code.id + "-runConsum" );
+                        loadConsumEl = document.getElementById( "code-" + code.id + "-loadConsum" );
+
+                        if( !rateEl )
+                            return;
+
+                        rateEl.innerHTML = rate( code );
+                        arriveRowsCountEl.innerHTML = code.arriveRowsCount;
+                        runErrorsEl.innerHTML = yesno( code.runErrors );
+                        runConsumEl.innerHTML = time( code.runConsum );
+                        loadConsumEl.innerHTML = time( code.loadConsum );
+
+                        code.lastUpdate = Tracker.Util.time();
+                    },
+
+                    autoUpdateCodeStart: function(){
+                        updateInterval = setInterval( autoUpdateCodeFn, 5e2 );
+                    },
+
+                    autoUpdateCodeStop: function(){
+                        clearInterval( updateInterval );
+                    },
+
+                    activeTab: function( name ){
+                        var topNav, baseIndex;
+
+                        topNav = document.getElementById( "top-nav" );
+                        baseIndex = 0;
+
+                        if( View.ControlFrame.getMode() == "embed" )
+                            baseIndex = 1;
+
+                        if( typeof name != "undefined" ){
+                            if( !topNav.active )
+                                return ;
+
+                            if( name == "code-list" )
+                                name = baseIndex + 0;
+
+                            topNav.active( name );
+                        }else{
+                            return topNav.actived;
+                        }
+                    },
+
+                    setControlPower: function( bool, hiddenPage ){
+                        var parent, window, b, c1, c2;
+
+                        if( View.ControlFrame.getMode() == "embed" ){
+                            parent = View.ControlFrame.getWindow( "tracker_main" );
+                            window = View.ControlFrame.getWindow( "tracker_controller" );
+                            c1 = bool ? Tracker.Util.addClass : Tracker.Util.removeClass;
+                            c2 = hiddenPage ? Tracker.Util.addClass : Tracker.Util.removeClass;
+                            b = parent.document.body;
+                            c1( b, "control-power-mode" );
+                            c2( b, "hidden-page-mode" );
+                        }
+                    },
+
+                    actions: function( acts ){
+                        for( var name in acts )
+                            actions[ name ] = acts[ name ];
+                    },
+
+                    htmlBuilder: function(){
+                        var pm = new Tracker.Promise(), mode;
+
+                        codeIndex = 0;
+                        mode = View.ControlFrame.getMode();
+
+                        Tracker.Util.delay( function(){
+                            pm.resolve( View.templates.controllerPage( withWidths( {
+                                charset: global.document.characterSet || "utf-8",
+                                trackerCss: chrome.runtime.getURL('/static/css/js-tracker.css'),
+
+                                header: View.templates.controllerTopbar( {
+                                    mode: mode
+                                } ),
+
+                                codeList: View.templates.controllerCodeList( withWidths() ),
+                                codeDetail: View.templates.controllerCodeDetail(),
+
+                                mode: mode,
+                                version: version,
+                                uid: host.tracker_uid
+                            } ) ) );
+                        } );
+
+                        return pm;
+                    },
+
+                    eventBuilder: function(){
+                        var me = this;
+
+                        var elementListCodes = document.getElementById( "list-codes" );
+                        var elementCodeDetail = document.getElementById( "code-detail" );
+                        var elementCodeToolbarInner = document.querySelector( ".code-toolbar-inner" );
+                        var elementCodeDetailHead = document.getElementById( "code-detail-head" );
+                        var elementCodeContent = document.getElementById( "code-content" );
+
+                        var tr, focusInList;
+                        var tabDescRegx = /tab-desc-(\d+)/;
+
+                        Tracker.Event.add( elementListCodes, {
+                            click: function( e ){
+                                var codeId;
+                                if( tr = Tracker.Util.findParent( e.target, "tr", elementListCodes ) )
+                                    if( codeId = tr.getAttribute( "data-code-id" ) )
+                                        focusInList = true,
+                                        codeId == currentSelectedCode ||
+                                            View.ControlPanel.showCodeDetail( codeId );
+                            }
+                        } );
+
+                        Tracker.Event.add( elementCodeDetail, {
+                            click: function(){
+                                focusInList = false;
+                            }
+                        } );
+
+                        Tracker.Event.add( document, {
+                            mouseup: function( e ){
+                                var action;
+                                if( ( action = e.target.getAttribute( "action" ) ) &&
+                                    actions[ action ] )
+                                    actions[ action ].call( me, e.target );
+                            },
+
+                            keydown: function( e ){
+
+                                var selectId;
+
+                                // command + R, F5
+                                if( ( e.metaKey && e.keyCode == 82 ) || e.keyCode == 116 ){
+                                    if( View.ControlFrame.getMode() == "window" ){
+                                    e.preventDefault && e.preventDefault();
+                                    }
+                                }
+
+                                if( focusInList && currentSelectedCode ){
+                                    var offset = 0;
+                                    if( e.keyCode == 38 ){ // up
+                                        offset = -1;
+                                    }else if( e.keyCode == 40 ){ // down
+                                        offset = 1;
+                                    }
+                                    if( offset ){
+                                        var trs = elementListCodes.querySelectorAll( "tr" ),
+                                            nowIndex = -1, tr;
+
+                                        for(var i = 0, l = trs.length; i < l; i ++){
+                                            if( trs[i].getAttribute( "data-code-id" ) ==
+                                                currentSelectedCode ){
+                                                nowIndex = i;
+                                                break;
+                                            }
+                                        }
+
+                                        if( nowIndex > -1 ){
+                                            nowIndex = ( nowIndex += offset ) < 0 ?
+                                                0 : nowIndex == trs.length ?
+                                                nowIndex - 1 : nowIndex;
+                                            tr = trs[ nowIndex ];
+
+                                            selectId = tr.getAttribute( "data-code-id" );
+                                            if( currentSelectedCode != selectId )
+                                                View.ControlPanel.showCodeDetail( selectId );
+
+                                            e.preventDefault && e.preventDefault();
+                                        }
+                                    }
+                                }
+                            }
+
+                            // mousewheel: function( e ){
+                            //     e.preventDefault();
+                            // }
+                        } );
+
+                        if( !actions[ "code#close" ] ){
+                            actions[ "code#close" ] = function( e ){
+                                focusInList = true;
+                                View.ControlPanel.showCodeDetail( false );
+                            };
+                        }
+
+                        if( View.ControlFrame.getMode() == "window" )
+                            document.getElementById( "window-mode-trigger" ).innerHTML = "内嵌模式";
+
+                        var lastScrollLeft = 0;
+                        Tracker.Event.add( elementCodeContent, {
+                            scroll: function( e ){
+
+                                if( this.scrollLeft == 0 )
+                                    this.scrollLeft = 1;
+
+                                if( this.scrollLeft == this.scrollWidth - this.clientWidth )
+                                    this.scrollLeft -= 1;
+
+                            if( lastScrollLeft == this.scrollLeft )
+                                return ;
+
+                            var gutter = this.querySelector( ".gutter" );
+                            gutter.style.left = this.scrollLeft + "px";
+                            lastScrollLeft = this.scrollLeft;
+                            }
+                        } );
+
+                        setupBootstrapPatch();
+
+                        elementCodeDetailHead.tabEvent.on( "active", function( index ){
+                            if( this.currentShown != currentSelectedCode ){
+                                this.currentShown = currentSelectedCode;
+                                if( index == 0 )
+                                    View.ControlPanel.showCode( currentSelectedCode );
+                                else if( index == 1 )
+                                View.ControlPanel.showCodeInfo( currentSelectedCode );
+                            }
+
+                            var tabDescs = elementCodeToolbarInner.querySelectorAll( ".tab-desc" );
+                            Tracker.Util.forEach( tabDescs, function( tabDesc ){
+                                tabDescRegx.test( tabDesc.className );
+                                tabDesc.style.display = RegExp.$1 == index ? "" : "none";
+                            } );
+                        } );
+
+                        if( currentSelectedCode )
+                            this.showCodeDetail( currentSelectedCode );
+                    }
+                } );
+            }()
+        }
+    }();
+
+    return View;
+})(this);

+ 276 - 0
chrome/static/js/tracker/watch.js

@@ -0,0 +1,276 @@
+/** 
+ * Watch Plugin
+ * @version 1.0
+ * @author dron
+ * @create 2013-05-07
+ */
+
+Tracker.setupWatchPlugin = function(){
+    Tracker.Plugins.addOn( "watch", function(){
+        var  tmpl, template, window, document, log, time, startBtnId, stopBtnId, clearBtnId,
+            stateId, logerId, started, buttonEventBind, stateEvent, start, stop, clear, hooking,
+            originalArrivedSnippetGroupFlag, buttonEnable, activeCodeCache, eventBuild,
+            original__tracker__, new__tracker__, showState, arrivedSnippetGroupCache,
+            buttonStateChange, logHtml;
+
+        tmpl = Tracker.Util.tmpl;
+        arrivedSnippetGroupCache = {};
+
+        stateEvent = Tracker.Event.bind( {} );
+
+        time = function( date ){
+            var t = [ date.getHours(), date.getMinutes(), date.getSeconds() ].join( ":" );
+            t = t.replace( /\b(\d)\b/g, "0$1" );
+            return t;
+        };
+
+        Tracker.Plugins.addStyle( [
+            ".red-dot{ display: inline; margin-left: 2px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACvElEQVQ4ja2T30uaYRTHT7W61ULDzFez1BaKYslkF7MfmNaVpLCbqOwNohiyGSuCzLUWBrpCWrTRUiIi2IgVW60NuqiNYbZs/bD8Vc5aqy3a3/Ds1MXcWFdjB76c9z3POZ/n+7w8L8D/jBQUC6VITQVNejqUZ2RAGeYyzKq0NOClpEAGrl0aV1DnTS0MBtzjcOAhRcFjoRCeoNwCATh4POjA+k2UFGt/hRyHb2dmwkBeHli4XJaVyWxz8PnPHBT1vA2f71IUa/TqVRgoKoKuigq4rlYnhykWC2gcduIuDQzGtVmx+MtmeTnZNRpJsKqKrKtU5KVYfNQlld6YVCrBo9PBo56eJEAjkYAdLd9is7NmhMLEtk5HDvv6SMJuJ/tWK4mYzWRNKiXzhYVHXoOB9wIBy6OjSYCxpATcIhF0UFT7mkx2MRCjaRKtqyNhk4ns6PVkQ60mfj6fzNXVPXhbWQlrvwNorRYmi4vBq1K98mVnk6BGQ8J4hMh5xsEgQtcpiviYTLJqNi99qK6G4MREEnDHZIJ5vR5mtNo3/qwsspmTQ8IFBSSWn09iuGsI37fYbPIR19ZpejlgNEJ8ejoJuN/cDO/r6+GdxeL4xOORHXSxl5tLDgQCcojax1qIwyEbXC7ZsdudW42NcLywkAQM9fZCoL0dEh6P2KdUnoaxMYE7f0MX30WiC0gEgSty+elX7Nm1WuFHIJAE9NjtsOfxwG5LC8Td7hq/XH4WR/vHqBOEJDD7ZbKzz4ODNSGahj2XC1YWF5MAAd60w9VViPX3Q6S1FY69Xkmou9u13dCwHESFbDbXiccjiTY1QdRmg9OlJTAYDH/eRIVCAQc+HyTGxiCGjqIWC4TxrGH8NlGExjo7IT40BGfYU19be/n/cO5kwOmE11NTMDsyArPDw780Nz4OT7FWWlp6+fC/xk/27ybygyy2GgAAAABJRU5ErkJggg==) no-repeat center center; }",
+            "#code-content .lines .arrive-2{ background-color: #ffba93; color: #000; }",
+            "#plugin-watch-page{  }",
+            "#plugin-watch-page .header{ height: 126px; background-color: #fafafa; border-bottom: 1px solid #d5d5d5; }",
+            "#plugin-watch-page .header .title{ line-height: 42px; padding-left: 30px; }",
+            "#plugin-watch-page .header .control{ padding-left: 30px; height: 42px; }",
+            "#plugin-watch-page .header .control .btn{ margin-right: 5px; }",
+            "#plugin-watch-page .header .state{ padding-left: 30px; line-height: 42px; color: #666; }",
+            "#plugin-watch-page .header .state .text{  }",
+            "#plugin-watch-page .body{ position: absolute; left: 0; right: 0; top: 127px; bottom: 0; padding: 10px; background-color: #1d1e1a; }",
+            "#plugin-watch-page .body .logger{ line-height: 20px; color: #f6f6f6; }",
+            "#plugin-watch-page .body .logger .line{  }",
+            "#plugin-watch-page .body .logger .line .time{ display: inline-block; width: 80px; }",
+            "#plugin-watch-page .body .logger .line .content{  }"
+        ].join( "" ) );
+
+        template = {
+            page: tmpl( [
+                "<div class='header'>",
+                    "<div class='title'>点击开始监控,即可发现从监控开始到监控结束之间活动的代码</div>", // TODO: unicode
+                    "<div class='control'>",
+                        "<button id='<%= startBtnId %>' class='btn'>开始监控</button>",
+                        "<button id='<%= stopBtnId %>' class='btn disabled'>停止监控</button>",
+                        "<button id='<%= clearBtnId %>' class='btn disabled'>清除结果</button>",
+                    "</div>",
+                    "<div class='state' id='<%= stateId %>'>",
+                        "<%= stateHtml %>",
+                    "</div>",
+                "</div>",
+                "<div class='body scrollable'>",
+                    "<div class='logger' id='<%= logerId %>'>",
+                    "</div>",
+                "</div>"
+            ].join( "" ) ),
+
+            state: tmpl( [
+                "<span class='text' style='color: <%= color %>;'><%= text %></span>",
+            ].join( "" ) ),
+
+            logLine: tmpl( [
+                "<span class='time'><%= time %></span>",
+                "<span class='content'><%= content %></span>"
+            ].join( "" ) )
+        };
+
+        activeCodeCache = function(){
+            var cache = {};
+
+            return {
+                add: function( id ){
+                    if( !cache[ id ] )
+                        cache[ id ] = 1;
+                },
+
+                get: function( id ){
+                    return cache[ id ];
+                },
+
+                clear: function(){
+                    for( var i in cache )
+                        delete cache[ i ];
+                }
+            }
+        }();
+
+        showState = function( stateText, color ){
+            color = color || "#999";
+            document.getElementById( stateId ).innerHTML = template.state( {
+                text: stateText,
+                color: color
+            } );
+        };
+
+        log = function( c ){
+            var t = time( new Date ), logerEl;
+            var line = document.createElement( "div" );
+            line.className = "line";
+            line.innerHTML = template.logLine( { time: t, content: c } );
+            logerEl = document.getElementById( logerId );
+            logerEl.appendChild( line );
+            logHtml = logerEl.innerHTML;
+        };
+
+        buttonEventBind = function( buttonId, buttonHandler ){
+            Tracker.Event.add( document.getElementById( buttonId ), "click", function(){
+                if( !Tracker.Util.hasClass( this, "disabled" ) )
+                    buttonHandler.call( null );
+            } );
+        };
+
+        buttonEnable = function( buttonId, enable ){
+            var button = document.getElementById( buttonId );
+
+            if( enable )
+                Tracker.Util.removeClass( button, "disabled" );
+            else
+                Tracker.Util.addClass( button, "disabled" );
+        };
+
+        new__tracker__ = function( groupId ){
+            var code;
+
+            Tracker.StatusPool.arrivedSnippetGroupPut( groupId );
+
+            if( !arrivedSnippetGroupCache[ groupId ] )
+                arrivedSnippetGroupCache[ groupId ] = 1;
+
+            if( code = Tracker.StatusPool.snippetGroupToCodeGet( groupId ) ){
+                if( activeCodeCache.get( code.id ) )
+                    return ;
+
+                activeCodeCache.add( code.id );
+
+                // TODO: unicode 编码
+                log( "发现新的活动代码在 " + ( code.fullUrl || code.fileName ) +
+                    "&nbsp; <a href='#' action='show-code' data-code-id='" + code.id +
+                    "' onclick='return false;'>查看</a>" );
+            }else{
+                log( "发现新的活动代码在 " + groupId );
+            }
+        };
+
+        start = function(){
+            var pageWindow = Tracker.View.ControlFrame.getWindow( "tracker_page" );
+            pageWindow.__tracker__ = new__tracker__;
+            Tracker.StatusPool.arrivedSnippetGroupFlagSet( 2 );
+            showState( "监控进行中...", "#c00" );
+            log( "监控开始" );
+            hooking = true;
+            stateEvent.fire( "stateChange", "start" );
+        };
+
+        stop = function(){
+            var pageWindow = Tracker.View.ControlFrame.getWindow( "tracker_page" );
+            pageWindow.__tracker__ = original__tracker__;
+            Tracker.StatusPool.arrivedSnippetGroupFlagSet( 1 );
+            showState( "已停止" );
+            log( "监控结束" );
+            activeCodeCache.clear();
+            hooking = false;
+            stateEvent.fire( "stateChange", "stop" );
+        };
+
+        clear = function(){
+            for( var groupId in arrivedSnippetGroupCache ){
+                Tracker.StatusPool.arrivedSnippetGroupDelete( groupId, 2 );
+                delete arrivedSnippetGroupCache[ groupId ];
+            }
+
+            document.getElementById( logerId ).innerHTML = logHtml = "";
+            log( "清除完成" );
+            buttonEnable( clearBtnId, false );
+        };
+
+        eventBuild = function(){
+            var topNav, titleEl;
+
+            Tracker.Event.add( document.getElementById( logerId ), "click", function( e ){
+                var target, codeId;
+
+                target = e.target;
+
+                if( target.getAttribute( "action" ) == "show-code" ){
+                    codeId = target.getAttribute( "data-code-id" );
+                    Tracker.View.ControlPanel.activeTab( "code-list" );
+                    Tracker.View.ControlPanel.showCodeDetail( codeId, 2 );
+                }
+            } );
+
+            topNav = document.getElementById( "top-nav" );
+
+            Tracker.Util.forEach( topNav.childNodes , function( li ){
+                if( li.getAttribute( "data-name" ) == "watch" ){
+                    titleEl = li;
+                    titleEl.setAttribute( "data-html-backup", li.innerHTML );
+                }
+            } );
+
+            topNav.tabEvent.on( "active", function( index, name ){
+                if( hooking ){
+                    if( name == "watch" ){
+                        titleEl.innerHTML = titleEl.getAttribute( "data-html-backup" );
+                    }else{
+                        titleEl.innerHTML = titleEl.getAttribute( "data-html-backup" )
+                            .replace( /<\/a>/i, "<span class='red-dot'>&#12288;</span></a>" );
+                    }
+                }
+            } );
+        };
+
+        buttonStateChange = function( state ){
+            if( state == "start" ){
+                buttonEnable( startBtnId, false );
+                buttonEnable( stopBtnId, true );
+                buttonEnable( clearBtnId, false );
+            }else if( state == "stop" ){
+                buttonEnable( startBtnId, true );
+                buttonEnable( stopBtnId, false );
+                buttonEnable( clearBtnId, true );
+            }
+        };
+
+        this.onStartUp( function( win, doc ){
+            window = win;
+            document = doc;
+
+            var pageWindow = Tracker.View.ControlFrame.getWindow( "tracker_page" );
+            original__tracker__ = pageWindow.__tracker__;
+
+            this.body.innerHTML = template.page( {
+                stateHtml: template.state( { text: "准备就绪", color: null } ),
+                startBtnId: startBtnId = Tracker.Util.id(),
+                stopBtnId: stopBtnId = Tracker.Util.id(),
+                clearBtnId: clearBtnId = Tracker.Util.id(),
+                stateId: stateId = Tracker.Util.id(),
+                logerId: logerId = Tracker.Util.id()
+            } );
+
+            buttonEventBind( startBtnId, start );
+            buttonEventBind( stopBtnId, stop );
+            buttonEventBind( clearBtnId, clear );
+
+            if( !started ){
+                log( "活动监视器初始化完成" );
+                log( "准备就绪" );
+
+                stateEvent.on( "stateChange", buttonStateChange );
+            }else{
+                if( hooking ){
+                    buttonStateChange( "start" );
+                    showState( "监控进行中...", "#c00" );
+                }else{
+                    buttonStateChange( "stop" );
+                }
+
+                if( logHtml )
+                    document.getElementById( logerId ).innerHTML = logHtml;
+            }
+
+            eventBuild();
+
+            started = true;
+        } );
+
+        this.onActive( function(){
+
+        } );
+    } );
+};

File diff suppressed because it is too large
+ 0 - 107
chrome/static/vendor/jquery-ui-1.8/css/jquery-ui-1.8.16.custom.blue_datauri.css


File diff suppressed because it is too large
+ 0 - 215
chrome/static/vendor/jquery-ui-1.8/css/jquery-ui-1.8.16.custom.color_datauri.css


+ 0 - 0
chrome/static/vendor/jquery-ui-1.8/css/jquery-ui-1.8.16.custom.hot_datauri.css → chrome/static/vendor/jquery-ui-1.8/jquery-ui.hot.css


+ 0 - 0
chrome/static/vendor/jquery-ui-1.8/js/jquery-ui-1.8.11.custom.min.js → chrome/static/vendor/jquery-ui-1.8/jquery-ui.min.js


+ 0 - 0
chrome/static/js/syntaxhighlighter/shBrushCss.js → chrome/static/vendor/syntaxhighlighter/shBrushCss.js


+ 0 - 0
chrome/static/js/syntaxhighlighter/shBrushJScript.js → chrome/static/vendor/syntaxhighlighter/shBrushJScript.js


+ 0 - 0
chrome/static/js/syntaxhighlighter/shBrushXml.js → chrome/static/vendor/syntaxhighlighter/shBrushXml.js


+ 0 - 0
chrome/static/js/syntaxhighlighter/shCore.js → chrome/static/vendor/syntaxhighlighter/shCore.js


File diff suppressed because it is too large
+ 0 - 0
chrome/template/fehelper_popup.html


Some files were not shown because too many files changed in this diff