webview.lua

1
--------------------------
2
-- WebKit WebView class --
3
--------------------------
4
 
5
-- Webview class table
6
webview = {}
7
 
8
-- Table of functions which are called on new webview widgets.
9
webview.init_funcs = {
10
    -- Set global properties
11
    set_global_props = function (view, w)
12
        -- Set proxy options
13
        local proxy = globals.http_proxy or os.getenv("http_proxy")
14
        if proxy then view:set_prop('proxy-uri', proxy) end
15
        view:set_prop('user-agent', globals.useragent)
16
 
17
        -- Set ssl options
18
        if globals.ssl_strict ~= nil then
19
            view:set_prop('ssl-strict', globals.ssl_strict)
20
        end
21
        if globals.ca_file and os.exists(globals.ca_file) then
22
            view:set_prop('ssl-ca-file', globals.ca_file)
23
            -- Warning: update the following variable if 'ssl-ca-file' is
24
            -- changed anywhere else.
25
            w.checking_ssl = true
26
        end
27
    end,
28
 
29
    -- Update window and tab titles
30
    title_update = function (view, w)
31
        view:add_signal("property::title", function (v)
32
            w:update_tab_labels()
33
            if w:is_current(v) then
34
                w:update_win_title()
35
            end
36
        end)
37
    end,
38
 
39
    -- Update uri label in statusbar
40
    uri_update = function (view, w)
41
        view:add_signal("property::uri", function (v)
42
            w:update_tab_labels()
43
            if w:is_current(v) then
44
                w:update_uri(v)
45
            end
46
        end)
47
    end,
48
 
49
    -- Update scroll widget
50
    scroll_update = function (view, w)
51
        view:add_signal("expose", function (v)
52
            if w:is_current(v) then
53
                w:update_scroll(v)
54
            end
55
        end)
56
    end,
57
 
58
    -- Update progress widget
59
    progress_update = function (view, w)
60
        for _, sig in ipairs({"load-status", "property::progress"}) do
61
            view:add_signal(sig, function (v)
62
                if w:is_current(v) then
63
                    w:update_progress(v)
64
                    w:update_ssl(v)
65
                end
66
            end)
67
        end
68
    end,
69
 
70
    -- Display hovered link in statusbar
71
    link_hover_display = function (view, w)
72
        view:add_signal("link-hover", function (v, link)
73
            if w:is_current(v) and link then
74
                w.sbar.l.uri.text = "Link: " .. lousy.util.escape(link)
75
            end
76
        end)
77
        view:add_signal("link-unhover", function (v)
78
            if w:is_current(v) then
79
                w:update_uri(v)
80
            end
81
        end)
82
    end,
83
 
84
    -- Clicking a form field automatically enters insert mode
85
    form_insert_mode = function (view, w)
86
        view:add_signal("form-active", function ()
87
            (w.search_state or {}).marker = nil
88
            w:set_mode("normal")
89
        end)
90
        view:add_signal("root-active", function ()
91
            (w.search_state or {}).marker = nil
92
            w:set_mode()
93
        end)
94
    end,
95
 
96
    -- Stop key events hitting the webview if the user isn't in insert mode
97
    mode_key_filter = function (view, w)
98
        view:add_signal("key-press", function ()
99
            if not w:is_mode("normal") then return true end
100
        end)
101
    end,
102
 
103
    -- Try to match a button event to a users button binding else let the
104
    -- press hit the webview.
105
    button_bind_match = function (view, w)
106
        -- Match button press
107
        view:add_signal("button-release", function (v, mods, button)
108
            (w.search_state or {}).marker = nil
109
            if w:hit(mods, button) then return true end
110
        end)
111
    end,
112
 
113
    -- Reset the mode on navigation
114
    mode_reset_on_nav = function (view, w)
115
        view:add_signal("load-status", function (v, status)
116
            if w:is_current(v) and status == "provisional" then w:set_mode() end
117
        end)
118
    end,
119
 
120
    -- Domain properties
121
    domain_properties = function (view, w)
122
        view:add_signal("load-status", function (v, status)
123
            if status ~= "provisional" then return end
124
            local domain = (v.uri and string.match(v.uri, "^%a+://([^/]*)/?")) or "about:blank"
125
            if string.match(domain, "^www.") then domain = string.sub(domain, 5) end
126
            local props = lousy.util.table.join(domain_props.all or {}, domain_props[domain] or {})
127
            for k, v in pairs(props) do
128
                info("Domain prop: %s = %s (%s)", k, tostring(v), domain)
129
                view:set_prop(k, v)
130
            end
131
        end)
132
    end,
133
 
134
    -- Action to take on mime type decision request.
135
    mime_decision = function (view, w)
136
        -- Return true to accept or false to reject from this signal.
137
        view:add_signal("mime-type-decision", function (v, link, mime)
138
            info("Requested link: %s (%s)", link, mime)
139
            -- i.e. block binary files like *.exe
140
            --if mime == "application/octet-stream" then
141
            --    return false
142
            --end
143
        end)
144
    end,
145
 
146
    -- Action to take on window open request.
147
    window_decision = function (view, w)
148
        -- 'link' contains the download link
149
        -- 'reason' contains the reason of the request (i.e. "link-clicked")
150
        -- return TRUE to handle the request by yourself or FALSE to proceed
151
        -- with default behaviour
152
        view:add_signal("new-window-decision", function (v, link, reason)
153
            info("New window decision: %s (%s)", link, reason)
154
            if reason == "link-clicked" then
155
                window.new({ link })
156
                return true
157
            end
158
            w:new_tab(link)
159
        end)
160
    end,
161
 
162
    create_webview = function (view, w)
163
        -- Return a newly created webview in a new tab
164
        view:add_signal("create-web-view", function (v)
165
            return w:new_tab()
166
        end)
167
    end,
168
 
169
    -- Action to take on download request.
170
    download_request = function (view, w)
171
        -- 'link' contains the download link
172
        -- 'filename' contains the suggested filename (from server or webkit)
173
        view:add_signal("download-request", function (v, link, filename)
174
            if not filename then return end
175
            -- Make download dir
176
            os.execute(string.format("mkdir -p %q", globals.download_dir))
177
            local dl = globals.download_dir .. "/" .. filename
178
            local wget = string.format("wget -q %q -O %q", link, dl)
179
            info("Launching: %s", wget)
180
            luakit.spawn(wget)
181
        end)
182
    end,
183
 
184
    -- Creates context menu popup from table (and nested tables).
185
    -- Use `true` for menu separators.
186
    populate_popup = function (view, w)
187
        view:add_signal("populate-popup", function (v)
188
            return {
189
                true,
190
                { "_Toggle Source", function () w:toggle_source() end },
191
                { "_Zoom", {
192
                    { "Zoom _In",    function () w:zoom_in(globals.zoom_step) end },
193
                    { "Zoom _Out",   function () w:zoom_out(globals.zoom_step) end },
194
                    true,
195
                    { "Zoom _Reset", function () w:zoom_reset() end }, }, },
196
            }
197
        end)
198
    end,
199
 
200
    -- Action to take on resource request.
201
    resource_request_decision = function (view, w)
202
        view:add_signal("resource-request-starting", function(v, uri)
203
            if luakit.verbose then print("Requesting: "..uri) end
204
            -- Return false to cancel the request.
205
        end)
206
    end,
207
}
208
 
209
-- These methods are present when you index a window instance and no window
210
-- method is found in `window.methods`. The window then checks if there is an
211
-- active webview and calls the following methods with the given view instance
212
-- as the first argument. All methods must take `view` & `w` as the first two
213
-- arguments.
214
webview.methods = {
215
    reload = function (view, w)
216
        view:reload()
217
    end,
218
 
219
    -- Property functions
220
    get = function (view, w, k)
221
        return view:get_prop(k)
222
    end,
223
 
224
    set = function (view, w, k, v)
225
        view:set_prop(k, v)
226
    end,
227
 
228
    -- evaluate javascript code and return string result
229
    eval_js = function (view, w, script, file)
230
        return view:eval_js(script, file or "(inline)")
231
    end,
232
 
233
    -- evaluate javascript code from file and return string result
234
    eval_js_from_file = function (view, w, file)
235
        local fh, err = io.open(file)
236
        if not fh then return error(err) end
237
        local script = fh:read("*a")
238
        fh:close()
239
        return view:eval_js(script, file)
240
    end,
241
 
242
    -- close the current tab
243
    close_tab = function (view, w)
244
        w.tabs:remove(view)
245
        view.uri = "about:blank"
246
        view:destroy()
247
        w:update_tab_count()
248
        w:update_tab_labels()
249
    end,
250
 
251
    -- Toggle source view
252
    toggle_source = function (view, w, show)
253
        if show == nil then show = not view:get_view_source() end
254
        view:set_view_source(show)
255
    end,
256
 
257
    -- Zoom functions
258
    zoom_in = function (view, w, step)
259
        view:set_prop("zoom-level", view:get_prop("zoom-level") + step)
260
    end,
261
 
262
    zoom_out = function (view, w, step)
263
        local value = view:get_prop("zoom-level") - step
264
        view:set_prop("zoom-level", ((value > 0.01) and value) or 0.01)
265
    end,
266
 
267
    zoom_reset = function (view, w)
268
        view:set_prop("zoom-level", 1.0)
269
    end,
270
 
271
    -- Searching functions
272
    start_search = function (view, w, text)
273
        if string.match(text, "^[\?\/]") then
274
            w:set_mode("search")
275
            local i = w.ibar.input
276
            i.text = text
277
            i:focus()
278
            i:set_position(-1)
279
        else
280
            return error("invalid search term, must start with '?' or '/'")
281
        end
282
    end,
283
 
284
    search = function (view, w, text, forward)
285
        if forward == nil then forward = true end
286
 
287
        -- Get search state (or new state)
288
        if not w.search_state then w.search_state = {} end
289
        local s = w.search_state
290
 
291
        -- Get search term
292
        text = text or s.last_search
293
        if not text or #text == 0 then
294
            return w:clear_search()
295
        end
296
        s.last_search = text
297
 
298
        if s.forward == nil then
299
            -- Haven't searched before, save some state.
300
            s.forward = forward
301
            s.marker = view:get_scroll_vert()
302
        else
303
            -- Invert direction if originally searching in reverse
304
            forward = (s.forward == forward)
305
        end
306
 
307
        view:search(text, false, forward, true);
308
    end,
309
 
310
    clear_search = function (view, w)
311
        view:clear_search()
312
        w.search_state = {}
313
    end,
314
 
315
    -- Webview scroll functions
316
    scroll_vert = function (view, w, value)
317
        local cur, max = view:get_scroll_vert()
318
        if type(value) == "string" then
319
            value = lousy.util.parse_scroll(cur, max, value)
320
        end
321
        view:set_scroll_vert(value)
322
    end,
323
 
324
    scroll_horiz = function (view, w, value)
325
        local cur, max = view:get_scroll_horiz()
326
        if type(value) == "string" then
327
            value = lousy.util.parse_scroll(cur, max, value)
328
        end
329
        view:set_scroll_horiz(value)
330
    end,
331
 
332
    -- vertical scroll of a multiple of the view_size
333
    scroll_page = function (view, w, value)
334
        local cur, max, size = view:get_scroll_vert()
335
        view:set_scroll_vert(cur + (size * value))
336
    end,
337
 
338
    -- History traversing functions
339
    back = function (view, w, n)
340
        view:go_back(n or 1)
341
    end,
342
 
343
    forward = function (view, w, n)
344
        view:go_forward(n or 1)
345
    end,
346
}
347
 
348
function webview.new(w, uri)
349
    local view = widget{type = "webview"}
350
 
351
    -- Call webview init functions
352
    for k, func in pairs(webview.init_funcs) do
353
        func(view, w)
354
    end
355
 
356
    if uri then view.uri = uri end
357
    view.show_scrollbars = false
358
    return view
359
end
360
 
361
-- Insert webview method lookup on window structure
362
table.insert(window.indexes, 1, function (w, k)
363
    -- Get current webview
364
    local view = w.tabs:atindex(w.tabs:current())
365
    if not view then return end
366
    -- Lookup webview method
367
    local func = webview.methods[k]
368
    if not func then return end
369
    -- Return webview method wrapper function
370
    return function (_, ...) return func(view, w, ...) end
371
end)
372
 
373
-- vim: et:sw=4:ts=8:sts=4:tw=80