Skip to content

Commit ed38347

Browse files
authored
Added webview header support (#35)
* Added webview header support for Android * Set custom headers on iOS * Clear headers when a URL is opened in a webview * review fix
1 parent 44d5f07 commit ed38347

File tree

8 files changed

+209
-24
lines changed

8 files changed

+209
-24
lines changed

.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ Thumbs.db
99
.cproject
1010
builtins
1111
docs/_site
12-
.editor_settings
13-
debug.keystore
14-
debug.keystore.pass.txt
12+
/.editor_settings
1513
manifest.private.der
1614
manifest.public.der
15+
debug.keystore
16+
debug.keystore.pass.txt

main/main.gui_script

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ local function webview_callback(self, webview_id, request_id, type, data)
2424

2525
elseif type == webview.CALLBACK_RESULT_URL_LOADING then
2626
print("CALLBACK_RESULT_URL_LOADING")
27+
pprint(data)
2728
-- a page is loading
2829
-- return false to prevent it from loading
2930
-- return true or nil to continue loading the page
@@ -87,7 +88,13 @@ function on_input(self, action_id, action)
8788
dirtylarry:button("button_open", action_id, action, function()
8889
if not webview_exists(self) then return end
8990

90-
webview.open(self.webview_id, "https://www.google.com")
91+
local options = {
92+
headers = {
93+
["Accept-Origin"] = "*",
94+
["User-Agent"] = "Foobar 1.0",
95+
}
96+
}
97+
webview.open(self.webview_id, "https://www.google.com", options)
9198
end)
9299

93100
dirtylarry:button("button_eval", action_id, action, function()

webview/api/webview.script_api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@
111111
- name: hidden
112112
type: boolean
113113
desc: If true, the webview will stay hidden (default=false)
114+
- name: headers
115+
type: table
116+
desc: A table of header keys and values
114117
- name: transparent
115118
type: boolean
116119
desc: If true, the webview background will be transparent (default=false)

webview/src/java/com/defold/webview/WebViewJNI.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,24 @@
2020
import android.widget.LinearLayout;
2121
import android.webkit.ConsoleMessage;
2222
import android.webkit.JavascriptInterface;
23+
import android.webkit.MimeTypeMap;
2324
import android.webkit.WebChromeClient;
2425
import android.webkit.WebView;
2526
import android.webkit.WebViewClient;
2627
import android.webkit.WebSettings;
28+
import android.webkit.WebResourceRequest;
29+
import android.webkit.WebResourceResponse;
2730

2831
import android.graphics.PixelFormat;
2932
import android.graphics.Color;
3033

3134
import android.util.Log;
3235

36+
import java.util.Map;
37+
import java.util.HashMap;
38+
import java.net.HttpURLConnection;
39+
import java.net.URL;
40+
3341

3442
public class WebViewJNI {
3543

@@ -55,6 +63,7 @@ private static class CustomWebViewClient extends WebViewClient {
5563
private String continueLoadingUrl;
5664
private WebViewJNI webviewJNI;
5765
private String PACKAGE_NAME;
66+
private Map<String, String> extraHeaders = new HashMap<>();
5867

5968
// This is a hack to counter for the case where the load() experiences an error
6069
// In that case, the onReceivedError will be called, and onPageFinished will be called TWICE
@@ -70,6 +79,19 @@ public CustomWebViewClient(Activity activity, WebViewJNI webviewJNI, int webview
7079
reset(-1);
7180
}
7281

82+
public void addHeader(final String header, final String value) {
83+
if (value == null) {
84+
extraHeaders.remove(header);
85+
}
86+
else {
87+
extraHeaders.put(header, value);
88+
}
89+
}
90+
91+
public void clearHeaders() {
92+
extraHeaders.clear();
93+
}
94+
7395
private String trimTrailingSlash(String url) {
7496
if (url.endsWith("/")) {
7597
url = url.substring(0, url.length() - 1);
@@ -98,6 +120,36 @@ private boolean shouldContinueLoadingUrl(String url) {
98120
return trimTrailingSlash(continueLoadingUrl).equals(trimTrailingSlash(url));
99121
}
100122

123+
@Override
124+
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
125+
final String method = request.getMethod();
126+
final String url = request.getUrl().toString();
127+
String ext = MimeTypeMap.getFileExtensionFromUrl(url);
128+
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
129+
130+
try {
131+
HttpURLConnection conn = (HttpURLConnection)new URL(url).openConnection();
132+
conn.setRequestMethod(method);
133+
for (String header : extraHeaders.keySet()) {
134+
String value = extraHeaders.get(header);
135+
conn.setRequestProperty(header, value);
136+
}
137+
conn.setDoInput(true);
138+
conn.setUseCaches(false);
139+
140+
return new WebResourceResponse(
141+
mime,
142+
conn.getContentEncoding(),
143+
conn.getInputStream()
144+
);
145+
146+
}
147+
catch (Exception e) {
148+
Log.e(TAG, "shouldInterceptRequest: " + e);
149+
}
150+
return null;
151+
}
152+
101153
@Override
102154
public boolean shouldOverrideUrlLoading(WebView view, String url) {
103155
if( url.startsWith(PACKAGE_NAME) )
@@ -353,6 +405,14 @@ public void run() {
353405
});
354406
}
355407

408+
public void addHeader(final int webview_id, final String header, final String value) {
409+
WebViewJNI.this.infos[webview_id].webviewClient.addHeader(header, value);
410+
}
411+
412+
public void clearHeaders(final int webview_id) {
413+
WebViewJNI.this.infos[webview_id].webviewClient.clearHeaders();
414+
}
415+
356416
public void loadRaw(final String html, final int webview_id, final int request_id, final int hidden, final int transparent) {
357417
this.activity.runOnUiThread(new Runnable() {
358418
@Override

webview/src/webview_android.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ struct WebViewExtensionState
6868
jmethodID m_SetVisible;
6969
jmethodID m_IsVisible;
7070
jmethodID m_SetPosition;
71+
jmethodID m_AddHeader;
72+
jmethodID m_ClearHeaders;
7173
dmMutex::HMutex m_Mutex;
7274
dmArray<WebViewCommand> m_CmdQueue;
7375
};
@@ -178,6 +180,7 @@ int Platform_Eval(lua_State* L, int webview_id, const char* code)
178180
JNIEnv* env = threadAttacher.GetEnv();
179181
jstring jcode = env->NewStringUTF(code);
180182
env->CallVoidMethod(g_WebView.m_WebViewJNI, g_WebView.m_Eval, jcode, webview_id, request_id);
183+
env->DeleteLocalRef(jcode);
181184
return request_id;
182185
}
183186

@@ -217,6 +220,28 @@ int Platform_SetPosition(lua_State* L, int webview_id, int x, int y, int width,
217220
return 0;
218221
}
219222

223+
int Platform_ClearHeaders(lua_State* L, int webview_id)
224+
{
225+
CHECK_WEBVIEW_AND_RETURN();
226+
dmAndroid::ThreadAttacher threadAttacher;
227+
JNIEnv* env = threadAttacher.GetEnv();
228+
env->CallVoidMethod(g_WebView.m_WebViewJNI, g_WebView.m_ClearHeaders, webview_id);
229+
return 0;
230+
}
231+
232+
int Platform_AddHeader(lua_State* L, int webview_id, const char* header, const char* value)
233+
{
234+
CHECK_WEBVIEW_AND_RETURN();
235+
dmAndroid::ThreadAttacher threadAttacher;
236+
JNIEnv* env = threadAttacher.GetEnv();
237+
jstring jheader = env->NewStringUTF(header);
238+
jstring jvalue = env->NewStringUTF(value);
239+
env->CallVoidMethod(g_WebView.m_WebViewJNI, g_WebView.m_AddHeader, webview_id, jheader, jvalue);
240+
env->DeleteLocalRef(jheader);
241+
env->DeleteLocalRef(jvalue);
242+
return 0;
243+
}
244+
220245
#undef CHECK_WEBVIEW_AND_RETURN
221246

222247
static char* CopyString(JNIEnv* env, jstring s)
@@ -401,6 +426,8 @@ dmExtension::Result Platform_AppInitialize(dmExtension::AppParams* params)
401426
g_WebView.m_SetTransparent = env->GetMethodID(webview_class, "setTransparent", "(II)V");
402427
g_WebView.m_IsVisible = env->GetMethodID(webview_class, "isVisible", "(I)I");
403428
g_WebView.m_SetPosition = env->GetMethodID(webview_class, "setPosition", "(IIIII)V");
429+
g_WebView.m_AddHeader = env->GetMethodID(webview_class, "addHeader", "(ILjava/lang/String;Ljava/lang/String;)V");
430+
g_WebView.m_ClearHeaders = env->GetMethodID(webview_class, "clearHeaders", "(I)V");
404431

405432
int32_t immersiveMode = dmConfigFile::GetInt(params->m_ConfigFile, "android.immersive_mode", 0);
406433
int32_t displayCutout = dmConfigFile::GetInt(params->m_ConfigFile, "android.display_cutout", 1);

webview/src/webview_common.cpp

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,60 @@ static int Destroy(lua_State* L)
136136
return 1;
137137
}
138138

139-
void ParseOptions(lua_State* L, int argumentindex, RequestInfo* requestinfo)
139+
140+
void ParseHeaders(lua_State* L, int argumentindex, int webview_id)
141+
{
142+
Platform_ClearHeaders(L, webview_id);
143+
luaL_checktype(L, argumentindex, LUA_TTABLE);
144+
lua_pushvalue(L, argumentindex);
145+
146+
// first key
147+
lua_pushnil(L);
148+
149+
// push key-value pair
150+
while (lua_next(L, -2))
151+
{
152+
// push copy of key (ie the header), convert it, pop it
153+
// note from Lua docs: If the value is a number, then lua_tolstring also
154+
// changes the actual value in the stack to a string. This change
155+
// confuses lua_next when lua_tolstring is applied to keys during a
156+
// table traversal
157+
lua_pushvalue(L, -2);
158+
const char* header = lua_tostring(L, -1);
159+
lua_pop(L, 1);
160+
161+
// push copy of value, convert it, pop it
162+
lua_pushvalue(L, -1);
163+
const char* value = lua_tostring(L, -1);
164+
lua_pop(L, 1);
165+
166+
Platform_AddHeader(L, webview_id, header, value);
167+
168+
// remove value, keep key for lua_next
169+
lua_pop(L, 1);
170+
}
171+
lua_pop(L, 1);
172+
}
173+
174+
175+
void ParseOptions(lua_State* L, int argumentindex, int webview_id, RequestInfo* requestinfo)
140176
{
141177
luaL_checktype(L, argumentindex, LUA_TTABLE);
142178
lua_pushvalue(L, argumentindex);
143179
lua_pushnil(L);
144180
while (lua_next(L, -2)) {
145-
const char* attr = lua_tostring(L, -2);
181+
lua_pushvalue(L, -2);
182+
const char* attr = lua_tostring(L, -1);
183+
lua_pop(L, 1);
146184
if( strcmp(attr, "hidden") == 0 )
147185
{
148186
luaL_checktype(L, -1, LUA_TBOOLEAN);
149187
requestinfo->m_Hidden = lua_toboolean(L, -1);
150188
}
189+
else if (strcmp(attr, "headers") == 0)
190+
{
191+
ParseHeaders(L, -1, webview_id);
192+
}
151193
else if( strcmp(attr, "transparent") == 0 )
152194
{
153195
luaL_checktype(L, -1, LUA_TBOOLEAN);
@@ -158,10 +200,6 @@ void ParseOptions(lua_State* L, int argumentindex, RequestInfo* requestinfo)
158200
lua_pop(L, 1);
159201
}
160202

161-
/** Opens an url in the view
162-
@param url url
163-
@return the request id
164-
*/
165203
static int Open(lua_State* L)
166204
{
167205
int top = lua_gettop(L);
@@ -171,7 +209,7 @@ static int Open(lua_State* L)
171209
RequestInfo requestinfo;
172210
if( top >= 3 && !lua_isnil(L, 3))
173211
{
174-
ParseOptions(L, 3, &requestinfo);
212+
ParseOptions(L, 3, webview_id, &requestinfo);
175213
}
176214

177215
int request_id = Platform_Open(L, webview_id, url, &requestinfo);
@@ -190,7 +228,7 @@ static int OpenRaw(lua_State* L)
190228
RequestInfo requestinfo;
191229
if( top >= 3 && !lua_isnil(L, 3))
192230
{
193-
ParseOptions(L, 3, &requestinfo);
231+
ParseOptions(L, 3, webview_id, &requestinfo);
194232
}
195233

196234
int request_id = Platform_OpenRaw(L, webview_id, html, &requestinfo);

webview/src/webview_common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ int Platform_SetTransparent(lua_State* L, int webview_id, int transparent);
6969
int Platform_SetVisible(lua_State* L, int webview_id, int visible);
7070
int Platform_IsVisible(lua_State* L, int webview_id);
7171
int Platform_SetPosition(lua_State* L, int webview_id, int x, int y, int width, int height);
72+
int Platform_AddHeader(lua_State* L, int webview_id, const char* header, const char* value);
73+
int Platform_ClearHeaders(lua_State* L, int webview_id);
7274

7375
// The extension life cycle functions
7476
dmExtension::Result Platform_AppInitialize(dmExtension::AppParams* params);

0 commit comments

Comments
 (0)