| 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 |