9

I have tried many things, with this as the base:

(() => {
    function getLevelIndentation(level){
        return level * 20 + "px";
    }
    var multiKeyMapFormatter = {
        header: function(x) {
            if (!(x instanceof MultiKeyMap)){
                return null;
            }

            let textArray = [];
            x.forEach((r, u, t, mkm) => textArray.push("[" + t + ", " + u + "] => " + (r instanceof Object ? r.constructor.name : r)));
            const header = "MultiKeyMap - " + textArray.join("|").substr(0, 50);

            return ["div", {"style":'color: green'}, header]
        },
        hasBody: function(){
            return true;
        },
        body: function(obj, config){
            return undefined;   
        },
    };


    window.devtoolsFormatters = [multiKeyMapFormatter];

    console.log("defined window.devtoolsFormatters");
    
})();

e.g. I tried...

  • substituting null for undefined in the return from the body function
  • omitting the body function
  • rewriting the default behavior (see below)
  • returning obj in various permutations
  • Setting hasBody to return false
    // attempt at rewriting the default behavior
    // ref: https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html
    body: function(obj, config){
        var level = config !== undefined ? config.level : 0;
    
        var elements = Object.keys(obj).map(function(key){
            var child;
            var childObj = obj[key];
            if (typeof childObj === "object"){
                child = ["object", {
                    object: childObj,
                    config: {
                        key: key,
                        level: level + 1
                    }
                }];
            } else {
                child = key + ": " + (childObj && childObj.toString ? childObj.toString() : childObj);
    
            }
            return ["div", {style: "margin-left: " + getLevelIndentation(level)}, child];
        })
    
        return ["div", {}].concat(elements);
    },

Is there a way to tell Chrome to use the default body behavior?

bnieland
  • 6,047
  • 4
  • 40
  • 66

2 Answers2

1

Good question, the closest I could come to is:

  • remove formatters
  • call "original" console.log
  • add formatters back

Like:

function consoleLogDefault(...args) {
  const old = window.devtoolsFormatters;
  delete window.devtoolsFormatters;
  console.log(...args);
  window.devtoolsFormatters = old;
}

Full example:

class Color {
  constructor(r, g, b) {
    this.r = r;
    this.g = g;
    this.b = b;
  }
}
function consoleLogDefault(...args) {
  const old = window.devtoolsFormatters;
  delete window.devtoolsFormatters;
  console.log(...args);
  window.devtoolsFormatters = old;
}
const FormatColor = {
  header(o) {
    if (!(o instanceof Color)) {
      return null;
    }
    const style = {
      style: `color: rgb(${o.r}, ${o.g}, ${o.b});`
    }
    return ["table", style,
      ["tr",
        ["td", "r"],
        ["td", "g"],
        ["td", "b"],
       ],
      ["tr",
        ["td", o.r],
        ["td", o.g],
        ["td", o.b],
      ]
    ];
  },
  hasBody() {
    return true;
  },
  body(o) {
    consoleLogDefault(...arguments);
    return ["ol", ["li", o.r], ["li", o.g], ["li", o.b]];
  }
};
window.devtoolsFormatters = [
  FormatColor
];
red = new Color(255,0,0);
green = new Color(0,255,0);
blue = new Color(0,0,255);
console.log(red);
console.log(green);
console.log(blue);

The problem is just that it will not appear "grouped" under the actual object, but just appended as console message. :(

Result:

enter image description here

Not perfect, still better than nothing.

kungfooman
  • 4,473
  • 1
  • 44
  • 33
0

Debugging the devtool! Got this. (For debugger inspection, not in console)

Tested on Chrome v108. Known issue: performance loss. Takes ~5s on global variables list.

Node script. Save as '.mjs'.

// chrome --remote-debugging-port=9223
// node --harmony-top-level-await this.mjs

let puppet = await import('puppeteer-core');
var browser = await puppet.connect({ browserURL: 'http://127.0.0.1:9223' });
var bSess = await browser.target().createCDPSession();
console.log('browser conn');

var selIdx = 0;
var infos = (await bSess.send('Target.getTargets')).targetInfos;
var isDevToolUrl = a => a.startsWith('devtools://devtools/bundled/devtools_app.html');
infos = infos.filter(a => isDevToolUrl(a.url));
if (infos.length > 1) {
    for (let i = 0; i < infos.length; i++)
        console.log(i+'.', infos[i].title)
    let rl = await import('node:readline');
    let rli = rl.createInterface({ input: process.stdin, output: process.stdout });
    selIdx = await new Promise(ok => rli.question("select: ", ok)); rli.close();
}
var sel = infos[selIdx];
var targetId = sel.targetId;
var conn = bSess.connection();
var sess = await conn.createSession(sel);
console.log('target conn');

await bSess.send('Target.setDiscoverTargets', { discover: true });
bSess.on('Target.targetDestroyed', data => {
    if (data.targetId === targetId) {
        console.log('target die');
        process.exit(0);
    }
});

await sess.send('Debugger.enable');
console.log('dbg on');

console.log('finding src');
let brks = await sess.send('Debugger.setBreakpointByUrl', {
    urlRegex: '.*/object_ui\\.js',
    lineNumber: 0
});
if (brks.locations.length > 1) throw '>1 key src';
await sess.send('Debugger.removeBreakpoint', { breakpointId: brks.breakpointId });
let scriptId = brks.locations[0].scriptId;
let src = await sess.send('Debugger.getScriptSource', { scriptId });
console.log('got src', scriptId);

let keyPos = src.scriptSource.indexOf('static createPropertyValueWithCustomSupport');
if (keyPos === -1) throw 'key func 404';
let ifPos = src.scriptSource.indexOf('if', keyPos);
let callPos = src.scriptSource.indexOf('createPropertyValue', ifPos);
let rtnPos = src.scriptSource.indexOf('}', callPos);
console.log('pos found', ifPos, rtnPos);

await sess.send('Debugger.setBreakpoint', {
    location: { scriptId, lineNumber: 0, columnNumber: ifPos },
    condition: `(()=>{window.cuspre=e.customPreview()?.header;e.customPreview=()=>{return null};})()`
});

await sess.send('Runtime.evaluate', { expression: `
window.addCss = function(e) {
    e.getRootNode().querySelector('style').sheet.addRule(
        '[data-custom-header]::before',
        'content:attr(title) " "'
    );
};
// https://stackoverflow.com/a/66394121/9355208
window.onVisible = function(element, callback) {
    new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if(entry.intersectionRatio > 0) {
                callback(element);
                observer.disconnect();
            }
        });
    }).observe(element);
}
`});

await sess.send('Debugger.setBreakpoint', {
    location: { scriptId, lineNumber: 0, columnNumber: rtnPos },
    condition: `window.cuspre`
});

sess.removeAllListeners('Debugger.paused');
sess.on('Debugger.paused', async data => {
    let frame = data.callFrames[0];
    let rtnVal = frame.returnValue;
    if (!rtnVal) throw ['ex brkpt', data];
    await sess.send('Runtime.callFunctionOn', {
        functionDeclaration: `function() {
            let json = JSON.parse(window.cuspre);
            this.element.setAttribute('title', json[2]);
            this.element.setAttribute('data-custom-header', '');
            window.onVisible(this.element, addCss);
        }`,
        objectId: rtnVal.objectId
    });
    sess.send('Debugger.resume');
});
await sess.send('Debugger.resume').catch(()=>{});
console.log('done');
SE12938683
  • 196
  • 1
  • 7