-------------------------- -- WebKit WebView class -- -------------------------- local window = require("window") local lousy = require("lousy") local globals = require("globals") -- Webview class table local webview = {} lousy.signal.setup(webview, true) local web_module = require_web_module("webview_wm") web_module:add_signal("form-active", function (_, page_id) for _, w in pairs(window.bywidget) do if w.view.id == page_id then w.view:emit_signal("form-active") end end end) web_module:add_signal("navigate", function (_, page_id, uri) msg.verbose("Got luakit:// -> file:// navigation: %s", uri) for _, w in pairs(window.bywidget) do if w.view.id == page_id then w.view.uri = uri end end end) local webview_state = setmetatable({}, { __mode = "k" }) -- Table of functions which are called on new webview widgets. local init_funcs = { -- Set useragent set_useragent = function (view) view.user_agent = globals.useragent end, -- Update window and tab titles title_update = function (view) view:add_signal("property::title", function (v) local w = webview.window(v) if w and w.view == v then w:update_win_title() end end) end, -- Clicking a form field automatically enters insert mode. form_insert_mode = function (view) -- Emit root-active event in button release to prevent "missing" -- buttons or links when the input bar hides. view:add_signal("button-press", function (v, _, button, context) if button == 1 then v:emit_signal(context.editable and "form-active" or "root-active") end end) view:add_signal("form-active", function (v) local w = webview.window(v) if not w.mode.passthrough then w:set_mode("insert") end end) view:add_signal("root-active", function (v) local w = webview.window(v) if w.mode.reset_on_focus ~= false then w:set_mode() end end) view:add_signal("load-status", function (v, status, _, err) if status == "finished" or (status == "failed" and err == "Load request cancelled") then web_module:emit_signal(v, "load-finished") end end) end, -- Try to match a button event to a users button binding else let the -- press hit the webview. button_bind_match = function (view) view:add_signal("button-release", function (v, mods, button, context) local w = webview.window(v) if w:hit(mods, button, { context = context }) then return true end end) end, -- Reset the mode on navigation mode_reset_on_nav = function (view) view:add_signal("load-status", function (v, status) local w = webview.window(v) if status == "provisional" and w and w.view == v then if w.mode.reset_on_navigation ~= false then w:set_mode() end end end) end, -- Action to take on mime type decision request. mime_decision = function (view) -- Return true to accept or false to reject from this signal. view:add_signal("mime-type-decision", function (_, uri, mime) msg.info("Requested link: %s (%s)", uri, mime) -- i.e. block binary files like *.exe --if mime == "application/octet-stream" then -- return false --end end) end, -- Action to take on window open request. --window_decision = function (view, w) -- view:add_signal("new-window-decision", function (v, uri, reason) -- if reason == "link-clicked" then -- window.new({uri}) -- else -- w:new_tab(uri) -- end -- return true -- end) --end, create_webview = function (view) -- Return a newly created webview in a new tab view:add_signal("create-web-view", function (v) return webview.window(v):new_tab(nil, { private = v.private }) end) end, popup_fix_open_link_label = function (view) view:add_signal("populate-popup", function (_, menu) for _, item in ipairs(menu) do if type(item) == "table" then -- Optional underscore represents alt-key shortcut letter item[1] = string.gsub(item[1], "New (_?)Window", "New %1Tab") end end end) end, } -- These methods are present when you index a window instance and no window -- method is found in `window.methods`. The window then checks if there is an -- active webview and calls the following methods with the given view instance -- as the first argument. All methods must take `view` & `w` as the first two -- arguments. webview.methods = { -- Reload with or without ignoring cache reload = function (view, _, bypass_cache) if bypass_cache then view:reload_bypass_cache() else view:reload() end end, -- Toggle source view toggle_source = function (view, _, show) if show == nil then view.view_source = not view.view_source else view.view_source = show end view:reload() end, -- Zoom functions zoom_in = function (view, _, step) step = step or globals.zoom_step or 0.1 view.zoom_level = view.zoom_level + step end, zoom_out = function (view, _, step) step = step or globals.zoom_step or 0.1 view.zoom_level = math.max(0.01, view.zoom_level) - step end, zoom_set = function (view, _, level) view.zoom_level = level or 1.0 end, -- History traversing functions back = function (view, _, n) view:go_back(n or 1) view:emit_signal("go-back-forward", -(n or 1)) end, forward = function (view, _, n) view:go_forward(n or 1) view:emit_signal("go-back-forward", (n or 1)) end, } function webview.methods.scroll(view, w, new) local s = view.scroll for _, axis in ipairs{ "x", "y" } do -- Relative px movement if rawget(new, axis.."rel") then s[axis] = s[axis] + new[axis.."rel"] -- Relative page movement elseif rawget(new, axis .. "pagerel") then s[axis] = s[axis] + math.ceil(s[axis.."page_size"] * new[axis.."pagerel"]) -- Absolute px movement elseif rawget(new, axis) then local n = new[axis] if n == -1 then local dir = axis == "x" and "Width" or "Height" local js = string.format([=[ Math.max(window.document.documentElement.scroll%s - window.inner%s, 0) ]=], dir, dir) w.view:eval_js(js, { callback = function (max) s[axis] = max end}) else s[axis] = n end -- Absolute page movement elseif rawget(new, axis.."page") then s[axis] = math.ceil(s[axis.."page_size"] * new[axis.."page"]) -- Absolute percent movement elseif rawget(new, axis .. "pct") then local dir = axis == "x" and "Width" or "Height" local js = string.format([=[ Math.max(window.document.documentElement.scroll%s - window.inner%s, 0) ]=], dir, dir) w.view:eval_js(js, { callback = function (max) s[axis] = math.ceil(max * (new[axis.."pct"]/100)) end}) end end end local wrap_widget_metatable do local wrapped = false wrap_widget_metatable = function (view) if wrapped then return end wrapped = true local mt = getmetatable(view) local oi = mt.__index mt.__index = function (w, k) if (k == "uri" or k == "session_state") and oi(w, "type") == "webview" then local ws = webview_state[w] if not next(ws.blockers) then return oi(w, k) end local ql = ws.queued_location or {} if k == "uri" then return ql.uri or oi(w, k) end if k == "session_state" then return ql.session_state or oi(w, k) end end return oi(w, k) end end end function webview.new(opts) assert(opts) local view = widget{type = "webview", private = opts.private} webview_state[view] = { blockers = {} } wrap_widget_metatable(view) -- Call webview init functions for _, func in pairs(init_funcs) do func(view) end webview.emit_signal("init", view) return view end luakit.idle_add(function () local undoclose = package.loaded.undoclose if not undoclose then return end undoclose.add_signal("save", function (view) if view.private then return false end end) end) function webview.window(view) assert(type(view) == "widget" and view.type == "webview") return window.ancestor(view) end function webview.modify_load_block(view, name, enable) assert(type(view) == "widget" and view.type == "webview") assert(type(name) == "string") local ws = webview_state[view] ws.blockers[name] = enable and true or nil msg.verbose("%s %s %s", view, name, enable and "block" or "unblock") if not next(ws.blockers) and ws.queued_location then msg.verbose("fully unblocked %s", view) local queued = ws.queued_location ws.queued_location = nil webview.set_location(view, queued) end end function webview.set_location(view, arg) assert(type(view) == "widget" and view.type == "webview") assert(type(arg) == "string" or type(arg) == "table") -- Always execute JS URIs immediately, even when webview is blocked if type(arg) == "string" and arg:match("^javascript:") then local js = string.match(arg, "^javascript:(.+)$") return view:eval_js(luakit.uri_decode(js)) end if type(arg) == "string" then arg = { uri = arg } end assert(arg.uri or arg.session_state) local ws = webview_state[view] if next(ws.blockers) then ws.queued_location = arg if arg.uri then view:emit_signal("property::uri") end return end if arg.session_state then view.session_state = arg.session_state if view.uri == "about:blank" and arg.uri then view.uri = arg.uri end else view.uri = arg.uri end end -- Insert webview method lookup on window structure table.insert(window.indexes, 1, function (w, k) if k == "view" then local view = w.tabs[w.tabs:current()] if view and type(view) == "widget" and view.type == "webview" then w.view = view return view end end -- Lookup webview method local func = webview.methods[k] if not func then return end local view = w.view if view then return function (_, ...) return func(view, w, ...) end end end) return webview -- vim: et:sw=4:ts=8:sts=4:tw=80