(ns devtools.formatters.budgeting (:require-macros [devtools.oops :refer [oget oset ocall]]) (:require [devtools.formatters.templating :refer [render-markup]] [devtools.formatters.state :refer [get-depth-budget set-depth-budget]] [devtools.formatters.helpers :refer [pref]] [devtools.formatters.markup :refer []])) ; This functionality provides a workaround to issue #22 (https://github.com/binaryage/cljs-devtools/issues/22). ; The idea is to track hierarchy depth for json-ml(s) we are generating. ; If we are about to cross the depth limit hardcoded in WebKit, ; instead we render simple expandable placeholders which resume full rendering in their bodies (when expanded by user). ; Note that this technique has some quirks, it may break styling in some pathological cases. ; we need to reserve some depth levels for our expander symbol markup (def header-expander-depth-cost 2) ; -- tracking over-budget values ------------------------------------------------------------------------------------------- ; note: phantomjs does not have WeakSet, so we have to emulate it when testing (def over-budget-values (if (exists? js/WeakSet) (js/WeakSet.) (volatile! #{}))) (defn add-over-budget-value! [value] (if (volatile? over-budget-values) (vreset! over-budget-values (conj @over-budget-values value)) (ocall over-budget-values "add" value))) (defn delete-over-budget-value! [value] (if (volatile? over-budget-values) (vreset! over-budget-values (disj @over-budget-values value)) (ocall over-budget-values "delete" value))) (defn has-over-budget-value? [value] (if (volatile? over-budget-values) (contains? @over-budget-values value) (ocall over-budget-values "has" value))) ; -- depth budget accounting ------------------------------------------------------------------------------------------------ (defn object-reference? [json-ml] (= (first json-ml) "object")) (defn determine-depth [json-ml] (if (array? json-ml) (inc (apply max (map determine-depth json-ml))) 0)) (defn has-any-object-reference? [json-ml] (if (array? json-ml) (if (object-reference? json-ml) true (some has-any-object-reference? json-ml)))) (defn transfer-remaining-depth-budget! [object-reference depth-budget] {:pre [(not (neg? depth-budget))]} (let [data (second object-reference) _ (assert (object? data)) config (oget data "config")] (oset data ["config"] (set-depth-budget config depth-budget)))) (defn distribute-budget! [json-ml depth-budget] {:pre [(not (neg? depth-budget))]} (if (array? json-ml) (let [new-depth-budget (dec depth-budget)] (if (object-reference? json-ml) (transfer-remaining-depth-budget! json-ml new-depth-budget) (doseq [item json-ml] (distribute-budget! item new-depth-budget))))) json-ml) ; -- api -------------------------------------------------------------------------------------------------------------------- (defn was-over-budget?! [value] (when (has-over-budget-value? value) (delete-over-budget-value! value) true)) (defn alter-json-ml-to-fit-in-remaining-budget! [value json-ml] (if-let [initial-hierarchy-depth-budget (pref :initial-hierarchy-depth-budget)] ; this is hardcoded in InjectedScriptSource.js in WebKit, look for maxCustomPreviewRecursionDepth (let [remaining-depth-budget (or (get-depth-budget) (dec initial-hierarchy-depth-budget)) depth (determine-depth json-ml) final? (not (has-any-object-reference? json-ml)) needed-depth (if final? depth (+ depth header-expander-depth-cost))] (if (>= remaining-depth-budget needed-depth) (distribute-budget! json-ml remaining-depth-budget) (let [expander-ml (render-markup ( value))] (add-over-budget-value! value) ; we need to record over-budget values to for later was-over-budget?! check, see has-body* in formatters.core expander-ml))) json-ml))