Dajbych.net


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

, 11 minut čtení

V pokračování článku o vytváření do­plňků pro IE si ukážeme, jak přis­tupo­vat ke cook­ies proh­lížeče a nenu­tit uži­vatele přih­lašo­vat se zvlášť na we­bové stránky a zvlášť do do­plňku proh­lížeče. Déle jak lze posílat HTTP poža­davky, odesílat data na server a při­jí­mat od­povědi, a to asyn­chronně bez blokování vláken. A také jak lze ma­nip­ulo­vat s ob­sa­hem we­bové stránky, která je v proh­lížeči zrovna otevřená, po­mocí API já­dra IE.

Čtení cookies

K většině úkonů na dnešním webu je potřeba znát iden­ti­fiká­tor uži­vatele.

Akce do­plňku proh­lížeče spo­jené s pro­filem uži­vatele vyžadují, aby byl uži­va­tel do svého pro­filu přih­lášen. Iden­ti­fiká­tor uži­vatele je pak uložen v cookie proh­lížeče. Ne­j­jedn­odušší způ­sob, jak ho získat, je přečtení souboru, do kterého In­ter­net Ex­plorer cookie uk­ládá. Je­den záz­nam v něm vy­padá ně­jak takto:

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

Al­gorit­mus si ne­jprve vyžádá cookie sou­bor požadované domény a poté v něm hledá to­ken, který server převádí na iden­ti­fiká­tor uži­vatele. Hodnota to­kenu je uložena pod klíčem we­bau­th­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­noz­načnosti názvu domény druhého řádu je potřeba kon­trolo­vat správný název domény u samotné hod­noty. Název souboru totiž neob­sahuje doménu prvního řádu. Pokud je název domény rozumný, větši­nou není prob­lém. V něk­terých pří­padech může být ale název souboru celkem di­voký. Je proto důležité podí­vat se do složky jak vlastně IE sou­bor s cookie vaší domény vlastně po­j­men­ovává.

To­ken server ob­drží spolu s poža­davkem a převede ho na iden­ti­fiká­tor uži­vatele. Napřík­lad pokud server ob­drží to­ken v POST dat­ech a používá Win­dows Live ID, vy­padá zpra­cová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ě odlišná si­tu­ace nastává, pokud je vaším cílem přečíst cook­ies právě načtené stránky. K tomu je k dis­pozici patř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 domény, je­jíž cook­ies chceme přečíst, musí být zrovna načtená. Do­pl­něk proh­lížeče na stránkách www.con­toso.com by tedy znal pro­fil uži­vatele až v mo­mentě, kdy by uži­va­tel na stránky zaví­tal. Do té doby by do­pl­něk ne­fun­go­val. Proto je vhodné za účelem získání iden­ti­fikátoru uži­vatele přečíst konkrétní cookie sou­bor.

Pokud do­pl­něk ne­na­jde ani cookie v souboru, pak uži­va­tel není na stránce www.con­toso.com přih­lášen. V tom pří­padě stačí je­den Mes­sage­Box, který uži­va­tel vyzve, aby se na stránky přih­lásil. Uži­va­tel větši­nou rozumí tomu, co se po něm chce a na stránku se přih­lásit umí. Ve srovnání s vlast­ním přih­lašo­vacím for­mulářem do­plňku pak není stresován pro něj do­sud neznámým uži­va­tel­ským rozhraním. A hlavní výhoda je, že se ten for­mulář vůbec ne­musí pro­gramo­vat.

Ukázkový skript ke stažení: Do­plňky pro IE – cook­ies

Komunikace se serverem

Ko­mu­nikace se serverem může být pro do­pl­něk proh­lížeče ste­jně důležitá, jako pro we­bovou stránku. V tomto díle si ukážeme, jak jedn­oduchým způ­sobem s využitím C++/CLI poslat poža­davek serveru a jak ob­držet od­pověď. Zároveň si vysvětlíme, jak to udělat asyn­chronně a nebloko­vat tak vlákna.

Odesílání dat na server je ne­j­jedn­odušší re­al­i­zo­vat přes .NET, pro­tože stačí kód rozdělit do tří me­tod, aby byl asyn­chronní. Ne­jprve je potřeba zaraferen­co­vat kni­hovnu Sys­tém.Web, která HTTP ko­mu­nikaci za­jišťuje. Kni­hoven je potřeba ref­eren­co­vat co ne­jméně. V pří­padě ob­jektu po­moc­níka proh­lížeče je­jich načítání do paměti zdržuje jeho spouštění.

Doplňky

První me­toda, která se volá vždy, když je potřeba na server ně­jaká data poslat, má na starosti otevření streamu poža­davku. Pro po­hodlné zpra­cování dat jsem zvo­lil sim­u­laci 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­notlivé hod­noty. V tomto pří­padě we­bau­th­to­ken k iden­ti­fikaci uži­vatele a ac­tion k iden­ti­fikaci toho, co že to má vlastně server s poža­davkem dělat. Me­toda Ur­lEn­code upraví řetězec pro použití v textu poža­davku. U parametru ac­tion není použita, pro­tože před­pok­ládám, že bude nabý­vat jen hod­not defi­no­vaných v rámci or­ga­ni­zace.

Ne­jprve se vytvoří řetězec data, který se následně vepíše do streamu. To je v pořádku jen pro kratší řetězce (a přeh­ledné ukázky zdro­jových kódů). Odesílání většího ob­jemu dat (napřík­lad ob­sahu souboru) tímto způ­sobem už ale v pořádku není. Tam je potřeba vepiso­vat jed­notlivé kusy dat po kouskách přímo do streamu.

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); } }

Poslední me­toda má na starosti zpra­cování od­povědi serveru. Mám ve zvyku vrátit řetězec OK, pokud vše proběhlo bez chyby. V opačném pří­padě vrátím přímo text chyby. Pokud od­pověď serveru ob­sahuje data ke zpra­cování, je na místě zjišťování chyby z HTTP sta­tus kódu. Tady už ale záleží přede­vším na vašich zvyk­lostech.

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 celou řadu dalších možností. Napřík­lad we­bové služby, kom­po­nenty Man­aged Ex­ten­si­bil­ity Frame­worku, připo­jení do SQL Serveru, zařízení připo­jená do PC, zkrátka celý .NET Frame­work.

Ukázkový skript ke stažení: Do­plňky pro IE – ko­mu­nikace

Manipulace s DOM

Na závěr si ukážeme, jak přis­tupo­vat k samotné we­bové stránce, ma­nip­ulo­vat s její struk­turou a měnit kaská­dové styly. Pro­tože se jedná přímo o API já­dra IE, ke kterému lze přis­tupo­vat jen po­mocí C++, man­aged kód nám ten­tokrát nemůže ni­jak po­moci.

Ne­jprve si ukážeme, jak docílit toho, aby po načtení stránky zmizely všechny obrázky. K tomu je potřeba vytvořit ob­jekt po­moc­níka proh­lížeče. Opět se jmenuje CMy­But­ton, ale jen proto, aby byly ukázky zdro­jových kódů kom­pat­i­bilní napříč celým ser­iálem. Pokud není třída zareg­istro­vána zároveň jako rozšíření proh­lížeče, nemá s tlačítkem nic společ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í doku­mentu může proběh­nout i v rámci a typ doku­mentu vůbec ne­musí být HTML či XHTML, provede se ne­jprve něko­lik kon­trol. Následně se zavolá me­toda Re­moveIm­ages, která se postará o samotné skrytí obrá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); } } } } } } }

Doc­u­ment ob­ject model, mno­hem častěji však zkrá­ceně DOM, je struk­tura, která popisuje we­bovou stránku. HTML či XHTML je ne­jprve převe­deno na DOM. Ten se následně převádí na dis­play tree, na kterém jsou ap­likovány vešk­eré kaská­dové styly. Teprve ten se pak ren­deruje. DOM je reprezen­tován rozhraním IHTML­Doc­u­ment2.

Rozhraní HTML doku­mentu zpřís­tupňuje me­todu get_im­ages, která vrací všechny obrázky. Přes ně se poté iteruje, přičemž se jed­notlivé položky kolekce převádí na rozhraní IHTM­LEle­ment. K CSS není nutné přis­tupo­vat přes atributy el­e­mentu a pra­co­vat s CSS v tex­tovém režimu. Místo toho je možné použít rozhraní IHTML­Style, které umožňuje nas­tavit přímo jed­notlivé vlast­nosti.

Rozhraní IHTML­Doc­u­ment2 má me­tody na vrá­cení ak­tivního el­e­mentu, vytvoření nového a otevření okna. Posky­tuje události na stisknutí klávesy, tlačítka myši, oz­načení textu, na­jetí myši na el­e­ment a mnoho dalšího. Je k dis­pozici plus mínus vše, co je dos­tupné z JavaScriptu. Po­dle toho, jak se API s novějšími verzemi IE rozšiřuje, je možné dí­vat se na DOM přes nová rozhraní. IE9 tak má už IHTML­Doc­u­ment7.

A ko­lik že těch rozhraní, které In­ter­net Ex­plorer nabízí, vlastně je? De­vátá verze jich má přes pět set. Necelá stovka přibyla s de­vá­tou verzí kvůli SVG. Ale napřík­lad Can­vas jich má jen sedm, Xml­Htt­pRe­quest pět. Naprostá většina rozhraní je však pro HTML. Celý je­jich sez­nam i s doku­men­tací je samozře­jmě k dis­pozici na MSDN Li­brary.

Ukázkový skript ke stažení: Do­plňky pro IE – ma­nip­u­lace s DOM

Závěr

Pokud vás někdy na­padla funkce, kterou by stálo za to mít v proh­lížeči, ale pro­gramo­vat kvůli ní celý proh­lížeč nemá cenu, je rozšíření proh­lížeče ne­js­nazší způ­sob, jak si něco odzk­oušet. In­ter­net Ex­plorer totiž nen­abízí jen okno, do kterého se vykres­lují we­bové stránky (i když i to je samo o sobě obrovským přínosem), ale posky­tuje plat­formu pro zpra­cování we­bových stránek.

Článek byl sep­sán pro Zdro­ják.