Разговаривая с Миком на Tasktop о том, что потребуется для того, чтобы перенести его революционный продукт на базе Mylyn на платформу Mac, Мик указал мне на проблему с тем, как основанный на Safari виджет браузера SWT обращается к GMail. Хотя Safari является поддерживаемым браузером для GMail, встроенный браузер Safari в Eclipse не распознается GMail. Этот пост освещает взлеты и падения моего исследования по этой проблеме .
Другие в Tasktop обнаружили, что браузер SWT работает, если доступ к GMail осуществляется через http://mail.google.com/mail/?nocheckbrowser URL, который был многообещающим. Это привело меня к мысли, что заголовок user-agent встроенного Safari отличается от заголовка, созданного Safari с рабочего стола. Быстрый тестовый просмотр на http://whatsmyuseragent.com подтвердил, что мои подозрения верны.
Вот заголовки агента пользователя:
Safari Inside Eclipse:
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/523.12.2 (KHTML, like Gecko)
Автономное Сафари:
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/523.12.2 (KHTML, like Gecko) Version/3.0.4 Safari/523.12.2
Следующим шагом было посмотреть, предоставляет ли Safari API для изменения заголовка агента пользователя. Уже установив XCode в моей системе, было легко искать документацию по API. Я нашел раздел под названием «Spoofing», который посвящен API для изменения заголовка «user-agent»:
- setCustomUserAgent
- setApplicationNameForUserAgent
Судя по разнице в заголовках пользовательских агентов, второй был именно тем, что я искал.
Зная, что SWT включает в себя нативные звонки в Какао, я немного опасался следующего шага. Глядя на Safari.java
этот create()
метод, я мог видеть другие вызовы веб-представления Safari для настройки различных свойств. Все они были выполнены с использованием собственных вызовов JNI, одного из различных Cocoa.objc_msgSend
вариантов. К сожалению, не было той подписи, которая мне была нужна. Тот, который мне нужен был бы выглядеть так:
public static final native int objc_msgSend(int object, int
selector, String string);
Я добавил метод в Cocoa.java и добавил к нему вызов Safari.create()
следующим образом:
// [webView setApplicationNameForUserAgent:];Cocoa.objc_msgSend(webView, Cocoa.S_setApplicationNameForUserAgent,"Safari/unknown");
Следующим шагом является предоставление собственной реализации метода.
Используя команду поиска eclipse, я увидел, что другие реализации objc_msgSend были в cocoa.c Никогда раньше не выполнял JNI, пришло время немного осмотреться. В FAQ по SWT было большинство того, что мне было нужно, с указанием того, как создавать нативные библиотеки и т. Д. Сложные части были следующими:
- Как определить искаженное имя функции для нативного метода?
- теперь, чтобы преобразовать аргумент jstring в NSString?
По первому вопросу — немного догадаться и посмотреть на спецификацию JNI, где говорится о калечении, и я понял, что это правильно. Потушив метод, построив его (согласно инструкциям в FAQ по SWT), я получил java.lang.UnsatisfiedLinkError
. Запустив его снова в отладчике, я смог увидеть имя метода, который искал, и зафиксировал имя метода cocoa.c
. Перестройте, перезапустите, и пока все хорошо! Мой заглушенный метод вызывался всякий раз, когда создается новый элемент управления SWT Browser. Это то, что у меня было до сих пор:
#ifndef NO_objc_1msgSend__IILjava_lang_String_2 JNIEXPORT jint JNICALL Cocoa_NATIVE(objc_1msgSend__IILjava_lang_String_2) (JNIEnv *env, jclass that, jint arg0, jint arg1, jstring arg3) { jint rc = 0; return rc; } #endif
Теперь перейдем к реализации метода: как вызвать правильный метод? Быстрый google на jstring к NSString привел к следующему:
#ifndef NO_objc_1msgSend__IILjava_lang_String_2 JNIEXPORT jint JNICALL Cocoa_NATIVE(objc_1msgSend__IILjava_lang_String_2) (JNIEnv *env, jclass that, jint arg0, jint arg1, jstring arg3) { jint rc = 0; Cocoa_NATIVE_ENTER(env, that,<p>objc_1msgSend__IILjava_lang_String_2I_FUNC); const char *str = (*env)->GetStringUTFChars(env, arg3, 0); /* use NSString as follows: [NSString stringWithCString: str] */ (*env)->ReleaseStringUTFChars(env, arg3, str); Cocoa_NATIVE_EXIT(env, that, objc_1msgSend__IILjava_lang_String_2I_FUNC); return rc; } #endif
Хорошо, теперь все, что мне нужно было сделать — это отправить объекту сообщение Objective-C! Копирование из других примеров в том же файле, вот что я закончил:
#ifndef NO_objc_1msgSend__IILjava_lang_String_2 JNIEXPORT jint JNICALL Cocoa_NATIVE(objc_1msgSend__IILjava_lang_String_2) (JNIEnv *env, jclass that, jint arg0, jint arg1, jstring arg3) { jint rc = 0; Cocoa_NATIVE_ENTER(env, that, objc_1msgSend__IILjava_lang_String_2I_FUNC); const char *str = (*env)->GetStringUTFChars(env, arg3, 0); rc = (jint)objc_msgSend((id) arg0, (SEL)arg1, [NSString stringWithCString: str]); (*env)->ReleaseStringUTFChars(env, arg3, str); Cocoa_NATIVE_EXIT(env, that, objc_1msgSend__IILjava_lang_String_2I_FUNC); return rc; } #endif
Я восстановил нативные библиотеки, перезапустил затмение и вуаля! Это сработало! Теперь виджет браузера SWT Safari отправлял строку агента пользователя следующим образом:
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/523.12.2 (KHTML, like Gecko) Safari/unknown
Я вернулся к GMail с этим новым патчем, и теперь GMail распознал Safari, встроенный в eclipse, как поддерживаемый браузер. Для моего первого попытки взлома SWT с использованием JNI и Objective-C это был большой успех … теперь, если только команда SWT примет мой патч;)