一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

NPAPI插件開發(fā)詳細(xì)記錄:與JS交互

 quasiceo 2014-11-28

NPAPI插件開發(fā)詳細(xì)記錄:與JS交互

時(shí)間 2014-04-05 17:39:40 CSDN博客 原文  http://blog.csdn.net/z6482/article/details/22990419
        插件主要用于HTML頁面中增強(qiáng)HTML頁面可以支持的資源類型,在HTML頁面中最重要的一個(gè)特性就是可以利用腳本語言來實(shí)現(xiàn)與用戶的交互,之前的文章也提及過相關(guān)的議題,不過在交流過程中還是發(fā)現(xiàn)好多開發(fā)者對于這個(gè)主題有不太明白的地方,這里再次詳細(xì)介紹一下在插件中如何與JS進(jìn)行交互。

以前的文章可以與本文互為參考:腳本化接口、插件接口腳本化。

本文的例子代碼可以下載:無boost需安裝CMake、 原來的scriptable demo

插件對象

          可以在JS中使用document.getElementsByTagName或者document.getElementById來獲取頁面中已經(jīng)存在的插件對象,還可以在JS中使用document.createElement("object");來動(dòng)態(tài)創(chuàng)建對象,并為該對象設(shè)置type屬性,接著將創(chuàng)建的這個(gè)對象添加到頁面中,這樣就動(dòng)態(tài)創(chuàng)建了一個(gè)插件對象。如下JS函數(shù)可以根據(jù)傳入的mimetype創(chuàng)建一個(gè)插件對象(chrome、firefox測試有效,其他未測試):
function newNPObject(mimetype)
{
    var obj = document.createElement("object");
    obj.type = mimetype;
    document.body.appendChild(obj);
    return obj;
}
        那么瀏覽器是如何完成將插件轉(zhuǎn)換為JS能夠識(shí)別的對象的呢?我們發(fā)現(xiàn),在NPP_GetValue的實(shí)現(xiàn)中有:
if (variable==NPPVpluginScriptableNPObject)
{
    *(NPObject **)value = plugin->GetScriptableObject();
}
        也就是說,瀏覽器會(huì)調(diào)用NPP_GetValue (instance, NPPVpluginScriptableNPObject, value)并將來獲取插件的scriptable對象。進(jìn)一步看看plugin是如何獲取scriptable對象的:
NPObject* CPlugin::GetScriptableObject()
{
  if (!m_pScriptableObject) {
     m_pScriptableObject = NPN_CreateObject(m_pNPInstance, &CScriptObject::nsScriptObjectClass);
  }

  if (m_pScriptableObject) {
     NPN_RetainObject(m_pScriptableObject);
  }

  return m_pScriptableObject;
}
        對象存在時(shí)用NPN_RetainObject來獲取對象,對象不存在時(shí)用NPN_CreateObject來創(chuàng)建一個(gè)對象。
        當(dāng)我們在JS中設(shè)置/獲取屬性或者調(diào)用方法時(shí),都會(huì)在這個(gè)scriptable對象中操作,在使用結(jié)束時(shí)(CPlugin的析構(gòu)函數(shù)中)使用NPN_ReleaseObject(m_pScriptableObject);來釋放這個(gè)對象。
         簡單解釋一下對象是如何創(chuàng)建的(一般情況下我們可以不知道,只需要按照demo中的代碼使用就可以了,如果只想知道如何實(shí)現(xiàn)與JS的交互請?zhí)料乱徊糠郑纯碝DN上相關(guān)說明:
NPObject *NPN_CreateObject(NPP npp, NPClass *aClass);
     The function has the following parameters:
npp
     The NPP indicating which plugin wants to instantiate the object.
aClass
     The class to instantiate an object of.
          第一個(gè)參數(shù)很好搞定,第二個(gè)參數(shù)比較費(fèi)解,創(chuàng)建時(shí)傳入的&CScriptObject::nsScriptObjectClass實(shí)際上是基類nsScriptObjectBase的NPClass變量,結(jié)合說明可以知道,NPN_CreateObject是根據(jù)所傳入的NPClass類創(chuàng)建一個(gè)NPObject并返回這個(gè)對象的指針。NPN_CreateObject中調(diào)用NPClass類的NPAllocateFunctionPtr成員來為NPObject分配內(nèi)存,看到NPClass的NPAllocateFunctionPtr成員是nsScriptObjectBase::_Allocate函數(shù),該函數(shù)則是調(diào)用nsScriptObjectBase::AllocateScriptPluginObject來實(shí)現(xiàn)的,AllocateScriptPluginObject的實(shí)現(xiàn)在Plugincpp中,可以看到其實(shí)現(xiàn)代碼就是return (NPObject*)new CScriptObject(npp);也就是創(chuàng)建一個(gè)新的CScriptObject對象,這里繞過來繞過去這么復(fù)雜,其實(shí)就是要做這樣一件事情:我們設(shè)計(jì)scriptableobject類時(shí)會(huì)新建一個(gè)類,而基類nsScriptObjectBase卻需要用我們設(shè)計(jì)的scriptableobject類的構(gòu)造函數(shù)來分配內(nèi)存并轉(zhuǎn)換成NPObject,最終由NPP_GetValue返回給瀏覽器,JS實(shí)際上就是與瀏覽器獲取到的這個(gè)對象來交互的。
        仔細(xì)研究過npruntime代碼的人可能會(huì)發(fā)現(xiàn),npruntime中有一個(gè)很晦澀的宏DECLARE_NPOBJECT_CLASS_WITH_BASE及GET_NPOBJECT_CLASS,當(dāng)然從名稱可以知道是用基類聲明一個(gè)變量,并用GET_NPOBJECT_CLASS來引用這個(gè)變量,這就相當(dāng)于是我在基類中定義的nsScriptObjectClass。
        實(shí)現(xiàn)一個(gè)scriptable對象的類其實(shí)并不難,只需要從NPObject派生一個(gè)類并逐一實(shí)現(xiàn)NPClass中的幾個(gè)函數(shù)指針?biāo)枰暮瘮?shù)。這里搞得如此復(fù)雜就是為了能夠設(shè)計(jì)一個(gè)基類,并一勞永逸的不再修改這個(gè)基類。本章最后一個(gè)示例會(huì)給出實(shí)現(xiàn)一個(gè)最簡單的scriptable對象的例子。

屬性

        在JS中一個(gè)對象具有的屬性可以比較靈活的設(shè)置,比如一個(gè)對象obj本來不具有屬性kit,調(diào)用obj.kit會(huì)是undefined,然而當(dāng)我們設(shè)置obj.kit=some_val之后,再次調(diào)用obj.kit就會(huì)有相應(yīng)的屬性了。
        另一方面,在實(shí)現(xiàn)插件dll的代碼中(后文稱為C++代碼中),插件對象是一個(gè)派生自NPObject的對象,我們也可以很方便的為其設(shè)置成員變量,要在插件中實(shí)現(xiàn)與JS交互,那么就需要C++代碼中的屬性(變量)與JS中屬性能夠互相訪問。
        可以進(jìn)行交互的屬性分為一般屬性及只讀屬性,只讀屬性是對于JS來說的,畢竟插件中的代碼相對于JS來說是更加底層的,可以不允許JS修改C++中保持的變量,但若想要防止C++更改JS中的變量值卻是比較不現(xiàn)實(shí)的。
        從NPObject的定義可以看到,NPObject包括一個(gè)指向NPClass對象的指針和一個(gè)引用計(jì)數(shù)器。NPClass則由諸如hasProperty、hasMethod等函數(shù)指針。要實(shí)現(xiàn)一個(gè)可以與JS交互的插件,就需要實(shí)現(xiàn)hasProperty、hasMethod等接口。前文我們知道瀏覽器調(diào)用NPN_CreateObject創(chuàng)建scriptable對象,這里介紹我們在scriptable對象中實(shí)現(xiàn)可交互屬性。
         大概過程是這樣的:瀏覽器在獲取到scriptable對象之后,就會(huì)調(diào)用對象的hasProperty、hasMethod來判斷該對象是否具有某個(gè)屬性或方法,當(dāng)JS中訪問屬性或調(diào)用函數(shù)是就會(huì)調(diào)用scriptable對象的getProperty、involve等函數(shù)來獲取屬性值或者執(zhí)行函數(shù)。
         要設(shè)置一個(gè)屬性(這里以foo為例),首先需要定義一個(gè)NPIdentifier來方便保存屬性的標(biāo)識(shí),一般的插件中是設(shè)置為全局static變量,我將其設(shè)置為CScriptObject類的static成員變量,不設(shè)置為全局的,如下:
static NPIdentifier foo_id;
         類的中聲明的static變量并不會(huì)初始化,還需要在cpp文件中,對其初始化:
NPIdentifier CScriptObject::foo_id;
        另外我設(shè)置一個(gè)變量來保存屬性值,這個(gè)變量要CScriptObject類可以訪問,或者通過某個(gè)函數(shù)訪問,簡便起見直接設(shè)置為CScriptObject類的私有成員變量:
int m_vfoo;
         接下來在適當(dāng)?shù)奈恢脤@個(gè)id與要設(shè)置的屬性關(guān)聯(lián)起來,我選擇在CPlugin類的構(gòu)造函數(shù)中執(zhí)行:
CScriptObject::foo_id = NPN_GetStringIdentifier("foo");
         如前所述,瀏覽器會(huì)調(diào)用CScriptObject類的HasProperty來判斷是否具有某屬性,那么我們在HasProperty中如下實(shí)現(xiàn):
bool CScriptObject::HasProperty(NPIdentifier name)
{
  return name == foo_id;
}
        在JS中可以設(shè)置屬性,需要實(shí)現(xiàn)SetProperty
bool CScriptObject::SetProperty(NPIdentifier name, const NPVariant *value)
{
  if(name==foo_id){
    if(value->type == NPVariantType_Int32)
      m_vfoo = NPVARIANT_TO_INT32(*value);
    return true;
  }
}
         最后在GetProperty中返回屬性值:
bool CScriptObject::GetProperty(NPIdentifier name, NPVariant *result)
{
  if (name == foo_id)
  {
    INT32_TO_NPVARIANT(m_vfoo,*result);
    m_vfoo++;
    return true;
  }
}
         為了與JS中設(shè)置屬性值進(jìn)行區(qū)別,每次獲取之后,把屬性的值+1可以在JS中多次獲取該屬性值,發(fā)現(xiàn)每次獲取的值都會(huì)增加一個(gè),說明確實(shí)是獲取到了插件中設(shè)置的屬性。
          在JS中設(shè)置/獲取foo屬性,HTML中相應(yīng)代碼為:
property test:
      	int property<input id = "fooinput" value="0"></input>
      	<button onclick = "btnsetfoo();">set FOO</button>
      	<button onclick = "btngetfoo();">get FOO</button><br />
         JS代碼(片段)如下:
function btnsetfoo()
  {
    var val = document.getElementById("fooinput");
    obj.foo = parseInt(val.value) ;
  }
  function btngetfoo()
  {
    alert(obj.foo);
  }
        如果想實(shí)現(xiàn)只讀屬性,不實(shí)現(xiàn)SetProperty即可。

供JS調(diào)用的插件接口

        實(shí)現(xiàn)可以在JS中調(diào)用的接口,過程與屬性相似,這里以實(shí)現(xiàn)一個(gè)函數(shù)func為例,首先在CScriptObject類中聲明一個(gè)標(biāo)識(shí):
static NPIdentifier func_id;
        初始化:
NPIdentifier CScriptObject::func_id;
        接下來將這個(gè)id與要設(shè)置的函數(shù)名關(guān)聯(lián)起來:
CScriptObject::func_id = NPN_GetStringIdentifier("func");
       瀏覽器會(huì)調(diào)用CScriptObject類的HasMethod來判斷是否具有某個(gè)函數(shù),那么我們在HasMethod中如下實(shí)現(xiàn):
bool CScriptObject::HasMethod(NPIdentifier name)
{
  return name == func_id;
}
        JS中調(diào)用obj.func()時(shí),會(huì)執(zhí)行到Invoke,其中我們利用messagebox彈出一個(gè)消息框,實(shí)現(xiàn)如下:
bool CScriptObject::Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
  if (name == func_id)
  {
    MessageBox(NULL,_T("func"),_T(""),0);
    return true;
  }
  return false;
}
         在JS中調(diào)用func函數(shù),HTML中相應(yīng)代碼為:
<button onclick ="btnclick();" >FUNC</button>
         JS代碼(片段)如下:
function btnclick()
    {
    	obj.func();
    }

供插件調(diào)用的JS函數(shù)(JS callback)

        可以將JS函數(shù)作為回調(diào)供插件調(diào)用,假設(shè)我們需要插件調(diào)用JS函數(shù)如下:
function objJSfunc()
    {
      alert("JS function called!");
    }

        在JS中,函數(shù)其實(shí)也是一個(gè)object,那么如何將這個(gè)object傳遞給插件,并在插件中執(zhí)行呢?我們可以將這個(gè)object作為插件的一個(gè)屬性,在執(zhí)行的時(shí)候利用NPN_InvokeDefault來執(zhí)行,以下是完整過程:

       首先需要一個(gè)變量來保存這個(gè)JS函數(shù)或者函數(shù)的標(biāo)識(shí),JS函數(shù)作為一個(gè)對象,因此可以將其保存為一個(gè)NPObject對象,可以用全局變量也可以將其作為某個(gè)類的成員變量,我將這個(gè)NPObject作為CPlugin類的一個(gè)成員,聲明成員 變量:

NPObject* m_jsObj;
       在構(gòu)造函數(shù)中初始化為NULL:
m_jsObj = NULL;
       使用完畢之后需要釋放這個(gè)對象,我們在析構(gòu)函數(shù)中執(zhí)行:
if (m_jsObj!=NULL)
    NPN_ReleaseObject(m_jsObj);
       接下來,將JS函數(shù)作為一個(gè)屬性,與前文設(shè)置一般屬性是一樣的,先聲明一個(gè)標(biāo)識(shí):
static NPIdentifier jsfunc_id;
       初始化:
NPIdentifier CScriptObject::jsfunc_id;
      與要設(shè)置的屬性名稱關(guān)聯(lián)起來:
CScriptObject::jsfunc_id = NPN_GetStringIdentifier("OnJsFunc");
      接下來在HasProperty中:
bool CScriptObject::HasProperty(NPIdentifier name)
{
  return name == jsfunc_id;
}
      然后SetProperty:
bool CScriptObject::SetProperty(NPIdentifier name, const NPVariant *value)
{
  if (name == jsfunc_id)
  {
    CPlugin * plugin = (CPlugin*) m_npp->pdata;
    if (plugin->m_jsObj == NULL)
    {
      plugin->m_jsObj = NPN_RetainObject(NPVARIANT_TO_OBJECT(*value));
    }
    return true;
  }
}
         當(dāng)然這個(gè)就不需要實(shí)現(xiàn)GetProperty了。這樣就實(shí)現(xiàn)了JS回調(diào)函數(shù)的設(shè)置,只需要在JS中使用obj.OnJsFunc = objJSfunc;為其設(shè)置好需要調(diào)用的JS函數(shù)就可以了。
         設(shè)置好之后,就是在C++中如何調(diào)用這個(gè)JS函數(shù)了,要調(diào)用這個(gè)函數(shù),就需要訪問我們設(shè)置的m_jsObj,因此只要能夠訪問我們設(shè)置的這個(gè)變量的位置都可以執(zhí)行這個(gè)JS函數(shù),之前我們設(shè)置了一個(gè)func供JS調(diào)用,這里我們假設(shè)func執(zhí)行完畢之后需要調(diào)用我們設(shè)置的這個(gè)JS函數(shù),可以在func的執(zhí)行代碼最后加上調(diào)用JS函數(shù)的代碼,Invoke函數(shù)就變?yōu)槿缦滦问搅耍?
bool CScriptObject::Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
  if (name == func_id)
  {
    MessageBox(NULL,_T("func"),_T(""),0);
    CPlugin* plugin = (CPlugin*) m_npp->pdata;
    if (!(!plugin->m_jsObj))
    {
      NPVariant result;
      NPN_InvokeDefault(m_npp,plugin->m_jsObj,NULL,0,&result);
      NPN_ReleaseVariantValue(&result);
    }
    return true;
  }
  return false;
}
        以上就是設(shè)置JS回調(diào)的完整過程,與JS交互有關(guān)的話題可能還包括編碼的轉(zhuǎn)換,在遇到中文時(shí)處理不好可能會(huì)導(dǎo)致亂碼,只要記住一個(gè)原則就是JS處理的字符都是UTF-8編碼的,而C++中的字符可能是unicode的也可能是ansi的,因此需要根據(jù)實(shí)際情況進(jìn)行編碼的轉(zhuǎn)換就可以解決中文亂碼的問題。我給出的scriptdemo還包含:str屬性可以設(shè)置字符串類型的屬性,funci2i處理輸入為int輸出為int的函數(shù),funcs2s處理輸入為字符串輸出為字符串的函數(shù)。目前沒有發(fā)現(xiàn)有亂碼的問題,因此這里就不再對編碼轉(zhuǎn)換的話題做過多的介紹了,如果有朋友發(fā)現(xiàn)scriptdemo中有亂碼的問題,請及時(shí)反饋給我,需要的話以后再來補(bǔ)充。
接下來實(shí)現(xiàn)一個(gè)簡單的JS數(shù)組對象,可以說是一個(gè)簡化的scriptable對象的設(shè)計(jì)。

簡單的JS數(shù)組對象

        這里設(shè)計(jì)的一個(gè)JS數(shù)組對象是比較簡單的,因?yàn)槲以贑++中將數(shù)據(jù)保存好,然后以數(shù)組對象的方式返回到JS中,JS中只需要調(diào)用Size方法獲取數(shù)組中元素的個(gè)數(shù),并使用At方法獲取數(shù)組中的某一個(gè)元素。當(dāng)然網(wǎng)上可能有更完善的JS數(shù)組的實(shí)現(xiàn),或者你可以在這個(gè)代碼的基礎(chǔ)上完善更多的功能,在此僅貼出代碼。
頭文件:
class JsArrayObject : public NPObject
{
private:
  vector<std::wstring> array_;
  static NPIdentifier method_At;
  static NPIdentifier method_Size;

public:
  static NPClass nsJsObject;
  JsArrayObject();
  ~JsArrayObject(){}

public:
  std::wstring At(size_t idx); 
  size_t Size();
  void Clear();
  bool Empty();
  void PushBack(std::wstring val);
  static NPObject *_Allocate(NPP npp, NPClass *aClass);
  static void _Deallocate(NPObject *npobj);
  static void _Invalidate(NPObject *npobj);
  static bool _HasMethod(NPObject *npobj, NPIdentifier name);
  static bool _Invoke(NPObject *npobj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result);
  static bool _InvokeDefault(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result);
  static bool _HasProperty(NPObject *npobj, NPIdentifier name);
  static bool _GetProperty(NPObject *npobj, NPIdentifier name, NPVariant *result);
  static bool _SetProperty(NPObject *npobj, NPIdentifier name, const NPVariant *value);
  static bool _RemoveProperty(NPObject *npobj, NPIdentifier name);
  static bool _Enumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count);
  static bool _Construct(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result);
};
實(shí)現(xiàn)文件:
NPIdentifier JsArrayObject::method_At;
NPIdentifier JsArrayObject::method_Size;

JsArrayObject::JsArrayObject()
{
  method_At = NPN_GetStringIdentifier("At");
  method_Size = NPN_GetStringIdentifier("Size");
}

NPObject* JsArrayObject::_Allocate(NPP npp, NPClass *aClass)
{
  return (NPObject*)new JsArrayObject();
}

void JsArrayObject::_Deallocate(NPObject *npobj)
{
  delete (JsArrayObject*)npobj;
}

void JsArrayObject::_Invalidate(NPObject *npobj)
{

}

bool JsArrayObject::_HasMethod(NPObject *npobj, NPIdentifier name)
{
  return name == method_At || name ==method_Size ;
}

bool JsArrayObject::_Invoke(NPObject *npobj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
  if (name == method_At)
  {
    if(argCount < 1) return false;
    int val = args[0].value.intValue;
    std::wstring str = ((JsArrayObject*)npobj)->At(val);
    std::string pstr = ult::UnicodeToUtf8(str);
    char* npOutString = (char*) NPN_MemAlloc(pstr.length() + 1);
    if (!npOutString)
      return false;
    strcpy(npOutString, pstr.c_str());
    STRINGZ_TO_NPVARIANT(npOutString,*result);
    return true;
  }
  if (name == method_Size)
  {
    int val = ((JsArrayObject*)npobj)->Size();
    INT32_TO_NPVARIANT(val, *result);
    return true;
  }	
  return false;
}

bool JsArrayObject::_InvokeDefault(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
  return false;
}

bool JsArrayObject::_HasProperty(NPObject *npobj, NPIdentifier name)
{
  return false;
}

bool JsArrayObject::_GetProperty(NPObject *npobj, NPIdentifier name, NPVariant *result)
{
  return false;
}

bool JsArrayObject::_SetProperty(NPObject *npobj, NPIdentifier name, const NPVariant *value)
{
  return false;
}

bool JsArrayObject::_RemoveProperty(NPObject *npobj, NPIdentifier name)
{
  return false;
}

bool JsArrayObject::_Enumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count)
{
  return false;
}

bool JsArrayObject::_Construct(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
  return false;
}

NPClass JsArrayObject::nsJsObject = {
  NP_CLASS_STRUCT_VERSION_CTOR,
  JsArrayObject::_Allocate,
  JsArrayObject::_Deallocate,
  JsArrayObject::_Invalidate,
  JsArrayObject::_HasMethod,
  JsArrayObject::_Invoke,
  JsArrayObject::_InvokeDefault,
  JsArrayObject::_HasProperty,
  JsArrayObject::_GetProperty,
  JsArrayObject::_SetProperty,
  JsArrayObject::_RemoveProperty,
  JsArrayObject::_Enumerate,
  JsArrayObject::_Construct
};

std::wstring JsArrayObject::At(size_t idx)
{
  return array_.at(idx);
}

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多

    国产精品涩涩成人一区二区三区| 欧美二区视频在线观看| 日韩黄片大全免费在线看| 日本本亚洲三级在线播放| 国产精品国产亚洲看不卡| 久久国产成人精品国产成人亚洲| 最近的中文字幕一区二区| 国产肥妇一区二区熟女精品| 亚洲av熟女国产一区二区三区站| 日韩精品在线观看完整版| 国产日韩欧美在线亚洲| 久久热在线免费视频精品| 黄色片国产一区二区三区| 中文字幕高清免费日韩视频| 欧美激情中文字幕综合八区| 欧美精品亚洲精品一区| 好吊日成人免费视频公开| 99国产成人免费一区二区| 亚洲av成人一区二区三区在线| 蜜桃av人妻精品一区二区三区| 久久99爱爱视频视频| 一区二区欧美另类稀缺| 国产又粗又硬又长又爽的剧情| 日韩人妻有码一区二区| 欧美乱视频一区二区三区| 国产精品第一香蕉视频| 日韩成人动画在线观看| 日韩视频在线观看成人| 日本不卡一本二本三区| 大香蕉伊人精品在线观看| 老司机激情五月天在线不卡| 91欧美激情在线视频| 国产熟女高清一区二区| 五月婷婷六月丁香亚洲| 老司机精品国产在线视频| 日韩人妻精品免费一区二区三区| 亚洲一区在线观看蜜桃| 日本人妻精品中文字幕不卡乱码 | 国产精品午夜视频免费观看| 日韩一区二区三区高清在| 国产又大又硬又粗又黄|