ブラウザのwindow間の値渡し

(2018-02-23)

直接Windowを参照する

プロトコル、ポート、ドメインが全て同じ場合は、親はopen()した返り値で、子はwindow.openerで相手のwindowが取れて、直接参照したりDOMを操作したりすることもできる。

$ cat index.html
<button id="btn">Open window</button>
<button id="btn2">Close window</button>
<div id="view"></div>
<script>
  let win2;
  const button = document.getElementById("btn");
  button.addEventListener("click", () => {
    window.foo = "bar from window1";
    win2 = window.open("index2.html");
  }, false);

  const button2 = document.getElementById("btn2");
  button2.addEventListener("click", () => {
    if (win2) {
      win2.close();
    }
  }, false);
</script>
$ cat index2.html
<button id="btn">Close window</button>
<div id="view"></div>
<script>
  console.log(window.aaa);
  const parentWindow = window.opener;
  const view = document.getElementById("view");
  view.textContent = parentWindow.foo; // window1 -> window2

  const button = document.getElementById("btn");
  button.addEventListener("click", () => {
    if(!parentWindow) {
      window.close();
    }
    const view = parentWindow.document.getElementById("view");
    if (view) {
      view.textContent = "Window2 has been closed by myself"; // window2 -> window1
    }
    window.close();
  }, false);
</script>

確認する際はドメインの制約を満たすためnode-staticなどでサーバーを立てる必要がある。

直接Windowを参照する値渡し

postMessage()を使う

postMessageで送り、messageのeventで受け取れる。ドメインなどが異なっていてもよいが、 どこからでも送れてしまうので、ハンドリングする際はevent.originが意図したものかチェックしなくてはならない。

$ cat index.html
<button id="btn">Open window</button>
<button id="btn2">Post to window</button>
<div id="view"></div>
<script>
const view = document.getElementById("view");
const receiveMessage = (event) => {
  if (event.origin === "http://localhost:8081") {
    view.textContent = event.data;
  }
}
window.addEventListener("message", receiveMessage, false);

let win2;
const button = document.getElementById("btn");
button.addEventListener("click", () => {
  win2 = window.open("http://localhost:8081/index2.html");
}, false);

const button2 = document.getElementById("btn2");
button2.addEventListener("click", () => {
  win2.postMessage("bar from window1", "http://localhost:8081");
}, false);
</script>
$ cat index2.html
<script>
const receiveMessage = (event) => {
  if (event.origin === "http://localhost:8080") {
    event.source.postMessage(
      `window2 received data: ${event.data}`,
      event.origin
    );
  }
  window.close();
}
window.addEventListener("message", receiveMessage, false);
</script>

別のポートでも値が渡されていることが確認できる。

postMessage()での値渡し

ReactでpostMessageする

componentDidMount()でaddEventListenerしてcomponentWillUnmount()でremoveする。

$ cat index.jsx
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
    this.handleChange = this.handleChange.bind(this);
    this.handleMessage = this.handleMessage.bind(this);
    this.openWindow = this.openWindow.bind(this);
  }
  componentDidMount() {
    window.addEventListener('message', this.handleMessage);
  }
  componentWillUnmount() {
    window.removeEventListener('message', this.handleMessage);
  }
  handleMessage(event) {
    if (
      (window.opener && event.origin === "http://localhost:8080") ||
      (!window.opener && event.origin === "http://localhost:8081")
    ) {
      this.setState({value: event.data});
    }
  }
  handleChange(event) {
    this.setState({value: event.target.value});
    if (this.newWindow) {
      this.newWindow.postMessage(this.state.value, "http://localhost:8081"); // window1 -> window2
    } else if (window.opener) {
      window.opener.postMessage(this.state.value, "http://localhost:8080"); // window2 -> window1
    }
  }
  openWindow(event) {
    this.newWindow = window.open('http://localhost:8081/index.html', '_blank', 'width=640, height=480');
  }
  render() {
    return <div>
      { !window.opener ? <button onClick={this.openWindow}>Open Window</button> : <div></div>}
      <input type="text" value={this.state.value} onChange={this.handleChange} />
    </div>;
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
$ cat index.html
<dic id="root"></div>
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<script src="index.js"></script>
$ yarn add --dev @babel/cli @babel/core @babel/preset-react
$ echo '{ "presets": ["@babel/preset-react"] }' > .babelrc
$ ./node_modules/.bin/babel index.jsx -o index.js

ReactでpostMessage()での値渡し