<commit_before>(in-package :nyxt/web-extensions)

(-> extension->extension-info ((or null nyxt/web-extensions:extension)) (values list &optional))
(defun extension->extension-info (extension)
  (when extension
    `(("description" . ,(or (nyxt/web-extensions:description extension) ""))
      ("homepageUrl" . ,(or (nyxt/web-extensions:homepage-url extension) ""))
      ("id" . ,(id extension))
      ("installType" . "development")
      ("mayDisable" . t)
      ("name" . ,(or (name extension) ""))
      ("permissions" . ,(nyxt/web-extensions:permissions extension))
      ("version" ,(or (nyxt/web-extensions:version extension) ""))
      ;; TODO: Make those meaningful
      ("disabledReason" . "unknown")
      ("enabled" . t)
      ("hostPermissions" . ,(vector))
      ("icons" . ,(vector))
      ("offlineEnabled" . nil)
      ("optionsUrl" . "")
      ("shortName" . ,(or (name extension) ""))
      ("type" . "extension")
      ("updateUrl" . "")
      ("versionName" . ""))))

(-> buffer->tab-description ((or null buffer)) (values list &optional))
(defun buffer->tab-description (buffer)
  (when buffer
    `(("active" . ,(if (member buffer (mapcar #'nyxt::active-buffer
                                              (alex:hash-table-values (nyxt::windows *browser*))))
                       t nil))
      #+webkit2-mute
      ("audible" . ,(not (webkit:webkit-web-view-is-muted (nyxt::gtk-object buffer))))
      ("height" . ,(gdk:gdk-rectangle-height
                    (gtk:gtk-widget-get-allocation (nyxt::gtk-object buffer))))
      ("width" . ,(gdk:gdk-rectangle-width
                   (gtk:gtk-widget-get-allocation (nyxt::gtk-object buffer))))
      ("highlighted" . ,(eq buffer (nyxt::active-buffer (current-window))))
      ("id" . ,(or (parse-integer (id buffer) :junk-allowed t) 0))
      ("incognito" . ,(nosave-buffer-p buffer))
      ("lastAccessed" . ,(* 1000 (local-time:timestamp-to-unix (nyxt::last-access buffer))))
      ("selected" . ,(eq buffer (nyxt::active-buffer (current-window))))
      ("status" . ,(if (web-buffer-p buffer)
                       (case (slot-value buffer 'nyxt::status)
                         ((:finished :failed) "complete")
                         ((:unloaded :loading) "loading"))
                       "complete"))
      ;; TODO: Check "tabs" permission for those two
      ("title" . ,(title buffer))
      ("url" . ,(render-url (url buffer)))

      ;; TODO: Make those meaningful:
      ("attention" . nil)
      ("autoDiscardable" . nil)
      ("cookieStoreId" . 0)
      ("currentWindow" . t)
      ("discarded" . nil)
      ("hidden" . nil)
      ("favIconUrl" . "")
      ("index" . 0)
      ("isArticle" . nil)
      ("isInReaderMode" . nil)
      ("mutedInfo" . nil)
      ("openerTabId" . 0)
      ("pinned" . nil)
      ("sessionId" . 0)
      ("successorTabId" . 0)
      ("windowId" . 0))))

(defmethod tabs-on-activated ((old-buffer buffer) (new-buffer buffer))
  (flet ((integer-id (object)
           (or (ignore-errors (parse-integer (id object)))
               0)))
    (dolist (buffer (buffer-list))
      (dolist (extension (sera:filter #'nyxt/web-extensions::extension-p (modes buffer)))
        (ffi-buffer-evaluate-javascript
         buffer (ps:ps (ps:chain browser tabs on-activated
                                 (run (ps:create previous-tab-id (ps:lisp (integer-id old-buffer))
                                                 tab-id (ps:lisp (integer-id new-buffer))
                                                 window-id (ps:lisp (integer-id (current-window)))))))
         (name extension))
        ;; tabs.onActiveChanged is deprecated. No harm in having it, though.
        (ffi-buffer-evaluate-javascript
         buffer (ps:ps (ps:chain browser tabs on-active-changed
                                 (run (ps:lisp (integer-id new-buffer))
                                      (ps:create window-id (ps:lisp (integer-id (current-window)))))))
         (name extension))))))

(-> tabs-query ((or null string)) (values string &optional))
(defun tabs-query (query-object)
  (flet ((%tabs-query (query-object)
           (let ((buffer-descriptions (mapcar #'buffer->tab-description (buffer-list))))
             (if query-object
                 (or (sera:filter (lambda (bd)
                                    (every #'identity
                                           (sera:maphash-return
                                            (lambda (key value)
                                              (equal value (str:s-assoc-value bd key)))
                                            query-object)))
                                  buffer-descriptions)
                     ;; nil translates to null, we need to pass empty vector instead.
                     (vector))
                 buffer-descriptions))))
    (encode-json
     (%tabs-query (decode-json (or query-object "{}"))))))

(-> tabs-create ((or null string)) (values string &optional))
(defun tabs-create (create-properties)
  (let* ((properties (decode-json (or create-properties "{}")))
         (parent-buffer (when (gethash "openerTabId" properties)
                          (nyxt::buffers-get
                           (format nil "~d" (gethash "openerTabId" properties)))))
         (url (quri:uri (or (gethash "url" properties)
                            "about:blank")))
         ;; See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/create<commit_msg>web-extensions-callbacks: Add event-running helpers.<commit_after>(in-package :nyxt/web-extensions)

(-> extension->extension-info ((or null nyxt/web-extensions:extension)) (values list &optional))
(defun extension->extension-info (extension)
  (when extension
    `(("description" . ,(or (nyxt/web-extensions:description extension) ""))
      ("homepageUrl" . ,(or (nyxt/web-extensions:homepage-url extension) ""))
      ("id" . ,(id extension))
      ("installType" . "development")
      ("mayDisable" . t)
      ("name" . ,(or (name extension) ""))
      ("permissions" . ,(nyxt/web-extensions:permissions extension))
      ("version" ,(or (nyxt/web-extensions:version extension) ""))
      ;; TODO: Make those meaningful
      ("disabledReason" . "unknown")
      ("enabled" . t)
      ("hostPermissions" . ,(vector))
      ("icons" . ,(vector))
      ("offlineEnabled" . nil)
      ("optionsUrl" . "")
      ("shortName" . ,(or (name extension) ""))
      ("type" . "extension")
      ("updateUrl" . "")
      ("versionName" . ""))))

(-> buffer->tab-description ((or null buffer)) (values list &optional))
(defun buffer->tab-description (buffer)
  (when buffer
    `(("active" . ,(if (member buffer (mapcar #'nyxt::active-buffer
                                              (alex:hash-table-values (nyxt::windows *browser*))))
                       t nil))
      #+webkit2-mute
      ("audible" . ,(not (webkit:webkit-web-view-is-muted (nyxt::gtk-object buffer))))
      ("height" . ,(gdk:gdk-rectangle-height
                    (gtk:gtk-widget-get-allocation (nyxt::gtk-object buffer))))
      ("width" . ,(gdk:gdk-rectangle-width
                   (gtk:gtk-widget-get-allocation (nyxt::gtk-object buffer))))
      ("highlighted" . ,(eq buffer (nyxt::active-buffer (current-window))))
      ("id" . ,(or (parse-integer (id buffer) :junk-allowed t) 0))
      ("incognito" . ,(nosave-buffer-p buffer))
      ("lastAccessed" . ,(* 1000 (local-time:timestamp-to-unix (nyxt::last-access buffer))))
      ("selected" . ,(eq buffer (nyxt::active-buffer (current-window))))
      ("status" . ,(if (web-buffer-p buffer)
                       (case (slot-value buffer 'nyxt::status)
                         ((:finished :failed) "complete")
                         ((:unloaded :loading) "loading"))
                       "complete"))
      ;; TODO: Check "tabs" permission for those two
      ("title" . ,(title buffer))
      ("url" . ,(render-url (url buffer)))

      ;; TODO: Make those meaningful:
      ("attention" . nil)
      ("autoDiscardable" . nil)
      ("cookieStoreId" . 0)
      ("currentWindow" . t)
      ("discarded" . nil)
      ("hidden" . nil)
      ("favIconUrl" . "")
      ("index" . 0)
      ("isArticle" . nil)
      ("isInReaderMode" . nil)
      ("mutedInfo" . nil)
      ("openerTabId" . 0)
      ("pinned" . nil)
      ("sessionId" . 0)
      ("successorTabId" . 0)
      ("windowId" . 0))))

(defun all-extensions (&key (buffers (buffer-list)))
  (loop for buffer in buffers
        append (sera:filter #'nyxt/web-extensions::extension-p (modes buffer))))

(defmacro fire-extension-event (extension object event &rest args)
  (alex:once-only (extension)
    `(ffi-buffer-evaluate-javascript
      (buffer ,extension)
      (ps:ps (ps:chain browser ,object ,event
                       (run ,@args)))
      (name ,extension))))

(defmethod tabs-on-activated ((old-buffer buffer) (new-buffer buffer))
  (flet ((integer-id (object)
           (or (ignore-errors (parse-integer (id object)))
               0)))
    (dolist (extension (all-extensions))
      (fire-extension-event
       extension tabs on-activated
       (ps:create previous-tab-id (ps:lisp (integer-id old-buffer))
                  tab-id (ps:lisp (integer-id new-buffer))
                  window-id (ps:lisp (integer-id (current-window)))))
      ;; tabs.onActiveChanged is deprecated. No harm in having it, though.
      (fire-extension-event
       extension tabs on-active-changed
       (ps:lisp (integer-id new-buffer))
       (ps:create window-id (ps:lisp (integer-id (current-window))))))))

(-> tabs-query ((or null string)) (values string &optional))
(defun tabs-query (query-object)
  (flet ((%tabs-query (query-object)
           (let ((buffer-descriptions (mapcar #'buffer->tab-description (buffer-list))))
             (if query-object
                 (or (sera:filter (lambda (bd)
                                    (every #'identity
                                           (sera:maphash-return
                                            (lambda (key value)
                                              (equal value (str:s-assoc-value bd key)))
                                            query-object)))
                                  buffer-descriptions)
                     ;; nil translates to null, we need to pass empty vector instead.
                     (vector))
                 buffer-descriptions))))
    (encode-json
     (%tabs-query (decode-json (or query-object "{}"))))))

(-> tabs-create ((or null string)) (values string &optional))
(defun tabs-create (create-properties)
  (let* ((properties (decode-json (or create-properties "{}")))
         (parent-buffer (when (gethash "openerTabId" properties)
                          (nyxt::buffers-get
                           (format nil "~d" (gethash "openerTabId" properties)))))
         (url (quri:uri (or (gethash "url" properties)
                            "about:blank")))
         ;; See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/create
{'fraction_non_alphanumeric': 0.16175406871609405, 'fraction_numerical': 0.0023508137432188066, 'mean_word_length': 1.997560975609756, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 31, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3852050', 'n_tokens_mistral': 3291, 'n_tokens_neox': 3159, 'n_words': 734}