forked from facebook/react
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make React selection handling be multi-window aware.
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)
- Loading branch information
Showing
9 changed files
with
210 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/** | ||
* Copyright 2017 Quip | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @providesModule ReactCurrentWindow | ||
*/ | ||
|
||
'use strict'; | ||
|
||
var ReactDOMComponentTree = require('ReactDOMComponentTree'); | ||
var ReactInstanceMap = require('ReactInstanceMap'); | ||
var ReactLastActiveThing = require('ReactLastActiveThing'); | ||
|
||
var getHostComponentFromComposite = require('getHostComponentFromComposite'); | ||
|
||
var lastCurrentWindow; | ||
|
||
function warn(message) { | ||
if (__DEV__) { | ||
console.warn(message); | ||
} | ||
} | ||
|
||
function windowFromNode(node) { | ||
if (node.ownerDocument) { | ||
return node.ownerDocument.defaultView || | ||
node.ownerDocument.parentWindow; | ||
} | ||
return null; | ||
} | ||
|
||
function extractCurrentWindow() { | ||
var thing = ReactLastActiveThing.thing; | ||
if (!thing) { | ||
warn('No active thing.'); | ||
return null; | ||
} | ||
|
||
// We can't use instanceof checks since the object may be from a different | ||
// window and thus have a different constructor (from a different JS | ||
// context). | ||
if (thing.window === thing) { | ||
// Already a window | ||
return thing; | ||
} | ||
|
||
if (typeof thing.nodeType !== 'undefined') { | ||
// DOM node | ||
var nodeParentWindow = windowFromNode(thing); | ||
if (nodeParentWindow) { | ||
return nodeParentWindow; | ||
} else { | ||
warn('Could not determine node parent window.'); | ||
return null; | ||
} | ||
} | ||
|
||
if (thing.getPublicInstance) { | ||
// Component | ||
var component = thing.getPublicInstance(); | ||
if (!component) { | ||
warn('Could not get component public instance.'); | ||
return null; | ||
} | ||
var inst = ReactInstanceMap.get(component); | ||
if (!inst) { | ||
warn('Component is not in the instance map.'); | ||
return null; | ||
} | ||
inst = getHostComponentFromComposite(inst); | ||
if (!inst) { | ||
warn('Cannot get host component.'); | ||
return null; | ||
} | ||
var componentNode = ReactDOMComponentTree.getNodeFromInstance(inst); | ||
if (!componentNode) { | ||
warn('Could not get node from component.'); | ||
return null; | ||
} | ||
var componentParentWindow = windowFromNode(componentNode); | ||
if (componentParentWindow) { | ||
return componentParentWindow; | ||
} | ||
warn('Could not determine component node parent window.'); | ||
return null; | ||
} | ||
|
||
warn('Fallthrough, unexpected active thing type'); | ||
return null; | ||
} | ||
|
||
var ReactCurrentWindow = { | ||
currentWindow: function() { | ||
if (window.top === window) { | ||
// Fast path for non-frame cases. | ||
return window; | ||
} | ||
|
||
var currentWindow = extractCurrentWindow(); | ||
if (currentWindow) { | ||
lastCurrentWindow = ReactLastActiveThing.thing = currentWindow; | ||
return currentWindow; | ||
} | ||
if (lastCurrentWindow) { | ||
warn('Could not determine current window, using the last value'); | ||
return lastCurrentWindow; | ||
} | ||
warn('Could not determine the current window, using the global value'); | ||
return window; | ||
}, | ||
}; | ||
|
||
module.exports = ReactCurrentWindow; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
src/renderers/dom/client/getActiveElementForCurrentWindow.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/** | ||
* Copyright Quip 2017 | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @providesModule getActiveElementForCurrentWindow | ||
* @typechecks | ||
*/ | ||
|
||
|
||
/** | ||
* Re-implementation of getActiveElement from fbjs that uses ReactCurrentWindow | ||
* to get the active element in the window that the currently executing component | ||
* is rendered into. | ||
*/ | ||
'use strict'; | ||
|
||
var ReactCurrentWindow = require('ReactCurrentWindow'); | ||
|
||
function getActiveElement() /*?DOMElement*/{ | ||
var currentWindow = ReactCurrentWindow.currentWindow(); | ||
var document = currentWindow.document; | ||
if (typeof document === 'undefined') { | ||
return null; | ||
} | ||
try { | ||
return document.activeElement || document.body; | ||
} catch (e) { | ||
return document.body; | ||
} | ||
} | ||
|
||
module.exports = getActiveElement; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/** | ||
* Copyright 2017 Quip | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @providesModule ReactLastActiveComponent | ||
*/ | ||
|
||
'use strict'; | ||
|
||
/** | ||
* Stores a reference to the most recently component DOM container (for the | ||
* initial render) or updated component (for updates). Meant to be used by | ||
* {@code ReactCurrentWindow} to determine the window that components are | ||
* currently being rendered into. | ||
*/ | ||
var ReactLastActiveThing = { | ||
/** | ||
* @type {Window|DOMElement|ReactComponent|null} | ||
*/ | ||
thing: null, | ||
|
||
}; | ||
|
||
module.exports = ReactLastActiveThing; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters