Repository: manusa/electronim
Pull request #32 (2020-08-12 18:18:15)

✨: Screen can be shared
Screen/Window to share can be selected from a list of windows
in an embedded popup in the current webapplication window

Clicking the window thumbnail shares that screen/window

Clicking on the surrounding areas cancels the selection

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=32&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=32&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/package-lock.json b/package-lock.json
index 56b85ab..6b3eed3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -368,18 +368,18 @@
       }
     },
     "@babel/runtime": {
-      "version": "7.10.4",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz",
-      "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==",
+      "version": "7.11.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+      "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
       "dev": true,
       "requires": {
         "regenerator-runtime": "^0.13.4"
       }
     },
     "@babel/runtime-corejs3": {
-      "version": "7.10.4",
-      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz",
-      "integrity": "sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw==",
+      "version": "7.11.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz",
+      "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==",
       "dev": true,
       "requires": {
         "core-js-pure": "^3.0.0",
@@ -752,15 +752,15 @@
       }
     },
     "@testing-library/dom": {
-      "version": "7.20.0",
-      "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.20.0.tgz",
-      "integrity": "sha512-TywaC+qDGm/Ro34kRYkFQPdT+pxSF4UjZGLIqcGfFQH5IGR43Y7sGLPnkieIW/GNsu337oxNsLUAgpI0JWhXHw==",
+      "version": "7.22.1",
+      "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.22.1.tgz",
+      "integrity": "sha512-bEszhvj9LcspaRz56mqGV7uc+vJTAYKCKPJcGb5X6U1qBysgTAgCexQXvKZ3BBjWu5S/TANP2NniOVsMWqKXcw==",
       "dev": true,
       "requires": {
         "@babel/runtime": "^7.10.3",
         "@types/aria-query": "^4.2.0",
         "aria-query": "^4.2.2",
-        "dom-accessibility-api": "^0.4.5",
+        "dom-accessibility-api": "^0.5.0",
         "pretty-format": "^25.5.0"
       },
       "dependencies": {
@@ -2368,9 +2368,9 @@
       }
     },
     "dom-accessibility-api": {
-      "version": "0.4.5",
-      "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.4.5.tgz",
-      "integrity": "sha512-HcPDilI95nKztbVikaN2vzwvmv0sE8Y2ZJFODy/m15n7mGXLeOKGiys9qWVbFbh+aq/KYj2lqMLybBOkYAEXqg==",
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.0.tgz",
+      "integrity": "sha512-eCVf9n4Ni5UQAFc2+fqfMPHdtiX7DA0rLakXgNBZfXNJzEbNo3MQIYd+zdYpFBqAaGYVrkd8leNSLGPrG4ODmA==",
       "dev": true
     },
     "domexception": {
@@ -6179,9 +6179,9 @@
       }
     },
     "regenerator-runtime": {
-      "version": "0.13.5",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
-      "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==",
+      "version": "0.13.7",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+      "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
       "dev": true
     },
     "regex-not": {
diff --git a/package.json b/package.json
index 80d5051..96e634d 100644
--- a/package.json
+++ b/package.json
@@ -140,7 +140,7 @@
     ]
   },
   "devDependencies": {
-    "@testing-library/dom": "^7.20.0",
+    "@testing-library/dom": "^7.22.1",
     "electron-builder": "^22.7.0",
     "electron-packager": "^15.0.0",
     "eslint": "^7.4.0",
diff --git a/src/tab-manager/browser-mediadevices-shim.js b/src/tab-manager/browser-mediadevices-shim.js
new file mode 100644
index 0000000..db99469
--- /dev/null
+++ b/src/tab-manager/browser-mediadevices-shim.js
@@ -0,0 +1,160 @@
+/*
+   Copyright 2019 Marc Nuri San Felix
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ */
+const {desktopCapturer} = require('electron');
+const {h, render} = require('preact');
+const {useEffect, useState} = require('preact/hooks');
+const htm = require('htm');
+const html = htm.bind(h);
+
+const ROOT_CLASS = 'electron-desktop-capturer-root';
+const DEFAULT_SOURCES_OPTIONS = {
+  types: ['screen', 'window']
+};
+
+let currentRoot = null;
+
+const removeRoot = () => {
+  const $root = document.querySelector(`.${ROOT_CLASS}`);
+  if ($root) {
+    render(null, $root, currentRoot);
+    $root.remove();
+  }
+};
+
+const getOrCreateRoot = () => {
+  removeRoot();
+  const $root = document.createElement('div');
+  $root.classList.add(ROOT_CLASS);
+  document.body.append($root);
+  return $root;
+};
+
+const stream = async id => window.navigator.mediaDevices.getUserMedia({
+  audio: false,
+  video: {
+    mandatory: {
+      chromeMediaSource: 'desktop',
+      chromeMediaSourceId: id
+    }
+  }
+});
+
+const Style = () => html`
+  <style>
+    .${ROOT_CLASS} {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100vh;
+      background: rgba(30,30,30,.75);
+      color: #fff;
+      z-index: 999999;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    .${ROOT_CLASS} .${ROOT_CLASS}__sources {
+      overflow: auto;
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: center;
+      max-height: 100vh;
+      padding: 6px;
+    }
+    .${ROOT_CLASS} .${ROOT_CLASS}__source {
+      display: flex;
+      flex-direction: column;
+      align-items: stretch;
+      width: 145px;
+      margin: 6px;
+      pointer-events: bounding-box;
+      cursor: pointer;
+      background: rgba(30,30,30,.5);
+      padding: 6px;
+      border-radius: 3px;
+    }
+    .${ROOT_CLASS} .${ROOT_CLASS}__source:hover,
+    .${ROOT_CLASS} .${ROOT_CLASS}__source:focus {
+      background: rgba(255,255,255,.1);
+    }
+    .${ROOT_CLASS} .${ROOT_CLASS}__thumbnail {
+      margin: 0 auto;
+      width: 100%;
+      height: 81px;
+      object-fit: cover;
+    }
+    .${ROOT_CLASS} .${ROOT_CLASS}__name {
+      margin-top: 3px;
+      text-align: center;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      user-select: none;
+    }
+  </style>
+`;
+
+// eslint-disable-next-line no-unused-vars
+const Source = ({id, name, thumbnail, display_id, appIcon, resolve}) => {
+  const selectStream = async event => {
+    event.preventDefault();
+    event.stopPropagation();
+    resolve(await stream(id));
+    removeRoot();
+  };
+  return html`
+    <div class="${ROOT_CLASS}__source" onclick=${selectStream}>
+      <img class="${ROOT_CLASS}__thumbnail" alt=${id} src=${thumbnail.toDataURL()} />
+      <span class="${ROOT_CLASS}__name">${name}</span>
+    </div>
+  `;
+};
+
+const LoadingSources = ({sources}) => sources === null && html`
+  <div>Loading sources...</div>
+`;
+
+const NoSourcesFound = ({sources}) => sources !== null && sources.length === 0 &&
+  html`<div>No sources found</div>`;
+
+const Container = ({resolve, reject}) => {
+  const [sources, setSources] = useState(null);
+  const updateSourcesFunction = async () => setSources(await desktopCapturer.getSources(DEFAULT_SOURCES_OPTIONS));
+  useEffect(() => {
+    setTimeout(updateSourcesFunction, sources ? 300 : 0);
+  }, [sources]);
+  const cancel = () => {
+    reject(new Error('Screen share aborted by user'));
+    removeRoot();
+  };
+  return html`
+    <${Style}/>
+    <div class="${ROOT_CLASS}__sources" onclick=${cancel}>
+      <${NoSourcesFound} sources=${sources}/>
+      <${LoadingSources} sources=${sources}/>
+      ${sources !== null && sources.map(source => (html`
+        <${Source} resolve=${resolve} ...${source} />
+      `))}
+    </div>
+  `;
+};
+
+const getDisplayMedia = async (resolve, reject) => {
+  currentRoot = render(html`<${Container} resolve=${resolve} reject=${reject}/>`, getOrCreateRoot());
+};
+
+window.navigator.mediaDevices.getDisplayMedia = () => new Promise(getDisplayMedia);
diff --git a/src/tab-manager/preload.js b/src/tab-manager/preload.js
index 5b92e41..97a77b1 100644
--- a/src/tab-manager/preload.js
+++ b/src/tab-manager/preload.js
@@ -15,8 +15,9 @@
  */
 require('../main/preload');
 const {webFrame} = require('electron');
-const {initKeyboardShortcuts} = require('./browser-keyboard-shortcuts');
 require('./browser-notification-shim');
+require('./browser-mediadevices-shim');
+const {initKeyboardShortcuts} = require('./browser-keyboard-shortcuts');
 const {initSpellChecker} = require('./browser-spell-check');
 
 initKeyboardShortcuts();


Test patch:
diff --git a/src/tab-manager/__tests__/browser-mediadevices-shim.test.js b/src/tab-manager/__tests__/browser-mediadevices-shim.test.js
new file mode 100644
index 0000000..beca9ee
--- /dev/null
+++ b/src/tab-manager/__tests__/browser-mediadevices-shim.test.js
@@ -0,0 +1,115 @@
+/*
+   Copyright 2019 Marc Nuri San Felix
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ */
+const {screen, fireEvent, waitFor} = require('@testing-library/dom');
+
+describe('Browser mediaDevices shim test suite', () => {
+  let mockThumbnail;
+  let electron;
+  let preact;
+  beforeAll(() => {
+    Object.defineProperty(window.navigator, 'mediaDevices', {
+      value: {
+      }
+    });
+  });
+  beforeEach(() => {
+    jest.resetModules();
+    mockThumbnail = {
+      toDataURL: jest.fn()
+    };
+    jest.mock('electron', () => ({
+      desktopCapturer: {
+        getSources: jest.fn(async () => [
+          {id: '1337', name: '1337', thumbnail: mockThumbnail},
+          {id: '313373', name: 'Other Window', thumbnail: mockThumbnail}
+        ])
+      }
+    }));
+    electron = require('electron');
+    preact = require('preact');
+    jest.spyOn(preact, 'render');
+    window.navigator.mediaDevices.getUserMedia = jest.fn(um => um.video.mandatory.chromeMediaSourceId);
+    document.body.innerHTML = '';
+    jest.isolateModules(() => {
+      require('../browser-mediadevices-shim');
+    });
+  });
+  describe('ui', () => {
+    test('mediaDevices.getDisplayMedia, should render overlay immediately', async () => {
+      // When
+      window.navigator.mediaDevices.getDisplayMedia();
+      // Then
+      expect(preact.render).toHaveBeenCalledTimes(1);
+      const $sources = document.querySelector('.electron-desktop-capturer-root');
+      expect($sources).not.toBeNull();
+      expect(await screen.findByText('Loading sources...')).not.toBeNull();
+      expect(electron.desktopCapturer.getSources).not.toHaveBeenCalled();
+    });
+    test('mediaDevices.getDisplayMedia, should render no sources found', async () => {
+      // Given
+      electron.desktopCapturer.getSources = jest.fn(() => []);
+      // When
+      window.navigator.mediaDevices.getDisplayMedia();
+      // Then
+      expect(preact.render).toHaveBeenCalledTimes(1);
+      expect(await screen.findByText('No sources found')).not.toBeNull();
+      expect(electron.desktopCapturer.getSources).toHaveBeenCalled();
+    });
+    test('mediaDevices.getDisplayMedia, should render media selector when sources are loaded', async () => {
+      // When
+      window.navigator.mediaDevices.getDisplayMedia();
+      // Then
+      expect(preact.render).toHaveBeenCalledTimes(1);
+      await waitFor(() =>
+        expect(document.querySelector('.electron-desktop-capturer-root__source')).not.toBeNull());
+      expect(electron.desktopCapturer.getSources).toHaveBeenCalled();
+      expect(document.querySelectorAll('.electron-desktop-capturer-root__source')).toHaveLength(2);
+      expect(mockThumbnail.toDataURL).toHaveBeenCalledTimes(2);
+    });
+  });
+  describe('events', () => {
+    test('select stream, should remove selector and resolve promise', async () => {
+      // Given
+      const promise = window.navigator.mediaDevices.getDisplayMedia();
+      const $source = await screen.findByText('Other Window');
+      // When
+      fireEvent.click($source);
+      // Then
+      await waitFor(() =>
+        expect(document.querySelector('.electron-desktop-capturer-root__sources')).toBeNull());
+      expect(window.navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith(
+        {audio: false, video: {mandatory: {
+          chromeMediaSource: 'desktop', chromeMediaSourceId: '313373'}}
+        });
+      expect(preact.render).toHaveBeenCalledTimes(2);
+      await expect(promise).resolves.toBe('313373');
+    });
+    test('cancel, should remove selector and do nothing', async () => {
+      // Given
+      const promise = window.navigator.mediaDevices.getDisplayMedia();
+      await waitFor(() =>
+        expect(document.querySelector('.electron-desktop-capturer-root__sources')).not.toBeNull());
+      // When
+      fireEvent.click(document.querySelector('.electron-desktop-capturer-root__sources'));
+      // Then
+      await waitFor(() =>
+        expect(document.querySelector('.electron-desktop-capturer-root__sources')).toBeNull());
+      expect(window.navigator.mediaDevices.getUserMedia).not.toHaveBeenCalled();
+      expect(preact.render).toHaveBeenCalledTimes(2);
+      await expect(promise).rejects.toEqual(new Error('Screen share aborted by user'));
+    });
+  });
+});
diff --git a/src/tab-manager/__tests__/browser-notification-shim.test.js b/src/tab-manager/__tests__/browser-notification-shim.test.js
index 026e0cd..e3f2492 100644
--- a/src/tab-manager/__tests__/browser-notification-shim.test.js
+++ b/src/tab-manager/__tests__/browser-notification-shim.test.js
@@ -1,3 +1,18 @@
+/*
+   Copyright 2019 Marc Nuri San Felix
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ */
 describe('Browser Notification Shim test suite', () => {
   let NativeNotification;
   let electron;
diff --git a/src/tab-manager/__tests__/preload.test.js b/src/tab-manager/__tests__/preload.test.js
index c2cdbff..0628dd2 100644
--- a/src/tab-manager/__tests__/preload.test.js
+++ b/src/tab-manager/__tests__/preload.test.js
@@ -27,6 +27,7 @@ describe('Tab Manager Module preload test suite', () => {
     });
     jest.mock('electron', () => mockElectron);
     jest.mock('../browser-keyboard-shortcuts', () => mockKeyboardShortcuts);
+    jest.mock('../browser-mediadevices-shim', () => ({}));
     jest.mock('../browser-notification-shim', () => ({}));
     jest.mock('../browser-spell-check', () => mockSpellCheck);
   });