Dajbych.net


Jak vytvářet doplňky pro Internet Explorer, díl 2

, 13 minut čtení

V po­kra­čo­vání článku o vy­tvá­ření do­plňků pro IE si uká­žeme, jak přistu­po­vat ke co­o­kies pro­hlí­žeče a ne­nu­tit uži­va­tele při­hla­šo­vat se zvlášť na we­bové stránky a zvlášť do do­plňku pro­hlí­žeče. Déle jak lze po­sílat HTTP po­ža­davky, ode­sílat data na ser­ver a při­jí­mat od­po­vědi, a to asyn­chronně bez blo­ko­vání vlá­ken. A také jak lze ma­ni­pulo­vat s ob­sahem we­bové stránky, která je v pro­hlí­žeči zrovna ote­vřená, po­mocí API já­dra IE.

Čtení cookies

K vět­šině úkonů na dneš­ním webu je po­třeba znát iden­ti­fi­ká­tor uži­va­tele.

Akce do­plňku pro­hlí­žeče spo­jené s pro­fi­lem uži­va­tele vy­ža­dují, aby byl uži­va­tel do svého pro­filu při­hlá­šen. Iden­ti­fi­ká­tor uži­va­tele je pak ulo­žen v co­o­kie pro­hlí­žeče. Nej­jed­no­dušší způ­sob, jak ho zís­kat, je pře­čtení sou­boru, do kte­rého In­ter­net Ex­plo­rer co­o­kie ukládá. Je­den zá­znam v něm vy­padá ně­jak takto:

webauthtoken w7tkNt...Q%3D%3D moje.domena.cz/ 1536 1882420992 30229098 2616903031 30155471 *

Al­go­rit­mus si nej­prve vy­žádá co­o­kie sou­bor po­ža­do­vané do­mény a poté v něm hledá to­ken, který ser­ver pře­vádí na iden­ti­fi­ká­tor uži­va­tele. Hodnota to­kenu je ulo­žena pod klí­čem we­bauth­to­ken.

// read the token String^ tokenValue; String^ cookies = Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::ApplicationData), L"Microsoft\\Windows\\Cookies"); for each (String^ file in Directory::GetFiles(cookies, L"*@www.contoso[*.txt", SearchOption::TopDirectoryOnly)) { bool value = false; String^ cookieValue = nullptr; for each (String^ line in File::ReadLines(file)) { if (value) { tokenValue = line; break; } else if (line == _T("webauthtoken")) { value = true; } } }

V zá­vis­losti na jed­no­znač­nosti ná­zvu do­mény druhého řádu je po­třeba kon­t­ro­lo­vat správný ná­zev do­mény u sa­motné hod­noty. Ná­zev sou­boru totiž ne­ob­sahuje do­ménu prv­ního řádu. Po­kud je ná­zev do­mény ro­zumný, vět­ši­nou není problém. V ně­kterých pří­pa­dech může být ale ná­zev sou­boru cel­kem di­voký. Je proto dů­le­žité podí­vat se do složky jak vlastně IE sou­bor s co­o­kie vaší do­mény vlastně po­jme­no­vává.

To­ken ser­ver ob­drží spolu s po­ža­dav­kem a pře­vede ho na iden­ti­fi­ká­tor uži­va­tele. Na­pří­klad po­kud ser­ver ob­drží to­ken v POST da­tech a po­u­žívá Win­dows Live ID, vy­padá zpra­co­vání takto:

string wlid; var token = context.Request.Form["webauthtoken"]; var wll = new WindowsLiveLogin(true); var user = wll.ProcessToken(token); if (user != null) { wlid = user.Id; } else { ... return; }

Úplně od­lišná si­tuace na­stává, po­kud je va­ším cílem pře­číst co­o­kies právě na­čtené stránky. K tomu je k dis­po­zici pa­t­řičné API:

void STDMETHODCALLTYPE CMyButton::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { HRESULT hr = S_OK; // Query for the IWebBrowser2 interface. CComQIPtr<IWebBrowser2> spTempWebBrowser = pDisp; // Is this event associated with the top-level browser? if (spTempWebBrowser && m_spWebBrowser && m_spWebBrowser.IsEqualObject(spTempWebBrowser)) { // Get the current document object from browser... CComPtr<IDispatch> spDispDoc; hr = m_spWebBrowser->get_Document(&spDispDoc); if (SUCCEEDED(hr)) { // ...and query for an HTML document. CComQIPtr<IHTMLDocument2> spHTMLDoc = spDispDoc; if (spHTMLDoc != NULL) { BSTR cookie; HRESULT hr = spHTMLDoc->get_cookie(&cookie); MessageBox(NULL, cookie, L"Cookie", 0); } } } }

Má to ovšem zcela zá­sadní nevýhodu. Spo­čívá v tom, že stránka z do­mény, jejíž co­o­kies chceme pře­číst, musí být zrovna na­čtená. Do­pl­něk pro­hlí­žeče na strán­kách www.con­toso.com by tedy znal pro­fil uži­va­tele až v mo­mentě, kdy by uži­va­tel na stránky za­vítal. Do té doby by do­pl­něk ne­fun­go­val. Proto je vhodné za úče­lem zís­kání iden­ti­fi­kátoru uži­va­tele pře­číst kon­krétní co­o­kie sou­bor.

Po­kud do­pl­něk ne­na­jde ani co­o­kie v sou­boru, pak uži­va­tel není na stránce www.con­toso.com při­hlá­šen. V tom pří­padě stačí je­den Message­Box, který uži­va­tel vy­zve, aby se na stránky při­hlá­sil. Uži­va­tel vět­ši­nou ro­zumí tomu, co se po něm chce a na stránku se při­hlá­sit umí. Ve srov­nání s vlast­ním při­hla­šo­va­cím for­mulá­řem do­plňku pak není stre­so­ván pro něj do­sud ne­známým uži­va­tel­ským roz­hra­ním. A hlavní výhoda je, že se ten for­mulář vů­bec ne­musí pro­gra­mo­vat.

Ukáz­kový skript ke sta­žení: Do­plňky pro IE – co­o­kies

Komunikace se serverem

Ko­mu­ni­kace se ser­ve­rem může být pro do­pl­něk pro­hlí­žeče stejně dů­le­žitá, jako pro we­bo­vou stránku. V tomto díle si uká­žeme, jak jed­no­duchým způ­so­bem s vyu­ži­tím C++/CLI po­slat po­ža­da­vek ser­veru a jak ob­dr­žet od­po­věď. Zá­ro­veň si vy­svět­líme, jak to udě­lat asyn­chronně a ne­blo­ko­vat tak vlákna.

Ode­sílání dat na ser­ver je nej­jed­no­dušší re­a­li­zo­vat přes .NET, pro­tože stačí kód roz­dě­lit do tří me­tod, aby byl asyn­chronní. Nej­prve je po­třeba za­ra­fe­ren­co­vat kni­hovnu Systém.Web, která HTTP ko­mu­ni­kaci za­jiš­ťuje. Kniho­ven je po­třeba re­fe­ren­co­vat co nejméně. V pří­padě ob­jektu po­moc­níka pro­hlí­žeče je­jich na­čítání do paměti zdr­žuje jeho spouš­tění.

První me­toda, která se volá vždy, když je po­třeba na ser­ver ně­jaká data po­slat, má na sta­rosti ote­vření stre­amu po­ža­davku. Pro po­ho­dlné zpra­co­vání dat jsem zvo­lil si­mulaci HTML for­muláře.

void MyNetClass::Exec() { try { // prepare web request HttpWebRequest^ request = static_cast<HttpWebRequest^>( WebRequest::Create(L"http://www.contoso.com/api.ashx")); request->Method = L"POST"; request->ContentType = L"application/x-www-form-urlencoded"; request->BeginGetRequestStream(gcnew AsyncCallback(GetRequestStreamCallback), request); } catch (System::Exception^ ex) { pin_ptr<const wchar_t> mgs = PtrToStringChars(ex->Message); MessageBox(NULL, mgs, _T("IE Toolbar Button"), 0); } }

Text po­ža­davku ob­sahuje jed­not­livé hod­noty. V tomto pří­padě we­bauth­to­ken k iden­ti­fi­kaci uži­va­tele a action k iden­ti­fi­kaci toho, co že to má vlastně ser­ver s po­ža­dav­kem dě­lat. Me­toda Ur­lEn­code upraví ře­tě­zec pro po­u­žití v textu po­ža­davku. U pa­ra­me­tru action není po­u­žita, pro­tože před­po­klá­dám, že bude nabý­vat jen hod­not de­fi­no­va­ných v rámci or­ga­ni­zace.

Nej­prve se vy­tvoří ře­tě­zec data, který se ná­sledně vepíše do stre­amu. To je v po­řádku jen pro kratší ře­tězce (a pře­hledné ukázky zdro­jo­vých kódů). Ode­sílání vět­šího ob­jemu dat (na­pří­klad ob­sahu sou­boru) tímto způ­so­bem už ale v po­řádku není. Tam je po­třeba vepi­so­vat jed­not­livé kusy dat po kous­kách přímo do stre­amu.

void MyNetClass::GetRequestStreamCallback(IAsyncResult^ asynchronousResult) { try { HttpWebRequest^ request = static_cast<HttpWebRequest^>(asynchronousResult->AsyncState); Stream^ stream = request->EndGetRequestStream(asynchronousResult); // prepare data String^ data = L"webauthtoken=" + HttpUtility::UrlEncode(tokenValue) + L"&action= ActionName"; array<unsigned char>^ post = Encoding::ASCII->GetBytes(data); stream->Write(post, 0, post->Length); stream->Close(); // send the request request->BeginGetResponse(gcnew AsyncCallback(GetResponseCallback), request); } catch (System::Exception^ ex) { pin_ptr<const wchar_t> mgs = PtrToStringChars(ex->Message); MessageBox(NULL, mgs, _T("IE Toolbar Button"), 0); } }

Po­slední me­toda má na sta­rosti zpra­co­vání od­po­vědi ser­veru. Mám ve zvyku vrá­tit ře­tě­zec OK, po­kud vše proběhlo bez chyby. V opač­ném pří­padě vrá­tím přímo text chyby. Po­kud od­po­věď ser­veru ob­sahuje data ke zpra­co­vání, je na místě zjiš­ťo­vání chyby z HTTP sta­tus kódu. Tady už ale zá­leží pře­de­vším na va­šich zvyk­los­tech.

void MyNetClass::GetResponseCallback(IAsyncResult^ asynchronousResult) { try { // read the response HttpWebRequest^ request = static_cast<HttpWebRequest^>(asynchronousResult->AsyncState); HttpWebResponse^ response = static_cast<HttpWebResponse^>(request->EndGetResponse(asynchronousResult)); Stream^ stream = response->GetResponseStream(); StreamReader^ reader = gcnew StreamReader(stream); String^ answer = reader->ReadToEnd(); if (answer != L"OK") throw gcnew Exception(answer); stream->Close(); reader->Close(); response->Close(); } catch (System::Exception^ ex) { pin_ptr<const wchar_t> mgs = PtrToStringChars(ex->Message); MessageBox(NULL, mgs, _T("IE Toolbar Button"), 0); } }

Je možné však vyu­žít ce­lou řadu dal­ších mož­ností. Na­pří­klad we­bové služby, kom­po­nenty Ma­naged Ex­tensi­bi­lity Fra­meworku, při­po­jení do SQL Ser­veru, za­ří­zení při­po­jená do PC, zkrátka celý .NET Fra­mework.

Ukáz­kový skript ke sta­žení: Do­plňky pro IE – ko­mu­ni­kace

Manipulace s DOM

Na zá­věr si uká­žeme, jak přistu­po­vat k sa­motné we­bové stránce, ma­ni­pulo­vat s její struk­tu­rou a mě­nit kaská­dové styly. Pro­tože se jedná přímo o API já­dra IE, ke kte­rému lze přistu­po­vat jen po­mocí C++, ma­naged kód nám ten­to­krát ne­může ni­jak po­moci.

Nej­prve si uká­žeme, jak do­cí­lit toho, aby po na­čtení stránky zmi­zely všechny ob­rázky. K tomu je po­třeba vy­tvo­řit ob­jekt po­moc­níka pro­hlí­žeče. Opět se jme­nuje CMyBut­ton, ale jen proto, aby byly ukázky zdro­jo­vých kódů kom­pa­ti­bilní na­příč celým se­ri­á­lem. Po­kud není třída za­re­gis­tro­vána zá­ro­veň jako roz­šíření pro­hlí­žeče, nemá s tla­čít­kem nic spo­leč­ného.

void STDMETHODCALLTYPE CMyButton::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { HRESULT hr = S_OK; // Query for the IWebBrowser2 interface. CComQIPtr<IWebBrowser2> spTempWebBrowser = pDisp; // Is this event associated with the top-level browser? if (spTempWebBrowser && m_spWebBrowser && m_spWebBrowser.IsEqualObject(spTempWebBrowser)) { // Get the current document object from browser... CComPtr<IDispatch> spDispDoc; hr = m_spWebBrowser->get_Document(&spDispDoc); if (SUCCEEDED(hr)) { // ...and query for an HTML document. CComQIPtr<IHTMLDocument2> spHTMLDoc = spDispDoc; if (spHTMLDoc != NULL) { // Finally, remove the images. RemoveImages(spHTMLDoc); } } } }

Pro­tože na­čtení do­ku­mentu může proběh­nout i v rámci a typ do­ku­mentu vů­bec ne­musí být HTML či XHTML, pro­vede se nej­prve ně­ko­lik kon­t­rol. Ná­sledně se za­volá me­toda Re­mo­ve­I­mages, která se po­stará o sa­motné skrytí ob­rázků.

void CMyButton::RemoveImages(IHTMLDocument2* pDocument) { CComPtr<IHTMLElementCollection> spImages; // Get the collection of images from the DOM. HRESULT hr = pDocument->get_images(&spImages); if (hr == S_OK && spImages != NULL) { // Get the number of images in the collection. long cImages = 0; hr = spImages->get_length(&cImages); if (hr == S_OK && cImages > 0) { for (int i = 0; i < cImages; i++) { CComVariant svarItemIndex(i); CComVariant svarEmpty; CComPtr<IDispatch> spdispImage; // Get the image out of the collection by index. hr = spImages->item(svarItemIndex, svarEmpty, &spdispImage); if (hr == S_OK && spdispImage != NULL) { // First, query for the generic HTML element interface... CComQIPtr<IHTMLElement> spElement = spdispImage; if (spElement) { // ...then ask for the style interface. CComPtr<IHTMLStyle> spStyle; hr = spElement->get_style(&spStyle); // Set display="none" to hide the image. if (hr == S_OK && spStyle != NULL) { static const CComBSTR sbstrNone(L"none"); spStyle->put_display(sbstrNone); } } } } } } }

Do­cu­ment ob­ject mo­del, mno­hem čas­těji však zkrá­ceně DOM, je struk­tura, která po­pi­suje we­bo­vou stránku. HTML či XHTML je nej­prve pře­ve­deno na DOM. Ten se ná­sledně pře­vádí na dis­play tree, na kte­rém jsou apli­ko­vány veš­keré kaská­dové styly. Te­prve ten se pak ren­de­ruje. DOM je re­pre­zen­to­ván roz­hra­ním IHTML­Do­cu­ment2.

Roz­hraní HTML do­ku­mentu zpřístup­ňuje me­todu get_images, která vrací všechny ob­rázky. Přes ně se poté ite­ruje, při­čemž se jed­not­livé po­ložky ko­lekce pře­vádí na roz­hraní IHTMLE­le­ment. K CSS není nutné přistu­po­vat přes atri­buty ele­mentu a pra­co­vat s CSS v tex­to­vém re­žimu. Místo toho je možné po­u­žít roz­hraní IHTML­Style, které umož­ňuje na­sta­vit přímo jed­not­livé vlast­nosti.

Roz­hraní IHTML­Do­cu­ment2 má me­tody na vrá­cení ak­tiv­ního ele­mentu, vy­tvo­ření no­vého a ote­vření okna. Po­sky­tuje udá­losti na stisk­nutí klá­vesy, tla­čítka myši, ozna­čení textu, najetí myši na ele­ment a mnoho dal­šího. Je k dis­po­zici plus mí­nus vše, co je do­stupné z Ja­vaScriptu. Podle toho, jak se API s no­věj­šími ver­zemi IE roz­ši­řuje, je možné dí­vat se na DOM přes nová roz­hraní. IE9 tak má už IHTML­Do­cu­ment7.

A ko­lik že těch roz­hraní, které In­ter­net Ex­plo­rer nabízí, vlastně je? De­vátá verze jich má přes pět set. Ne­celá stovka při­byla s de­vá­tou verzí kvůli SVG. Ale na­pří­klad Ca­n­vas jich má jen sedm, Xml­Htt­pRequest pět. Na­prostá vět­šina roz­hraní je však pro HTML. Celý je­jich se­znam i s do­ku­men­tací je sa­mo­zřejmě k dis­po­zici na MSDN Lib­rary.

Ukáz­kový skript ke sta­žení: Do­plňky pro IE – ma­ni­pulace s DOM

Závěr

Po­kud vás ně­kdy na­padla funkce, kte­rou by stálo za to mít v pro­hlí­žeči, ale pro­gra­mo­vat kvůli ní celý pro­hlí­žeč nemá cenu, je roz­šíření pro­hlí­žeče nej­snazší způ­sob, jak si něco odzkou­šet. In­ter­net Ex­plo­rer totiž ne­nabízí jen okno, do kte­rého se vy­kres­lují we­bové stránky (i když i to je samo o sobě ob­rov­ským pří­no­sem), ale po­sky­tuje plat­formu pro zpra­co­vání we­bo­vých strá­nek.

Člá­nek byl se­psán pro Zdro­ják.