Skip to content
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

Support iframes (nested browsing contexts) in selection event handling (stale version) #7936

Closed
wants to merge 8 commits into from

Conversation

acusti
Copy link
Contributor

@acusti acusti commented Oct 11, 2016

As mentioned in #7866 (comment), this PR takes the approach of checking all nested browsing contexts and saving and restoring their selections regardless of whether or not they are the primary focused element on the page. It is a more complete alternative to the solution in #7866, and like that PR, fixes #427. It is also, like the original, dependent on facebook/fbjs#188, but that PR has been merged into master, so as soon as a new fbjs version is released, this will be ready to go.

@acusti
Copy link
Contributor Author

acusti commented Oct 13, 2016

You can see this version of the solution in practice with an actual use case in this demo: http://codepen.io/acusti/pen/yajJbZ?editors=0010

The essential point is that I no longer need to manually manage focus in the app code and instead can rely on the ReactInputSelection functionality to take care of preserving visible “focus” (i.e. visible selections).

@acusti
Copy link
Contributor Author

acusti commented Oct 13, 2016

If anyone wants to play around with this, I pushed up a copy of the built react and react-dom scripts:

https://rawgit.com/Brandcast/react/nested-browsing-contexts-2-built/build/react.js
https://rawgit.com/Brandcast/react/nested-browsing-contexts-2-built/build/react-dom.js


it('gets and restores selection for inputs in an iframe that get remounted', () => {
var iframe = document.createElement('iframe');
iframe.setAttribute('tabIndex', 0);
Copy link
Contributor Author

@acusti acusti Oct 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting tabIndex on the iframe element here and then invoking iframe.focus() below at line 202 was only necessary because jsdom didn’t handle setting activeElement in the parent correctly. This is fixed in jsdom v9.7.0, so if we updated jest’s jsdom dependency, I could remove the hacky workaround.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the version of jsdom used by jest has landed (includes a fix of a major perf regression, so it’ll be fun to see how that affects test running times), so when a new version is released, should be able to remove the hacky workarounds in this test.

@acusti
Copy link
Contributor Author

acusti commented Oct 18, 2016

(EDIT: I updated the commit hash in the react dependency string below because the previous commit I pointed to didn’t have the correct lib/* files)

If anyone wants npm-installable versions of this PR to experiment with, you can use these in your package.json (requires the latest fbjs changes in master which have not yet been released, which is why that dependency also needs to be included):

    "fbjs": "Brandcast/fbjs-built#97b8e54",
    "react": "Brandcast/react#9a81d1a",

The dependency on fbjs changes also means the tests won’t pass until there’s a new release and that dep can be updated. Specifically, the assertions on the result of ReactInputSelection.getSelectionInformation() around this line and below.

@acusti
Copy link
Contributor Author

acusti commented Nov 10, 2016

@spicyj I’ve put together a Draft.js demo of this PR along with the Draft.js PR to illustrate the utility of making React work across nested browsing contexts. The PR is facebookarchive/draft-js#765, and the demo is here: http://codepen.io/acusti/pen/RGEJZE?editors=0011

In that demo, I used built versions of React and Draft.js which include all the changes for working across nested browsing contexts. Those built versions are usable in node-land or the browser:

EDIT See later comment for updated usable deps

In node

    "draft-js": "Brandcast/draft-js#f9affa3",
    "fbjs": "Brandcast/fbjs-built#97b8e54",
    "react": "Brandcast/react#9a81d1a",

In the browser

    <script src="https://rawgit.com/Brandcast/react/nested-browsing-contexts-2-built/build/react.js"></script>
    <script src="https://rawgit.com/Brandcast/react/nested-browsing-contexts-2-built/build/react-dom.js"></script>
    <script src="https://cdn.jsdelivr.net/immutable.js/latest/immutable.min.js"></script>
    <script src="https://cdn.jsdelivr.net/es6.shim/0.35.1/es6-shim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.js"></script>
    <script src="https://rawgit.com/Brandcast/draft-js/iframe-compatibility-built/dist/Draft.js"></script>

I also rebased my branch (for this PR to React) to remove all upstream conflicts.

@acusti
Copy link
Contributor Author

acusti commented Dec 2, 2016

Just updated the PR after rebasing with master. There was a conflict in ReactInputSelection as a result of the new code added in #8401 from @spicyj to preserve scroll position when restoring selection and focusing an element. I incorporated that new functionality into these changes.

I also verified that I could remove the hacky workaround in the new ReactInputSelection tests that I had mentioned previously thanks to jsdom being updated to have more accurate iframe focus behavior, so the tests are a bit cleaner now. And npm test is all passing.

@acusti
Copy link
Contributor Author

acusti commented Dec 7, 2016

Thought I should mention that I’ve also updated my built versions of everything, including draft-js (v0.9.0), react (v15.4.1), and react-dom (v15.4.1). This is particularly important because the previous built versions were based on the React master branch 26 days ago (i.e. 16.0.0-alpha, or fiber), whereas these built versions are built from 15.4.1. To try out the PRs:

npm / yarn

    "draft-js": "brandcast/draft-js-built#333bf90",
    "fbjs": "brandcast/fbjs-built#3961252",
    "react": "brandcast/react-built#ab31c46",
    "react-dom": "brandcast/react-dom-built#6cd1db3",

in the browser

    <script src="https://rawgit.com/brandcast/react-built/15-stable/dist/react.js"></script>
    <script src="https://rawgit.com/brandcast/react-dom-built/15-stable/dist/react-dom.js"></script>
    <script src="https://cdn.jsdelivr.net/immutable.js/latest/immutable.min.js"></script>
    <script src="https://cdn.jsdelivr.net/es6.shim/0.35.1/es6-shim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.js"></script>
    <script src="https://rawgit.com/brandcast/draft-js-built/master/dist/Draft.js"></script>

@aleksey-shmatov
Copy link

There is minor issue in Safari. I have one iframe with my content and one iframe for Google Maps. My website accessed with "http" and Google Maps hosted with "https". In the getElementsWithSelections following code does not throw error:
try {doc = win.document;} catch (e) {return acc;}
"doc" will be undefined after this code. This causes "undefined is not an object" exception and breaks functionality.

@acusti
Copy link
Contributor Author

acusti commented Jan 5, 2017

Interesting. I suppose just adding an additional early return if doc is falsy would handle that. Thanks for the feedback!

@aleksey-shmatov
Copy link

Thanks for adding this functionality, hope it get merged into master soon(together with draft-js). I have been working with this for about a week now and the only issue I encountered was that one for Safari.

@philipp-spiess
Copy link
Contributor

@spicyj What's the status here? We'd need that behaviour but we can't afford to maintain a fork of React. Do you still have plans to merge this? Your last update is unfortunately already a few months old.

@acusti
Copy link
Contributor Author

acusti commented Mar 10, 2017

@sebmarkbage To clarify, did you remove the GH Review: review-needed label because someone did a review?

@sophiebits
Copy link
Collaborator

@acusti No, we have switched to using the github review feature for all PRs.

@acusti acusti force-pushed the nested-browsing-contexts-2 branch 2 times, most recently from 2ca7449 to b48f472 Compare March 15, 2017 04:43
acusti added a commit to brandcast/react that referenced this pull request Mar 15, 2017
@acusti acusti changed the title Support iframes (nested browsing contexts) in selection event handling Support iframes (nested browsing contexts) in selection event handling (stale version) Mar 15, 2017
@acusti
Copy link
Contributor Author

acusti commented Mar 15, 2017

I tried to rebase this branch from master and ran into a world of hurt, so I created a fresh branch off latest master cherry-picked over all the commits, and made a new PR: #9184

@aleksey-shmatov, I added a guard for the Safari bug you found in f8b202d, as well as dealing with a Firefox issue I found in my own testing. Thanks for trying it out and reporting back! I’ll hoping to make new built versions of everything off of these commits applied to 15-stable and update the npm / yarn instructions on using those soon. When I do, I will update both this now-stale PR and #9184 with the updated dependency strings.

acusti added a commit to brandcast/react that referenced this pull request Apr 9, 2017
acusti added a commit to brandcast/react that referenced this pull request May 4, 2017
acusti added a commit to brandcast/react that referenced this pull request May 4, 2017
acusti added a commit to brandcast/react that referenced this pull request May 4, 2017
mihaip added a commit to quip/react that referenced this pull request Jun 29, 2017
React generally handles being rendered into another window context correctly
(we have been doing this for a while in native Mac popovers).

The main place where there are global window/document accesses are in places
where we deal with the DOM selection (window.getSelection() and
document.activeElement). There has been some discussion about this in the
public React GitHub repo:

facebook/fbjs#188
facebook#7866
facebook#7936
facebook#9184

While this was a good starting point, those proposed changes did not go far
enough, since they assumed that React was executing in the top-most window,
and the focus was in a child frame (in the same origin). Thus for them it was
possible to check document.activeElement in the top window, find which iframe
had focus and then recurse into it. In our case, the controller and view frames
are siblings, and the top window is in another origin, so we can't use that code
path.

The main reason why we can't get the current window/document is that
ReactInputSelection runs as a transaction wrapper, which doesn't have access
to components or DOM nodes (and may run across multiple nodes). To work around
this I added a ReactLastActiveThing which keeps track of the last DOM node that
we mounted a component into (for the initial render) or the last component that
we updated (for re-renders). It's kind of gross, but I couldn't think of any
better alternatives.

All of the modifications are no-ops when not running inside a frame, so this
should have no impact for non-elements uses.

I did not update any of the IE8 selection API code paths, we don't support it.
acusti added a commit to brandcast/react that referenced this pull request Jul 27, 2017
mihaip added a commit to quip/react that referenced this pull request Sep 29, 2017
React generally handles being rendered into another window context correctly
(we have been doing this for a while in native Mac popovers).

The main place where there are global window/document accesses are in places
where we deal with the DOM selection (window.getSelection() and
document.activeElement). There has been some discussion about this in the
public React GitHub repo:

facebook/fbjs#188
facebook#7866
facebook#7936
facebook#9184

While this was a good starting point, those proposed changes did not go far
enough, since they assumed that React was executing in the top-most window,
and the focus was in a child frame (in the same origin). Thus for them it was
possible to check document.activeElement in the top window, find which iframe
had focus and then recurse into it. In our case, the controller and view frames
are siblings, and the top window is in another origin, so we can't use that code
path.

The main reason why we can't get the current window/document is that
ReactInputSelection runs as a transaction wrapper, which doesn't have access
to components or DOM nodes (and may run across multiple nodes). To work around
this I added a ReactLastActiveThing which keeps track of the last DOM node that
we mounted a component into (for the initial render) or the last component that
we updated (for re-renders). It's kind of gross, but I couldn't think of any
better alternatives.

All of the modifications are no-ops when not running inside a frame, so this
should have no impact for non-elements uses.

I did not update any of the IE8 selection API code paths, we don't support it.

(cherry picked from commit 94b759b in
the 0.14-stable. Appears to work mostly as is, needed to be updated to
take 5c5d2ec into account)
@acusti
Copy link
Contributor Author

acusti commented Oct 31, 2017

For anyone who has been following along with this effort, I’m closing this in favor of a new PR: #11410

If you want to use the changes in the latest PR, which is based off of react 16, in your own project, you can do so by updating the react and react-dom dependencies in your package.json to:

    "react": "brandcast/react-built#7c8fc6b",
    "react-dom": "brandcast/react-dom-built#017534a",

If you’re also using draft.js and want the full latest and greatest cross-iframe experience, use:

    "draft-js": "brandcast/draft-js-built#003f24b",

@acusti acusti closed this Oct 31, 2017
acusti added a commit to brandcast/react that referenced this pull request Nov 1, 2017
@gaearon
Copy link
Collaborator

gaearon commented Aug 2, 2018

We went with #12037

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

Successfully merging this pull request may close these issues.

Use the right document in iframe selection events
7 participants