xwwp.el 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. ;;; xwwp.el --- Enhance xwidget webkit browser -*- lexical-binding: t; -*-
  2. ;; Author: Damien Merenne
  3. ;; URL: https://github.com/canatella/xwwp
  4. ;; Created: 2020-03-11
  5. ;; Keywords: convenience
  6. ;; Version: 0.1
  7. ;; Package-Requires: ((emacs "26.1"))
  8. ;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
  9. ;; This file is NOT part of GNU Emacs.
  10. ;;; License:
  11. ;; This program is free software: you can redistribute it and/or modify
  12. ;; it under the terms of the GNU General Public License as published by
  13. ;; the Free Software Foundation, either version 3 of the License, or
  14. ;; (at your option) any later version.
  15. ;; This program is distributed in the hope that it will be useful,
  16. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;; GNU General Public License for more details.
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  21. ;;; Commentary:
  22. ;; This package provides the common functionnality for other xwidget webkit plus
  23. ;; packages. It provides the customize group and a framework to inject css and
  24. ;; javascript functions into an `xwidget-webkit' session.
  25. ;;; Code:
  26. (defgroup xwwp nil
  27. "`xwidget-webkit' browser enhancement suite."
  28. :group 'convenience)
  29. (require 'json)
  30. (require 'subr-x)
  31. (require 'xwidget)
  32. (defun xwwp-css-make-class (class style)
  33. "Generate a css CLASS definition from the STYLE alist."
  34. (format ".%s { %s }\\n" class (mapconcat (lambda (v) (format "%s: %s;" (car v) (cdr v))) style " ")))
  35. (defmacro xwwp--js (js _ &rest replacements)
  36. "Apply `format' on JS with REPLACEMENTS providing MMM mode delimiters.
  37. This file has basic support for javascript using MMM mode and
  38. local variables (see at the end of the file)."
  39. (declare (indent 2))
  40. `(format ,js ,@replacements))
  41. (defun xwwp-js-string-escape (string)
  42. "Escape STRING for injection."
  43. (replace-regexp-in-string "\n" "\\\\n" (replace-regexp-in-string "'" "\\\\'" string)))
  44. (defun xwwp-html-inject-head-element (xwidget tag id type content)
  45. "Insert TAG element under XWIDGET head with ID TYPE and CONTENT."
  46. (let* ((id (xwwp-js-string-escape id))
  47. (tag (xwwp-js-string-escape tag))
  48. (type (xwwp-js-string-escape type))
  49. (content (xwwp-js-string-escape content))
  50. (script (xwwp--js "
  51. __xwidget_id = '%s';
  52. if (!document.getElementById(__xwidget_id)) {
  53. var e = document.createElement('%s');
  54. e.type = '%s';
  55. e.id = __xwidget_id;
  56. e.innerHTML = '%s';
  57. document.getElementsByTagName('head')[0].appendChild(e);
  58. };
  59. null;
  60. " js-- id tag type content)))
  61. (xwidget-webkit-execute-script xwidget script)))
  62. (defun xwwp-html-inject-script (xwidget id script)
  63. "Inject javascript SCRIPT in XWIDGET session using a script element with ID."
  64. (xwwp-html-inject-head-element xwidget "script" id "text/javascript" script))
  65. (defun xwwp-html-inject-style (xwidget id style)
  66. "Inject css STYLE in XWIDGET session using a style element with ID."
  67. (xwwp-html-inject-head-element xwidget "style" id "text/css" style))
  68. (defun xwwp-js-lisp-to-js (identifier)
  69. "Convert IDENTIFIER from Lisp style to javascript style."
  70. (replace-regexp-in-string "-" "_" (if (symbolp identifier) (symbol-name identifier) identifier)))
  71. (defvar xwwp-js-scripts '() "An alist of list of javascript function.")
  72. (defun xwwp-js-register-function (ns-name name js-script)
  73. "Register javascript function NAME in namespace NS-NAME with body JS-SCRIPT."
  74. (let* ((namespace (assoc ns-name xwwp-js-scripts))
  75. (fun (when namespace (assoc name (cdr namespace)))))
  76. (cond (fun
  77. (delete fun namespace)
  78. (xwwp-js-register-function ns-name name js-script))
  79. ((not namespace)
  80. (push (cons ns-name '()) xwwp-js-scripts)
  81. (xwwp-js-register-function ns-name name js-script))
  82. (t
  83. (push (cons name js-script) (cdr namespace))))
  84. (cons ns-name name)))
  85. (defun xwwp-js-funcall (xwidget namespace name &rest arguments)
  86. "Invoke javascript function NAME in XWIDGET instance passing ARGUMENTS witch CALLBACK in NAMESPACE."
  87. ;;; Try to be smart
  88. (let* ((callback (car (last arguments)))
  89. (arguments (if (functionp callback) (reverse (cdr (reverse arguments))) arguments))
  90. (json-args (seq-map #'json-encode arguments))
  91. (arg-string (string-join json-args ", "))
  92. (namespace (xwwp-js-lisp-to-js namespace))
  93. (name (xwwp-js-lisp-to-js name))
  94. (script (format "__xwidget_plus_%s_%s(%s)" namespace name arg-string)))
  95. (xwidget-webkit-execute-script xwidget script (and (functionp callback) callback))))
  96. (defmacro xwwp-js-def (namespace name arguments docstring js-body)
  97. "Create a function NAME with ARGUMENTS, DOCSTRING and JS-BODY.
  98. This will define a javascript function in the namespace NAMESPACE
  99. and a Lisp function to call it."
  100. (declare (indent 3) (doc-string 4))
  101. (let* ((js-arguments (seq-map #'xwwp-js-lisp-to-js arguments))
  102. (js-name (xwwp-js-lisp-to-js name))
  103. (js-namespace (xwwp-js-lisp-to-js namespace))
  104. (lisp-arguments (append '(xwidget) arguments '(&optional callback)))
  105. (script (xwwp--js "function __xwidget_plus_%s_%s(%s) {%s};" js--
  106. js-namespace js-name (string-join js-arguments ", ") (eval js-body)))
  107. (lisp-def `(defun ,(intern (format "xwwp-%s-%s" namespace name)) ,lisp-arguments
  108. ,docstring
  109. (xwwp-js-funcall xwidget (quote ,namespace) (quote ,name) ,@arguments callback)))
  110. (lisp-store `(xwwp-js-register-function (quote ,namespace) (quote ,name) ,script)))
  111. `(progn ,lisp-def ,lisp-store)))
  112. (defun xwwp-js-inject (xwidget ns-name)
  113. "Inject the functions defined in NS-NAME into XWIDGET session."
  114. (let* ((namespace (assoc ns-name xwwp-js-scripts))
  115. (script (mapconcat #'cdr (cdr namespace) "\n")))
  116. (xwwp-html-inject-script xwidget (format "--xwwp-%s" (symbol-name ns-name)) script)))
  117. ;; Local Variables:
  118. ;; eval: (mmm-mode)
  119. ;; eval: (mmm-add-group 'elisp-js '((elisp-rawjs :submode js-mode
  120. ;; :face mmm-code-submode-face
  121. ;; :delimiter-mode nil
  122. ;; :front "xwwp--js \"" :back "\" js--")
  123. ;; (elisp-defjs :submode js-mode
  124. ;; :face mmm-code-submode-face
  125. ;; :delimiter-mode nil
  126. ;; :front "xwwp-defjs .*\n.*\"\"\n" :back "\")\n")))
  127. ;; mmm-classes: elisp-js
  128. ;; End:
  129. (provide 'xwwp)
  130. ;;; xwwp.el ends here