Skip to content

Console.log() printing to browser console instead of p5 console #2518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
CubeOfCheese opened this issue Oct 19, 2023 · 10 comments
Closed

Console.log() printing to browser console instead of p5 console #2518

CubeOfCheese opened this issue Oct 19, 2023 · 10 comments

Comments

@CubeOfCheese
Copy link

p5.js version

most recent I'm assuming.

What is your operating system?

Windows

Web browser and version

Firefox Version 118.0.1

Actual Behavior

Console.log is printing to the browser's console

Expected Behavior

console.log should print to the p5 editor console

Steps to reproduce

Steps:

  1. open new editor window
  2. create a console.log statement
  3. hit run
  4. open dev tools to see that it is printing to the browser console

Snippet:

console.log("HI");

p5 console log error screenshot

@welcome
Copy link

welcome bot commented Oct 19, 2023

Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, be sure to follow the issue template if you haven't already.

@marestaing
Copy link

I've been running into this issue too both on mobile and web.

@dcarroll242
Copy link

dcarroll242 commented Oct 20, 2023

If you're looking for a timeline, this issue just started yesterday (10/19/2023) and seems to be unrelated to the version of p5.js.

This issue is extremely problematic for us teachers. We'll eventually get to all the amazing graphics that p5 has to offer within the canvas, but we start with the basics using the console and vanilla JavaScript within p5's web editor. (students aren't allowed to inspect the page in most school districts)

@raclim
Copy link
Collaborator

raclim commented Oct 20, 2023

We just pushed a new release to production earlier this week on Wednesday, so this issue probably stems from one of the changes in this release. I'll look into it and will try to have an update for it soon!

@CubeOfCheese
Copy link
Author

FYI I forgot to mention that this is affecting everything meant for the console. So not only console.log statements but all errors and warnings as well.

@KrishavRajSingh
Copy link

can you give me a little hint on how to approach this issue?

@madtitan02
Copy link

I guess the console handler is not Implementing a system to capture and display code editor errors in your console window , With addition of Error Handling in Code Editor and use of dispatching errors to redux Store and would have to update console.jsx , Am i going in right direction @lindapaiste @raclim , I think adding these can solve this error if there are some suggestion please let me know and if this approach seems fine do assign me this issue i will start working on it

@lindapaiste
Copy link
Collaborator

It is working fine for me in Windows 10 on Chrome, Firefox, Edge, and Opera.

@raclim If I have to guess, I think the issue is probably related to your note in the fix PR "to address an error where the target origin does not match the recipient for the iFrame."

Another suspect would be #2426. But we've always had the console errors script running first with head.prepend so I don't think that's it?

sketchDoc.head.prepend(consoleErrorsScript);

@lindapaiste
Copy link
Collaborator

lindapaiste commented Oct 21, 2023

For anyone looking at this, it's honestly pretty complicated. The user's code is executed in an iframe from preview.p5js.org. We send messages from that frame to the editor using postMessage.

The relevant code is primarily in:

import loopProtect from 'loop-protect';
import { Hook, Decode, Encode } from 'console-feed';
import StackTrace from 'stacktrace-js';
import evaluateExpression from './evaluateExpression';
// should postMessage user the dispatcher? does the parent window need to
// be registered as a frame? or a just a listener?
// could maybe send these as a message idk
// const { editor } = window;
const editor = window.parent.parent;
const { editorOrigin } = window;
const htmlOffset = 12;
window.objectUrls[window.location.href] = '/index.html';
const blobPath = window.location.href.split('/').pop();
window.objectPaths[blobPath] = 'index.html';
window.loopProtect = loopProtect;
const consoleBuffer = [];
const LOGWAIT = 500;
Hook(window.console, (log) => {
consoleBuffer.push({
log
});
});
setInterval(() => {
if (consoleBuffer.length > 0) {
const message = {
messages: consoleBuffer,
source: 'sketch'
};
editor.postMessage(message, editorOrigin);
consoleBuffer.length = 0;
}
}, LOGWAIT);
function handleMessageEvent(e) {
// maybe don't need this?? idk!
if (window.origin !== e.origin) return;
const { data } = e;
const { source, messages } = data;
if (source === 'console' && Array.isArray(messages)) {
const decodedMessages = messages.map((message) => Decode(message.log));
decodedMessages.forEach((message) => {
const { data: args } = message;
const { result, error } = evaluateExpression(args);
const resultMessages = [
{ log: Encode({ method: error ? 'error' : 'result', data: [result] }) }
];
editor.postMessage(
{
messages: resultMessages,
source: 'sketch'
},
editorOrigin
);
});
}
}
window.addEventListener('message', handleMessageEvent);
// catch reference errors, via http://stackoverflow.com/a/12747364/2994108
window.onerror = async function onError(
msg,
source,
lineNumber,
columnNo,
error
) {
// maybe i can use error.stack sometime but i'm having a hard time triggering
// this function
let data;
if (!error) {
data = msg;
} else {
data = `${error.name}: ${error.message}`;
const resolvedFileName = window.objectUrls[source];
let resolvedLineNo = lineNumber;
if (window.objectUrls[source] === 'index.html') {
resolvedLineNo = lineNumber - htmlOffset;
}
const line = `\n at ${resolvedFileName}:${resolvedLineNo}:${columnNo}`;
data = data.concat(line);
}
editor.postMessage(
{
source: 'sketch',
messages: [
{
log: [
{
method: 'error',
data: [data],
id: Date.now().toString()
}
]
}
]
},
editorOrigin
);
return false;
};
// catch rejected promises
window.onunhandledrejection = async function onUnhandledRejection(event) {
if (event.reason && event.reason.message) {
let stackLines = [];
if (event.reason.stack) {
stackLines = await StackTrace.fromError(event.reason);
}
let data = `${event.reason.name}: ${event.reason.message}`;
stackLines.forEach((stackLine) => {
const { fileName, functionName, lineNumber, columnNumber } = stackLine;
const resolvedFileName = window.objectUrls[fileName] || fileName;
const resolvedFuncName = functionName || '(anonymous function)';
let line;
if (lineNumber && columnNumber) {
let resolvedLineNumber = lineNumber;
if (resolvedFileName === 'index.html') {
resolvedLineNumber = lineNumber - htmlOffset;
}
line = `\n at ${resolvedFuncName} (${resolvedFileName}:${resolvedLineNumber}:${columnNumber})`;
} else {
line = `\n at ${resolvedFuncName} (${resolvedFileName})`;
}
data = data.concat(line);
});
editor.postMessage(
{
source: 'sketch',
messages: [
{
log: [
{
method: 'error',
data: [data],
id: Date.now().toString()
}
]
}
]
},
editorOrigin
);
}
};
// Monkeypatch p5._friendlyError
const { _report } = window.p5;
if (_report) {
window.p5._report = function resolvedReport(message, method, color) {
const urls = Object.keys(window.objectUrls);
const paths = Object.keys(window.objectPaths);
let newMessage = message;
urls.forEach((url) => {
newMessage = newMessage.replaceAll(url, window.objectUrls[url]);
if (newMessage.match('index.html')) {
const onLineRegex = /on line (?<lineNo>.\d) in/gm;
const lineNoRegex = /index\.html:(?<lineNo>.\d):/gm;
const match = onLineRegex.exec(newMessage);
const line = match.groups.lineNo;
const resolvedLine = parseInt(line, 10) - htmlOffset;
newMessage = newMessage.replace(
onLineRegex,
`on line ${resolvedLine} in`
);
newMessage = newMessage.replace(
lineNoRegex,
`index.html:${resolvedLine}:`
);
}
});
paths.forEach((path) => {
newMessage = newMessage.replaceAll(path, window.objectPaths[path]);
});
_report.apply(window.p5, [newMessage, method, color]);
};
}

const frames = {};
let frameIndex = 1;
let listener = null;
export const MessageTypes = {
START: 'START',
STOP: 'STOP',
FILES: 'FILES',
SKETCH: 'SKETCH',
REGISTER: 'REGISTER',
EXECUTE: 'EXECUTE'
};
export function registerFrame(newFrame, newOrigin) {
const frameId = frameIndex;
frameIndex += 1;
frames[frameId] = { frame: newFrame, origin: newOrigin };
return () => {
delete frames[frameId];
};
}
function notifyListener(message) {
if (listener) listener(message);
}
function notifyFrames(message) {
const rawMessage = JSON.parse(JSON.stringify(message));
Object.keys(frames).forEach((frameId) => {
const { frame, origin } = frames[frameId];
if (frame && frame.postMessage) {
frame.postMessage(rawMessage, origin);
}
});
}
export function dispatchMessage(message) {
if (!message) return;
// maybe one day i will understand why in the codesandbox
// code they leave notifyListeners in here?
// notifyListener(message);
notifyFrames(message);
}
/**
* Call callback to remove listener
*/
export function listen(callback) {
listener = callback;
return () => {
listener = null;
};
}
function eventListener(e) {
const { data } = e;
// should also store origin of parent? idk
// if (data && e.origin === origin) {
if (data) {
notifyListener(data);
}
}
window.addEventListener('message', eventListener);

useEffect(() => {
const unsubscribe = registerFrame(
iframe.current.contentWindow,
window.origin
);
return () => {
unsubscribe();
};
});

const previewUrl = getConfig('PREVIEW_URL');
useEffect(() => {
const unsubscribe = registerFrame(iframe.current.contentWindow, previewUrl);
return () => {
unsubscribe();
};
});

const handleMessageEvent = useHandleMessageEvent();
useEffect(() => {
const unsubscribe = listen(handleMessageEvent);
return function cleanup() {
unsubscribe();
};
});

import { useDispatch } from 'react-redux';
import { Decode } from 'console-feed';
import { dispatchConsoleEvent } from '../actions/console';
import { stopSketch, expandConsole } from '../actions/ide';
export default function useHandleMessageEvent() {
const dispatch = useDispatch();
const handleMessageEvent = (data) => {
const { source, messages } = data;
if (source === 'sketch' && Array.isArray(messages)) {
const decodedMessages = messages.map((message) => Decode(message.log));
decodedMessages.every((message, index, arr) => {
const { data: args } = message;
let hasInfiniteLoop = false;
Object.keys(args).forEach((key) => {
if (
typeof args[key] === 'string' &&
args[key].includes('Exiting potential infinite loop')
) {
dispatch(stopSketch());
dispatch(expandConsole());
hasInfiniteLoop = true;
}
});
if (hasInfiniteLoop) {
return false;
}
return true;
});
dispatch(dispatchConsoleEvent(decodedMessages));
}
};
return handleMessageEvent;
}

(The lack of a dependency array on all of the useEffects is wonky but it's not new so I doubt that's the problem)

@raclim
Copy link
Collaborator

raclim commented Oct 31, 2023

Hi all, I think this error should be resolved by a different @lindapaiste refers to earlier in @2426. I just reverted it in #2547 and deployed it to production just now. I think this should fix it, but please let me know if this is still happening!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants