Skip to content

Commit

Permalink
do strict marshalling of calls between background and implant
Browse files Browse the repository at this point in the history
This was broken in :advanced builds. Took me half day to figure it out.
  • Loading branch information
darwin committed May 20, 2016
1 parent eeb43cd commit 1745b2a
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 28 deletions.
60 changes: 45 additions & 15 deletions src/background/dirac/background/helpers.cljs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(ns dirac.background.helpers
(:require-macros [cljs.core.async.macros :refer [go go-loop]])
(:require [cljs.core.async :refer [<! chan]]
(:require [cljs.core.async :refer [<! chan put! close!]]
[chromex.support :refer-macros [oget oset ocall oapply]]
[chromex.logging :refer-macros [log info warn error group group-end]]
[chromex.ext.tabs :as tabs]
Expand All @@ -14,6 +14,11 @@
(:import goog.Uri
goog.Uri.QueryData))

(defn ^:dynamic warn-about-unexpected-number-views [devtools-id views]
(warn (str "found unexpected number views with enabled automation support for devtools #" devtools-id "\n")
views "\n"
"targeting only the first one"))

; -- uri helpers ------------------------------------------------------------------------------------------------------------

(defn make-uri-object [url]
Expand Down Expand Up @@ -94,21 +99,46 @@
{:post [(fn? %)]}
(oget view (get-automation-entry-point-key)))

(defn safe-serialize [value]
(try
(pr-str value)
(catch :default e
(error "failed to serialize value:" value e))))

(defn safe-unserialize [serialized-value]
(try
(reader/read-string serialized-value)
(catch :default e
(error "failed to unserialize value:" serialized-value e)
::reply-unserialization-failed)))

(defn automate-action! [automate-fn action]
(let [channel (chan)]
(if-let [serialized-action (safe-serialize action)]
(let [reply-callback (fn [serialized-reply]
(if-let [reply (safe-unserialize serialized-reply)]
(put! channel reply))
(close! channel))]
; WARNING: here we are crossing boundary between background and implant projects
; both cljs code-bases are potentially compiled under :advanced mode but resulting in different minification
; that is why cannot pass any cljs values over this boundary
; we have to strictly serialize results on both ends, that is why we use callbacks here and do not pass channels
(automate-fn serialized-action reply-callback))
(put! channel ::action-serialization-failed))
channel))

(defn automate-devtools! [devtools-id action]
(go
(let [matching-views (get-devtools-views devtools-id)
matching-views-with-automation-support (filter has-automation-support? matching-views)]
(if (> (count matching-views-with-automation-support) 1)
(warn (str "found unexpected number views with enabled automation support for devtools #" devtools-id "\n")
matching-views-with-automation-support "\n"
"targeting only the first one"))
(if-let [view (first matching-views-with-automation-support)]
(let [automate-fn (get-automation-entry-point view)]
(try
(<! (automate-fn (pr-str action)))
(catch :default e
(error (str "unable to automate dirac devtools #" devtools-id "\n")
view e))))))))
(let [matching-views-to-devtools-id (get-devtools-views devtools-id)
matching-views-with-automation-support (filter has-automation-support? matching-views-to-devtools-id)]
(if (> (count matching-views-with-automation-support) 1)
(warn-about-unexpected-number-views devtools-id matching-views-with-automation-support))
(if-let [view (first matching-views-with-automation-support)]
(try
(automate-action! (get-automation-entry-point view) action)
(catch :default e
(error (str "unable to automate dirac devtools #" devtools-id) view e)
(go ::failure)))
(go ::no-views))))

(defn close-all-extension-tabs! []
(let [views (extension/get-views #js {:type "tab"})]
Expand Down
55 changes: 42 additions & 13 deletions src/implant/dirac/implant/automation.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@

(defn show-inspector-panel! [panel]
(let [panel-name (name panel)
inspector-view (get-inspector-view)]
(ocall inspector-view "showPanel" panel-name)))
inspector-view (get-inspector-view)
panel-promise (ocall inspector-view "showPanel" panel-name)]
(if panel-promise
(ocall panel-promise "then" (fn [_panel] true)))))

(defn get-inspector-current-panel-name []
(let [inspector-view (get-inspector-view)]
Expand All @@ -34,7 +36,9 @@
inspector-view (get-inspector-view)]
(if (ocall inspector-view "drawerVisible")
(if-not (= (ocall inspector-view "selectedViewInDrawer") view-name)
(ocall inspector-view "showViewInDrawer" view-name true)))))
(do
(ocall inspector-view "showViewInDrawer" view-name true)
true)))))

(defn open-drawer-console-if-not-on-console-panel! []
(when-not (= (get-inspector-current-panel-name) "console")
Expand Down Expand Up @@ -107,22 +111,47 @@

; -- automation -------------------------------------------------------------------------------------------------------------

(defn automate [command]
(defn safe-automate! [command]
{:pre [(map? command)]}
(try
(let [result (dispatch-command! command)]
(utils/to-channel result))
(dispatch-command! command)
(catch :default e
(feedback-support/post! (str "automation exception while performing " (pr-str command) " => " e "\n"
(.-stack e)))
(throw e))))
(error "failed to dispatch automation command: " (pr-str command) e)
::automation-dispatch-failed)))

; -- installation -----------------------------------------------------------------------------------------------------------

(defn automation-handler [message]
{:pre [(string? message)]}
(let [command (reader/read-string message)]
(automate command)))
(defn safe-serialize [value]
(try
(pr-str value)
(catch :default e
(error "dirac.implant.automation: unable to serialize value" e "\n" value))))

(defn safe-unserialize [serialized-value]
(try
(reader/read-string serialized-value)
(catch :default e
(error "dirac.implant.automation: unable to unserialize value" e "\n" value))))

(defn make-marshalled-callback [callback]
(fn [reply]
(if-let [serialized-reply (safe-serialize reply)]
(callback serialized-reply)
(callback ::reply-serialization-failed))))

; WARNING: here we are crossing boundary between background and implant projects
; both cljs code-bases are potentially compiled under :advanced mode but resulting in different minification
; that is why cannot pass any cljs values over this boundary
; we have to strictly serialize results on both ends, that is why we use callbacks here and do not pass channels
(defn automation-handler [message callback]
{:pre [(string? message)
(fn? callback)]}
(if-let [command (safe-unserialize message)]
(let [result (safe-automate! command)]
; result can potentially be promise or core.async channel,
; here we use generic code to turn it back to callback
(utils/to-callback result (make-marshalled-callback callback)))
(callback ::command-unserialization-failed)))

(defn install-automation-support! []
(oset js/window [(get-automation-entry-point-key)] automation-handler))
Expand Down

0 comments on commit 1745b2a

Please sign in to comment.