浏览代码

Merge pull request #1775 from jitsi/start_screen

Add config.startScreenSharing
j8
Saúl Ibarra Corretgé 8 年前
父节点
当前提交
c97daff506
共有 5 个文件被更改,包括 346 次插入188 次删除
  1. 291
    179
      conference.js
  2. 1
    0
      config.js
  3. 2
    0
      lang/main.json
  4. 52
    3
      modules/UI/UI.js
  5. 0
    6
      service/UI/UIEvents.js

+ 291
- 179
conference.js 查看文件

@@ -72,11 +72,6 @@ let connection;
72 72
 let localAudio, localVideo;
73 73
 let initialAudioMutedState = false, initialVideoMutedState = false;
74 74
 
75
-/**
76
- * Indicates whether extension external installation is in progress or not.
77
- */
78
-let DSExternalInstallationInProgress = false;
79
-
80 75
 import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
81 76
 
82 77
 /*
@@ -118,68 +113,6 @@ function connect(roomName) {
118 113
     });
119 114
 }
120 115
 
121
-/**
122
- * Creates local media tracks and connects to room. Will show error
123
- * dialogs in case if accessing local microphone and/or camera failed. Will
124
- * show guidance overlay for users on how to give access to camera and/or
125
- * microphone,
126
- * @param {string} roomName
127
- * @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
128
- */
129
-function createInitialLocalTracksAndConnect(roomName) {
130
-    let audioAndVideoError,
131
-        audioOnlyError,
132
-        videoOnlyError;
133
-
134
-    JitsiMeetJS.mediaDevices.addEventListener(
135
-        JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
136
-        browser =>
137
-            APP.store.dispatch(
138
-                mediaPermissionPromptVisibilityChanged(true, browser))
139
-    );
140
-
141
-    // First try to retrieve both audio and video.
142
-    let tryCreateLocalTracks = createLocalTracks(
143
-            { devices: ['audio', 'video'] }, true)
144
-        .catch(err => {
145
-            // If failed then try to retrieve only audio.
146
-            audioAndVideoError = err;
147
-            return createLocalTracks({ devices: ['audio'] }, true);
148
-        })
149
-        .catch(err => {
150
-            audioOnlyError = err;
151
-
152
-            // Try video only...
153
-            return createLocalTracks({ devices: ['video'] }, true);
154
-        })
155
-        .catch(err => {
156
-            videoOnlyError = err;
157
-
158
-            return [];
159
-        });
160
-
161
-    return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
162
-        .then(([tracks, con]) => {
163
-            APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
164
-            if (audioAndVideoError) {
165
-                if (audioOnlyError) {
166
-                    // If both requests for 'audio' + 'video' and 'audio' only
167
-                    // failed, we assume that there is some problems with user's
168
-                    // microphone and show corresponding dialog.
169
-                    APP.UI.showDeviceErrorDialog(
170
-                        audioOnlyError, videoOnlyError);
171
-                } else {
172
-                    // If request for 'audio' + 'video' failed, but request for
173
-                    // 'audio' only was OK, we assume that we had problems with
174
-                    // camera and show corresponding dialog.
175
-                    APP.UI.showDeviceErrorDialog(null, audioAndVideoError);
176
-                }
177
-            }
178
-
179
-            return [tracks, con];
180
-        });
181
-}
182
-
183 116
 /**
184 117
  * Share data to other users.
185 118
  * @param command the command
@@ -567,6 +500,106 @@ export default {
567 500
      * Whether the local participant is the dominant speaker in the conference.
568 501
      */
569 502
     isDominantSpeaker: false,
503
+
504
+    /**
505
+     * Creates local media tracks and connects to a room. Will show error
506
+     * dialogs in case accessing the local microphone and/or camera failed. Will
507
+     * show guidance overlay for users on how to give access to camera and/or
508
+     * microphone,
509
+     * @param {string} roomName
510
+     * @param {object} options
511
+     * @param {boolean} options.startScreenSharing - if <tt>true</tt> should
512
+     * start with screensharing instead of camera video.
513
+     * @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
514
+     */
515
+    createInitialLocalTracksAndConnect(roomName, options = {}) {
516
+        let audioAndVideoError,
517
+            audioOnlyError,
518
+            screenSharingError,
519
+            videoOnlyError;
520
+
521
+        JitsiMeetJS.mediaDevices.addEventListener(
522
+            JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
523
+            browser =>
524
+                APP.store.dispatch(
525
+                    mediaPermissionPromptVisibilityChanged(true, browser))
526
+        );
527
+
528
+        // First try to retrieve both audio and video.
529
+        let tryCreateLocalTracks;
530
+
531
+        // FIXME the logic about trying to go audio only on error is duplicated
532
+        if (options.startScreenSharing) {
533
+            tryCreateLocalTracks = this._createDesktopTrack()
534
+                .then(desktopStream => {
535
+                    return createLocalTracks({ devices: ['audio'] }, true)
536
+                        .then(([audioStream]) => {
537
+                            return [desktopStream, audioStream];
538
+                        })
539
+                        .catch(error => {
540
+                            audioOnlyError = error;
541
+                            return [desktopStream];
542
+                        });
543
+                }).catch(error => {
544
+                    logger.error('Failed to obtain desktop stream', error);
545
+                    screenSharingError = error;
546
+                    return createLocalTracks({ devices: ['audio'] }, true);
547
+                }).catch(error => {
548
+                    audioOnlyError = error;
549
+                    return [];
550
+                });
551
+        } else {
552
+            tryCreateLocalTracks = createLocalTracks(
553
+                {devices: ['audio', 'video']}, true)
554
+                .catch(err => {
555
+                    // If failed then try to retrieve only audio.
556
+                    audioAndVideoError = err;
557
+                    return createLocalTracks({devices: ['audio']}, true);
558
+                })
559
+                .catch(err => {
560
+                    audioOnlyError = err;
561
+
562
+                    // Try video only...
563
+                    return createLocalTracks({devices: ['video']}, true);
564
+                })
565
+                .catch(err => {
566
+                    videoOnlyError = err;
567
+
568
+                    return [];
569
+                });
570
+        }
571
+
572
+        return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
573
+            .then(([tracks, con]) => {
574
+                APP.store.dispatch(
575
+                    mediaPermissionPromptVisibilityChanged(false));
576
+                // FIXME If there will be microphone error it will cover any
577
+                // screensharing dialog, but it's still better than in
578
+                // the reverse order where the screensharing dialog will
579
+                // sometimes be closing the microphone alert ($.prompt.close();
580
+                // is called). Need to figure out dialogs chaining to fix that.
581
+                if (screenSharingError) {
582
+                    this._handleScreenSharingError(screenSharingError);
583
+                }
584
+                if (audioAndVideoError || audioOnlyError) {
585
+                    if (audioOnlyError || videoOnlyError) {
586
+                        // If both requests for 'audio' + 'video' and 'audio'
587
+                        // only failed, we assume that there are some problems
588
+                        // with user's microphone and show corresponding dialog.
589
+                        APP.UI.showDeviceErrorDialog(
590
+                            audioOnlyError, videoOnlyError);
591
+                    } else {
592
+                        // If request for 'audio' + 'video' failed, but request
593
+                        // for 'audio' only was OK, we assume that we had
594
+                        // problems with camera and show corresponding dialog.
595
+                        APP.UI.showDeviceErrorDialog(null, audioAndVideoError);
596
+                    }
597
+                }
598
+
599
+                return [tracks, con];
600
+            });
601
+    },
602
+
570 603
     /**
571 604
      * Open new connection and join to the conference.
572 605
      * @param {object} options
@@ -602,7 +635,10 @@ export default {
602 635
                 {enableAnalyticsLogging: analytics.isEnabled()}, config)
603 636
             ).then(() => {
604 637
                 analytics.init();
605
-                return createInitialLocalTracksAndConnect(options.roomName);
638
+                return this.createInitialLocalTracksAndConnect(
639
+                    options.roomName, {
640
+                        startScreenSharing: config.startScreenSharing
641
+                    });
606 642
             }).then(([tracks, con]) => {
607 643
                 tracks.forEach(track => {
608 644
                     if((track.isAudioTrack() && initialAudioMutedState)
@@ -1215,7 +1251,6 @@ export default {
1215 1251
 
1216 1252
     /**
1217 1253
      * Toggles between screensharing and camera video.
1218
-     * @param {boolean} [shareScreen]
1219 1254
      * @param {Object} [options] - Screen sharing options that will be passed to
1220 1255
      * createLocalTracks.
1221 1256
      * @param {Array<string>} [options.desktopSharingSources] - Array with the
@@ -1239,118 +1274,206 @@ export default {
1239 1274
         }
1240 1275
 
1241 1276
         if (!this._untoggleScreenSharing) {
1242
-            this.videoSwitchInProgress = true;
1243
-            let externalInstallation = false;
1244
-            const didHaveVideo = Boolean(localVideo);
1245
-            const wasVideoMuted = this.videoMuted;
1246
-
1247
-            return createLocalTracks({
1248
-                desktopSharingSources: options.desktopSharingSources,
1249
-                devices: ['desktop'],
1250
-                desktopSharingExtensionExternalInstallation: {
1251
-                    interval: 500,
1252
-                    checkAgain: () => {
1253
-                        return DSExternalInstallationInProgress;
1254
-                    },
1255
-                    listener: (status, url) => {
1256
-                        switch(status) {
1257
-                            case "waitingForExtension":
1258
-                                DSExternalInstallationInProgress = true;
1259
-                                externalInstallation = true;
1260
-                                APP.UI.showExtensionExternalInstallationDialog(
1261
-                                    url);
1262
-                                break;
1263
-                            case "extensionFound":
1264
-                                if(externalInstallation) //close the dialog
1265
-                                    $.prompt.close();
1266
-                                break;
1267
-                            default:
1268
-                                //Unknown status
1277
+            return this._switchToScreenSharing(options);
1278
+        } else {
1279
+            return this._untoggleScreenSharing();
1280
+        }
1281
+    },
1282
+
1283
+    /**
1284
+     * Creates desktop (screensharing) {@link JitsiLocalTrack}
1285
+     * @param {Object} [options] - Screen sharing options that will be passed to
1286
+     * createLocalTracks.
1287
+     *
1288
+     * @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
1289
+     * {@link JitsiLocalTrack} for the screensharing or rejected with
1290
+     * {@link JitsiTrackError}.
1291
+     *
1292
+     * @private
1293
+     */
1294
+    _createDesktopTrack(options = {}) {
1295
+        let externalInstallation = false;
1296
+        let DSExternalInstallationInProgress = false;
1297
+        const didHaveVideo = Boolean(localVideo);
1298
+        const wasVideoMuted = this.videoMuted;
1299
+
1300
+        return createLocalTracks({
1301
+            desktopSharingSources: options.desktopSharingSources,
1302
+            devices: ['desktop'],
1303
+            desktopSharingExtensionExternalInstallation: {
1304
+                interval: 500,
1305
+                checkAgain: () => {
1306
+                    return DSExternalInstallationInProgress;
1307
+                },
1308
+                listener: (status, url) => {
1309
+                    switch(status) {
1310
+                        case "waitingForExtension": {
1311
+                            DSExternalInstallationInProgress = true;
1312
+                            externalInstallation = true;
1313
+                            const listener = () => {
1314
+                                // Wait a little bit more just to be sure that
1315
+                                // we won't miss the extension installation
1316
+                                setTimeout(
1317
+                                    () => {
1318
+                                    DSExternalInstallationInProgress = false;
1319
+                                    }, 500);
1320
+                                APP.UI.removeListener(
1321
+                                    UIEvents.EXTERNAL_INSTALLATION_CANCELED,
1322
+                                    listener);
1323
+                            };
1324
+                            APP.UI.addListener(
1325
+                                UIEvents.EXTERNAL_INSTALLATION_CANCELED,
1326
+                                listener);
1327
+                            APP.UI.showExtensionExternalInstallationDialog(url);
1328
+                            break;
1329
+                        }
1330
+                        case "extensionFound": {
1331
+                            if (externalInstallation) { //close the dialog
1332
+                                $.prompt.close();
1333
+                            }
1334
+                            break;
1335
+                        }
1336
+                        default: {
1337
+                            //Unknown status
1269 1338
                         }
1270 1339
                     }
1271 1340
                 }
1272
-            }).then(([stream]) => {
1273
-                // Stores the "untoggle" handler which remembers whether was
1274
-                // there any video before and whether was it muted.
1275
-                this._untoggleScreenSharing
1276
-                    = this._turnScreenSharingOff
1277
-                          .bind(this, didHaveVideo, wasVideoMuted);
1278
-                DSExternalInstallationInProgress = false;
1279
-                // close external installation dialog on success.
1280
-                if(externalInstallation)
1281
-                    $.prompt.close();
1282
-                stream.on(
1283
-                    TrackEvents.LOCAL_TRACK_STOPPED,
1284
-                    () => {
1285
-                        // If the stream was stopped during screen sharing
1286
-                        // session then we should switch back to video.
1287
-                        if (this.isSharingScreen){
1288
-                            this._untoggleScreenSharing
1289
-                                && this._untoggleScreenSharing();
1290
-                        }
1341
+            }
1342
+        }).then(([desktopStream]) => {
1343
+            // Stores the "untoggle" handler which remembers whether was
1344
+            // there any video before and whether was it muted.
1345
+            this._untoggleScreenSharing
1346
+                = this._turnScreenSharingOff
1347
+                      .bind(this, didHaveVideo, wasVideoMuted);
1348
+            desktopStream.on(
1349
+                TrackEvents.LOCAL_TRACK_STOPPED,
1350
+                () => {
1351
+                    // If the stream was stopped during screen sharing
1352
+                    // session then we should switch back to video.
1353
+                    if (this.isSharingScreen) {
1354
+                        this._untoggleScreenSharing
1355
+                            && this._untoggleScreenSharing();
1291 1356
                     }
1292
-                );
1293
-                return this.useVideoStream(stream);
1294
-            }).then(() => {
1295
-                this.videoSwitchInProgress = false;
1296
-                JitsiMeetJS.analytics.sendEvent(
1297
-                    'conference.sharingDesktop.start');
1298
-                logger.log('sharing local desktop');
1299
-            }).catch(err => {
1300
-                // close external installation dialog to show the error.
1301
-                if(externalInstallation)
1302
-                    $.prompt.close();
1303
-                this.videoSwitchInProgress = false;
1304
-
1305
-                if (err.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) {
1306
-                    return Promise.reject(err);
1307 1357
                 }
1358
+            );
1359
+            // close external installation dialog on success.
1360
+            if (externalInstallation) {
1361
+                $.prompt.close();
1362
+            }
1363
+            return desktopStream;
1364
+        }, error => {
1365
+            DSExternalInstallationInProgress = false;
1366
+            // close external installation dialog on success.
1367
+            if (externalInstallation) {
1368
+                $.prompt.close();
1369
+            }
1370
+            throw error;
1371
+        });
1372
+    },
1308 1373
 
1309
-                // Pawel: With this call I'm trying to preserve the original
1310
-                // behaviour although it is not clear why would we "untoggle"
1311
-                // on failure. I suppose it was to restore video in case there
1312
-                // was some problem during "this.useVideoStream(desktopStream)".
1313
-                // It's important to note that the handler will not be available
1314
-                // if we fail early on trying to get desktop media (which makes
1315
-                // sense, because the camera video is still being used, so
1316
-                // nothing to "untoggle").
1317
-                if (this._untoggleScreenSharing) {
1318
-                    this._untoggleScreenSharing();
1319
-                }
1374
+    /**
1375
+     * Tries to switch to the screenshairng mode by disposing camera stream and
1376
+     * replacing it with a desktop one.
1377
+     *
1378
+     * @param {Object} [options] - Screen sharing options that will be passed to
1379
+     * createLocalTracks.
1380
+     *
1381
+     * @return {Promise} - A Promise resolved if the operation succeeds or
1382
+     * rejected with some unknown type of error in case it fails. Promise will
1383
+     * be rejected immediately if {@link videoSwitchInProgress} is true.
1384
+     *
1385
+     * @private
1386
+     */
1387
+    _switchToScreenSharing(options = {}) {
1388
+        if (this.videoSwitchInProgress) {
1389
+            return Promise.reject('Switch in progress.');
1390
+        }
1320 1391
 
1321
-                logger.error('failed to share local desktop', err);
1392
+        this.videoSwitchInProgress = true;
1393
+        return this._createDesktopTrack(options).then(stream => {
1394
+            return this.useVideoStream(stream);
1395
+        }).then(() => {
1396
+            this.videoSwitchInProgress = false;
1397
+            JitsiMeetJS.analytics.sendEvent('conference.sharingDesktop.start');
1398
+            logger.log('sharing local desktop');
1399
+        }).catch(error => {
1400
+            this.videoSwitchInProgress = false;
1401
+            // Pawel: With this call I'm trying to preserve the original
1402
+            // behaviour although it is not clear why would we "untoggle"
1403
+            // on failure. I suppose it was to restore video in case there
1404
+            // was some problem during "this.useVideoStream(desktopStream)".
1405
+            // It's important to note that the handler will not be available
1406
+            // if we fail early on trying to get desktop media (which makes
1407
+            // sense, because the camera video is still being used, so
1408
+            // nothing to "untoggle").
1409
+            if (this._untoggleScreenSharing) {
1410
+                this._untoggleScreenSharing();
1411
+            }
1322 1412
 
1323
-                if (err.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
1324
-                    APP.UI.showExtensionRequiredDialog(
1325
-                        config.desktopSharingFirefoxExtensionURL
1326
-                    );
1327
-                    return Promise.reject(err);
1328
-                }
1413
+            // FIXME the code inside of _handleScreenSharingError is
1414
+            // asynchronous, but does not return a Promise and is not part of
1415
+            // the current Promise chain.
1416
+            this._handleScreenSharingError(error);
1417
+        });
1418
+    },
1329 1419
 
1330
-                // Handling:
1331
-                // TrackErrors.PERMISSION_DENIED
1332
-                // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
1333
-                // TrackErrors.GENERAL
1334
-                // and any other
1335
-                let dialogTxt;
1336
-                let dialogTitleKey;
1337
-
1338
-                if (err.name === TrackErrors.PERMISSION_DENIED) {
1339
-                    dialogTxt = APP.translation.generateTranslationHTML(
1340
-                        "dialog.screenSharingPermissionDeniedError");
1341
-                    dialogTitleKey = "dialog.error";
1342
-                } else {
1343
-                    dialogTxt = APP.translation.generateTranslationHTML(
1344
-                        "dialog.failtoinstall");
1345
-                    dialogTitleKey = "dialog.permissionDenied";
1420
+    /**
1421
+     * Handles {@link JitsiTrackError} returned by the lib-jitsi-meet when
1422
+     * trying to create screensharing track. It will either do nothing if
1423
+     * the dialog was canceled on user's request or display inline installation
1424
+     * dialog and ask the user to install the extension, once the extension is
1425
+     * installed it will switch the conference to screensharing. The last option
1426
+     * is that an unrecoverable error dialog will be displayed.
1427
+     * @param {JitsiTrackError} error - The error returned by
1428
+     * {@link _createDesktopTrack} Promise.
1429
+     * @private
1430
+     */
1431
+    _handleScreenSharingError(error) {
1432
+        if (error.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) {
1433
+            return;
1434
+        }
1435
+
1436
+        logger.error('failed to share local desktop', error);
1437
+
1438
+        if (error.name === TrackErrors.CHROME_EXTENSION_USER_GESTURE_REQUIRED) {
1439
+            // If start with screen sharing the extension will fail to install
1440
+            // (if not found), because the request has been triggered by the
1441
+            // script. Show a dialog which asks user to click "install" and try
1442
+            // again switching to the screen sharing.
1443
+            APP.UI.showExtensionInlineInstallationDialog(
1444
+                () => {
1445
+                    this.toggleScreenSharing();
1346 1446
                 }
1447
+            );
1347 1448
 
1348
-                APP.UI.messageHandler.openDialog(
1349
-                    dialogTitleKey, dialogTxt, false);
1350
-            });
1449
+            return;
1450
+        } else if (error.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
1451
+            APP.UI.showExtensionRequiredDialog(
1452
+                config.desktopSharingFirefoxExtensionURL
1453
+            );
1454
+
1455
+            return;
1456
+        }
1457
+
1458
+        // Handling:
1459
+        // TrackErrors.PERMISSION_DENIED
1460
+        // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
1461
+        // TrackErrors.GENERAL
1462
+        // and any other
1463
+        let dialogTxt;
1464
+        let dialogTitleKey;
1465
+
1466
+        if (error.name === TrackErrors.PERMISSION_DENIED) {
1467
+            dialogTxt = APP.translation.generateTranslationHTML(
1468
+                "dialog.screenSharingPermissionDeniedError");
1469
+            dialogTitleKey = "dialog.error";
1351 1470
         } else {
1352
-            return this._untoggleScreenSharing();
1471
+            dialogTxt = APP.translation.generateTranslationHTML(
1472
+                "dialog.failtoinstall");
1473
+            dialogTitleKey = "dialog.permissionDenied";
1353 1474
         }
1475
+
1476
+        APP.UI.messageHandler.openDialog(dialogTitleKey, dialogTxt, false);
1354 1477
     },
1355 1478
     /**
1356 1479
      * Setup interaction between conference and UI.
@@ -1637,17 +1760,6 @@ export default {
1637 1760
             APP.UI.updateDTMFSupport(isDTMFSupported);
1638 1761
         });
1639 1762
 
1640
-        APP.UI.addListener(UIEvents.EXTERNAL_INSTALLATION_CANCELED, () => {
1641
-            // Wait a little bit more just to be sure that we won't miss the
1642
-            // extension installation
1643
-            setTimeout(() => DSExternalInstallationInProgress = false, 500);
1644
-        });
1645
-        APP.UI.addListener(UIEvents.OPEN_EXTENSION_STORE, (url) => {
1646
-            window.open(
1647
-                url, "extension_store_window",
1648
-                "resizable,scrollbars=yes,status=1");
1649
-        });
1650
-
1651 1763
         APP.UI.addListener(UIEvents.AUDIO_MUTED, muteLocalAudio);
1652 1764
         APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
1653 1765
             if (this.isAudioOnly() && !muted) {

+ 1
- 0
config.js 查看文件

@@ -72,6 +72,7 @@ var config = { // eslint-disable-line no-unused-vars
72 72
                               // page redirection when call is hangup
73 73
     disableSimulcast: false,
74 74
 //    requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room.
75
+    startScreenSharing: false, // Will try to start with screensharing instead of camera
75 76
 //    startAudioMuted: 10, // every participant after the Nth will start audio muted
76 77
 //    startVideoMuted: 10, // every participant after the Nth will start video muted
77 78
 //    defaultLanguage: "en",

+ 2
- 0
lang/main.json 查看文件

@@ -333,6 +333,8 @@
333 333
         "goToStore": "Go to the webstore",
334 334
         "externalInstallationTitle": "Extension required",
335 335
         "externalInstallationMsg": "You need to install our desktop sharing extension.",
336
+        "inlineInstallationMsg": "You need to install our desktop sharing extension.",
337
+        "inlineInstallExtension": "Install now",
336 338
         "muteParticipantTitle": "Mute this participant?",
337 339
         "muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
338 340
         "muteParticipantButton": "Mute",

+ 52
- 3
modules/UI/UI.js 查看文件

@@ -1158,15 +1158,34 @@ UI.showExtensionRequiredDialog = function (url) {
1158 1158
  * @param url {string} the url of the extension.
1159 1159
  */
1160 1160
 UI.showExtensionExternalInstallationDialog = function (url) {
1161
+    let openedWindow = null;
1162
+
1161 1163
     let submitFunction = function(e,v){
1162 1164
         if (v) {
1163 1165
             e.preventDefault();
1164
-            eventEmitter.emit(UIEvents.OPEN_EXTENSION_STORE, url);
1166
+            if (openedWindow === null || openedWindow.closed) {
1167
+                openedWindow
1168
+                    = window.open(
1169
+                        url,
1170
+                        "extension_store_window",
1171
+                        "resizable,scrollbars=yes,status=1");
1172
+            } else {
1173
+                openedWindow.focus();
1174
+            }
1165 1175
         }
1166 1176
     };
1167 1177
 
1168
-    let closeFunction = function () {
1169
-        eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED);
1178
+    let closeFunction = function (e, v) {
1179
+        if (openedWindow) {
1180
+            // Ideally we would close the popup, but this does not seem to work
1181
+            // on Chrome. Leaving it uncommented in case it could work
1182
+            // in some version.
1183
+            openedWindow.close();
1184
+            openedWindow = null;
1185
+        }
1186
+        if (!v) {
1187
+            eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED);
1188
+        }
1170 1189
     };
1171 1190
 
1172 1191
     messageHandler.openTwoButtonDialog({
@@ -1179,6 +1198,36 @@ UI.showExtensionExternalInstallationDialog = function (url) {
1179 1198
     });
1180 1199
 };
1181 1200
 
1201
+/**
1202
+ * Shows a dialog which asks user to install the extension. This one is
1203
+ * displayed after installation is triggered from the script, but fails because
1204
+ * it must be initiated by user gesture.
1205
+ * @param callback {function} function to be executed after user clicks
1206
+ * the install button - it should make another attempt to install the extension.
1207
+ */
1208
+UI.showExtensionInlineInstallationDialog = function (callback) {
1209
+    let submitFunction = function(e,v){
1210
+        if (v) {
1211
+            callback();
1212
+        }
1213
+    };
1214
+
1215
+    let closeFunction = function (e, v) {
1216
+        if (!v) {
1217
+            eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED);
1218
+        }
1219
+    };
1220
+
1221
+    messageHandler.openTwoButtonDialog({
1222
+        titleKey: 'dialog.externalInstallationTitle',
1223
+        msgKey: 'dialog.inlineInstallationMsg',
1224
+        leftButtonKey: 'dialog.inlineInstallExtension',
1225
+        submitFunction,
1226
+        loadedFunction: $.noop,
1227
+        closeFunction
1228
+    });
1229
+};
1230
+
1182 1231
 
1183 1232
 /**
1184 1233
  * Shows dialog with combined information about camera and microphone errors.

+ 0
- 6
service/UI/UIEvents.js 查看文件

@@ -97,12 +97,6 @@ export default {
97 97
     // changed.
98 98
     RESOLUTION_CHANGED: "UI.resolution_changed",
99 99
 
100
-    /**
101
-     * Notifies that the button "Go to webstore" is pressed on the dialog for
102
-     * external extension installation.
103
-     */
104
-    OPEN_EXTENSION_STORE: "UI.open_extension_store",
105
-
106 100
     /**
107 101
      * Notifies that the button "Cancel" is pressed on the dialog for
108 102
      * external extension installation.

正在加载...
取消
保存