-
Notifications
You must be signed in to change notification settings - Fork 49.1k
Fix: ReactDevTool crashes if we have sandbox iframe in props #19994
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
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 77bac8c:
|
Thanks for the PR @omarsy. I have a few questions though before moving forward. |
case 'window': | ||
try { | ||
// eslint-disable-next-line no-unused-expressions | ||
data.origin; | ||
} catch { | ||
cleaned.push(path); | ||
return { | ||
inspectable: false, | ||
preview_short: 'Window', | ||
preview_long: 'Window', | ||
name: 'Window', | ||
type, | ||
}; | ||
} | ||
// eslint-disable-next-line no-fallthrough |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should use the fall through here. Even with the explicit lint rule, it's hard to read and would be easy to break.
Why shouldn't we return the window object regardless of the try/catch?
If we do need the current behavior for a reason I'm not seeing, can we rewrite to make it more explicit, e.g.
case 'object':
case 'window':
if (type === 'window') {
try {
// eslint-disable-next-line no-unused-expressions
data.origin;
} catch {
cleaned.push(path);
return {
inspectable: false,
preview_short: 'Window',
preview_long: 'Window',
name: 'Window',
type,
};
}
}
isPathAllowedCheck = isPathAllowed(path);
if (level >= LEVEL_THRESHOLD && !isPathAllowedCheck) {
return createDehydrated(type, true, data, cleaned, path);
} else {
const object = {};
getAllEnumerableKeys(data).forEach(key => {
const name = key.toString();
object[name] = dehydrate(
data[key],
cleaned,
unserializable,
path.concat([name]),
isPathAllowed,
isPathAllowedCheck ? 1 : level + 1,
);
});
return object;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why shouldn't we return the window object regardless of the try/catch?
I want to keep the behaviour we had before. We used to display all the elements of the Window object when we could access it.
@@ -325,7 +339,6 @@ export function dehydrate( | |||
return { | |||
type, | |||
}; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Keep the empty line on 298 and 328 for consistency with the rest of the file 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:-D Reverted
if (data.self === data && data.parent && data.top) { | ||
return 'window'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like an convoluted way to check for window objects. Why wouldn't something like data instanceof Window
work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also a little confused about why this fix (for window elements) is required to fix the linked bug about iframes. Wouldn't we need to be adding a check for data instanceof HTMLIFrameElement
here instead of window?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @bvaughn
This seems like an convoluted way to check for window objects. Why wouldn't something like data instanceof Window work?
It doesn't always work for Iframe it considers that sometimes it is an Object.
I've been looking at how the others are dealing with it:
JQuery: https://api.jquery.com/jquery.iswindow/
Anglular: https://github.com/angular/angular.js/blob/master/src/Angular.js#L677
I added some check to make it more robust but i flow the same behaviour.
I'm also a little confused about why this fix (for window elements) is required to fix the linked bug about iframes. Wouldn't we need to be adding a check for data instanceof HTMLIFrameElement here instead of window?
I tested with the iframe element. It works
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand. This check doesn't really look like the two you linked to (JQuery and Angular). It also looks like it could hit a false positive, e.g.
// This would be mistake as type "window"
const fake = {
parent: 1,
top: 1,
// ...
};
fake.self = fake;
But it wouldn't if the check was:
if (data instanceof Window || data instanceof HTMLIFrameElement) {
return 'window';
}
So I'm curious why this doesn't work?
The JQuery function you linked to has been deprecated since v3.3 which is almost 3 years old, and the Angular check you linked to looks like it's in Angular v1 which is also deprecated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I think I've come around to the necessity of this "looks like a window" check, although I think we need an inline comment here explaining the reason we check this way (aka the iframe.contentWindow
case).
Maybe we could also handle the more common case explicitly? I dunno, maybe something like this?
// We fallback to duck typing to handle an edge case:
// An HTMLIFrameElement's "contentWindow" is not an instance of Window,
// but rather an instance of the iframe's own Window (which we can't reliably access).
if (
data instanceof Window ||
data instanceof HTMLIFrameElement ||
(data.self === data && data.parent != null && data.top != null)
) {
return "window";
}
Also, maybe this whole conditional should be moved down into the case 'object':
block.
Sorry @bvaughn for the delay. |
// Todo: Uncomment if this feature is implemented https://github.com/jsdom/jsdom/issues/1892 | ||
// const iframe = document.createElement('iframe'); | ||
// iframe.setAttribute('sandbox', ''); | ||
// window.document.body.appendChild(iframe); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add something like this as a fixture test in the meanwhile? (It doesn't seem too likely that JSDom will implement this.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I will add the test :)
if (data.self === data && data.parent && data.top) { | ||
return 'window'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand. This check doesn't really look like the two you linked to (JQuery and Angular). It also looks like it could hit a false positive, e.g.
// This would be mistake as type "window"
const fake = {
parent: 1,
top: 1,
// ...
};
fake.self = fake;
But it wouldn't if the check was:
if (data instanceof Window || data instanceof HTMLIFrameElement) {
return 'window';
}
So I'm curious why this doesn't work?
The JQuery function you linked to has been deprecated since v3.3 which is almost 3 years old, and the Angular check you linked to looks like it's in Angular v1 which is also deprecated.
Yes, I changed it on purpose. To avoid as many false positives as possible. But it is the same idea
I don't know why browsers only consider the main window as a window type and the window of iframes are considered as objects. That's why I've been looking at how other libs do it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see! Thanks for explaining. I was checking the main window
or the iframe
itself, not the contentWindow
.
Ping me once the fixture is ready 😄
if (type === 'window') { | ||
try { | ||
// eslint-disable-next-line no-unused-expressions | ||
data.origin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use an inline comment explaining the case(s) it's intended to catch.
@@ -78,7 +78,7 @@ export function getAllEnumerableKeys( | |||
const descriptors = Object.getOwnPropertyDescriptors(current); | |||
currentKeys.forEach(key => { | |||
// $FlowFixMe: key can be a Symbol https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor | |||
if (descriptors[key].enumerable) { | |||
if (descriptors[key] && descriptors[key].enumerable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have an example of where this would have thrown before? (Can we add a test for it to ensure that we don't regress?)
if (data.self === data && data.parent && data.top) { | ||
return 'window'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I think I've come around to the necessity of this "looks like a window" check, although I think we need an inline comment here explaining the reason we check this way (aka the iframe.contentWindow
case).
Maybe we could also handle the more common case explicitly? I dunno, maybe something like this?
// We fallback to duck typing to handle an edge case:
// An HTMLIFrameElement's "contentWindow" is not an instance of Window,
// but rather an instance of the iframe's own Window (which we can't reliably access).
if (
data instanceof Window ||
data instanceof HTMLIFrameElement ||
(data.self === data && data.parent != null && data.top != null)
) {
return "window";
}
Also, maybe this whole conditional should be moved down into the case 'object':
block.
This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated. |
bump |
Summary
When we have an iframe in the props it causes a crash in reactdevtool.
You can see here a demo of the crash: https://codesandbox.io/s/modest-moser-jg9x9?file=/src/index.js.
Before:
After:

Maybe correct this issue #19854 (comment)
Test Plan