• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            牽著老婆滿街逛

            嚴以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            基于 C 語言的 JavaScript 引擎探索

            轉載自:http://www.ibm.com/developerworks/cn/linux/l-cn-spidermonkey/index.html

            基礎知識

            SpiderMonkey 簡介

            和其他的 JavaScript 引擎一樣,SpiderMonkey 不直接提供像 DOM 這樣的對象,而是提供解析,執行 JavaSccript 代碼,垃圾回收等機制。SpidlerMonkey 是一個在 Mozilla 之下的開源項目,要使用 SpiderMonkey,需要下載其源碼,然后編譯為靜態 / 動態庫使用。

            要在自己的應用程序中使用 SpiderMonkey,首先需要了解以下三個核心概念:

            運行時環境運行時環境是所有 JavaScript 變量,對象,腳本以及代碼的上下文所存在的空間。每一個上下文對象,以及所有的對象均存在于此。一般應用僅需要一個運行時即可。

            上下文上下文即腳本執行的環境,在 SpiderMonkey 中,上下文可以編譯執行腳本,可以存取對象的屬性,調用 JavaScript 的函數,轉換類型,創建 / 維護對象等。幾乎所有的 SpiderMonkey 函數都需要上下文作為其第一個參數 (JSContext *)。

            上下文與線程密不可分,一般來講,單線程應用可以使用一個上下文來完成所有的操作,每一個上下文每次只能完成一個操作,所有在多線程應用中,同一時刻只能有一個線程來使用上下文對象。一般而言,多線程應用中,每個線程對應一個上下文。

            全局對象全局對象包含 JavaScript 代碼所用到的所有類,函數,變量。在 DOM 操作中,我們使用的:

             alter("something"); 
            

            事實上使用的是全局變量 window 的一個屬性 alter( 這個屬性正好是一個函數 ),事實上上邊的語句在執行時會別解釋為:

             window.alter("something"); 
            

            三者的關系如下圖所示:


            圖 1. 引擎內部結構依賴關系
            圖 1. 引擎內部結構依賴關系 

            安裝 SpiderMonkey

            首先從 SpiderMonkey 的代碼庫中下載其源碼包 js-1.7.0.tar.gz 本文在 Linux 環境下編譯,SpiderMonkey 的編譯安裝很容易:

             # 解壓縮
             tar xvzf js-1.7.0.tar.gz 
            
             # 切換至源碼目錄
             cd js-1.7.0/src 
            
             # 編譯
             make -f Makefile.ref 
            

            編譯完成之后,會生成一個新的目錄,這個目錄的名稱依賴于平臺,比如在 Linux 下,名稱為:Linux_All_DBG.OBJ,其中包含靜態鏈接庫 libjs.a 和動態鏈接庫 libjs.so 等。本文后續的編譯環境就需要依賴于我們此處編譯出來的庫文件。應該注意的是,此處編譯出來的庫文件包含對調試的支持,體積較大,在應用程序發布時,可以去掉這些調試支持,使用下列重新編譯庫:

             # 創建非 debug 模式的庫
             make BUILD_OPT=1 -f Makefile.ref 
            

            Windows 及其他平臺的編譯此處不再贅述,讀者可以自行參考 SpiderMonkey 的官方文檔。

            JavaScript 對象與 C 對象間的轉換關系

            JavaScript 是一門弱類型的語言,變量的值的類型在運行時才確定,而且可以在運行時被修改為其他類型的變量;而 C 語言,是一門靜態類型的語言,變量類型在編譯時就已經確定。因此,這兩者之間變量的互訪就有了一定的難度,SpiderMonkey 提供了一個通用的數據類型 jsval 來完成兩者之間的交互。

            事實上,在 C 代碼中定義的 jsval 類型的變量可以是 JavaScript 中的字符串,數字,對象,布爾值,以及 null 或者 undefined。基于這個類型,SpiderMonkey 提供了大量的類型判斷及類型轉換的宏和函數。可以參看下表:


            表 1. JavaScript 對象與 C 對象轉換表
            JavaScript 類型 jsval 類型判斷 jsval 常量 jsval 轉化
            null JSVAL_IS_NULL(v) JSVAL_NULL
            Undefined JSVAL_IS_VOID(v) JSVAL_VOID
            Boolean JSVAL_IS_BOOLEAN(v) JSVAL_TRUE, 
            JSVAL_FALSE, 
            BOOLEAN_TO_JSVAL(b)
            JSVAL_TO_BOOLEAN(v)
            number JSVAL_IS_NUMBER(v), 
            JSVAL_IS_INT(v), 
            JSVAL_IS_DOUBLE(v)
            INT_TO_JSVAL(i), 
            DOUBLE_TO_JSVAL(d)
            JSVAL_TO_INT(v), 
            JSVAL_TO_DOUBLE(v)
            string JSVAL_IS_STRING(v) STRING_TO_JSVAL(s) JSVAL_TO_STRING(v), 
            JS_GetStringChars(s), 
            JS_GetStringLength(s)
            object JSVAL_IS_OBJECT(v) 
            && JSVAL_IS_NULL(v)
            OBJECT_TO_JSVAL(o) JSVAL_TO_OBJECT(v)

            應該注意的是,jsval 有一定的缺陷:

            • jsval 并非完全的類型安全,在進行類型轉換之前,你需要明確被轉換的對象的真正類型,比如一個變量的值為 number 類型,而對其做向字符串的轉化,則可能引起程序崩潰。解決方法是,在轉換之前,先做判斷。
            • jsval 是 SpiderMonkey 垃圾回收機制的主要目標,如果 jsval 引用一個 JavaScript 對象,但是垃圾收集器無法得知這一點,一旦該對象被釋放,jsval 就會引用到一個懸空指針。這樣很容易使得程序崩潰。解決方法是,在引用了 JavaScript 對象之后,需要顯式的告知垃圾收集器,不引用時,再次通知垃圾收集器。

            簡單示例

            基本代碼模板

            基本流程

            使用 SpiderMonkey,一般來講會使用以下流程:

            • 創建運行時環境
            • 創建一個 / 多個上下文對象
            • 初始化全局對象
            • 執行腳本,處理結果
            • 釋放引擎資源

            在下一小節詳細說明每個流程

            代碼模板

            使用 SpiderMonkey,有部分代碼是幾乎每個應用程序都會使用的,比如錯誤報告,初始化運行時環境,上下文,全局變量,實例化全局變量等操作。這里是一個典型的模板:


            清單 1. 必須包含的頭文件
            				
            #include "jsapi.h"
            

            引入 jsapi.h,聲明引擎中的所用到的記號,結構體,函數簽名等,這是使用 SpiderMonkey 所需的唯一一個接口文件 ( 當然,jsapi.h 中不可能定義所有的接口,這些文件在 jsapi.h 頭部引入 jsapi.h,如果對 C 語言的接口,頭文件引入方式不熟悉的讀者,請參閱相關資料 )。


            清單 2. 全局變量聲明
            				
             /* 全局變量的類聲明 */ 
            static JSClass global_class = { 
                "global", 
                JSCLASS_GLOBAL_FLAGS, 
                JS_PropertyStub, 
                JS_PropertyStub, 
                JS_PropertyStub, 
                JS_PropertyStub, 
                JS_EnumerateStub, 
                JS_ResolveStub, 
                JS_ConvertStub, 
                JS_FinalizeStub, 
                JSCLASS_NO_OPTIONAL_MEMBERS 
             }; 
            

            JSClass 是一個較為重要的數據結構,定義了 JavaScript 對象的基本結構 ---“類”,這個類可以通過 SpiderMonkey 引擎來實例化為對象。JS_PropertyStub 是 JS_PropertyOp 類型的變量,這里的 JS_PropertyStub 是為了提供一個默認值。JS_PropertyOp 可以用做對象的 setter/getter 等的,這些內容我們將在后邊的章節詳細討論。


            清單 3. 錯誤處理函數
            				
             /* 錯誤處理函數,用于回調,打印詳細信息 */ 
             void report_error(JSContext *cx,  const char *message, JSErrorReport *report){ 
                fprintf(stderr, "%s:%u:%s\n", 
                		 report->filename ? report->filename : "<no filename>", 
                        (unsigned int) report->lineno, 
                        message); 
             } 
            

            定義好這些結構之后,我們需要實例化這些結構,使之成為內存對象,流程如下:


            清單 4. 主流程
            				
            				int main(int argc, char *argv[]){ 
                JSRuntime *runtime; 
                JSContext *context; 
                JSObject *global; 
            
            	 // 創建新的運行時 8M 
                runtime = JS_NewRuntime(8L * 1024L * 1024L); 
                if (runtime == NULL){ 
                    return -1; 
                } 
            
            	 // 創建新的上下文
                context = JS_NewContext(runtime, 8*1024); 
                if (context == NULL){ 
                    return -1; 
                } 
            
            	 // 
                JS_SetOptions(context, JSOPTION_VAROBJFIX); 
                // 設置錯誤回調函數 , report_error 函數定義如上
            	 JS_SetErrorReporter(context, report_error); 
            
            	 // 創建一個新的 JavaScript 對象
                global = JS_NewObject(context, &global_class, NULL, NULL); 
                if (global == NULL){ 
                    return -1; 
                } 
            
            	 // 實例化 global, 加入對象,數組等支持
                if (!JS_InitStandardClasses(context, global)){ 
                    return -1; 
                } 
            
            	 // 
            	 // 使用 global, context 等來完成其他操作,用戶定制代碼由此開始
            	 // 
            
            	 // 釋放上下文對象
                JS_DestroyContext(context); 
            	 // 釋放運行時環境
                JS_DestroyRuntime(runtime); 
            	 // 停止 JS 虛擬機
                JS_ShutDown(); 
            
            	 return 0; 
             } 
            

            用戶自己的代碼從上邊代碼中部注釋部分開始,用戶代碼可以使用此處的 context 對象及預設過一定屬性,方法的 global 對象。

            執行 JavaScript 代碼

            執行 JavaScript 代碼片段

            執行 JS 最簡單的方式,是將腳本作為字符串交給引擎來解釋執行,執行完成之后釋放臨時的腳本對象等。SpiderMonkey 提供一個 JS_EvaluateScript 函數,原型如下:


            清單 5. 執行 JS 代碼的函數原型
            				
             JSBool JS_EvaluateScript(JSContext *cx, JSObject *obj, 
                const char *src, uintN length, const
            				char *filename, 
                uintN lineno, jsval *rval); 
            

            使用這個函數,需要提供上下文,全局變量,字符串形式的腳本,腳本長度及返回值指針,腳本名和行號參數可以填空值 ( 分別為 NULL 和 0)。如果函數返回 JS_TRUE,表示執行成功,執行結果存放在 rval 參數中,否則執行失敗,rval 中的值為 undefined。我們可以具體來看一個例子:


            清單 6. 執行 JS 代碼片段
            				
            				char *script = "(function(a, b){return a * b;})(15, 6);"; 
            	 jsval rval; 
            
            	 status = JS_EvaluateScript(context, global, script, strlen(script)\ 
            	        , NULL, 0, &rval); 
            
            	 if (status == JS_TRUE){ 
            	    jsdouble d; 
            	    JS_ValueToNumber(context, rval, &d); 
            	    printf("eval result = %f\n", d); 
            	 } 
            

            執行結果為:

             eval result = 90.000000 
            

            編譯 JavaScript 代碼

            通常,我們可能會多次執行一段腳本,SpiderMonkey 可以將腳本編譯成 JSScript 對象,然后可以供后續的多次調用。現在來看一個例子,使用 C 代碼編譯一個 JavaScript 腳本,然后運行這個腳本。


            清單 7. 從文件加載并執行腳本
            				
             JSBool evalScriptFromFile(JSContext *cntext, const
            	char *file){ 
                JSScript *script; 
                JSString *jss; 
                JSBool status; 
                jsval value; 
            
                //get the global object 
                JSObject *global = JS_GetGlobalObject(context); 
            
                //compile the script for further using 
                script = JS_CompileFile(context, global, file); 
            
                if (script == NULL){ 
                    return JS_FALSE; 
                } 
            
                //execute it once 
                status = JS_ExecuteScript(context, global, script, &value); 
                jss = JS_ValueToString(context, value); 
                printf("eval script result is : %s\n", JS_GetStringBytes(jss)); 
            
                //destory the script object 
                JS_DestroyScript(context, script); 
            
                return status; 
             } 
            

            這里傳遞給函數 evalScriptFromFile的 JSContext* 參數為外部創建好的 Context 對象,創建的方法參看上一節。


            清單 8. 執行
            				
             JSBool status = evalScriptFromFile(context, "jstest.js"); 
             if (status == JS_FALSE){ 
                    fprintf(stderr, "error while evaluate the script\n"); 
             } 
            

            假設我們將如下腳本內容保存進一個腳本 jstest.js:


            清單 9. jstest.js 腳本內容
            				
            				varPerson = function(name){ 
                var _name_ = name; 
                this.getName = function(){ 
                    return _name_; 
                } 
            
                this.setName = function(newname){ 
                    _name_ = newname; 
                } 
             } 
            
             varjack = new Person("jack"); 
             jack.setName("john"); 
             // 最后一句將作為腳本的執行結果返回給 C 代碼
             jack.getName(); 
            

            jack 對象的名字現在設置為了”john”, 腳本的最后一條語句的值將作為腳本的返回值返回到 C 代碼處,并打印出來:

             eval script result is : john 
            

            C 與 JavaScript 的交互

            C 程序調用 JavaScript 函數

            由于兩者的數據類型上有較大的差異,因此無法直接從 C 代碼中調用 JavaScript 代碼,需要通過一定的轉化,將 C 的變量轉換為 JavaScript 可以設別的變量類型,然后進行參數的傳遞,返回值的處理也同樣要經過轉換。

            我們在 JavaScript 中定義一個函數 add,這個函數接受兩個參數然后返回傳入的兩個參數的和。定義如下:


            清單 10. JavaScript 版本的 add
            				
            				function add(x, y){ 
                return x + y; 
             } 
            

            然后,我們在 C 語言中根據名稱調用這個 JS 函數:


            清單 11. 從 C 代碼中調用 JavaScript 函數
            				
             JSBool func_test(JSContext *context){ 
                jsval res; 
                JSObject *global = JS_GetGlobalObject(context); 
                jsval argv[2]; 
            
                //new 2 number to pass into the function "add" in script 
                JS_NewNumberValue(context, 18.5, &res); 
                argv[0] = res; 
                JS_NewNumberValue(context, 23.1, &res); 
                argv[1] = res; 
            
                JS_CallFunctionName(context, global, "add", 2, argv, &res); 
            
                jsdouble d; 
            
                //convert the result to jsdouble 
                JS_ValueToNumber(context, res, &d); 
                printf("add result = %f\n", d); 
            
                return JS_TRUE; 
             } 
            

            這里需要注意的是,JS_CallFunctionName 函數的參數列表:


            清單 12. JS_CallFunctionName 原型
            				
             JSBool  JS_CallFunctionName(JSContext *cx, JSObject *obj, 
             const char *name, uintN argc, jsval *argv, jsval *rval); 
            


            表 2. JS_CallFunctionName 參數列表含義
            名稱 類型 類型描述
            cx JSContext * 上下文定義
            obj JSObject * 調用該方法的對象
            name const char * 函數名
            argc uintN 函數參數個數
            argv jsval * 函數實際參數形成的數組
            rval jsval * 返回值

            參數中的 argv 是一個 jsval 形成的數組,如果直接傳遞 C 類型的值,則很容易出現 core dump(Linux 下的段錯誤所導致 ),因此,需要 JS_NewNumberValue 函數轉換 C 語言的 double 到 number( 原因見對象轉換小節 )。

            JavaScript 程序調用 C 函數

            從 JS 中調用 C 函數較上一節為復雜,我們來看一個較為有趣的例子:SpiderMonkey 中原生的 JavaScript 的全局變量中沒有 print 函數,我們可以使用 C 的 printf 來實現這個功能。我們定義了一個函數 print, print 使用 logging 函數,而 logging 函數是定義在 C 語言中的,接受一個字符串作為參數,打印這個字符串到標準輸出上 :


            清單 13. JavaScript 調用 C 函數
            				
             //log user log in information 
             logging("user jack login on 2010/7/6"); 
            
             //user do nothing else 
             nothing(); 
            
             //log user log out information 
             logging("user jack logout on 2010/7/7"); 
            
             function print(){ 
                for (vari = 0; i < arguments.length; i++){ 
                    logging(arguments[i]); 
                } 
             } 
            
             print("hello", "all", "my", "friend"); 
            

            在 C 語言中,我們定義 logging 函數和 nothing 函數的原型如下:


            清單 14. C 函數的實現
            				
             /** 
             * define an exposed function to be used in scripts 
             * print out all the incoming arguments as string. 
             */ 
             static JSBool  logging(JSContext *context, JSObject *object, uintN argc, 
                    jsval *argv, jsval *value){ 
                int i = 0; 
                JSString *jss; 
            
                for(i = 0; i < argc; i++){ 
                   jss = JS_ValueToString(context, argv[i]); 
                   printf("message from script environment : %s\n", \ 
                           JS_GetStringBytes(jss)); 
                } 
                return JS_TRUE; 
             } 
            
             /** 
             * define an exposed function to be used in scripts 
             * do nothing but print out a single line. 
             */ 
             static JSBool  nothing(JSContext *context, 
             JSObject *object, uintN argc, jsval *argv, jsval *value) 
             { 
                printf("got nothing to do at all\n"); 
                return JS_TRUE; 
             } 
            

            從函數的簽名上可以看出,C 中暴露給 JS 使用的函數,參數的個數,及對應位置上的類型,返回值都是固定的。所有的從 C 中暴露給 JS 的函數都需要“實現這個接口”。

            定義好了函數之后,還需要一些設置才能在 JS 中使用這些函數。首先定義一個 JSFunctionSpec 類型的數組,然后通過 JS_DefineFunctions 將這些函數放到 global 對象上,然后在 JS 代碼中就可以訪問上邊列出的 C 函數了。具體步驟如下:


            清單 15. 注冊函數列表
            				
            	static JSFunctionSpec functions[] = { 
                {"logging", logging, LOG_MINARGS, 0, 0}, 
                {"nothing", nothing, NOT_MINARGS, 0, 0}, 
                {0, 0, 0, 0, 0} 
             }; 
            
                //define function list here 
                if (!JS_DefineFunctions(context, global, functions)){ 
                    return -1; 
             } 
            

            運行結果如下:

             message from script environment : user jack login on 2010/7/6 
             got nothing to do at all 
             message from script environment : user jack logout on 2010/7/7 
             message from script environment : hello 
             message from script environment : all 
             message from script environment : my 
             message from script environment : friend 
            

            在 C 程序中定義 JavaScript 對象

            在 SpiderMonkey 中,在 JavaScript 中使用由 C 語言定義的對象較為復雜,一旦我們可以定義對象,使得兩個世界通過 JS 交互就變得非常簡單而有趣,很容易使用這樣的方式來定制我們的應用,在系統發布之后仍然可以輕松的修改系統的行為。

            首先,我們要定義好基本的數據結構,即我們要暴露給 JS 世界的對象的屬性,結構;然后,使用 JSAPI 定義這個對象的屬性;然后,使用 JSAPI 定義對象的方法;最后,創佳這個對象,并綁定其屬性表和方法列表,放入全局對象。

            假設我們有這樣一個數據結構,用來表述一個人的簡單信息 :


            清單 16. PersionInfo 結構
            				
             typedef struct{ 
                char name[32]; 
                char addr[128]; 
             }PersonInfo; 
            

            定義屬性表為枚舉類型:


            清單 17. 屬性表
            				
            enum person{ 
                NAME, 
                ADDRESS
             }; 
            

            我們需要將 C 語言原生的數據結構定義為 JSAPI 所識別的那樣:


            清單 18. 屬性定義
            				
             //define person properties 
             JSPropertySpec pprops[] = { 
                {"name", NAME, JSPROP_ENUMERATE}, 
                {"address", ADDRESS, JSPROP_ENUMERATE}, 
                {0} 
             }; 
            


            清單 19. 方法定義
            				
             //define person methods 
             JSFunctionSpec pfuncs[] = { 
                {"print", printing, 0}, 
                {"getName", getName, 0}, 
                {"setName", setName, 0}, 
                {"getAddress", getAddress, 0}, 
                {"setAddress", setAddress, 0}, 
                {0} 
             }; 
            


            清單 20. 類定義
            				
             //define person class (JSClass) 
             JSClass pclass = { 
                "person", 0, 
                JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, 
                JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub 
             }; 
            

            一旦這些基本信息定義好 (pfuncs 數組中的 getter/setter 的實現比較簡單,這里由于篇幅不列出代碼,感興趣的朋友可以參看附錄 ),我們就可以實例化它,并將其放入上下文中,使得 JS 代碼可以訪問。


            清單 21. 定義對象及對象的屬性,方法
            				
                JSObject *person; 
            
                //define the object 
                person = JS_DefineObject(\ 
                        context, global, "person", &pclass, 0, JSPROP_ENUMERATE); 
            
                //install the properties and methods on the person object 
                JS_DefineProperties(context, person, pprops); 
                JS_DefineFunctions(context, person, pfuncs); 
            

            這樣,在 JavaScript 代碼中,我們就可以通過 person 這個標識符來訪問 person 這個對象了:


            清單 22. 測試腳本
            				
             //undefined of course 
             person.print(); 
            
             //person.name = "abruzzi"; 
             //person.address = "Huang Quan Road"; 
            
             person.setName("Desmond"); 
             person.setAddress("HuangQuan Road"); 
            
             //print is global function, access properties directly 
             print("person name = " + person.name); 
             print("person address = " + person.address); 
            
             person.print(); 
            
             (function(){ 
                //using getter/setter to access properties 
                return person.getName() + " : " + person.getAddress(); 
             })(); 
            

            對運行結果如下:

             name : undefined
             address : undefined
             person name = Desmond 
             person address = HuangQuan Road 
             name : Desmond 
             address : HuangQuan Road 
             eval script result is : Desmond : HuangQuan Road 
            

            結束語

            本文中詳細討論了如何使用基于 C 的 JavaScript 引擎:SpiderMonkey 的用法。包括最基本的代碼模板,C 代碼與 JavaScript 代碼之間的交互,以及在 C 代碼中定義 JavaScript 對象等內容,使用這些基本概念,很容易將實現應用程序的腳本化。在實際的應用中,可以將應用程序的部分組件 ( 提供給用戶自定義的組件 ) 暴露給 JavaScript 訪問,或者在 JavaScript 腳本中提供函數的存根 ( 僅僅定義函數的原型 ),用戶可以通過實現這些函數的具體邏輯,來實現腳本化。


            下載

            描述 名字 大小 下載方法
            樣例代碼 jsfun.zip 8KB HTTP

            關于下載方法的信息


            參考資料

            學習

            討論

            關于作者

            邱俊濤,畢業于昆明理工大學計算機科學與技術專業,對機械控制、電子、人工智能、函數式編程等領域有濃厚的興趣,對計算機科學的底層比較熟悉。喜歡 C/JAVA/Python/JavaScript 等語言。現就職于云電同方研發中心。

            posted on 2011-07-25 19:19 楊粼波 閱讀(1364) 評論(0)  編輯 收藏 引用

            精品久久久久久久无码| 久久久久国产日韩精品网站| 亚洲国产精久久久久久久| 麻豆亚洲AV永久无码精品久久| 亚洲国产小视频精品久久久三级| 激情久久久久久久久久| 99久久夜色精品国产网站| 国产精品99久久久久久人| MM131亚洲国产美女久久| 久久综合中文字幕| 国产精品成人久久久久久久| 久久精品人妻一区二区三区| 久久精品国产清自在天天线| 污污内射久久一区二区欧美日韩| 内射无码专区久久亚洲| 久久毛片一区二区| 欧美黑人激情性久久| 国产亚洲欧美成人久久片 | 2020最新久久久视精品爱| 精品国产一区二区三区久久| 精品久久久久久国产免费了| 亚洲欧洲中文日韩久久AV乱码| 久久人妻少妇嫩草AV蜜桃| 99国产欧美精品久久久蜜芽 | 久久狠狠色狠狠色综合| 久久精品国产亚洲Aⅴ香蕉| 久久青青草视频| 9久久9久久精品| 99久久综合国产精品免费| 久久精品九九亚洲精品| 久久国产精品免费一区二区三区| 一本久久精品一区二区| 久久精品中文闷骚内射| 久久久久久久久久免免费精品 | 久久99国产亚洲高清观看首页| 国产—久久香蕉国产线看观看 | 99久久无色码中文字幕| 日批日出水久久亚洲精品tv| 久久影院综合精品| 久久精品国产免费观看三人同眠| 欧美777精品久久久久网|